MediaWiki  REL1_31
RevisionStore.php
Go to the documentation of this file.
1 <?php
27 namespace MediaWiki\Storage;
28 
32 use Content;
35 use Hooks;
37 use InvalidArgumentException;
38 use IP;
39 use LogicException;
43 use Message;
46 use Psr\Log\LoggerAwareInterface;
47 use Psr\Log\LoggerInterface;
48 use Psr\Log\NullLogger;
50 use stdClass;
51 use Title;
52 use User;
54 use Wikimedia\Assert\Assert;
59 
69  implements IDBAccessObject, RevisionFactory, RevisionLookup, LoggerAwareInterface {
70 
74  private $blobStore;
75 
79  private $wikiId;
80 
84  private $contentHandlerUseDB = true;
85 
89  private $loadBalancer;
90 
94  private $cache;
95 
99  private $commentStore;
100 
105 
109  private $logger;
110 
121  public function __construct(
127  $wikiId = false
128  ) {
129  Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
130 
131  $this->loadBalancer = $loadBalancer;
132  $this->blobStore = $blobStore;
133  $this->cache = $cache;
134  $this->commentStore = $commentStore;
135  $this->actorMigration = $actorMigration;
136  $this->wikiId = $wikiId;
137  $this->logger = new NullLogger();
138  }
139 
140  public function setLogger( LoggerInterface $logger ) {
141  $this->logger = $logger;
142  }
143 
147  public function isReadOnly() {
148  return $this->blobStore->isReadOnly();
149  }
150 
154  public function getContentHandlerUseDB() {
156  }
157 
162  $this->contentHandlerUseDB = $contentHandlerUseDB;
163  }
164 
168  private function getDBLoadBalancer() {
169  return $this->loadBalancer;
170  }
171 
177  private function getDBConnection( $mode ) {
178  $lb = $this->getDBLoadBalancer();
179  return $lb->getConnection( $mode, [], $this->wikiId );
180  }
181 
185  private function releaseDBConnection( IDatabase $connection ) {
186  $lb = $this->getDBLoadBalancer();
187  $lb->reuseConnection( $connection );
188  }
189 
195  private function getDBConnectionRef( $mode ) {
196  $lb = $this->getDBLoadBalancer();
197  return $lb->getConnectionRef( $mode, [], $this->wikiId );
198  }
199 
214  public function getTitle( $pageId, $revId, $queryFlags = self::READ_NORMAL ) {
215  if ( !$pageId && !$revId ) {
216  throw new InvalidArgumentException( '$pageId and $revId cannot both be 0 or null' );
217  }
218 
219  // This method recalls itself with READ_LATEST if READ_NORMAL doesn't get us a Title
220  // So ignore READ_LATEST_IMMUTABLE flags and handle the fallback logic in this method
221  if ( DBAccessObjectUtils::hasFlags( $queryFlags, self::READ_LATEST_IMMUTABLE ) ) {
222  $queryFlags = self::READ_NORMAL;
223  }
224 
225  $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->wikiId === false );
226  list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
227  $titleFlags = ( $dbMode == DB_MASTER ? Title::GAID_FOR_UPDATE : 0 );
228 
229  // Loading by ID is best, but Title::newFromID does not support that for foreign IDs.
230  if ( $canUseTitleNewFromId ) {
231  // TODO: better foreign title handling (introduce TitleFactory)
232  $title = Title::newFromID( $pageId, $titleFlags );
233  if ( $title ) {
234  return $title;
235  }
236  }
237 
238  // rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
239  $canUseRevId = ( $revId !== null && $revId > 0 );
240 
241  if ( $canUseRevId ) {
242  $dbr = $this->getDBConnectionRef( $dbMode );
243  // @todo: Title::getSelectFields(), or Title::getQueryInfo(), or something like that
244  $row = $dbr->selectRow(
245  [ 'revision', 'page' ],
246  [
247  'page_namespace',
248  'page_title',
249  'page_id',
250  'page_latest',
251  'page_is_redirect',
252  'page_len',
253  ],
254  [ 'rev_id' => $revId ],
255  __METHOD__,
256  $dbOptions,
257  [ 'page' => [ 'JOIN', 'page_id=rev_page' ] ]
258  );
259  if ( $row ) {
260  // TODO: better foreign title handling (introduce TitleFactory)
261  return Title::newFromRow( $row );
262  }
263  }
264 
265  // If we still don't have a title, fallback to master if that wasn't already happening.
266  if ( $dbMode !== DB_MASTER ) {
267  $title = $this->getTitle( $pageId, $revId, self::READ_LATEST );
268  if ( $title ) {
269  $this->logger->info(
270  __METHOD__ . ' fell back to READ_LATEST and got a Title.',
271  [ 'trace' => wfBacktrace() ]
272  );
273  return $title;
274  }
275  }
276 
277  throw new RevisionAccessException(
278  "Could not determine title for page ID $pageId and revision ID $revId"
279  );
280  }
281 
289  private function failOnNull( $value, $name ) {
290  if ( $value === null ) {
291  throw new IncompleteRevisionException(
292  "$name must not be " . var_export( $value, true ) . "!"
293  );
294  }
295 
296  return $value;
297  }
298 
306  private function failOnEmpty( $value, $name ) {
307  if ( $value === null || $value === 0 || $value === '' ) {
308  throw new IncompleteRevisionException(
309  "$name must not be " . var_export( $value, true ) . "!"
310  );
311  }
312 
313  return $value;
314  }
315 
328  public function insertRevisionOn( RevisionRecord $rev, IDatabase $dbw ) {
329  // TODO: pass in a DBTransactionContext instead of a database connection.
330  $this->checkDatabaseWikiId( $dbw );
331 
332  if ( !$rev->getSlotRoles() ) {
333  throw new InvalidArgumentException( 'At least one slot needs to be defined!' );
334  }
335 
336  if ( $rev->getSlotRoles() !== [ 'main' ] ) {
337  throw new InvalidArgumentException( 'Only the main slot is supported for now!' );
338  }
339 
340  // TODO: we shouldn't need an actual Title here.
341  $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
342  $pageId = $this->failOnEmpty( $rev->getPageId(), 'rev_page field' ); // check this early
343 
344  $parentId = $rev->getParentId() === null
345  ? $this->getPreviousRevisionId( $dbw, $rev )
346  : $rev->getParentId();
347 
348  // Record the text (or external storage URL) to the blob store
349  $slot = $rev->getSlot( 'main', RevisionRecord::RAW );
350 
351  $size = $this->failOnNull( $rev->getSize(), 'size field' );
352  $sha1 = $this->failOnEmpty( $rev->getSha1(), 'sha1 field' );
353 
354  if ( !$slot->hasAddress() ) {
355  $content = $slot->getContent();
356  $format = $content->getDefaultFormat();
357  $model = $content->getModel();
358 
359  $this->checkContentModel( $content, $title );
360 
361  $data = $content->serialize( $format );
362 
363  // Hints allow the blob store to optimize by "leaking" application level information to it.
364  // TODO: with the new MCR storage schema, we rev_id have this before storing the blobs.
365  // When we have it, add rev_id as a hint. Can be used with rev_parent_id for
366  // differential storage or compression of subsequent revisions.
367  $blobHints = [
368  BlobStore::DESIGNATION_HINT => 'page-content', // BlobStore may be used for other things too.
369  BlobStore::PAGE_HINT => $pageId,
370  BlobStore::ROLE_HINT => $slot->getRole(),
371  BlobStore::PARENT_HINT => $parentId,
372  BlobStore::SHA1_HINT => $slot->getSha1(),
373  BlobStore::MODEL_HINT => $model,
374  BlobStore::FORMAT_HINT => $format,
375  ];
376 
377  $blobAddress = $this->blobStore->storeBlob( $data, $blobHints );
378  } else {
379  $blobAddress = $slot->getAddress();
380  $model = $slot->getModel();
381  $format = $slot->getFormat();
382  }
383 
384  $textId = $this->blobStore->getTextIdFromAddress( $blobAddress );
385 
386  if ( !$textId ) {
387  throw new LogicException(
388  'Blob address not supported in 1.29 database schema: ' . $blobAddress
389  );
390  }
391 
392  // getTextIdFromAddress() is free to insert something into the text table, so $textId
393  // may be a new value, not anything already contained in $blobAddress.
394  $blobAddress = 'tt:' . $textId;
395 
396  $comment = $this->failOnNull( $rev->getComment( RevisionRecord::RAW ), 'comment' );
397  $user = $this->failOnNull( $rev->getUser( RevisionRecord::RAW ), 'user' );
398  $timestamp = $this->failOnEmpty( $rev->getTimestamp(), 'timestamp field' );
399 
400  // Checks.
401  $this->failOnNull( $user->getId(), 'user field' );
402  $this->failOnEmpty( $user->getName(), 'user_text field' );
403 
404  # Record the edit in revisions
405  $row = [
406  'rev_page' => $pageId,
407  'rev_parent_id' => $parentId,
408  'rev_text_id' => $textId,
409  'rev_minor_edit' => $rev->isMinor() ? 1 : 0,
410  'rev_timestamp' => $dbw->timestamp( $timestamp ),
411  'rev_deleted' => $rev->getVisibility(),
412  'rev_len' => $size,
413  'rev_sha1' => $sha1,
414  ];
415 
416  if ( $rev->getId() !== null ) {
417  // Needed to restore revisions with their original ID
418  $row['rev_id'] = $rev->getId();
419  }
420 
421  list( $commentFields, $commentCallback ) =
422  $this->commentStore->insertWithTempTable( $dbw, 'rev_comment', $comment );
423  $row += $commentFields;
424 
425  list( $actorFields, $actorCallback ) =
426  $this->actorMigration->getInsertValuesWithTempTable( $dbw, 'rev_user', $user );
427  $row += $actorFields;
428 
429  if ( $this->contentHandlerUseDB ) {
430  // MCR migration note: rev_content_model and rev_content_format will go away
431 
432  $defaultModel = ContentHandler::getDefaultModelFor( $title );
433  $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
434 
435  $row['rev_content_model'] = ( $model === $defaultModel ) ? null : $model;
436  $row['rev_content_format'] = ( $format === $defaultFormat ) ? null : $format;
437  }
438 
439  $dbw->insert( 'revision', $row, __METHOD__ );
440 
441  if ( !isset( $row['rev_id'] ) ) {
442  // only if auto-increment was used
443  $row['rev_id'] = intval( $dbw->insertId() );
444  }
445  $commentCallback( $row['rev_id'] );
446  $actorCallback( $row['rev_id'], $row );
447 
448  // Insert IP revision into ip_changes for use when querying for a range.
449  if ( $user->getId() === 0 && IP::isValid( $user->getName() ) ) {
450  $ipcRow = [
451  'ipc_rev_id' => $row['rev_id'],
452  'ipc_rev_timestamp' => $row['rev_timestamp'],
453  'ipc_hex' => IP::toHex( $user->getName() ),
454  ];
455  $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
456  }
457 
458  $newSlot = SlotRecord::newSaved( $row['rev_id'], $textId, $blobAddress, $slot );
459  $slots = new RevisionSlots( [ 'main' => $newSlot ] );
460 
462  $title,
463  $user,
464  $comment,
465  (object)$row,
466  $slots,
467  $this->wikiId
468  );
469 
470  $newSlot = $rev->getSlot( 'main', RevisionRecord::RAW );
471 
472  // sanity checks
473  Assert::postcondition( $rev->getId() > 0, 'revision must have an ID' );
474  Assert::postcondition( $rev->getPageId() > 0, 'revision must have a page ID' );
475  Assert::postcondition(
476  $rev->getComment( RevisionRecord::RAW ) !== null,
477  'revision must have a comment'
478  );
479  Assert::postcondition(
480  $rev->getUser( RevisionRecord::RAW ) !== null,
481  'revision must have a user'
482  );
483 
484  Assert::postcondition( $newSlot !== null, 'revision must have a main slot' );
485  Assert::postcondition(
486  $newSlot->getAddress() !== null,
487  'main slot must have an addess'
488  );
489 
490  Hooks::run( 'RevisionRecordInserted', [ $rev ] );
491 
492  return $rev;
493  }
494 
504  private function checkContentModel( Content $content, Title $title ) {
505  // Note: may return null for revisions that have not yet been inserted
506 
507  $model = $content->getModel();
508  $format = $content->getDefaultFormat();
509  $handler = $content->getContentHandler();
510 
511  $name = "$title";
512 
513  if ( !$handler->isSupportedFormat( $format ) ) {
514  throw new MWException( "Can't use format $format with content model $model on $name" );
515  }
516 
517  if ( !$this->contentHandlerUseDB ) {
518  // if $wgContentHandlerUseDB is not set,
519  // all revisions must use the default content model and format.
520 
521  $defaultModel = ContentHandler::getDefaultModelFor( $title );
522  $defaultHandler = ContentHandler::getForModelID( $defaultModel );
523  $defaultFormat = $defaultHandler->getDefaultFormat();
524 
525  if ( $model != $defaultModel ) {
526  throw new MWException( "Can't save non-default content model with "
527  . "\$wgContentHandlerUseDB disabled: model is $model, "
528  . "default for $name is $defaultModel"
529  );
530  }
531 
532  if ( $format != $defaultFormat ) {
533  throw new MWException( "Can't use non-default content format with "
534  . "\$wgContentHandlerUseDB disabled: format is $format, "
535  . "default for $name is $defaultFormat"
536  );
537  }
538  }
539 
540  if ( !$content->isValid() ) {
541  throw new MWException(
542  "New content for $name is not valid! Content model is $model"
543  );
544  }
545  }
546 
567  public function newNullRevision(
568  IDatabase $dbw,
569  Title $title,
570  CommentStoreComment $comment,
571  $minor,
572  User $user
573  ) {
574  $this->checkDatabaseWikiId( $dbw );
575 
576  $fields = [ 'page_latest', 'page_namespace', 'page_title',
577  'rev_id', 'rev_text_id', 'rev_len', 'rev_sha1' ];
578 
579  if ( $this->contentHandlerUseDB ) {
580  $fields[] = 'rev_content_model';
581  $fields[] = 'rev_content_format';
582  }
583 
584  $current = $dbw->selectRow(
585  [ 'page', 'revision' ],
586  $fields,
587  [
588  'page_id' => $title->getArticleID(),
589  'page_latest=rev_id',
590  ],
591  __METHOD__,
592  [ 'FOR UPDATE' ] // T51581
593  );
594 
595  if ( $current ) {
596  $fields = [
597  'page' => $title->getArticleID(),
598  'user_text' => $user->getName(),
599  'user' => $user->getId(),
600  'actor' => $user->getActorId(),
601  'comment' => $comment,
602  'minor_edit' => $minor,
603  'text_id' => $current->rev_text_id,
604  'parent_id' => $current->page_latest,
605  'slot_origin' => $current->page_latest,
606  'len' => $current->rev_len,
607  'sha1' => $current->rev_sha1
608  ];
609 
610  if ( $this->contentHandlerUseDB ) {
611  $fields['content_model'] = $current->rev_content_model;
612  $fields['content_format'] = $current->rev_content_format;
613  }
614 
615  $fields['title'] = Title::makeTitle( $current->page_namespace, $current->page_title );
616 
617  $mainSlot = $this->emulateMainSlot_1_29( $fields, self::READ_LATEST, $title );
618  $revision = new MutableRevisionRecord( $title, $this->wikiId );
619  $this->initializeMutableRevisionFromArray( $revision, $fields );
620  $revision->setSlot( $mainSlot );
621  } else {
622  $revision = null;
623  }
624 
625  return $revision;
626  }
627 
638  $rc = $this->getRecentChange( $rev );
639  if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_UNPATROLLED ) {
640  return $rc->getAttribute( 'rc_id' );
641  } else {
642  return 0;
643  }
644  }
645 
659  public function getRecentChange( RevisionRecord $rev, $flags = 0 ) {
660  $dbr = $this->getDBConnection( DB_REPLICA );
661 
662  list( $dbType, ) = DBAccessObjectUtils::getDBOptions( $flags );
663 
664  $userIdentity = $rev->getUser( RevisionRecord::RAW );
665 
666  if ( !$userIdentity ) {
667  // If the revision has no user identity, chances are it never went
668  // into the database, and doesn't have an RC entry.
669  return null;
670  }
671 
672  // TODO: Select by rc_this_oldid alone - but as of Nov 2017, there is no index on that!
673  $actorWhere = $this->actorMigration->getWhere( $dbr, 'rc_user', $rev->getUser(), false );
675  [
676  $actorWhere['conds'],
677  'rc_timestamp' => $dbr->timestamp( $rev->getTimestamp() ),
678  'rc_this_oldid' => $rev->getId()
679  ],
680  __METHOD__,
681  $dbType
682  );
683 
684  $this->releaseDBConnection( $dbr );
685 
686  // XXX: cache this locally? Glue it to the RevisionRecord?
687  return $rc;
688  }
689 
697  private static function mapArchiveFields( $archiveRow ) {
698  $fieldMap = [
699  // keep with ar prefix:
700  'ar_id' => 'ar_id',
701 
702  // not the same suffix:
703  'ar_page_id' => 'rev_page',
704  'ar_rev_id' => 'rev_id',
705 
706  // same suffix:
707  'ar_text_id' => 'rev_text_id',
708  'ar_timestamp' => 'rev_timestamp',
709  'ar_user_text' => 'rev_user_text',
710  'ar_user' => 'rev_user',
711  'ar_actor' => 'rev_actor',
712  'ar_minor_edit' => 'rev_minor_edit',
713  'ar_deleted' => 'rev_deleted',
714  'ar_len' => 'rev_len',
715  'ar_parent_id' => 'rev_parent_id',
716  'ar_sha1' => 'rev_sha1',
717  'ar_comment' => 'rev_comment',
718  'ar_comment_cid' => 'rev_comment_cid',
719  'ar_comment_id' => 'rev_comment_id',
720  'ar_comment_text' => 'rev_comment_text',
721  'ar_comment_data' => 'rev_comment_data',
722  'ar_comment_old' => 'rev_comment_old',
723  'ar_content_format' => 'rev_content_format',
724  'ar_content_model' => 'rev_content_model',
725  ];
726 
727  $revRow = new stdClass();
728  foreach ( $fieldMap as $arKey => $revKey ) {
729  if ( property_exists( $archiveRow, $arKey ) ) {
730  $revRow->$revKey = $archiveRow->$arKey;
731  }
732  }
733 
734  return $revRow;
735  }
736 
747  private function emulateMainSlot_1_29( $row, $queryFlags, Title $title ) {
748  $mainSlotRow = new stdClass();
749  $mainSlotRow->role_name = 'main';
750  $mainSlotRow->model_name = null;
751  $mainSlotRow->slot_revision_id = null;
752  $mainSlotRow->content_address = null;
753  $mainSlotRow->slot_content_id = null;
754 
755  $content = null;
756  $blobData = null;
757  $blobFlags = null;
758 
759  if ( is_object( $row ) ) {
760  // archive row
761  if ( !isset( $row->rev_id ) && ( isset( $row->ar_user ) || isset( $row->ar_actor ) ) ) {
762  $row = $this->mapArchiveFields( $row );
763  }
764 
765  if ( isset( $row->rev_text_id ) && $row->rev_text_id > 0 ) {
766  $mainSlotRow->slot_content_id = $row->rev_text_id;
767  $mainSlotRow->content_address = 'tt:' . $row->rev_text_id;
768  }
769 
770  // This is used by null-revisions
771  $mainSlotRow->slot_origin = isset( $row->slot_origin )
772  ? intval( $row->slot_origin )
773  : null;
774 
775  if ( isset( $row->old_text ) ) {
776  // this happens when the text-table gets joined directly, in the pre-1.30 schema
777  $blobData = isset( $row->old_text ) ? strval( $row->old_text ) : null;
778  // Check against selects that might have not included old_flags
779  if ( !property_exists( $row, 'old_flags' ) ) {
780  throw new InvalidArgumentException( 'old_flags was not set in $row' );
781  }
782  $blobFlags = ( $row->old_flags === null ) ? '' : $row->old_flags;
783  }
784 
785  $mainSlotRow->slot_revision_id = intval( $row->rev_id );
786 
787  $mainSlotRow->content_size = isset( $row->rev_len ) ? intval( $row->rev_len ) : null;
788  $mainSlotRow->content_sha1 = isset( $row->rev_sha1 ) ? strval( $row->rev_sha1 ) : null;
789  $mainSlotRow->model_name = isset( $row->rev_content_model )
790  ? strval( $row->rev_content_model )
791  : null;
792  // XXX: in the future, we'll probably always use the default format, and drop content_format
793  $mainSlotRow->format_name = isset( $row->rev_content_format )
794  ? strval( $row->rev_content_format )
795  : null;
796  } elseif ( is_array( $row ) ) {
797  $mainSlotRow->slot_revision_id = isset( $row['id'] ) ? intval( $row['id'] ) : null;
798 
799  $mainSlotRow->slot_content_id = isset( $row['text_id'] )
800  ? intval( $row['text_id'] )
801  : null;
802  $mainSlotRow->slot_origin = isset( $row['slot_origin'] )
803  ? intval( $row['slot_origin'] )
804  : null;
805  $mainSlotRow->content_address = isset( $row['text_id'] )
806  ? 'tt:' . intval( $row['text_id'] )
807  : null;
808  $mainSlotRow->content_size = isset( $row['len'] ) ? intval( $row['len'] ) : null;
809  $mainSlotRow->content_sha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
810 
811  $mainSlotRow->model_name = isset( $row['content_model'] )
812  ? strval( $row['content_model'] ) : null; // XXX: must be a string!
813  // XXX: in the future, we'll probably always use the default format, and drop content_format
814  $mainSlotRow->format_name = isset( $row['content_format'] )
815  ? strval( $row['content_format'] ) : null;
816  $blobData = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
817  // XXX: If the flags field is not set then $blobFlags should be null so that no
818  // decoding will happen. An empty string will result in default decodings.
819  $blobFlags = isset( $row['flags'] ) ? trim( strval( $row['flags'] ) ) : null;
820 
821  // if we have a Content object, override mText and mContentModel
822  if ( !empty( $row['content'] ) ) {
823  if ( !( $row['content'] instanceof Content ) ) {
824  throw new MWException( 'content field must contain a Content object.' );
825  }
826 
828  $content = $row['content'];
829  $handler = $content->getContentHandler();
830 
831  $mainSlotRow->model_name = $content->getModel();
832 
833  // XXX: in the future, we'll probably always use the default format.
834  if ( $mainSlotRow->format_name === null ) {
835  $mainSlotRow->format_name = $handler->getDefaultFormat();
836  }
837  }
838  } else {
839  throw new MWException( 'Revision constructor passed invalid row format.' );
840  }
841 
842  // With the old schema, the content changes with every revision,
843  // except for null-revisions.
844  if ( !isset( $mainSlotRow->slot_origin ) ) {
845  $mainSlotRow->slot_origin = $mainSlotRow->slot_revision_id;
846  }
847 
848  if ( $mainSlotRow->model_name === null ) {
849  $mainSlotRow->model_name = function ( SlotRecord $slot ) use ( $title ) {
850  // TODO: MCR: consider slot role in getDefaultModelFor()! Use LinkTarget!
851  // TODO: MCR: deprecate $title->getModel().
853  };
854  }
855 
856  if ( !$content ) {
857  $content = function ( SlotRecord $slot )
858  use ( $blobData, $blobFlags, $queryFlags, $mainSlotRow )
859  {
860  return $this->loadSlotContent(
861  $slot,
862  $blobData,
863  $blobFlags,
864  $mainSlotRow->format_name,
865  $queryFlags
866  );
867  };
868  }
869 
870  $mainSlotRow->slot_id = $mainSlotRow->slot_revision_id;
871  return new SlotRecord( $mainSlotRow, $content );
872  }
873 
893  private function loadSlotContent(
894  SlotRecord $slot,
895  $blobData = null,
896  $blobFlags = null,
897  $blobFormat = null,
898  $queryFlags = 0
899  ) {
900  if ( $blobData !== null ) {
901  Assert::parameterType( 'string', $blobData, '$blobData' );
902  Assert::parameterType( 'string|null', $blobFlags, '$blobFlags' );
903 
904  $cacheKey = $slot->hasAddress() ? $slot->getAddress() : null;
905 
906  if ( $blobFlags === null ) {
907  // No blob flags, so use the blob verbatim.
908  $data = $blobData;
909  } else {
910  $data = $this->blobStore->expandBlob( $blobData, $blobFlags, $cacheKey );
911  if ( $data === false ) {
912  throw new RevisionAccessException(
913  "Failed to expand blob data using flags $blobFlags (key: $cacheKey)"
914  );
915  }
916  }
917 
918  } else {
919  $address = $slot->getAddress();
920  try {
921  $data = $this->blobStore->getBlob( $address, $queryFlags );
922  } catch ( BlobAccessException $e ) {
923  throw new RevisionAccessException(
924  "Failed to load data blob from $address: " . $e->getMessage(), 0, $e
925  );
926  }
927  }
928 
929  // Unserialize content
931 
932  $content = $handler->unserializeContent( $data, $blobFormat );
933  return $content;
934  }
935 
950  public function getRevisionById( $id, $flags = 0 ) {
951  return $this->newRevisionFromConds( [ 'rev_id' => intval( $id ) ], $flags );
952  }
953 
970  public function getRevisionByTitle( LinkTarget $linkTarget, $revId = 0, $flags = 0 ) {
971  $conds = [
972  'page_namespace' => $linkTarget->getNamespace(),
973  'page_title' => $linkTarget->getDBkey()
974  ];
975  if ( $revId ) {
976  // Use the specified revision ID.
977  // Note that we use newRevisionFromConds here because we want to retry
978  // and fall back to master if the page is not found on a replica.
979  // Since the caller supplied a revision ID, we are pretty sure the revision is
980  // supposed to exist, so we should try hard to find it.
981  $conds['rev_id'] = $revId;
982  return $this->newRevisionFromConds( $conds, $flags );
983  } else {
984  // Use a join to get the latest revision.
985  // Note that we don't use newRevisionFromConds here because we don't want to retry
986  // and fall back to master. The assumption is that we only want to force the fallback
987  // if we are quite sure the revision exists because the caller supplied a revision ID.
988  // If the page isn't found at all on a replica, it probably simply does not exist.
989  $db = $this->getDBConnection( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
990 
991  $conds[] = 'rev_id=page_latest';
992  $rev = $this->loadRevisionFromConds( $db, $conds, $flags );
993 
994  $this->releaseDBConnection( $db );
995  return $rev;
996  }
997  }
998 
1015  public function getRevisionByPageId( $pageId, $revId = 0, $flags = 0 ) {
1016  $conds = [ 'page_id' => $pageId ];
1017  if ( $revId ) {
1018  // Use the specified revision ID.
1019  // Note that we use newRevisionFromConds here because we want to retry
1020  // and fall back to master if the page is not found on a replica.
1021  // Since the caller supplied a revision ID, we are pretty sure the revision is
1022  // supposed to exist, so we should try hard to find it.
1023  $conds['rev_id'] = $revId;
1024  return $this->newRevisionFromConds( $conds, $flags );
1025  } else {
1026  // Use a join to get the latest revision.
1027  // Note that we don't use newRevisionFromConds here because we don't want to retry
1028  // and fall back to master. The assumption is that we only want to force the fallback
1029  // if we are quite sure the revision exists because the caller supplied a revision ID.
1030  // If the page isn't found at all on a replica, it probably simply does not exist.
1031  $db = $this->getDBConnection( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
1032 
1033  $conds[] = 'rev_id=page_latest';
1034  $rev = $this->loadRevisionFromConds( $db, $conds, $flags );
1035 
1036  $this->releaseDBConnection( $db );
1037  return $rev;
1038  }
1039  }
1040 
1052  public function getRevisionByTimestamp( $title, $timestamp ) {
1053  $db = $this->getDBConnection( DB_REPLICA );
1054  return $this->newRevisionFromConds(
1055  [
1056  'rev_timestamp' => $db->timestamp( $timestamp ),
1057  'page_namespace' => $title->getNamespace(),
1058  'page_title' => $title->getDBkey()
1059  ],
1060  0,
1061  $title
1062  );
1063  }
1064 
1082  public function newRevisionFromArchiveRow(
1083  $row,
1084  $queryFlags = 0,
1085  Title $title = null,
1086  array $overrides = []
1087  ) {
1088  Assert::parameterType( 'object', $row, '$row' );
1089 
1090  // check second argument, since Revision::newFromArchiveRow had $overrides in that spot.
1091  Assert::parameterType( 'integer', $queryFlags, '$queryFlags' );
1092 
1093  if ( !$title && isset( $overrides['title'] ) ) {
1094  if ( !( $overrides['title'] instanceof Title ) ) {
1095  throw new MWException( 'title field override must contain a Title object.' );
1096  }
1097 
1098  $title = $overrides['title'];
1099  }
1100 
1101  if ( !isset( $title ) ) {
1102  if ( isset( $row->ar_namespace ) && isset( $row->ar_title ) ) {
1103  $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
1104  } else {
1105  throw new InvalidArgumentException(
1106  'A Title or ar_namespace and ar_title must be given'
1107  );
1108  }
1109  }
1110 
1111  foreach ( $overrides as $key => $value ) {
1112  $field = "ar_$key";
1113  $row->$field = $value;
1114  }
1115 
1116  try {
1118  isset( $row->ar_user ) ? $row->ar_user : null,
1119  isset( $row->ar_user_text ) ? $row->ar_user_text : null,
1120  isset( $row->ar_actor ) ? $row->ar_actor : null
1121  );
1122  } catch ( InvalidArgumentException $ex ) {
1123  wfWarn( __METHOD__ . ': ' . $ex->getMessage() );
1124  $user = new UserIdentityValue( 0, '', 0 );
1125  }
1126 
1127  $comment = $this->commentStore
1128  // Legacy because $row may have come from self::selectFields()
1129  ->getCommentLegacy( $this->getDBConnection( DB_REPLICA ), 'ar_comment', $row, true );
1130 
1131  $mainSlot = $this->emulateMainSlot_1_29( $row, $queryFlags, $title );
1132  $slots = new RevisionSlots( [ 'main' => $mainSlot ] );
1133 
1134  return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->wikiId );
1135  }
1136 
1150  private function newRevisionFromRow_1_29( $row, $queryFlags = 0, Title $title = null ) {
1151  Assert::parameterType( 'object', $row, '$row' );
1152 
1153  if ( !$title ) {
1154  $pageId = isset( $row->rev_page ) ? $row->rev_page : 0; // XXX: also check page_id?
1155  $revId = isset( $row->rev_id ) ? $row->rev_id : 0;
1156 
1157  $title = $this->getTitle( $pageId, $revId, $queryFlags );
1158  }
1159 
1160  if ( !isset( $row->page_latest ) ) {
1161  $row->page_latest = $title->getLatestRevID();
1162  if ( $row->page_latest === 0 && $title->exists() ) {
1163  wfWarn( 'Encountered title object in limbo: ID ' . $title->getArticleID() );
1164  }
1165  }
1166 
1167  try {
1169  isset( $row->rev_user ) ? $row->rev_user : null,
1170  isset( $row->rev_user_text ) ? $row->rev_user_text : null,
1171  isset( $row->rev_actor ) ? $row->rev_actor : null
1172  );
1173  } catch ( InvalidArgumentException $ex ) {
1174  wfWarn( __METHOD__ . ': ' . $ex->getMessage() );
1175  $user = new UserIdentityValue( 0, '', 0 );
1176  }
1177 
1178  $comment = $this->commentStore
1179  // Legacy because $row may have come from self::selectFields()
1180  ->getCommentLegacy( $this->getDBConnection( DB_REPLICA ), 'rev_comment', $row, true );
1181 
1182  $mainSlot = $this->emulateMainSlot_1_29( $row, $queryFlags, $title );
1183  $slots = new RevisionSlots( [ 'main' => $mainSlot ] );
1184 
1185  return new RevisionStoreRecord( $title, $user, $comment, $row, $slots, $this->wikiId );
1186  }
1187 
1199  public function newRevisionFromRow( $row, $queryFlags = 0, Title $title = null ) {
1200  return $this->newRevisionFromRow_1_29( $row, $queryFlags, $title );
1201  }
1202 
1218  array $fields,
1219  $queryFlags = 0,
1220  Title $title = null
1221  ) {
1222  if ( !$title && isset( $fields['title'] ) ) {
1223  if ( !( $fields['title'] instanceof Title ) ) {
1224  throw new MWException( 'title field must contain a Title object.' );
1225  }
1226 
1227  $title = $fields['title'];
1228  }
1229 
1230  if ( !$title ) {
1231  $pageId = isset( $fields['page'] ) ? $fields['page'] : 0;
1232  $revId = isset( $fields['id'] ) ? $fields['id'] : 0;
1233 
1234  $title = $this->getTitle( $pageId, $revId, $queryFlags );
1235  }
1236 
1237  if ( !isset( $fields['page'] ) ) {
1238  $fields['page'] = $title->getArticleID( $queryFlags );
1239  }
1240 
1241  // if we have a content object, use it to set the model and type
1242  if ( !empty( $fields['content'] ) ) {
1243  if ( !( $fields['content'] instanceof Content ) ) {
1244  throw new MWException( 'content field must contain a Content object.' );
1245  }
1246 
1247  if ( !empty( $fields['text_id'] ) ) {
1248  throw new MWException(
1249  "Text already stored in external store (id {$fields['text_id']}), " .
1250  "can't serialize content object"
1251  );
1252  }
1253  }
1254 
1255  if (
1256  isset( $fields['comment'] )
1257  && !( $fields['comment'] instanceof CommentStoreComment )
1258  ) {
1259  $commentData = isset( $fields['comment_data'] ) ? $fields['comment_data'] : null;
1260 
1261  if ( $fields['comment'] instanceof Message ) {
1262  $fields['comment'] = CommentStoreComment::newUnsavedComment(
1263  $fields['comment'],
1264  $commentData
1265  );
1266  } else {
1267  $commentText = trim( strval( $fields['comment'] ) );
1268  $fields['comment'] = CommentStoreComment::newUnsavedComment(
1269  $commentText,
1270  $commentData
1271  );
1272  }
1273  }
1274 
1275  $mainSlot = $this->emulateMainSlot_1_29( $fields, $queryFlags, $title );
1276 
1277  $revision = new MutableRevisionRecord( $title, $this->wikiId );
1278  $this->initializeMutableRevisionFromArray( $revision, $fields );
1279  $revision->setSlot( $mainSlot );
1280 
1281  return $revision;
1282  }
1283 
1289  MutableRevisionRecord $record,
1290  array $fields
1291  ) {
1293  $user = null;
1294 
1295  if ( isset( $fields['user'] ) && ( $fields['user'] instanceof UserIdentity ) ) {
1296  $user = $fields['user'];
1297  } else {
1298  try {
1300  isset( $fields['user'] ) ? $fields['user'] : null,
1301  isset( $fields['user_text'] ) ? $fields['user_text'] : null,
1302  isset( $fields['actor'] ) ? $fields['actor'] : null
1303  );
1304  } catch ( InvalidArgumentException $ex ) {
1305  $user = null;
1306  }
1307  }
1308 
1309  if ( $user ) {
1310  $record->setUser( $user );
1311  }
1312 
1313  $timestamp = isset( $fields['timestamp'] )
1314  ? strval( $fields['timestamp'] )
1315  : wfTimestampNow(); // TODO: use a callback, so we can override it for testing.
1316 
1317  $record->setTimestamp( $timestamp );
1318 
1319  if ( isset( $fields['page'] ) ) {
1320  $record->setPageId( intval( $fields['page'] ) );
1321  }
1322 
1323  if ( isset( $fields['id'] ) ) {
1324  $record->setId( intval( $fields['id'] ) );
1325  }
1326  if ( isset( $fields['parent_id'] ) ) {
1327  $record->setParentId( intval( $fields['parent_id'] ) );
1328  }
1329 
1330  if ( isset( $fields['sha1'] ) ) {
1331  $record->setSha1( $fields['sha1'] );
1332  }
1333  if ( isset( $fields['size'] ) ) {
1334  $record->setSize( intval( $fields['size'] ) );
1335  }
1336 
1337  if ( isset( $fields['minor_edit'] ) ) {
1338  $record->setMinorEdit( intval( $fields['minor_edit'] ) !== 0 );
1339  }
1340  if ( isset( $fields['deleted'] ) ) {
1341  $record->setVisibility( intval( $fields['deleted'] ) );
1342  }
1343 
1344  if ( isset( $fields['comment'] ) ) {
1345  Assert::parameterType(
1347  $fields['comment'],
1348  '$row[\'comment\']'
1349  );
1350  $record->setComment( $fields['comment'] );
1351  }
1352  }
1353 
1368  public function loadRevisionFromId( IDatabase $db, $id ) {
1369  return $this->loadRevisionFromConds( $db, [ 'rev_id' => intval( $id ) ] );
1370  }
1371 
1387  public function loadRevisionFromPageId( IDatabase $db, $pageid, $id = 0 ) {
1388  $conds = [ 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) ];
1389  if ( $id ) {
1390  $conds['rev_id'] = intval( $id );
1391  } else {
1392  $conds[] = 'rev_id=page_latest';
1393  }
1394  return $this->loadRevisionFromConds( $db, $conds );
1395  }
1396 
1413  public function loadRevisionFromTitle( IDatabase $db, $title, $id = 0 ) {
1414  if ( $id ) {
1415  $matchId = intval( $id );
1416  } else {
1417  $matchId = 'page_latest';
1418  }
1419 
1420  return $this->loadRevisionFromConds(
1421  $db,
1422  [
1423  "rev_id=$matchId",
1424  'page_namespace' => $title->getNamespace(),
1425  'page_title' => $title->getDBkey()
1426  ],
1427  0,
1428  $title
1429  );
1430  }
1431 
1447  public function loadRevisionFromTimestamp( IDatabase $db, $title, $timestamp ) {
1448  return $this->loadRevisionFromConds( $db,
1449  [
1450  'rev_timestamp' => $db->timestamp( $timestamp ),
1451  'page_namespace' => $title->getNamespace(),
1452  'page_title' => $title->getDBkey()
1453  ],
1454  0,
1455  $title
1456  );
1457  }
1458 
1474  private function newRevisionFromConds( $conditions, $flags = 0, Title $title = null ) {
1475  $db = $this->getDBConnection( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
1476  $rev = $this->loadRevisionFromConds( $db, $conditions, $flags, $title );
1477  $this->releaseDBConnection( $db );
1478 
1479  $lb = $this->getDBLoadBalancer();
1480 
1481  // Make sure new pending/committed revision are visibile later on
1482  // within web requests to certain avoid bugs like T93866 and T94407.
1483  if ( !$rev
1484  && !( $flags & self::READ_LATEST )
1485  && $lb->getServerCount() > 1
1486  && $lb->hasOrMadeRecentMasterChanges()
1487  ) {
1488  $flags = self::READ_LATEST;
1489  $db = $this->getDBConnection( DB_MASTER );
1490  $rev = $this->loadRevisionFromConds( $db, $conditions, $flags, $title );
1491  $this->releaseDBConnection( $db );
1492  }
1493 
1494  return $rev;
1495  }
1496 
1510  private function loadRevisionFromConds(
1511  IDatabase $db,
1512  $conditions,
1513  $flags = 0,
1514  Title $title = null
1515  ) {
1516  $row = $this->fetchRevisionRowFromConds( $db, $conditions, $flags );
1517  if ( $row ) {
1518  $rev = $this->newRevisionFromRow( $row, $flags, $title );
1519 
1520  return $rev;
1521  }
1522 
1523  return null;
1524  }
1525 
1533  private function checkDatabaseWikiId( IDatabase $db ) {
1534  $storeWiki = $this->wikiId;
1535  $dbWiki = $db->getDomainID();
1536 
1537  if ( $dbWiki === $storeWiki ) {
1538  return;
1539  }
1540 
1541  // XXX: we really want the default database ID...
1542  $storeWiki = $storeWiki ?: wfWikiID();
1543  $dbWiki = $dbWiki ?: wfWikiID();
1544 
1545  if ( $dbWiki === $storeWiki ) {
1546  return;
1547  }
1548 
1549  // HACK: counteract encoding imposed by DatabaseDomain
1550  $storeWiki = str_replace( '?h', '-', $storeWiki );
1551  $dbWiki = str_replace( '?h', '-', $dbWiki );
1552 
1553  if ( $dbWiki === $storeWiki ) {
1554  return;
1555  }
1556 
1557  throw new MWException( "RevisionStore for $storeWiki "
1558  . "cannot be used with a DB connection for $dbWiki" );
1559  }
1560 
1573  private function fetchRevisionRowFromConds( IDatabase $db, $conditions, $flags = 0 ) {
1574  $this->checkDatabaseWikiId( $db );
1575 
1576  $revQuery = self::getQueryInfo( [ 'page', 'user' ] );
1577  $options = [];
1578  if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
1579  $options[] = 'FOR UPDATE';
1580  }
1581  return $db->selectRow(
1582  $revQuery['tables'],
1583  $revQuery['fields'],
1584  $conditions,
1585  __METHOD__,
1586  $options,
1587  $revQuery['joins']
1588  );
1589  }
1590 
1609  public function getQueryInfo( $options = [] ) {
1610  $ret = [
1611  'tables' => [],
1612  'fields' => [],
1613  'joins' => [],
1614  ];
1615 
1616  $ret['tables'][] = 'revision';
1617  $ret['fields'] = array_merge( $ret['fields'], [
1618  'rev_id',
1619  'rev_page',
1620  'rev_text_id',
1621  'rev_timestamp',
1622  'rev_minor_edit',
1623  'rev_deleted',
1624  'rev_len',
1625  'rev_parent_id',
1626  'rev_sha1',
1627  ] );
1628 
1629  $commentQuery = $this->commentStore->getJoin( 'rev_comment' );
1630  $ret['tables'] = array_merge( $ret['tables'], $commentQuery['tables'] );
1631  $ret['fields'] = array_merge( $ret['fields'], $commentQuery['fields'] );
1632  $ret['joins'] = array_merge( $ret['joins'], $commentQuery['joins'] );
1633 
1634  $actorQuery = $this->actorMigration->getJoin( 'rev_user' );
1635  $ret['tables'] = array_merge( $ret['tables'], $actorQuery['tables'] );
1636  $ret['fields'] = array_merge( $ret['fields'], $actorQuery['fields'] );
1637  $ret['joins'] = array_merge( $ret['joins'], $actorQuery['joins'] );
1638 
1639  if ( $this->contentHandlerUseDB ) {
1640  $ret['fields'][] = 'rev_content_format';
1641  $ret['fields'][] = 'rev_content_model';
1642  }
1643 
1644  if ( in_array( 'page', $options, true ) ) {
1645  $ret['tables'][] = 'page';
1646  $ret['fields'] = array_merge( $ret['fields'], [
1647  'page_namespace',
1648  'page_title',
1649  'page_id',
1650  'page_latest',
1651  'page_is_redirect',
1652  'page_len',
1653  ] );
1654  $ret['joins']['page'] = [ 'INNER JOIN', [ 'page_id = rev_page' ] ];
1655  }
1656 
1657  if ( in_array( 'user', $options, true ) ) {
1658  $ret['tables'][] = 'user';
1659  $ret['fields'] = array_merge( $ret['fields'], [
1660  'user_name',
1661  ] );
1662  $u = $actorQuery['fields']['rev_user'];
1663  $ret['joins']['user'] = [ 'LEFT JOIN', [ "$u != 0", "user_id = $u" ] ];
1664  }
1665 
1666  if ( in_array( 'text', $options, true ) ) {
1667  $ret['tables'][] = 'text';
1668  $ret['fields'] = array_merge( $ret['fields'], [
1669  'old_text',
1670  'old_flags'
1671  ] );
1672  $ret['joins']['text'] = [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ];
1673  }
1674 
1675  return $ret;
1676  }
1677 
1691  public function getArchiveQueryInfo() {
1692  $commentQuery = $this->commentStore->getJoin( 'ar_comment' );
1693  $actorQuery = $this->actorMigration->getJoin( 'ar_user' );
1694  $ret = [
1695  'tables' => [ 'archive' ] + $commentQuery['tables'] + $actorQuery['tables'],
1696  'fields' => [
1697  'ar_id',
1698  'ar_page_id',
1699  'ar_namespace',
1700  'ar_title',
1701  'ar_rev_id',
1702  'ar_text_id',
1703  'ar_timestamp',
1704  'ar_minor_edit',
1705  'ar_deleted',
1706  'ar_len',
1707  'ar_parent_id',
1708  'ar_sha1',
1709  ] + $commentQuery['fields'] + $actorQuery['fields'],
1710  'joins' => $commentQuery['joins'] + $actorQuery['joins'],
1711  ];
1712 
1713  if ( $this->contentHandlerUseDB ) {
1714  $ret['fields'][] = 'ar_content_format';
1715  $ret['fields'][] = 'ar_content_model';
1716  }
1717 
1718  return $ret;
1719  }
1720 
1730  public function getRevisionSizes( array $revIds ) {
1731  return $this->listRevisionSizes( $this->getDBConnection( DB_REPLICA ), $revIds );
1732  }
1733 
1746  public function listRevisionSizes( IDatabase $db, array $revIds ) {
1747  $this->checkDatabaseWikiId( $db );
1748 
1749  $revLens = [];
1750  if ( !$revIds ) {
1751  return $revLens; // empty
1752  }
1753 
1754  $res = $db->select(
1755  'revision',
1756  [ 'rev_id', 'rev_len' ],
1757  [ 'rev_id' => $revIds ],
1758  __METHOD__
1759  );
1760 
1761  foreach ( $res as $row ) {
1762  $revLens[$row->rev_id] = intval( $row->rev_len );
1763  }
1764 
1765  return $revLens;
1766  }
1767 
1778  public function getPreviousRevision( RevisionRecord $rev, Title $title = null ) {
1779  if ( $title === null ) {
1780  $title = $this->getTitle( $rev->getPageId(), $rev->getId() );
1781  }
1782  $prev = $title->getPreviousRevisionID( $rev->getId() );
1783  if ( $prev ) {
1784  return $this->getRevisionByTitle( $title, $prev );
1785  }
1786  return null;
1787  }
1788 
1799  public function getNextRevision( RevisionRecord $rev, Title $title = null ) {
1800  if ( $title === null ) {
1801  $title = $this->getTitle( $rev->getPageId(), $rev->getId() );
1802  }
1803  $next = $title->getNextRevisionID( $rev->getId() );
1804  if ( $next ) {
1805  return $this->getRevisionByTitle( $title, $next );
1806  }
1807  return null;
1808  }
1809 
1822  $this->checkDatabaseWikiId( $db );
1823 
1824  if ( $rev->getPageId() === null ) {
1825  return 0;
1826  }
1827  # Use page_latest if ID is not given
1828  if ( !$rev->getId() ) {
1829  $prevId = $db->selectField(
1830  'page', 'page_latest',
1831  [ 'page_id' => $rev->getPageId() ],
1832  __METHOD__
1833  );
1834  } else {
1835  $prevId = $db->selectField(
1836  'revision', 'rev_id',
1837  [ 'rev_page' => $rev->getPageId(), 'rev_id < ' . $rev->getId() ],
1838  __METHOD__,
1839  [ 'ORDER BY' => 'rev_id DESC' ]
1840  );
1841  }
1842  return intval( $prevId );
1843  }
1844 
1855  public function getTimestampFromId( $title, $id, $flags = 0 ) {
1856  $db = $this->getDBConnection(
1857  ( $flags & IDBAccessObject::READ_LATEST ) ? DB_MASTER : DB_REPLICA
1858  );
1859 
1860  $conds = [ 'rev_id' => $id ];
1861  $conds['rev_page'] = $title->getArticleID();
1862  $timestamp = $db->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
1863 
1864  $this->releaseDBConnection( $db );
1865  return ( $timestamp !== false ) ? wfTimestamp( TS_MW, $timestamp ) : false;
1866  }
1867 
1877  public function countRevisionsByPageId( IDatabase $db, $id ) {
1878  $this->checkDatabaseWikiId( $db );
1879 
1880  $row = $db->selectRow( 'revision',
1881  [ 'revCount' => 'COUNT(*)' ],
1882  [ 'rev_page' => $id ],
1883  __METHOD__
1884  );
1885  if ( $row ) {
1886  return intval( $row->revCount );
1887  }
1888  return 0;
1889  }
1890 
1900  public function countRevisionsByTitle( IDatabase $db, $title ) {
1901  $id = $title->getArticleID();
1902  if ( $id ) {
1903  return $this->countRevisionsByPageId( $db, $id );
1904  }
1905  return 0;
1906  }
1907 
1926  public function userWasLastToEdit( IDatabase $db, $pageId, $userId, $since ) {
1927  $this->checkDatabaseWikiId( $db );
1928 
1929  if ( !$userId ) {
1930  return false;
1931  }
1932 
1934  $res = $db->select(
1935  $revQuery['tables'],
1936  [
1937  'rev_user' => $revQuery['fields']['rev_user'],
1938  ],
1939  [
1940  'rev_page' => $pageId,
1941  'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
1942  ],
1943  __METHOD__,
1944  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ],
1945  $revQuery['joins']
1946  );
1947  foreach ( $res as $row ) {
1948  if ( $row->rev_user != $userId ) {
1949  return false;
1950  }
1951  }
1952  return true;
1953  }
1954 
1968  public function getKnownCurrentRevision( Title $title, $revId ) {
1969  $db = $this->getDBConnectionRef( DB_REPLICA );
1970 
1971  $pageId = $title->getArticleID();
1972 
1973  if ( !$pageId ) {
1974  return false;
1975  }
1976 
1977  if ( !$revId ) {
1978  $revId = $title->getLatestRevID();
1979  }
1980 
1981  if ( !$revId ) {
1982  wfWarn(
1983  'No latest revision known for page ' . $title->getPrefixedDBkey()
1984  . ' even though it exists with page ID ' . $pageId
1985  );
1986  return false;
1987  }
1988 
1989  $row = $this->cache->getWithSetCallback(
1990  // Page/rev IDs passed in from DB to reflect history merges
1991  $this->cache->makeGlobalKey( 'revision-row-1.29', $db->getDomainID(), $pageId, $revId ),
1993  function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
1994  $setOpts += Database::getCacheSetOptions( $db );
1995 
1996  $conds = [
1997  'rev_page' => intval( $pageId ),
1998  'page_id' => intval( $pageId ),
1999  'rev_id' => intval( $revId ),
2000  ];
2001 
2002  $row = $this->fetchRevisionRowFromConds( $db, $conds );
2003  return $row ?: false; // don't cache negatives
2004  }
2005  );
2006 
2007  // Reflect revision deletion and user renames
2008  if ( $row ) {
2009  return $this->newRevisionFromRow( $row, 0, $title );
2010  } else {
2011  return false;
2012  }
2013  }
2014 
2015  // TODO: move relevant methods from Title here, e.g. getFirstRevision, isBigDeletion, etc.
2016 
2017 }
Content\getContentHandler
getContentHandler()
Convenience method that returns the ContentHandler singleton for handling the content model that this...
MediaWiki\User\UserIdentityValue
Value object representing a user's identity.
Definition: UserIdentityValue.php:32
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:247
IP\toHex
static toHex( $ip)
Return a zero-padded upper case hexadecimal representation of an IP address.
Definition: IP.php:417
MediaWiki\Storage\RevisionStore\getArchiveQueryInfo
getArchiveQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new archived revision objec...
Definition: RevisionStore.php:1691
MediaWiki\Storage\RevisionStore\$commentStore
CommentStore $commentStore
Definition: RevisionStore.php:99
MediaWiki\Storage\BlobStore\PAGE_HINT
const PAGE_HINT
Hint key for use with storeBlob, indicating the page the blob is associated with.
Definition: BlobStore.php:46
ContentHandler
A content handler knows how do deal with a specific type of content on a wiki page.
Definition: ContentHandler.php:49
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:293
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:48
Content\getDefaultFormat
getDefaultFormat()
Convenience method that returns the default serialization format for the content model that this Cont...
$handler
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:903
CommentStoreComment\newUnsavedComment
static newUnsavedComment( $comment, array $data=null)
Create a new, unsaved CommentStoreComment.
Definition: CommentStoreComment.php:65
MediaWiki\Storage\BlobStore\DESIGNATION_HINT
const DESIGNATION_HINT
Hint key for use with storeBlob, indicating the general role the block takes in the application.
Definition: BlobStore.php:40
MediaWiki\Storage\BlobAccessException
Exception representing a failure to access a data blob.
Definition: BlobAccessException.php:32
MediaWiki\Storage\RevisionStore\newNullRevision
newNullRevision(IDatabase $dbw, Title $title, CommentStoreComment $comment, $minor, User $user)
Create a new null-revision for insertion into a page's history.
Definition: RevisionStore.php:567
MediaWiki\Storage\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:69
MediaWiki\Storage\RevisionLookup
Service for looking up page revisions.
Definition: RevisionLookup.php:37
MediaWiki\Storage\RevisionStore\getDBLoadBalancer
getDBLoadBalancer()
Definition: RevisionStore.php:168
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
MediaWiki\Storage\RevisionStore\newMutableRevisionFromArray
newMutableRevisionFromArray(array $fields, $queryFlags=0, Title $title=null)
Constructs a new MutableRevisionRecord based on the given associative array following the MW1....
Definition: RevisionStore.php:1217
Content\isValid
isValid()
Returns whether the content is valid.
array
the array() calling protocol came about after MediaWiki 1.4rc1.
MediaWiki\Storage\MutableRevisionRecord\setVisibility
setVisibility( $visibility)
Definition: MutableRevisionRecord.php:223
MediaWiki\Storage\RevisionStore\$wikiId
bool string $wikiId
Definition: RevisionStore.php:79
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:194
MediaWiki\Storage\MutableRevisionRecord\setParentId
setParentId( $parentId)
Definition: MutableRevisionRecord.php:99
MediaWiki\Storage\RevisionStore\insertRevisionOn
insertRevisionOn(RevisionRecord $rev, IDatabase $dbw)
Insert a new revision into the database, returning the new revision record on success and dies horrib...
Definition: RevisionStore.php:328
MediaWiki\Storage\SlotRecord\getModel
getModel()
Returns the content model.
Definition: SlotRecord.php:518
MediaWiki\Storage\SqlBlobStore
Service for storing and loading Content objects.
Definition: SqlBlobStore.php:50
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:68
MediaWiki\Storage\RevisionStore\newRevisionFromRow_1_29
newRevisionFromRow_1_29( $row, $queryFlags=0, Title $title=null)
Definition: RevisionStore.php:1150
MediaWiki\Storage\RevisionStore\failOnEmpty
failOnEmpty( $value, $name)
Definition: RevisionStore.php:306
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1980
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:2005
MediaWiki\Storage\MutableRevisionRecord\setId
setId( $id)
Set the revision ID.
Definition: MutableRevisionRecord.php:258
MediaWiki\Storage\RevisionStore\countRevisionsByPageId
countRevisionsByPageId(IDatabase $db, $id)
Get count of revisions per page...not very efficient.
Definition: RevisionStore.php:1877
MediaWiki\Storage\SlotRecord\newSaved
static newSaved( $revisionId, $contentId, $contentAddress, SlotRecord $protoSlot)
Constructs a complete SlotRecord for a newly saved revision, based on the incomplete proto-slot.
Definition: SlotRecord.php:159
MediaWiki\Storage\RevisionStore\loadRevisionFromConds
loadRevisionFromConds(IDatabase $db, $conditions, $flags=0, Title $title=null)
Given a set of conditions, fetch a revision from the given database connection.
Definition: RevisionStore.php:1510
IP
A collection of public static functions to play with IP address and IP ranges.
Definition: IP.php:67
CommentStore
CommentStore handles storage of comments (edit summaries, log reasons, etc) in the database.
Definition: CommentStore.php:31
MediaWiki\Storage\RevisionStore\listRevisionSizes
listRevisionSizes(IDatabase $db, array $revIds)
Do a batched query for the sizes of a set of revisions.
Definition: RevisionStore.php:1746
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:657
$res
$res
Definition: database.txt:21
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
ActorMigration
This class handles the logic for the actor table migration.
Definition: ActorMigration.php:35
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
User
User
Definition: All_system_messages.txt:425
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
MediaWiki\Storage\RevisionSlots
Value object representing the set of slots belonging to a revision.
Definition: RevisionSlots.php:34
MediaWiki\Storage\RevisionStore\getRevisionByTitle
getRevisionByTitle(LinkTarget $linkTarget, $revId=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: RevisionStore.php:970
cache
you have access to all of the normal MediaWiki so you can get a DB use the cache
Definition: maintenance.txt:55
$revQuery
$revQuery
Definition: testCompression.php:51
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
Wikimedia\Rdbms\IDatabase\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
MediaWiki\Storage\RevisionStore\getRcIdIfUnpatrolled
getRcIdIfUnpatrolled(RevisionRecord $rev)
MCR migration note: this replaces Revision::isUnpatrolled.
Definition: RevisionStore.php:637
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:37
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
MediaWiki\Storage\RevisionStore\getTimestampFromId
getTimestampFromId( $title, $id, $flags=0)
Get rev_timestamp from rev_id, without loading the rest of the row.
Definition: RevisionStore.php:1855
$dbr
$dbr
Definition: testCompression.php:50
MediaWiki\Storage\RevisionFactory
Service for constructing revision objects.
Definition: RevisionFactory.php:36
MediaWiki\Storage\RevisionStore\setContentHandlerUseDB
setContentHandlerUseDB( $contentHandlerUseDB)
Definition: RevisionStore.php:161
MediaWiki\Storage\RevisionStore\getKnownCurrentRevision
getKnownCurrentRevision(Title $title, $revId)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: RevisionStore.php:1968
MediaWiki\Storage\RevisionRecord\getComment
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment, if it's available to the specified audience.
Definition: RevisionRecord.php:346
MediaWiki\Storage\SlotRecord\hasAddress
hasAddress()
Whether this slot has an address.
Definition: SlotRecord.php:429
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
MediaWiki\Storage\RevisionRecord\getUser
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's author's user identity, if it's available to the specified audience.
Definition: RevisionRecord.php:321
MediaWiki\Storage\MutableRevisionRecord\setComment
setComment(CommentStoreComment $comment)
Definition: MutableRevisionRecord.php:186
MediaWiki\Storage\RevisionStore\getRevisionByPageId
getRevisionByPageId( $pageId, $revId=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given page ID.
Definition: RevisionStore.php:1015
MediaWiki\Storage\RevisionStore\$logger
LoggerInterface $logger
Definition: RevisionStore.php:109
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
ContentHandler\getDefaultModelFor
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
Definition: ContentHandler.php:178
MediaWiki\Storage\MutableRevisionRecord\setUser
setUser(UserIdentity $user)
Sets the user identity associated with the revision.
Definition: MutableRevisionRecord.php:269
MWException
MediaWiki exception.
Definition: MWException.php:26
MediaWiki\Storage\BlobStore\SHA1_HINT
const SHA1_HINT
Hint key for use with storeBlob, providing the SHA1 hash of the blob as passed to the method.
Definition: BlobStore.php:70
MediaWiki\Storage\RevisionStore\$cache
WANObjectCache $cache
Definition: RevisionStore.php:94
Wikimedia\Rdbms\Database\getCacheSetOptions
static getCacheSetOptions(IDatabase $db1, IDatabase $db2=null)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:4069
Title\newFromRow
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:464
MediaWiki\Storage\RevisionStore\loadSlotContent
loadSlotContent(SlotRecord $slot, $blobData=null, $blobFlags=null, $blobFormat=null, $queryFlags=0)
Loads a Content object based on a slot row.
Definition: RevisionStore.php:893
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition: Title.php:244
MediaWiki\Storage\SlotRecord\getAddress
getAddress()
Returns the address of this slot's content.
Definition: SlotRecord.php:459
MediaWiki\Storage\RevisionStore\getRecentChange
getRecentChange(RevisionRecord $rev, $flags=0)
Get the RC object belonging to the current revision, if there's one.
Definition: RevisionStore.php:659
MediaWiki\Storage\RevisionStore\getTitle
getTitle( $pageId, $revId, $queryFlags=self::READ_NORMAL)
Determines the page Title based on the available information.
Definition: RevisionStore.php:214
MediaWiki\Storage\RevisionStore\__construct
__construct(LoadBalancer $loadBalancer, SqlBlobStore $blobStore, WANObjectCache $cache, CommentStore $commentStore, ActorMigration $actorMigration, $wikiId=false)
Definition: RevisionStore.php:121
MediaWiki\Storage\RevisionRecord\getSha1
getSha1()
Returns the base36 sha1 of this revision.
MediaWiki\Storage\BlobStore\MODEL_HINT
const MODEL_HINT
Hint key for use with storeBlob, indicating the model of the content encoded in the given blob.
Definition: BlobStore.php:76
MediaWiki\Storage\RevisionStore\getNextRevision
getNextRevision(RevisionRecord $rev, Title $title=null)
Get next revision for this title.
Definition: RevisionStore.php:1799
MediaWiki\Storage\RevisionStoreRecord
A RevisionRecord representing an existing revision persisted in the revision table.
Definition: RevisionStoreRecord.php:38
MediaWiki\Storage\RevisionStore\getContentHandlerUseDB
getContentHandlerUseDB()
Definition: RevisionStore.php:154
MediaWiki\Storage\RevisionStore\getPreviousRevision
getPreviousRevision(RevisionRecord $rev, Title $title=null)
Get previous revision for this title.
Definition: RevisionStore.php:1778
MediaWiki\Storage\RevisionStore\failOnNull
failOnNull( $value, $name)
Definition: RevisionStore.php:289
function
when a variable name is used in a function
Definition: design.txt:93
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:534
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2009
MediaWiki\Storage\RevisionStore\loadRevisionFromPageId
loadRevisionFromPageId(IDatabase $db, $pageid, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: RevisionStore.php:1387
DB_MASTER
const DB_MASTER
Definition: defines.php:26
MediaWiki\Storage\RevisionStore\setLogger
setLogger(LoggerInterface $logger)
Definition: RevisionStore.php:140
DBAccessObjectUtils
Helper class for DAO classes.
Definition: DBAccessObjectUtils.php:29
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
MediaWiki\Storage\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:44
Wikimedia\Rdbms\LoadBalancer
Database connection, tracking, load balancing, and transaction manager for a cluster.
Definition: LoadBalancer.php:41
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2001
MediaWiki\Storage\MutableRevisionRecord\setSha1
setSha1( $sha1)
Set revision hash, for optimization.
Definition: MutableRevisionRecord.php:199
MediaWiki\Storage\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:57
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2763
$value
$value
Definition: styleTest.css.php:45
DBAccessObjectUtils\hasFlags
static hasFlags( $bitfield, $flags)
Definition: DBAccessObjectUtils.php:35
MediaWiki\Storage\RevisionAccessException
Exception representing a failure to look up a revision.
Definition: RevisionAccessException.php:32
MediaWiki\Storage\RevisionArchiveRecord
A RevisionRecord representing a revision of a deleted page persisted in the archive table.
Definition: RevisionArchiveRecord.php:39
Wikimedia\Rdbms\IDatabase\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
Wikimedia\Rdbms\IDatabase\getDomainID
getDomainID()
Title\GAID_FOR_UPDATE
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:54
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:87
MediaWiki\Storage\MutableRevisionRecord\setMinorEdit
setMinorEdit( $minorEdit)
Definition: MutableRevisionRecord.php:241
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
MediaWiki\Storage\RevisionStore\checkDatabaseWikiId
checkDatabaseWikiId(IDatabase $db)
Throws an exception if the given database connection does not belong to the wiki this RevisionStore i...
Definition: RevisionStore.php:1533
MediaWiki\Storage\RevisionStore\getDBConnection
getDBConnection( $mode)
Definition: RevisionStore.php:177
MediaWiki\Storage\RevisionStore\getQueryInfo
getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object.
Definition: RevisionStore.php:1609
IP\isValid
static isValid( $ip)
Validate an IP address.
Definition: IP.php:111
MediaWiki\Storage
Definition: BlobAccessException.php:23
MediaWiki\Storage\RevisionStore\$actorMigration
ActorMigration $actorMigration
Definition: RevisionStore.php:104
MediaWiki\Storage\RevisionStore\emulateMainSlot_1_29
emulateMainSlot_1_29( $row, $queryFlags, Title $title)
Constructs a RevisionRecord for the revisions main slot, based on the MW1.29 schema.
Definition: RevisionStore.php:747
MediaWiki\Storage\RevisionStore\userWasLastToEdit
userWasLastToEdit(IDatabase $db, $pageId, $userId, $since)
Check if no edits were made by other users since the time a user started editing the page.
Definition: RevisionStore.php:1926
Content
Base interface for content objects.
Definition: Content.php:34
IExpiringStore\TTL_WEEK
const TTL_WEEK
Definition: IExpiringStore.php:37
MediaWiki\Storage\RevisionStore\fetchRevisionRowFromConds
fetchRevisionRowFromConds(IDatabase $db, $conditions, $flags=0)
Given a set of conditions, return a row with the fields necessary to build RevisionRecord objects.
Definition: RevisionStore.php:1573
MediaWiki\Storage\RevisionRecord\getSize
getSize()
Returns the nominal size of this revision, in bogo-bytes.
$rev
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1777
Wikimedia\Rdbms\DBConnRef
Helper class to handle automatically marking connections as reusable (via RAII pattern) as well handl...
Definition: DBConnRef.php:15
MediaWiki\Storage\RevisionStore\newRevisionFromArchiveRow
newRevisionFromArchiveRow( $row, $queryFlags=0, Title $title=null, array $overrides=[])
Make a fake revision object from an archive table row.
Definition: RevisionStore.php:1082
Title
Represents a title within MediaWiki.
Definition: Title.php:39
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
MediaWiki\Storage\MutableRevisionRecord
Mutable RevisionRecord implementation, for building new revision entries programmatically.
Definition: MutableRevisionRecord.php:39
MediaWiki\Storage\RevisionStore\mapArchiveFields
static mapArchiveFields( $archiveRow)
Maps fields of the archive row to corresponding revision rows.
Definition: RevisionStore.php:697
MediaWiki\Storage\RevisionStore\getRevisionSizes
getRevisionSizes(array $revIds)
Do a batched query for the sizes of a set of revisions.
Definition: RevisionStore.php:1730
Wikimedia\Rdbms\IDatabase\addQuotes
addQuotes( $s)
Adds quotes and backslashes.
MediaWiki\Storage\RevisionStore\countRevisionsByTitle
countRevisionsByTitle(IDatabase $db, $title)
Get count of revisions per page...not very efficient.
Definition: RevisionStore.php:1900
MediaWiki\Storage\RevisionStore\isReadOnly
isReadOnly()
Definition: RevisionStore.php:147
MediaWiki\Storage\RevisionStore\getRevisionByTimestamp
getRevisionByTimestamp( $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: RevisionStore.php:1052
MediaWiki\Storage\IncompleteRevisionException
Exception throw when trying to access undefined fields on an incomplete RevisionRecord.
Definition: IncompleteRevisionException.php:30
MediaWiki\Storage\RevisionStore\newRevisionFromConds
newRevisionFromConds( $conditions, $flags=0, Title $title=null)
Given a set of conditions, fetch a revision.
Definition: RevisionStore.php:1474
MediaWiki\Storage\RevisionStore\checkContentModel
checkContentModel(Content $content, Title $title)
MCR migration note: this corresponds to Revision::checkContentModel.
Definition: RevisionStore.php:504
MediaWiki\Storage\RevisionStore\newRevisionFromRow
newRevisionFromRow( $row, $queryFlags=0, Title $title=null)
Definition: RevisionStore.php:1199
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:77
MediaWiki\Storage\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:38
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:22
MediaWiki\Storage\RevisionStore\releaseDBConnection
releaseDBConnection(IDatabase $connection)
Definition: RevisionStore.php:185
MediaWiki\Storage\RevisionRecord\getTimestamp
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
Definition: RevisionRecord.php:392
Message
The Message class provides methods which fulfil two basic services:
Definition: Message.php:159
MediaWiki\Storage\RevisionStore\loadRevisionFromTitle
loadRevisionFromTitle(IDatabase $db, $title, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: RevisionStore.php:1413
MediaWiki\Storage\MutableRevisionRecord\setSize
setSize( $size)
Set nominal revision size, for optimization.
Definition: MutableRevisionRecord.php:214
Wikimedia\Rdbms\IDatabase\select
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
wfBacktrace
wfBacktrace( $raw=null)
Get a debug backtrace as a string.
Definition: GlobalFunctions.php:1510
MediaWiki\Storage\BlobStore\PARENT_HINT
const PARENT_HINT
Hint key for use with storeBlob, indicating the parent revision of the revision the blob is associate...
Definition: BlobStore.php:64
Content\getModel
getModel()
Returns the ID of the content model used by this Content object.
MediaWiki\Storage\MutableRevisionRecord\setTimestamp
setTimestamp( $timestamp)
Definition: MutableRevisionRecord.php:232
MediaWiki\Storage\BlobStore\ROLE_HINT
const ROLE_HINT
Hint key for use with storeBlob, indicating the slot the blob is associated with.
Definition: BlobStore.php:52
MWUnknownContentModelException
Exception thrown when an unregistered content model is requested.
Definition: MWUnknownContentModelException.php:10
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1137
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:56
MediaWiki\Storage\RevisionStore\$loadBalancer
LoadBalancer $loadBalancer
Definition: RevisionStore.php:89
Wikimedia\Rdbms\IDatabase\insertId
insertId()
Get the inserted value of an auto-increment row.
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
MediaWiki\Storage\RevisionStore\getRevisionById
getRevisionById( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: RevisionStore.php:950
MediaWiki\Storage\RevisionStore\getPreviousRevisionId
getPreviousRevisionId(IDatabase $db, RevisionRecord $rev)
Get previous revision Id for this page_id This is used to populate rev_parent_id on save.
Definition: RevisionStore.php:1821
MediaWiki\Storage\RevisionStore\$contentHandlerUseDB
boolean $contentHandlerUseDB
Definition: RevisionStore.php:84
MediaWiki\Storage\RevisionStore\loadRevisionFromId
loadRevisionFromId(IDatabase $db, $id)
Load a page revision from a given revision ID number.
Definition: RevisionStore.php:1368
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:416
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
MediaWiki\Storage\RevisionStore\loadRevisionFromTimestamp
loadRevisionFromTimestamp(IDatabase $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: RevisionStore.php:1447
MediaWiki\Storage\MutableRevisionRecord\setPageId
setPageId( $pageId)
Definition: MutableRevisionRecord.php:276
MediaWiki\Storage\RevisionStore\initializeMutableRevisionFromArray
initializeMutableRevisionFromArray(MutableRevisionRecord $record, array $fields)
Definition: RevisionStore.php:1288
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
CommentStoreComment
CommentStoreComment represents a comment stored by CommentStore.
Definition: CommentStoreComment.php:28
Hooks
Hooks class.
Definition: Hooks.php:34
MediaWiki\Storage\RevisionStore\$blobStore
SqlBlobStore $blobStore
Definition: RevisionStore.php:74
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2171
MediaWiki\Storage\RevisionRecord\getPageId
getPageId()
Get the page ID.
Definition: RevisionRecord.php:281
MediaWiki\Storage\BlobStore\FORMAT_HINT
const FORMAT_HINT
Hint key for use with storeBlob, indicating the serialization format used to create the blob,...
Definition: BlobStore.php:82
MediaWiki\Storage\RevisionStore\getDBConnectionRef
getDBConnectionRef( $mode)
Definition: RevisionStore.php:195