예제 #1
0
 /**
  * Back-end article deletion
  * Deletes the article with database consistency, writes logs, purges caches
  *
  * @since 1.19
  *
  * @param string $reason Delete reason for deletion log
  * @param bool $suppress Suppress all revisions and log the deletion in
  *   the suppression log instead of the deletion log
  * @param int $u1 Unused
  * @param bool $u2 Unused
  * @param array|string &$error Array of errors to append to
  * @param User $user The deleting user
  * @param array $tags Tags to apply to the deletion action
  * @return Status Status object; if successful, $status->value is the log_id of the
  *   deletion log entry. If the page couldn't be deleted because it wasn't
  *   found, $status is a non-fatal 'cannotdelete' error
  */
 public function doDeleteArticleReal($reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null, $tags = [])
 {
     global $wgUser, $wgContentHandlerUseDB;
     wfDebug(__METHOD__ . "\n");
     $status = Status::newGood();
     if ($this->mTitle->getDBkey() === '') {
         $status->error('cannotdelete', wfEscapeWikiText($this->getTitle()->getPrefixedText()));
         return $status;
     }
     $user = is_null($user) ? $wgUser : $user;
     if (!Hooks::run('ArticleDelete', [&$this, &$user, &$reason, &$error, &$status, $suppress])) {
         if ($status->isOK()) {
             // Hook aborted but didn't set a fatal status
             $status->fatal('delete-hook-aborted');
         }
         return $status;
     }
     $dbw = wfGetDB(DB_MASTER);
     $dbw->startAtomic(__METHOD__);
     $this->loadPageData(self::READ_LATEST);
     $id = $this->getId();
     // T98706: lock the page from various other updates but avoid using
     // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to
     // the revisions queries (which also JOIN on user). Only lock the page
     // row and CAS check on page_latest to see if the trx snapshot matches.
     $lockedLatest = $this->lockAndGetLatest();
     if ($id == 0 || $this->getLatest() != $lockedLatest) {
         $dbw->endAtomic(__METHOD__);
         // Page not there or trx snapshot is stale
         $status->error('cannotdelete', wfEscapeWikiText($this->getTitle()->getPrefixedText()));
         return $status;
     }
     // Given the lock above, we can be confident in the title and page ID values
     $namespace = $this->getTitle()->getNamespace();
     $dbKey = $this->getTitle()->getDBkey();
     // At this point we are now comitted to returning an OK
     // status unless some DB query error or other exception comes up.
     // This way callers don't have to call rollback() if $status is bad
     // unless they actually try to catch exceptions (which is rare).
     // we need to remember the old content so we can use it to generate all deletion updates.
     $revision = $this->getRevision();
     try {
         $content = $this->getContent(Revision::RAW);
     } catch (Exception $ex) {
         wfLogWarning(__METHOD__ . ': failed to load content during deletion! ' . $ex->getMessage());
         $content = null;
     }
     $fields = Revision::selectFields();
     $bitfield = false;
     // Bitfields to further suppress the content
     if ($suppress) {
         $bitfield = Revision::SUPPRESSED_ALL;
         $fields = array_diff($fields, ['rev_deleted']);
     }
     // For now, shunt the revision data into the archive table.
     // Text is *not* removed from the text table; bulk storage
     // is left intact to avoid breaking block-compression or
     // immutable storage schemes.
     // In the future, we may keep revisions and mark them with
     // the rev_deleted field, which is reserved for this purpose.
     // Get all of the page revisions
     $res = $dbw->select('revision', $fields, ['rev_page' => $id], __METHOD__, 'FOR UPDATE');
     // Build their equivalent archive rows
     $rowsInsert = [];
     foreach ($res as $row) {
         $rowInsert = ['ar_namespace' => $namespace, 'ar_title' => $dbKey, 'ar_comment' => $row->rev_comment, 'ar_user' => $row->rev_user, 'ar_user_text' => $row->rev_user_text, 'ar_timestamp' => $row->rev_timestamp, 'ar_minor_edit' => $row->rev_minor_edit, 'ar_rev_id' => $row->rev_id, 'ar_parent_id' => $row->rev_parent_id, 'ar_text_id' => $row->rev_text_id, 'ar_text' => '', 'ar_flags' => '', 'ar_len' => $row->rev_len, 'ar_page_id' => $id, 'ar_deleted' => $suppress ? $bitfield : $row->rev_deleted, 'ar_sha1' => $row->rev_sha1];
         if ($wgContentHandlerUseDB) {
             $rowInsert['ar_content_model'] = $row->rev_content_model;
             $rowInsert['ar_content_format'] = $row->rev_content_format;
         }
         $rowsInsert[] = $rowInsert;
     }
     // Copy them into the archive table
     $dbw->insert('archive', $rowsInsert, __METHOD__);
     // Save this so we can pass it to the ArticleDeleteComplete hook.
     $archivedRevisionCount = $dbw->affectedRows();
     // Clone the title and wikiPage, so we have the information we need when
     // we log and run the ArticleDeleteComplete hook.
     $logTitle = clone $this->mTitle;
     $wikiPageBeforeDelete = clone $this;
     // Now that it's safely backed up, delete it
     $dbw->delete('page', ['page_id' => $id], __METHOD__);
     $dbw->delete('revision', ['rev_page' => $id], __METHOD__);
     // Log the deletion, if the page was suppressed, put it in the suppression log instead
     $logtype = $suppress ? 'suppress' : 'delete';
     $logEntry = new ManualLogEntry($logtype, 'delete');
     $logEntry->setPerformer($user);
     $logEntry->setTarget($logTitle);
     $logEntry->setComment($reason);
     $logEntry->setTags($tags);
     $logid = $logEntry->insert();
     $dbw->onTransactionPreCommitOrIdle(function () use($dbw, $logEntry, $logid) {
         // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
         $logEntry->publish($logid);
     }, __METHOD__);
     $dbw->endAtomic(__METHOD__);
     $this->doDeleteUpdates($id, $content, $revision);
     Hooks::run('ArticleDeleteComplete', [&$wikiPageBeforeDelete, &$user, $reason, $id, $content, $logEntry, $archivedRevisionCount]);
     $status->value = $logid;
     // Show log excerpt on 404 pages rather than just a link
     $cache = ObjectCache::getMainStashInstance();
     $key = wfMemcKey('page-recent-delete', md5($logTitle->getPrefixedText()));
     $cache->set($key, 1, $cache::TTL_DAY);
     return $status;
 }
