Example #1
0
 /**
  * Record a file upload in the upload log and the image table
  * @param string $oldver
  * @param string $comment
  * @param string $pageText
  * @param bool|array $props
  * @param string|bool $timestamp
  * @param null|User $user
  * @param string[] $tags
  * @return bool
  */
 function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = array())
 {
     if (is_null($user)) {
         global $wgUser;
         $user = $wgUser;
     }
     $dbw = $this->repo->getMasterDB();
     # Imports or such might force a certain timestamp; otherwise we generate
     # it and can fudge it slightly to keep (name,timestamp) unique on re-upload.
     if ($timestamp === false) {
         $timestamp = $dbw->timestamp();
         $allowTimeKludge = true;
     } else {
         $allowTimeKludge = false;
     }
     $props = $props ?: $this->repo->getFileProps($this->getVirtualUrl());
     $props['description'] = $comment;
     $props['user'] = $user->getId();
     $props['user_text'] = $user->getName();
     $props['timestamp'] = wfTimestamp(TS_MW, $timestamp);
     // DB -> TS_MW
     $this->setProps($props);
     # Fail now if the file isn't there
     if (!$this->fileExists) {
         wfDebug(__METHOD__ . ": File " . $this->getRel() . " went missing!\n");
         return false;
     }
     $dbw->startAtomic(__METHOD__);
     # Test to see if the row exists using INSERT IGNORE
     # This avoids race conditions by locking the row until the commit, and also
     # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
     $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $dbw->encodeBlob($this->metadata), 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE');
     $reupload = $dbw->affectedRows() == 0;
     if ($reupload) {
         if ($allowTimeKludge) {
             # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
             $ltimestamp = $dbw->selectField('image', 'img_timestamp', array('img_name' => $this->getName()), __METHOD__, array('LOCK IN SHARE MODE'));
             $lUnixtime = $ltimestamp ? wfTimestamp(TS_UNIX, $ltimestamp) : false;
             # Avoid a timestamp that is not newer than the last version
             # TODO: the image/oldimage tables should be like page/revision with an ID field
             if ($lUnixtime && wfTimestamp(TS_UNIX, $timestamp) <= $lUnixtime) {
                 sleep(1);
                 // fast enough re-uploads would go far in the future otherwise
                 $timestamp = $dbw->timestamp($lUnixtime + 1);
                 $this->timestamp = wfTimestamp(TS_MW, $timestamp);
                 // DB -> TS_MW
             }
         }
         # (bug 34993) Note: $oldver can be empty here, if the previous
         # version of the file was broken. Allow registration of the new
         # version to continue anyway, because that's better than having
         # an image that's not fixable by user operations.
         # Collision, this is an update of a file
         # Insert previous contents into oldimage
         $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__);
         # Update the current image row
         $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $dbw->encodeBlob($this->metadata), 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__);
     }
     $descTitle = $this->getTitle();
     $descId = $descTitle->getArticleID();
     $wikiPage = new WikiFilePage($descTitle);
     $wikiPage->setFile($this);
     // Add the log entry...
     $logEntry = new ManualLogEntry('upload', $reupload ? 'overwrite' : 'upload');
     $logEntry->setTimestamp($this->timestamp);
     $logEntry->setPerformer($user);
     $logEntry->setComment($comment);
     $logEntry->setTarget($descTitle);
     // Allow people using the api to associate log entries with the upload.
     // Log has a timestamp, but sometimes different from upload timestamp.
     $logEntry->setParameters(array('img_sha1' => $this->sha1, 'img_timestamp' => $timestamp));
     // Note we keep $logId around since during new image
     // creation, page doesn't exist yet, so log_page = 0
     // but we want it to point to the page we're making,
     // so we later modify the log entry.
     // For a similar reason, we avoid making an RC entry
     // now and wait until the page exists.
     $logId = $logEntry->insert();
     if ($descTitle->exists()) {
         // Use own context to get the action text in content language
         $formatter = LogFormatter::newFromEntry($logEntry);
         $formatter->setContext(RequestContext::newExtraneousContext($descTitle));
         $editSummary = $formatter->getPlainActionText();
         $nullRevision = Revision::newNullRevision($dbw, $descId, $editSummary, false, $user);
         if ($nullRevision) {
             $nullRevision->insertOn($dbw);
             Hooks::run('NewRevisionFromEditComplete', array($wikiPage, $nullRevision, $nullRevision->getParentId(), $user));
             $wikiPage->updateRevisionOn($dbw, $nullRevision);
             // Associate null revision id
             $logEntry->setAssociatedRevId($nullRevision->getId());
         }
         $newPageContent = null;
     } else {
         // Make the description page and RC log entry post-commit
         $newPageContent = ContentHandler::makeContent($pageText, $descTitle);
     }
     # Defer purges, page creation, and link updates in case they error out.
     # The most important thing is that files and the DB registry stay synced.
     $dbw->endAtomic(__METHOD__);
     # Do some cache purges after final commit so that:
     # a) Changes are more likely to be seen post-purge
     # b) They won't cause rollback of the log publish/update above
     $that = $this;
     $dbw->onTransactionIdle(function () use($that, $reupload, $wikiPage, $newPageContent, $comment, $user, $logEntry, $logId, $descId, $tags) {
         # Update memcache after the commit
         $that->invalidateCache();
         $updateLogPage = false;
         if ($newPageContent) {
             # New file page; create the description page.
             # There's already a log entry, so don't make a second RC entry
             # CDN and file cache for the description page are purged by doEditContent.
             $status = $wikiPage->doEditContent($newPageContent, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user);
             if (isset($status->value['revision'])) {
                 // Associate new page revision id
                 $logEntry->setAssociatedRevId($status->value['revision']->getId());
             }
             // This relies on the resetArticleID() call in WikiPage::insertOn(),
             // which is triggered on $descTitle by doEditContent() above.
             if (isset($status->value['revision'])) {
                 /** @var $rev Revision */
                 $rev = $status->value['revision'];
                 $updateLogPage = $rev->getPage();
             }
         } else {
             # Existing file page: invalidate description page cache
             $wikiPage->getTitle()->invalidateCache();
             $wikiPage->getTitle()->purgeSquid();
             # Allow the new file version to be patrolled from the page footer
             Article::purgePatrolFooterCache($descId);
         }
         # Update associated rev id. This should be done by $logEntry->insert() earlier,
         # but setAssociatedRevId() wasn't called at that point yet...
         $logParams = $logEntry->getParameters();
         $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId();
         $update = array('log_params' => LogEntryBase::makeParamBlob($logParams));
         if ($updateLogPage) {
             # Also log page, in case where we just created it above
             $update['log_page'] = $updateLogPage;
         }
         $that->getRepo()->getMasterDB()->update('logging', $update, array('log_id' => $logId), __METHOD__);
         $that->getRepo()->getMasterDB()->insert('log_search', array('ls_field' => 'associated_rev_id', 'ls_value' => $logEntry->getAssociatedRevId(), 'ls_log_id' => $logId), __METHOD__);
         # Now that the log entry is up-to-date, make an RC entry.
         $recentChange = $logEntry->publish($logId);
         if ($tags) {
             ChangeTags::addTags($tags, $recentChange ? $recentChange->getAttribute('rc_id') : null, $logEntry->getAssociatedRevId(), $logId);
         }
         # Run hook for other updates (typically more cache purging)
         Hooks::run('FileUpload', array($that, $reupload, !$newPageContent));
         if ($reupload) {
             # Delete old thumbnails
             $that->purgeThumbnails();
             # Remove the old file from the CDN cache
             DeferredUpdates::addUpdate(new CdnCacheUpdate(array($that->getUrl())), DeferredUpdates::PRESEND);
         } else {
             # Update backlink pages pointing to this title if created
             LinksUpdate::queueRecursiveJobsForTable($that->getTitle(), 'imagelinks');
         }
     });
     if (!$reupload) {
         # This is a new file, so update the image count
         DeferredUpdates::addUpdate(SiteStatsUpdate::factory(array('images' => 1)));
     }
     # Invalidate cache for all pages using this file
     DeferredUpdates::addUpdate(new HTMLCacheUpdate($this->getTitle(), 'imagelinks'));
     return true;
 }