1<?php 2 3use MediaWiki\Revision\MutableRevisionRecord; 4use MediaWiki\Revision\RevisionStore; 5use MediaWiki\Revision\SlotRoleRegistry; 6use Psr\Log\LoggerInterface; 7use Wikimedia\Rdbms\ILoadBalancer; 8 9/** 10 * @since 1.31 11 */ 12class ImportableOldRevisionImporter implements OldRevisionImporter { 13 14 /** 15 * @var LoggerInterface 16 */ 17 private $logger; 18 19 /** 20 * @var bool 21 */ 22 private $doUpdates; 23 24 /** 25 * @var ILoadBalancer 26 */ 27 private $loadBalancer; 28 29 /** 30 * @var RevisionStore 31 */ 32 private $revisionStore; 33 34 /** 35 * @var SlotRoleRegistry 36 */ 37 private $slotRoleRegistry; 38 39 /** 40 * @param bool $doUpdates 41 * @param LoggerInterface $logger 42 * @param ILoadBalancer $loadBalancer 43 * @param RevisionStore $revisionStore 44 * @param SlotRoleRegistry|null $slotRoleRegistry 45 */ 46 public function __construct( 47 $doUpdates, 48 LoggerInterface $logger, 49 ILoadBalancer $loadBalancer, 50 RevisionStore $revisionStore, 51 SlotRoleRegistry $slotRoleRegistry = null 52 ) { 53 $this->doUpdates = $doUpdates; 54 $this->logger = $logger; 55 $this->loadBalancer = $loadBalancer; 56 $this->revisionStore = $revisionStore; 57 // @todo: temporary - remove when FileImporter extension is updated 58 if ( !$slotRoleRegistry ) { 59 $slotRoleRegistry = \MediaWiki\MediaWikiServices::getInstance()->getSlotRoleRegistry(); 60 } 61 $this->slotRoleRegistry = $slotRoleRegistry; 62 } 63 64 public function import( ImportableOldRevision $importableRevision, $doUpdates = true ) { 65 $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER ); 66 67 # Sneak a single revision into place 68 $user = $importableRevision->getUserObj() ?: User::newFromName( $importableRevision->getUser() ); 69 if ( $user ) { 70 $userId = intval( $user->getId() ); 71 $userText = $user->getName(); 72 } else { 73 $userId = 0; 74 $userText = $importableRevision->getUser(); 75 $user = new User; 76 } 77 78 // avoid memory leak...? 79 Title::clearCaches(); 80 81 $page = WikiPage::factory( $importableRevision->getTitle() ); 82 $page->loadPageData( 'fromdbmaster' ); 83 if ( !$page->exists() ) { 84 // must create the page... 85 $pageId = $page->insertOn( $dbw ); 86 $created = true; 87 $oldcountable = null; 88 } else { 89 $pageId = $page->getId(); 90 $created = false; 91 92 // Note: sha1 has been in XML dumps since 2012. If you have an 93 // older dump, the duplicate detection here won't work. 94 if ( $importableRevision->getSha1Base36() !== false ) { 95 $prior = $dbw->selectField( 'revision', '1', 96 [ 'rev_page' => $pageId, 97 'rev_timestamp' => $dbw->timestamp( $importableRevision->getTimestamp() ), 98 'rev_sha1' => $importableRevision->getSha1Base36() ], 99 __METHOD__ 100 ); 101 if ( $prior ) { 102 // @todo FIXME: This could fail slightly for multiple matches :P 103 $this->logger->debug( __METHOD__ . ": skipping existing revision for [[" . 104 $importableRevision->getTitle()->getPrefixedText() . "]], timestamp " . 105 $importableRevision->getTimestamp() . "\n" ); 106 return false; 107 } 108 } 109 } 110 111 if ( !$pageId ) { 112 // This seems to happen if two clients simultaneously try to import the 113 // same page 114 $this->logger->debug( __METHOD__ . ': got invalid $pageId when importing revision of [[' . 115 $importableRevision->getTitle()->getPrefixedText() . ']], timestamp ' . 116 $importableRevision->getTimestamp() . "\n" ); 117 return false; 118 } 119 120 // Select previous version to make size diffs correct 121 // @todo This assumes that multiple revisions of the same page are imported 122 // in order from oldest to newest. 123 $qi = $this->revisionStore->getQueryInfo(); 124 $prevRevRow = $dbw->selectRow( $qi['tables'], $qi['fields'], 125 [ 126 'rev_page' => $pageId, 127 'rev_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $importableRevision->getTimestamp() ) ), 128 ], 129 __METHOD__, 130 [ 'ORDER BY' => [ 131 'rev_timestamp DESC', 132 'rev_id DESC', // timestamp is not unique per page 133 ] 134 ], 135 $qi['joins'] 136 ); 137 138 # @todo FIXME: Use original rev_id optionally (better for backups) 139 # Insert the row 140 $revisionRecord = new MutableRevisionRecord( $importableRevision->getTitle() ); 141 $revisionRecord->setParentId( $prevRevRow ? (int)$prevRevRow->rev_id : 0 ); 142 $revisionRecord->setComment( 143 CommentStoreComment::newUnsavedComment( $importableRevision->getComment() ) 144 ); 145 146 try { 147 $revUser = User::newFromAnyId( 148 $userId, 149 $userText, 150 null 151 ); 152 } catch ( InvalidArgumentException $ex ) { 153 $revUser = RequestContext::getMain()->getUser(); 154 } 155 $revisionRecord->setUser( $revUser ); 156 157 $originalRevision = $prevRevRow 158 ? $this->revisionStore->newRevisionFromRow( 159 $prevRevRow, 160 IDBAccessObject::READ_LATEST, 161 $importableRevision->getTitle() 162 ) 163 : null; 164 165 foreach ( $importableRevision->getSlotRoles() as $role ) { 166 if ( !$this->slotRoleRegistry->isDefinedRole( $role ) ) { 167 throw new MWException( "Undefined slot role $role" ); 168 } 169 170 $newContent = $importableRevision->getContent( $role ); 171 if ( !$originalRevision || !$originalRevision->hasSlot( $role ) ) { 172 $revisionRecord->setContent( $role, $newContent ); 173 } else { 174 $originalSlot = $originalRevision->getSlot( $role ); 175 if ( !$originalSlot->hasSameContent( $importableRevision->getSlot( $role ) ) ) { 176 $revisionRecord->setContent( $role, $newContent ); 177 } else { 178 $revisionRecord->inheritSlot( $originalRevision->getSlot( $role ) ); 179 } 180 } 181 } 182 183 $revisionRecord->setTimestamp( $importableRevision->getTimestamp() ); 184 $revisionRecord->setMinorEdit( $importableRevision->getMinor() ); 185 $revisionRecord->setPageId( $pageId ); 186 187 $latestRevId = $page->getLatest(); 188 189 $inserted = $this->revisionStore->insertRevisionOn( $revisionRecord, $dbw ); 190 if ( $latestRevId ) { 191 // If not found (false), cast to 0 so that the page is updated 192 // Just to be on the safe side, even though it should always be found 193 $latestRevTimestamp = (int)$this->revisionStore->getTimestampFromId( 194 $latestRevId, 195 RevisionStore::READ_LATEST 196 ); 197 } else { 198 $latestRevTimestamp = 0; 199 } 200 if ( $importableRevision->getTimestamp() > $latestRevTimestamp ) { 201 $changed = $page->updateRevisionOn( $dbw, $inserted, $latestRevId ); 202 } else { 203 $changed = false; 204 } 205 206 $tags = $importableRevision->getTags(); 207 if ( $tags !== [] ) { 208 ChangeTags::addTags( $tags, null, $inserted->getId() ); 209 } 210 211 if ( $changed !== false && $this->doUpdates ) { 212 $this->logger->debug( __METHOD__ . ": running updates" ); 213 // countable/oldcountable stuff is handled in WikiImporter::finishImportPage 214 // @todo replace deprecated function 215 $page->doEditUpdates( 216 $inserted, 217 $user, 218 [ 'created' => $created, 'oldcountable' => 'no-change' ] 219 ); 220 } 221 222 return true; 223 } 224 225} 226