예제 #2
0
 /**
  * Show the error text for a missing article. For articles in the MediaWiki
  * namespace, show the default message text. To be called from Article::view().
  */
 public function showMissingArticle()
 {
     global $wgSend404Code;
     $outputPage = $this->getContext()->getOutput();
     // Whether the page is a root user page of an existing user (but not a subpage)
     $validUserPage = false;
     $title = $this->getTitle();
     # Show info in user (talk) namespace. Does the user exist? Is he blocked?
     if ($title->getNamespace() == NS_USER || $title->getNamespace() == NS_USER_TALK) {
         $parts = explode('/', $title->getText());
         $rootPart = $parts[0];
         $user = User::newFromName($rootPart, false);
         $ip = User::isIP($rootPart);
         $block = Block::newFromTarget($user, $user);
         if (!($user && $user->isLoggedIn()) && !$ip) {
             # User does not exist
             $outputPage->wrapWikiMsg("<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>", array('userpage-userdoesnotexist-view', wfEscapeWikiText($rootPart)));
         } elseif (!is_null($block) && $block->getType() != Block::TYPE_AUTO) {
             # Show log extract if the user is currently blocked
             LogEventsList::showLogExtract($outputPage, 'block', MWNamespace::getCanonicalName(NS_USER) . ':' . $block->getTarget(), '', array('lim' => 1, 'showIfEmpty' => false, 'msgKey' => array('blocked-notice-logextract', $user->getName())));
             $validUserPage = !$title->isSubpage();
         } else {
             $validUserPage = !$title->isSubpage();
         }
     }
     Hooks::run('ShowMissingArticle', array($this));
     # Show delete and move logs if there were any such events.
     # The logging query can DOS the site when bots/crawlers cause 404 floods,
     # so be careful showing this. 404 pages must be cheap as they are hard to cache.
     $cache = ObjectCache::getMainStashInstance();
     $key = wfMemcKey('page-recent-delete', md5($title->getPrefixedText()));
     $loggedIn = $this->getContext()->getUser()->isLoggedIn();
     if ($loggedIn || $cache->get($key)) {
         $logTypes = array('delete', 'move');
         $conds = array("log_action != 'revision'");
         // Give extensions a chance to hide their (unrelated) log entries
         Hooks::run('Article::MissingArticleConditions', array(&$conds, $logTypes));
         LogEventsList::showLogExtract($outputPage, $logTypes, $title, '', array('lim' => 10, 'conds' => $conds, 'showIfEmpty' => false, 'msgKey' => array($loggedIn ? 'moveddeleted-notice' : 'moveddeleted-notice-recent')));
     }
     if (!$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage) {
         // If there's no backing content, send a 404 Not Found
         // for better machine handling of broken links.
         $this->getContext()->getRequest()->response()->statusHeader(404);
     }
     // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
     $policy = $this->getRobotPolicy('view');
     $outputPage->setIndexPolicy($policy['index']);
     $outputPage->setFollowPolicy($policy['follow']);
     $hookResult = Hooks::run('BeforeDisplayNoArticleText', array($this));
     if (!$hookResult) {
         return;
     }
     # Show error message
     $oldid = $this->getOldID();
     if (!$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText()) {
         $outputPage->addParserOutput($this->getContentObject()->getParserOutput($title));
     } else {
         if ($oldid) {
             $text = wfMessage('missing-revision', $oldid)->plain();
         } elseif ($title->quickUserCan('create', $this->getContext()->getUser()) && $title->quickUserCan('edit', $this->getContext()->getUser())) {
             $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
             $text = wfMessage($message)->plain();
         } else {
             $text = wfMessage('noarticletext-nopermission')->plain();
         }
         $dir = $this->getContext()->getLanguage()->getDir();
         $lang = $this->getContext()->getLanguage()->getCode();
         $outputPage->addWikiText(Xml::openElement('div', array('class' => "noarticletext mw-content-{$dir}", 'dir' => $dir, 'lang' => $lang)) . "\n{$text}\n</div>");
     }
 }
예제 #3
0
 /**
  * Back-end article deletion
  * Deletes the article with database consistency, writes logs, purges caches
  *
  * @since 1.19
  *
  * @param string $reason Delete reason for deletion log
  * @param bool $suppress Suppress all revisions and log the deletion in
  *   the suppression log instead of the deletion log
  * @param int $id Article ID
  * @param bool $commit Defaults to true, triggers transaction end
  * @param array &$error Array of errors to append to
  * @param User $user The deleting user
  * @return Status Status object; if successful, $status->value is the log_id of the
  *   deletion log entry. If the page couldn't be deleted because it wasn't
  *   found, $status is a non-fatal 'cannotdelete' error
  */
 public function doDeleteArticleReal($reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null)
 {
     global $wgUser, $wgContentHandlerUseDB;
     wfDebug(__METHOD__ . "\n");
     $status = Status::newGood();
     if ($this->mTitle->getDBkey() === '') {
         $status->error('cannotdelete', wfEscapeWikiText($this->getTitle()->getPrefixedText()));
         return $status;
     }
     $user = is_null($user) ? $wgUser : $user;
     if (!Hooks::run('ArticleDelete', array(&$this, &$user, &$reason, &$error, &$status, $suppress))) {
         if ($status->isOK()) {
             // Hook aborted but didn't set a fatal status
             $status->fatal('delete-hook-aborted');
         }
         return $status;
     }
     $dbw = wfGetDB(DB_MASTER);
     $dbw->begin(__METHOD__);
     if ($id == 0) {
         $this->loadPageData(self::READ_LATEST);
         $id = $this->getID();
         // T98706: lock the page from various other updates but avoid using
         // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to
         // the revisions queries (which also JOIN on user). Only lock the page
         // row and CAS check on page_latest to see if the trx snapshot matches.
         $lockedLatest = $this->lock();
         if ($id == 0 || $this->getLatest() != $lockedLatest) {
             // Page not there or trx snapshot is stale
             $dbw->rollback(__METHOD__);
             $status->error('cannotdelete', wfEscapeWikiText($this->getTitle()->getPrefixedText()));
             return $status;
         }
     }
     // we need to remember the old content so we can use it to generate all deletion updates.
     $content = $this->getContent(Revision::RAW);
     // Bitfields to further suppress the content
     if ($suppress) {
         $bitfield = 0;
         // This should be 15...
         $bitfield |= Revision::DELETED_TEXT;
         $bitfield |= Revision::DELETED_COMMENT;
         $bitfield |= Revision::DELETED_USER;
         $bitfield |= Revision::DELETED_RESTRICTED;
     } else {
         $bitfield = 'rev_deleted';
     }
     /**
      * For now, shunt the revision data into the archive table.
      * Text is *not* removed from the text table; bulk storage
      * is left intact to avoid breaking block-compression or
      * immutable storage schemes.
      *
      * For backwards compatibility, note that some older archive
      * table entries will have ar_text and ar_flags fields still.
      *
      * In the future, we may keep revisions and mark them with
      * the rev_deleted field, which is reserved for this purpose.
      */
     $row = array('ar_namespace' => 'page_namespace', 'ar_title' => 'page_title', 'ar_comment' => 'rev_comment', 'ar_user' => 'rev_user', 'ar_user_text' => 'rev_user_text', 'ar_timestamp' => 'rev_timestamp', 'ar_minor_edit' => 'rev_minor_edit', 'ar_rev_id' => 'rev_id', 'ar_parent_id' => 'rev_parent_id', 'ar_text_id' => 'rev_text_id', 'ar_text' => '\'\'', 'ar_flags' => '\'\'', 'ar_len' => 'rev_len', 'ar_page_id' => 'page_id', 'ar_deleted' => $bitfield, 'ar_sha1' => 'rev_sha1');
     if ($wgContentHandlerUseDB) {
         $row['ar_content_model'] = 'rev_content_model';
         $row['ar_content_format'] = 'rev_content_format';
     }
     $dbw->insertSelect('archive', array('page', 'revision'), $row, array('page_id' => $id, 'page_id = rev_page'), __METHOD__);
     // Now that it's safely backed up, delete it
     $dbw->delete('page', array('page_id' => $id), __METHOD__);
     $ok = $dbw->affectedRows() > 0;
     // $id could be laggy
     if (!$ok) {
         $dbw->rollback(__METHOD__);
         $status->error('cannotdelete', wfEscapeWikiText($this->getTitle()->getPrefixedText()));
         return $status;
     }
     if (!$dbw->cascadingDeletes()) {
         $dbw->delete('revision', array('rev_page' => $id), __METHOD__);
     }
     // Clone the title, so we have the information we need when we log
     $logTitle = clone $this->mTitle;
     // Log the deletion, if the page was suppressed, put it in the suppression log instead
     $logtype = $suppress ? 'suppress' : 'delete';
     $logEntry = new ManualLogEntry($logtype, 'delete');
     $logEntry->setPerformer($user);
     $logEntry->setTarget($logTitle);
     $logEntry->setComment($reason);
     $logid = $logEntry->insert();
     $dbw->onTransactionPreCommitOrIdle(function () use($dbw, $logEntry, $logid) {
         // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
         $logEntry->publish($logid);
     });
     if ($commit) {
         $dbw->commit(__METHOD__);
     }
     // Show log excerpt on 404 pages rather than just a link
     $key = wfMemcKey('page-recent-delete', md5($logTitle->getPrefixedText()));
     ObjectCache::getMainStashInstance()->set($key, 1, 86400);
     $this->doDeleteUpdates($id, $content);
     Hooks::run('ArticleDeleteComplete', array(&$this, &$user, $reason, $id, $content, $logEntry));
     $status->value = $logid;
     return $status;
 }
예제 #4
0
파일: LBFactory.php 프로젝트: paladox/2
 /**
  * @return ChronologyProtector
  */
 protected function newChronologyProtector()
 {
     $request = RequestContext::getMain()->getRequest();
     $chronProt = new ChronologyProtector(ObjectCache::getMainStashInstance(), array('ip' => $request->getIP(), 'agent' => $request->getHeader('User-Agent')));
     if (PHP_SAPI === 'cli') {
         $chronProt->setEnabled(false);
     } elseif ($request->getHeader('ChronologyProtection') === 'false') {
         // Request opted out of using position wait logic. This is useful for requests
         // done by the job queue or background ETL that do not have a meaningful session.
         $chronProt->setWaitEnabled(false);
     }
     return $chronProt;
 }
예제 #5
0
 /**
  * Reduce pending delta counters after updates have been applied
  * @param array $pd Result of getPendingDeltas(), used for DB update
  */
 protected function removePendingDeltas(array $pd)
 {
     $cache = ObjectCache::getMainStashInstance();
     foreach ($pd as $type => $deltas) {
         foreach ($deltas as $sign => $magnitude) {
             // Lower the pending counter now that we applied these changes
             $cache->decr($this->getTypeCacheKey($type, $sign), $magnitude);
         }
     }
 }
예제 #6
0
 /**
  * Set the current status of a chunked upload (used for polling)
  *
  * The value will be set in cache for 1 day
  *
  * @param User $user
  * @param string $statusKey
  * @param array|bool $value
  * @return void
  */
 public static function setSessionStatus(User $user, $statusKey, $value)
 {
     $key = wfMemcKey('uploadstatus', $user->getId() ?: md5($user->getName()), $statusKey);
     $cache = ObjectCache::getMainStashInstance();
     if ($value === false) {
         $cache->delete($key);
     } else {
         $cache->set($key, $value, $cache::TTL_DAY);
     }
 }
 function clear($index)
 {
     ObjectCache::getMainStashInstance()->delete(wfMemcKey('captcha', $index));
 }