protected function logTagAdded($tags, $revId, $user, $reason) { // log it $logEntry = new ManualLogEntry('tag', 'update'); $logEntry->setPerformer($user); $logEntry->setComment($reason); // find the appropriate target page if ($revId) { $rev = Revision::newFromId($revId); if ($rev) { $logEntry->setTarget($rev->getTitle()); } } if (!$logEntry->getTarget()) { // target is required, so we have to set something $logEntry->setTarget(SpecialPage::getTitleFor('Tags')); } $logParams = array('4::revid' => $revId, '6:list:tagsAdded' => $tags, '7:number:tagsAddedCount' => count($tags)); $logEntry->setParameters($logParams); $logEntry->setRelations(array('Tag' => $tags)); $dbw = wfGetDB(DB_MASTER); $logId = $logEntry->insert($dbw); // Only send this to UDP, not RC, similar to patrol events $logEntry->publish($logId, 'udp'); //$logEntry->publish( $logId ); }
public function onSubmit(array $formData) { global $IP, $wgCreateWikiSQLfiles; $DBname = $formData['dbname']; $founderName = $formData['founder']; $siteName = $formData['sitename']; $language = $formData['language']; $private = $formData['private']; $reason = $formData['reason']; $dbw = wfGetDB(DB_MASTER); $farmerLogEntry = new ManualLogEntry('farmer', 'createwiki'); $farmerLogEntry->setPerformer($this->getUser()); $farmerLogEntry->setTarget($this->getTitle()); $farmerLogEntry->setComment($reason); $farmerLogEntry->setParameters(array('4::wiki' => $DBname)); $farmerLogID = $farmerLogEntry->insert(); $farmerLogEntry->publish($farmerLogID); $dbw->query('SET storage_engine=InnoDB;'); $dbw->query('CREATE DATABASE ' . $dbw->addIdentifierQuotes($DBname) . ';'); $dbw->selectDB($DBname); foreach ($wgCreateWikiSQLfiles as $sqlfile) { $dbw->sourceFile($sqlfile); } $this->writeToDBlist($DBname, $siteName, $language, $private); $this->createMainPage($language); $shcreateaccount = exec("/usr/bin/php " . "{$IP}/extensions/CentralAuth/maintenance/createLocalAccount.php " . wfEscapeShellArg($founderName) . " --wiki " . wfEscapeShellArg($DBname)); if (!strpos($shcreateaccount, 'created')) { wfDebugLog('CreateWiki', 'Failed to create local account for founder. - error: ' . $shcreateaccount); return wfMessage('createwiki-error-usernotcreated')->escaped(); } $shpromoteaccount = exec("/usr/bin/php " . "{$IP}/maintenance/createAndPromote.php " . wfEscapeShellArg($founderName) . " --bureaucrat --sysop --force --wiki " . wfEscapeShellArg($DBname)); $this->getOutput()->addHTML('<div class="successbox">' . wfMessage('createwiki-success')->escaped() . '</div>'); return true; }
public function execute($par) { // Shortcut by using $par if ($par) { $this->getOutput()->redirect($this->getTitle()->getLinkURL(array('user' => $par))); return; } $this->setHeaders(); $this->outputHeader(); // Parse options $opt = new \FormOptions(); $opt->add('user', ''); $opt->add('delete', ''); $opt->add('reason', ''); $opt->fetchValuesFromRequest($this->getRequest()); // Parse user $user = $opt->getValue('user'); $userObj = \User::newFromName($user); $userExists = $userObj && $userObj->getId() !== 0; // If current task is delete and user is not allowed $canDoAdmin = $this->getUser()->isAllowed('avataradmin'); if ($opt->getValue('delete')) { if (!$canDoAdmin) { throw new \PermissionsError('avataradmin'); } // Delete avatar if the user exists if ($userExists) { if (Avatars::deleteAvatar($userObj)) { global $wgAvatarLogInRC; $logEntry = new \ManualLogEntry('avatar', 'delete'); $logEntry->setPerformer($this->getUser()); $logEntry->setTarget($userObj->getUserPage()); $logEntry->setComment($opt->getValue('reason')); $logId = $logEntry->insert(); $logEntry->publish($logId, $wgAvatarLogInRC ? 'rcandudp' : 'udp'); } } } $this->getOutput()->addModules(array('mediawiki.userSuggest')); $this->showForm($user); if ($userExists) { $haveAvatar = Avatars::hasAvatar($userObj); if ($haveAvatar) { $html = \Xml::tags('img', array('src' => Avatars::getLinkFor($user, 'original') . '&nocache&ver=' . dechex(time()), 'height' => 400), ''); $html = \Xml::tags('p', array(), $html); $this->getOutput()->addHTML($html); // Add a delete button if ($canDoAdmin) { $this->showDeleteForm($user); } } else { $this->getOutput()->addWikiMsg('viewavatar-noavatar'); } } else { if ($user) { $this->getOutput()->addWikiMsg('viewavatar-nouser'); } } }
public function newLogEntry($action, $params) { $logEntry = new ManualLogEntry('phpunit', $action); $logEntry->setPerformer($this->user); $logEntry->setTarget($this->title); $logEntry->setComment('A very good reason'); $logEntry->setParameters($params); return $logEntry; }
/** * Log the promotion of a local unattached to a global * * @param string $oldName * @param string $wiki * @param string $newName * @param string $reason */ public function logPromotion($oldName, $wiki, $newName, $reason) { $logEntry = new ManualLogEntry('gblrename', 'promote'); $logEntry->setPerformer($this->performingUser); $logEntry->setTarget(Title::makeTitleSafe(NS_SPECIAL, 'CentralAuth/' . $newName)); $logEntry->setComment($reason); $logEntry->setParameters(array('4::olduser' => $oldName, '5::newuser' => $newName, '6::oldwiki' => $wiki)); $logEntry->setRelations(array('oldname' => $oldName)); $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * @param CentralAuthUser[] $oldNames * @param string $newName * @param string $reason */ public function log(array $oldNames, $newName, $reason) { $logEntry = new ManualLogEntry('gblrename', 'merge'); $logEntry->setPerformer($this->performingUser); $logEntry->setTarget(Title::makeTitleSafe(NS_SPECIAL, 'CentralAuth/' . $newName)); $imploded = implode('|', array_map(function (CentralAuthUser $user) { return $user->getName(); }, $oldNames)); $logEntry->setComment($reason); $logEntry->setParameters(array('4::olduser' => $imploded, '5::newuser' => $newName)); $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * adds a log entry to the database. * * @param $type string: type of the log entry * @param $subtype string: subtype of the log entry * @param $user User: user that performs the logged operation * @param $ns int: number of the namespace for the entry's target's title * @param $title string: title of the entry's target * @param $comment string: comment of the log entry * @param $parameters Array: (optional) accompanying data that is attached * to the entry * * @return int id of the added log entry */ private function addLogEntry($type, $subtype, User $user, $ns, $title, $comment = null, $parameters = null) { $logEntry = new ManualLogEntry($type, $subtype); $logEntry->setPerformer($user); $logEntry->setTarget(Title::newFromText($title, $ns)); if ($comment !== null) { $logEntry->setComment($comment); } if ($parameters !== null) { $logEntry->setParameters($parameters); } return $logEntry->insert(); }
/** * Create new Log entry * * @param string $action Action name as defined above * @param integer $mapId Map id * @param string $comment Comment * @param array $params Additional params * @return ManualLogEntry */ public static function newLogEntry($action, $user, $mapId, $comment, $params = []) { $logEntry = new ManualLogEntry(self::LOG_TYPE_NAME, $action); $logEntry->setPerformer($user); $logEntry->setTarget(SpecialPage::getTitleFor(WikiaMapsSpecialController::PAGE_NAME, $mapId)); $logEntry->setComment($comment); if (!empty($params)) { // we can't allow to pass those elements // more info: https://www.mediawiki.org/wiki/Manual:Logging_to_Special:Log#1.19_and_later unset($params[1], $params[2], $params[3]); $logEntry->setParameters($params); } return $logEntry; }
/** * Create a log entry using the provided info. * Takes care about the logging interface changes in MediaWiki 1.19. * * @since 0.1 * * @param array $info */ public static function log(array $info) { $user = array_key_exists('user', $info) ? $info['user'] : $GLOBALS['wgUser']; $logEntry = new ManualLogEntry($info['type'], $info['subtype']); $logEntry->setPerformer($user); $logEntry->setTarget($info['title']); if (array_key_exists('comment', $info)) { $logEntry->setComment($info['comment']); } if (array_key_exists('parameters', $info)) { $logEntry->setParameters($info['parameters']); } $logid = $logEntry->insert(); $logEntry->publish($logid); }
function onSubmitInput(array $params) { if (!$this->getUser()->isAllowed('managewiki')) { throw new MWException("User '{$this->getUser()->getName()}' without managewiki right tried to change wiki settings!"); } $values = array('wiki_sitename' => $params['sitename'], 'wiki_language' => $params['language'], 'wiki_closed' => $params['closed'] == true ? 1 : 0); if ($this->getUser()->isAllowed('managewiki-restricted')) { $values['wiki_private'] = $params['private'] == true ? 1 : 0; } $dbw = wfGetDB(DB_MASTER); $dbw->update('cw_wikis', $values, array('wiki_dbname' => $params['dbname']), __METHOD__); $farmerLogEntry = new ManualLogEntry('farmer', 'managewiki'); $farmerLogEntry->setPerformer($this->getUser()); $farmerLogEntry->setTarget($this->getTitle()); $farmerLogEntry->setComment($params['reason']); $farmerLogEntry->setParameters(array('4::wiki' => $params['dbname'])); $farmerLogID = $farmerLogEntry->insert(); $farmerLogEntry->publish($farmerLogID); $this->getOutput()->addHTML('<div class="successbox">' . wfMessage('managewiki-success')->escaped() . '</div>'); return true; }
/** * Create a log entry using the provided info. * Takes care about the logging interface changes in MediaWiki 1.19. * * @since 0.1 * * @param array $info */ public static function log(array $info) { $user = array_key_exists('user', $info) ? $info['user'] : $GLOBALS['wgUser']; if ($info !== false) { if (class_exists('ManualLogEntry')) { $logEntry = new ManualLogEntry($info['type'], $info['subtype']); $logEntry->setPerformer($user); $logEntry->setTarget($info['title']); if (array_key_exists('comment', $info)) { $logEntry->setComment($info['comment']); } if (array_key_exists('parameters', $info)) { $logEntry->setParameters($info['parameters']); } $logid = $logEntry->insert(); $logEntry->publish($logid); } else { // Compatibility with MediaWiki 1.18. $log = new LogPage($info['type']); $log->addEntry($info['subtype'], $info['title'], array_key_exists('comment', $info) ? $info['comment'] : '', array_key_exists('parameters', $info) ? $info['parameters'] : array(), $user); } } }
/** * Move page to a title which is either a redirect to the * source page or nonexistent * * @param $nt Title the page to move to, which should be a redirect or nonexistent * @param $reason String The reason for the move * @param $createRedirect Bool Whether to leave a redirect at the old title. Ignored * if the user doesn't have the suppressredirect right */ private function moveToInternal(&$nt, $reason = '', $createRedirect = true) { global $wgUser, $wgContLang; if ($nt->exists()) { $moveOverRedirect = true; $logType = 'move_redir'; } else { $moveOverRedirect = false; $logType = 'move'; } $redirectSuppressed = !$createRedirect && $wgUser->isAllowed('suppressredirect'); $logEntry = new ManualLogEntry('move', $logType); $logEntry->setPerformer($wgUser); $logEntry->setTarget($this); $logEntry->setComment($reason); $logEntry->setParameters(array('4::target' => $nt->getPrefixedText(), '5::noredir' => $redirectSuppressed ? '1' : '0')); $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext(RequestContext::newExtraneousContext($this)); $comment = $formatter->getPlainActionText(); if ($reason) { $comment .= wfMsgForContent('colon-separator') . $reason; } # Truncate for whole multibyte characters. $comment = $wgContLang->truncate($comment, 255); $oldid = $this->getArticleID(); $latest = $this->getLatestRevID(); $dbw = wfGetDB(DB_MASTER); $newpage = WikiPage::factory($nt); if ($moveOverRedirect) { $newid = $nt->getArticleID(); # Delete the old redirect. We don't save it to history since # by definition if we've got here it's rather uninteresting. # We have to remove it so that the next step doesn't trigger # a conflict on the unique namespace+title index... $dbw->delete('page', array('page_id' => $newid), __METHOD__); $newpage->doDeleteUpdates($newid); } # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true); if (!is_object($nullRevision)) { throw new MWException('No valid null revision produced in ' . __METHOD__); } $nullRevId = $nullRevision->insertOn($dbw); # Change the name of the target page: $dbw->update('page', array('page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey()), array('page_id' => $oldid), __METHOD__); $this->resetArticleID(0); $nt->resetArticleID($oldid); $newpage->updateRevisionOn($dbw, $nullRevision); wfRunHooks('NewRevisionFromEditComplete', array($newpage, $nullRevision, $latest, $wgUser)); $newpage->doEditUpdates($nullRevision, $wgUser, array('changed' => false)); if (!$moveOverRedirect) { WikiPage::onArticleCreate($nt); } # Recreate the redirect, this time in the other direction. if ($redirectSuppressed) { WikiPage::onArticleDelete($this); } else { $mwRedir = MagicWord::get('redirect'); $redirectText = $mwRedir->getSynonym(0) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = WikiPage::factory($this); $newid = $redirectArticle->insertOn($dbw); if ($newid) { // sanity $redirectRevision = new Revision(array('page' => $newid, 'comment' => $comment, 'text' => $redirectText)); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); wfRunHooks('NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser)); $redirectArticle->doEditUpdates($redirectRevision, $wgUser, array('created' => true)); } } # Log the move $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * @param array $formData * @param HtmlForm $form * * @return bool|string * @throws DBUnexpectedError * @throws Exception * @throws MWException */ public static function processInput(array $formData, HtmlForm $form) { error_reporting(0); global $wgCreateWikiSQLfiles, $IP; $DBname = $formData['dbname']; $founderName = $formData['founder']; $dbw = wfGetDB(DB_MASTER); $dbTest = $dbw->query('SHOW DATABASES LIKE ' . $dbw->addQuotes($DBname) . ';'); $rows = $dbTest->numRows(); $dbTest->free(); if ($rows !== 0) { return wfMessage('createwiki-error-dbexists')->plain(); } $farmerLogEntry = new ManualLogEntry('farmer', 'createandpromote'); $farmerLogEntry->setPerformer($form->getUser()); $farmerLogEntry->setTarget($form->getTitle()); $farmerLogEntry->setComment($formData['comment']); $farmerLogEntry->setParameters(array('4::wiki' => $DBname, '5::founder' => $founderName)); $farmerLogID = $farmerLogEntry->insert(); $farmerLogEntry->publish($farmerLogID); $dbw->query('SET storage_engine=InnoDB;'); $dbw->query('CREATE DATABASE ' . $dbw->addIdentifierQuotes($DBname) . ';'); $dbw->selectDB($DBname); foreach ($wgCreateWikiSQLfiles as $file) { $dbw->sourceFile($file); } $dbw->insert('site_stats', array('ss_row_id' => 1)); $dbw->close(); // Add DNS record to cloudflare global $wgCreateWikiUseCloudFlare, $wgCloudFlareUser, $wgCloudFlareKey; if ($wgCreateWikiUseCloudFlare) { $domainPrefix = substr($DBname, 0, -4); $cloudFlare = new cloudflare_api($wgCloudFlareUser, $wgCloudFlareKey); $cloudFlareResult = $cloudFlare->rec_new('orain.org', 'CNAME', $domainPrefix, 'lb.orain.org'); if (!is_object($cloudFlareResult) || $cloudFlareResult->result !== 'success') { wfDebugLog('CreateWiki', 'CloudFlare FAILED to add CNAME for ' . $domainPrefix . '.orain.org'); } else { wfDebugLog('CreateWiki', 'CloudFlare CNAME added for ' . $domainPrefix . '.orain.org'); } } // Create local account for founder (hack) $out = exec("php5 {$IP}/extensions/CentralAuth/maintenance/createLocalAccount.php " . escapeshellarg($founderName) . ' --wiki ' . escapeshellarg($DBname)); if (!strpos($out, 'created')) { return wfMessage('createwiki-error-usernotcreated')->plain(); } require_once "{$IP}/includes/UserRightsProxy.php"; // Grant founder sysop and bureaucrat rights $founderUser = UserRightsProxy::newFromName($DBname, $founderName); $newGroups = array('sysop', 'bureaucrat'); array_map(array($founderUser, 'addGroup'), $newGroups); $founderUser->invalidateCache(); $form->getOutput()->addWikiMsg('createwiki-success', $DBname); return true; }
/** * Writes a tag action into the tag management log. * * @param string $action * @param string $tag * @param string $reason * @param User $user Who to attribute the action to * @param int $tagCount For deletion only, how many usages the tag had before * it was deleted. * @return int ID of the inserted log entry * @since 1.25 */ protected static function logTagManagementAction($action, $tag, $reason, User $user, $tagCount = null) { $dbw = wfGetDB(DB_MASTER); $logEntry = new ManualLogEntry('managetags', $action); $logEntry->setPerformer($user); // target page is not relevant, but it has to be set, so we just put in // the title of Special:Tags $logEntry->setTarget(Title::newFromText('Special:Tags')); $logEntry->setComment($reason); $params = array('4::tag' => $tag); if (!is_null($tagCount)) { $params['5:number:count'] = $tagCount; } $logEntry->setParameters($params); $logEntry->setRelations(array('Tag' => $tag)); $logId = $logEntry->insert($dbw); $logEntry->publish($logId); return $logId; }
/** * 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 * @return bool */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null) { wfProfileIn(__METHOD__); if (is_null($user)) { global $wgUser; $user = $wgUser; } $dbw = $this->repo->getMasterDB(); $dbw->begin(__METHOD__); if (!$props) { wfProfileIn(__METHOD__ . '-getProps'); $props = $this->repo->getFileProps($this->getVirtualUrl()); wfProfileOut(__METHOD__ . '-getProps'); } if ($timestamp === false) { $timestamp = $dbw->timestamp(); } $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"); wfProfileOut(__METHOD__); return false; } $reupload = false; # 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'); if ($dbw->affectedRows() == 0) { # (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. $reupload = true; # 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__); } else { # This is a new file, so update the image count DeferredUpdates::addUpdate(SiteStatsUpdate::factory(array('images' => 1))); } $descTitle = $this->getTitle(); $wikiPage = new WikiFilePage($descTitle); $wikiPage->setFile($this); # Add the log entry $action = $reupload ? 'overwrite' : 'upload'; $logEntry = new ManualLogEntry('upload', $action); $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(); $exists = $descTitle->exists(); if ($exists) { // Page exists, do RC entry now (otherwise we wait for later). $logEntry->publish($logId); } wfProfileIn(__METHOD__ . '-edit'); if ($exists) { # Create a null revision $latest = $descTitle->getLatestRevID(); $editSummary = LogFormatter::newFromEntry($logEntry)->getPlainActionText(); $nullRevision = Revision::newNullRevision($dbw, $descTitle->getArticleID(), $editSummary, false); if (!is_null($nullRevision)) { $nullRevision->insertOn($dbw); wfRunHooks('NewRevisionFromEditComplete', array($wikiPage, $nullRevision, $latest, $user)); $wikiPage->updateRevisionOn($dbw, $nullRevision); } } # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives # NOTE: once we have support for nested transactions, the commit may be moved # to after $wikiPage->doEdit has been called. $dbw->commit(__METHOD__); # Save to memcache. # We shall not saveToCache before the commit since otherwise # in case of a rollback there is an usable file from memcached # which in fact doesn't really exist (bug 24978) $this->saveToCache(); if ($exists) { # Invalidate the cache for the description page $descTitle->invalidateCache(); $descTitle->purgeSquid(); } else { # New file; create the description page. # There's already a log entry, so don't make a second RC entry # Squid and file cache for the description page are purged by doEditContent. $content = ContentHandler::makeContent($pageText, $descTitle); $status = $wikiPage->doEditContent($content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user); $dbw->begin(__METHOD__); // XXX; doEdit() uses a transaction // Now that the page exists, make an RC entry. $logEntry->publish($logId); if (isset($status->value['revision'])) { $dbw->update('logging', array('log_page' => $status->value['revision']->getPage()), array('log_id' => $logId), __METHOD__); } $dbw->commit(__METHOD__); // commit before anything bad can happen } wfProfileOut(__METHOD__ . '-edit'); if ($reupload) { # Delete old thumbnails wfProfileIn(__METHOD__ . '-purge'); $this->purgeThumbnails(); wfProfileOut(__METHOD__ . '-purge'); # Remove the old file from the squid cache SquidUpdate::purge(array($this->getURL())); } # Hooks, hooks, the magic of hooks... wfProfileIn(__METHOD__ . '-hooks'); wfRunHooks('FileUpload', array($this, $reupload, $descTitle->exists())); wfProfileOut(__METHOD__ . '-hooks'); # Invalidate cache for all pages using this file $update = new HTMLCacheUpdate($this->getTitle(), 'imagelinks'); $update->doUpdate(); if (!$reupload) { LinksUpdate::queueRecursiveJobsForTable($this->getTitle(), 'imagelinks'); } wfProfileOut(__METHOD__); return true; }
/** * Add a newuser log entry for this user. * Before 1.19 the return value was always true. * * @param string|bool $action Account creation type. * - String, one of the following values: * - 'create' for an anonymous user creating an account for himself. * This will force the action's performer to be the created user itself, * no matter the value of $wgUser * - 'create2' for a logged in user creating an account for someone else * - 'byemail' when the created user will receive its password by e-mail * - 'autocreate' when the user is automatically created (such as by CentralAuth). * - Boolean means whether the account was created by e-mail (deprecated): * - true will be converted to 'byemail' * - false will be converted to 'create' if this object is the same as * $wgUser and to 'create2' otherwise * * @param string $reason User supplied reason * * @return int|bool True if not $wgNewUserLog; otherwise ID of log item or 0 on failure */ public function addNewUserLogEntry($action = false, $reason = '') { global $wgUser, $wgNewUserLog; if (empty($wgNewUserLog)) { return true; // disabled } if ($action === true) { $action = 'byemail'; } elseif ($action === false) { if ($this->equals($wgUser)) { $action = 'create'; } else { $action = 'create2'; } } if ($action === 'create' || $action === 'autocreate') { $performer = $this; } else { $performer = $wgUser; } $logEntry = new ManualLogEntry('newusers', $action); $logEntry->setPerformer($performer); $logEntry->setTarget($this->getUserPage()); $logEntry->setComment($reason); $logEntry->setParameters(array('4::userid' => $this->getId())); $logid = $logEntry->insert(); if ($action !== 'autocreate') { $logEntry->publish($logid); } return (int) $logid; }
public function onSubmit(array $data) { global $wgContLang; if ($data['pagetitle'] === '') { // Initial form view of special page, pass return false; } // At this point, it has to be a POST request. This is enforced by HTMLForm, // but lets be safe verify that. if (!$this->getRequest()->wasPosted()) { throw new RuntimeException("Form submission was not POSTed"); } $this->title = Title::newFromText($data['pagetitle']); $user = $this->getUser(); // Check permissions and make sure the user has permission to edit the specific page $errors = $this->title->getUserPermissionsErrors('editcontentmodel', $user); $errors = wfMergeErrorArrays($errors, $this->title->getUserPermissionsErrors('edit', $user)); if ($errors) { $out = $this->getOutput(); $wikitext = $out->formatPermissionsErrorMessage($errors); // Hack to get our wikitext parsed return Status::newFatal(new RawMessage('$1', array($wikitext))); } $page = WikiPage::factory($this->title); if ($this->oldRevision === null) { $this->oldRevision = $page->getRevision() ?: false; } $oldModel = $this->title->getContentModel(); if ($this->oldRevision) { $oldContent = $this->oldRevision->getContent(); try { $newContent = ContentHandler::makeContent($oldContent->getNativeData(), $this->title, $data['model']); } catch (MWException $e) { return Status::newFatal($this->msg('changecontentmodel-cannot-convert')->params($this->title->getPrefixedText(), ContentHandler::getLocalizedName($data['model']))); } } else { // Page doesn't exist, create an empty content object $newContent = ContentHandler::getForModelID($data['model'])->makeEmptyContent(); } $flags = $this->oldRevision ? EDIT_UPDATE : EDIT_NEW; if ($user->isAllowed('bot')) { $flags |= EDIT_FORCE_BOT; } $log = new ManualLogEntry('contentmodel', 'change'); $log->setPerformer($user); $log->setTarget($this->title); $log->setComment($data['reason']); $log->setParameters(array('4::oldmodel' => $oldModel, '5::newmodel' => $data['model'])); $formatter = LogFormatter::newFromEntry($log); $formatter->setContext(RequestContext::newExtraneousContext($this->title)); $reason = $formatter->getPlainActionText(); if ($data['reason'] !== '') { $reason .= $this->msg('colon-separator')->inContentLanguage()->text() . $data['reason']; } # Truncate for whole multibyte characters. $reason = $wgContLang->truncate($reason, 255); $status = $page->doEditContent($newContent, $reason, $flags, $this->oldRevision ? $this->oldRevision->getId() : false, $user); if (!$status->isOK()) { return $status; } $logid = $log->insert(); $log->publish($logid); return $status; }
/** * 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; }
/** * 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; }
/** * Move page to a title which is either a redirect to the * source page or nonexistent * * @param Title $nt The page to move to, which should be a redirect or nonexistent * @param string $reason The reason for the move * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check * if the user has the suppressredirect right * @throws MWException */ private function moveToInternal(&$nt, $reason = '', $createRedirect = true) { global $wgUser, $wgContLang; if ($nt->exists()) { $moveOverRedirect = true; $logType = 'move_redir'; } else { $moveOverRedirect = false; $logType = 'move'; } if ($createRedirect) { if ($this->getNamespace() == NS_CATEGORY && !wfMessage('category-move-redirect-override')->inContentLanguage()->isDisabled()) { $redirectContent = new WikitextContent(wfMessage('category-move-redirect-override')->params($nt->getPrefixedText())->inContentLanguage()->plain()); } else { $contentHandler = ContentHandler::getForTitle($this); $redirectContent = $contentHandler->makeRedirectContent($nt, wfMessage('move-redirect-text')->inContentLanguage()->plain()); } // NOTE: If this page's content model does not support redirects, $redirectContent will be null. } else { $redirectContent = null; } // bug 57084: log_page should be the ID of the *moved* page $oldid = $this->getArticleID(); $logTitle = clone $this; $logEntry = new ManualLogEntry('move', $logType); $logEntry->setPerformer($wgUser); $logEntry->setTarget($logTitle); $logEntry->setComment($reason); $logEntry->setParameters(array('4::target' => $nt->getPrefixedText(), '5::noredir' => $redirectContent ? '0' : '1')); $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext(RequestContext::newExtraneousContext($this)); $comment = $formatter->getPlainActionText(); if ($reason) { $comment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } # Truncate for whole multibyte characters. $comment = $wgContLang->truncate($comment, 255); $dbw = wfGetDB(DB_MASTER); $newpage = WikiPage::factory($nt); if ($moveOverRedirect) { $newid = $nt->getArticleID(); $newcontent = $newpage->getContent(); # Delete the old redirect. We don't save it to history since # by definition if we've got here it's rather uninteresting. # We have to remove it so that the next step doesn't trigger # a conflict on the unique namespace+title index... $dbw->delete('page', array('page_id' => $newid), __METHOD__); $newpage->doDeleteUpdates($newid, $newcontent); } # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true, $wgUser); if (!is_object($nullRevision)) { throw new MWException('No valid null revision produced in ' . __METHOD__); } $nullRevision->insertOn($dbw); # Change the name of the target page: $dbw->update('page', array('page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey()), array('page_id' => $oldid), __METHOD__); // clean up the old title before reset article id - bug 45348 if (!$redirectContent) { WikiPage::onArticleDelete($this); } $this->resetArticleID(0); // 0 == non existing $nt->resetArticleID($oldid); $newpage->loadPageData(WikiPage::READ_LOCKING); // bug 46397 $newpage->updateRevisionOn($dbw, $nullRevision); wfRunHooks('NewRevisionFromEditComplete', array($newpage, $nullRevision, $nullRevision->getParentId(), $wgUser)); $newpage->doEditUpdates($nullRevision, $wgUser, array('changed' => false)); if (!$moveOverRedirect) { WikiPage::onArticleCreate($nt); } # Recreate the redirect, this time in the other direction. if ($redirectContent) { $redirectArticle = WikiPage::factory($this); $redirectArticle->loadFromRow(false, WikiPage::READ_LOCKING); // bug 46397 $newid = $redirectArticle->insertOn($dbw); if ($newid) { // sanity $this->resetArticleID($newid); $redirectRevision = new Revision(array('title' => $this, 'page' => $newid, 'user_text' => $wgUser->getName(), 'user' => $wgUser->getId(), 'comment' => $comment, 'content' => $redirectContent)); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); wfRunHooks('NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser)); $redirectArticle->doEditUpdates($redirectRevision, $wgUser, array('created' => true)); } } # Log the move $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * Process the form * * Change tags can be provided via $data['Tags'], but the calling function * must check if the tags can be added by the user prior to this function. * * @param array $data * @param IContextSource $context * @throws ErrorPageError * @return array|bool Array(message key, parameters) on failure, True on success */ public static function processUnblock(array $data, IContextSource $context) { $performer = $context->getUser(); $target = $data['Target']; $block = Block::newFromTarget($data['Target']); if (!$block instanceof Block) { return [['ipb_cant_unblock', $target]]; } # bug 15810: blocked admins should have limited access here. This # won't allow sysops to remove autoblocks on themselves, but they # should have ipblock-exempt anyway $status = SpecialBlock::checkUnblockSelf($target, $performer); if ($status !== true) { throw new ErrorPageError('badaccess', $status); } # If the specified IP is a single address, and the block is a range block, don't # unblock the whole range. list($target, $type) = SpecialBlock::getTargetAndType($target); if ($block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP) { $range = $block->getTarget(); return [['ipb_blocked_as_range', $target, $range]]; } # If the name was hidden and the blocking user cannot hide # names, then don't allow any block removals... if (!$performer->isAllowed('hideuser') && $block->mHideName) { return ['unblock-hideuser']; } # Delete block if (!$block->delete()) { return ['ipb_cant_unblock', htmlspecialchars($block->getTarget())]; } # Unset _deleted fields as needed if ($block->mHideName) { # Something is deeply FUBAR if this is not a User object, but who knows? $id = $block->getTarget() instanceof User ? $block->getTarget()->getId() : User::idFromName($block->getTarget()); RevisionDeleteUser::unsuppressUserName($block->getTarget(), $id); } # Redact the name (IP address) for autoblocks if ($block->getType() == Block::TYPE_AUTO) { $page = Title::makeTitle(NS_USER, '#' . $block->getId()); } else { $page = $block->getTarget() instanceof User ? $block->getTarget()->getUserPage() : Title::makeTitle(NS_USER, $block->getTarget()); } # Make log entry $logEntry = new ManualLogEntry('block', 'unblock'); $logEntry->setTarget($page); $logEntry->setComment($data['Reason']); $logEntry->setPerformer($performer); if (isset($data['Tags'])) { $logEntry->setTags($data['Tags']); } $logId = $logEntry->insert(); $logEntry->publish($logId); return true; }
/** * @param User $user * @param string $oldModel * @param string $newModel * @param string $reason */ protected function addContentModelChangeLogEntry(User $user, $oldModel, $newModel, $reason) { $log = new ManualLogEntry('contentmodel', 'change'); $log->setPerformer($user); $log->setTarget($this->mTitle); $log->setComment($reason); $log->setParameters(array('4::oldmodel' => $oldModel, '5::newmodel' => $newModel)); $logid = $log->insert(); $log->publish($logid); }
/** * Backend implementation of doRollback(), please refer there for parameter * and return value documentation * * NOTE: This function does NOT check ANY permissions, it just commits the * rollback to the DB. Therefore, you should only call this function direct- * ly if you want to use custom permissions checks. If you don't, use * doRollback() instead. * @param string $fromP Name of the user whose edits to rollback. * @param string $summary Custom summary. Set to default summary if empty. * @param bool $bot If true, mark all reverted edits as bot. * * @param array $resultDetails Contains result-specific array of additional values * @param User $guser The user performing the rollback * @param array|null $tags Change tags to apply to the rollback * Callers are responsible for permission checks * (with ChangeTags::canAddTagsAccompanyingChange) * * @return array */ public function commitRollback($fromP, $summary, $bot, &$resultDetails, User $guser, $tags = null) { global $wgUseRCPatrol, $wgContLang; $dbw = wfGetDB(DB_MASTER); if (wfReadOnly()) { return [['readonlytext']]; } // Get the last editor $current = $this->getRevision(); if (is_null($current)) { // Something wrong... no page? return [['notanarticle']]; } $from = str_replace('_', ' ', $fromP); // User name given should match up with the top revision. // If the user was deleted then $from should be empty. if ($from != $current->getUserText()) { $resultDetails = ['current' => $current]; return [['alreadyrolled', htmlspecialchars($this->mTitle->getPrefixedText()), htmlspecialchars($fromP), htmlspecialchars($current->getUserText())]]; } // Get the last edit not by this person... // Note: these may not be public values $user = intval($current->getUser(Revision::RAW)); $user_text = $dbw->addQuotes($current->getUserText(Revision::RAW)); $s = $dbw->selectRow('revision', ['rev_id', 'rev_timestamp', 'rev_deleted'], ['rev_page' => $current->getPage(), "rev_user != {$user} OR rev_user_text != {$user_text}"], __METHOD__, ['USE INDEX' => 'page_timestamp', 'ORDER BY' => 'rev_timestamp DESC']); if ($s === false) { // No one else ever edited this page return [['cantrollback']]; } elseif ($s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER) { // Only admins can see this text return [['notvisiblerev']]; } // Generate the edit summary if necessary $target = Revision::newFromId($s->rev_id, Revision::READ_LATEST); if (empty($summary)) { if ($from == '') { // no public user name $summary = wfMessage('revertpage-nouser'); } else { $summary = wfMessage('revertpage'); } } // Allow the custom summary to use the same args as the default message $args = [$target->getUserText(), $from, $s->rev_id, $wgContLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp)), $current->getId(), $wgContLang->timeanddate($current->getTimestamp())]; if ($summary instanceof Message) { $summary = $summary->params($args)->inContentLanguage()->text(); } else { $summary = wfMsgReplaceArgs($summary, $args); } // Trim spaces on user supplied text $summary = trim($summary); // Truncate for whole multibyte characters. $summary = $wgContLang->truncate($summary, 255); // Save $flags = EDIT_UPDATE | EDIT_INTERNAL; if ($guser->isAllowed('minoredit')) { $flags |= EDIT_MINOR; } if ($bot && $guser->isAllowedAny('markbotedits', 'bot')) { $flags |= EDIT_FORCE_BOT; } $targetContent = $target->getContent(); $changingContentModel = $targetContent->getModel() !== $current->getContentModel(); // Actually store the edit $status = $this->doEditContent($targetContent, $summary, $flags, $target->getId(), $guser, null, $tags); // Set patrolling and bot flag on the edits, which gets rollbacked. // This is done even on edit failure to have patrolling in that case (bug 62157). $set = []; if ($bot && $guser->isAllowed('markbotedits')) { // Mark all reverted edits as bot $set['rc_bot'] = 1; } if ($wgUseRCPatrol) { // Mark all reverted edits as patrolled $set['rc_patrolled'] = 1; } if (count($set)) { $dbw->update('recentchanges', $set, ['rc_cur_id' => $current->getPage(), 'rc_user_text' => $current->getUserText(), 'rc_timestamp > ' . $dbw->addQuotes($s->rev_timestamp)], __METHOD__); } if (!$status->isOK()) { return $status->getErrorsArray(); } // raise error, when the edit is an edit without a new version $statusRev = isset($status->value['revision']) ? $status->value['revision'] : null; if (!$statusRev instanceof Revision) { $resultDetails = ['current' => $current]; return [['alreadyrolled', htmlspecialchars($this->mTitle->getPrefixedText()), htmlspecialchars($fromP), htmlspecialchars($current->getUserText())]]; } if ($changingContentModel) { // If the content model changed during the rollback, // make sure it gets logged to Special:Log/contentmodel $log = new ManualLogEntry('contentmodel', 'change'); $log->setPerformer($guser); $log->setTarget($this->mTitle); $log->setComment($summary); $log->setParameters(['4::oldmodel' => $current->getContentModel(), '5::newmodel' => $targetContent->getModel()]); $logId = $log->insert($dbw); $log->publish($logId); } $revId = $statusRev->getId(); Hooks::run('ArticleRollbackComplete', [$this, $guser, $target, $current]); $resultDetails = ['summary' => $summary, 'current' => $current, 'target' => $target, 'newid' => $revId]; return []; }
/** * @param string $expected Expected IRC text without colors codes * @param string $type Log type (move, delete, suppress, patrol ...) * @param string $action A log type action * @param array $params * @param string $comment (optional) A comment for the log action * @param string $msg (optional) A message for PHPUnit :-) */ protected function assertIRCComment($expected, $type, $action, $params, $comment = null, $msg = '', $legacy = false) { $logEntry = new ManualLogEntry($type, $action); $logEntry->setPerformer($this->user); $logEntry->setTarget($this->title); if ($comment !== null) { $logEntry->setComment($comment); } $logEntry->setParameters($params); $logEntry->setLegacy($legacy); $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext($this->context); // Apply the same transformation as done in IRCColourfulRCFeedFormatter::getLine for rc_comment $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC($formatter->getIRCActionComment()); $this->assertEquals($expected, $ircRcComment, $msg); }
/** * Really delete the file * * @param $title Title object * @param File $file: file object * @param string $oldimage archive name * @param string $reason reason of the deletion * @param $suppress Boolean: whether to mark all deleted versions as restricted * @param $user User object performing the request * @throws MWException * @return bool|Status */ public static function doDelete(&$title, &$file, &$oldimage, $reason, $suppress, User $user = null) { if ($user === null) { global $wgUser; $user = $wgUser; } if ($oldimage) { $page = null; $status = $file->deleteOld($oldimage, $reason, $suppress); if ($status->ok) { // Need to do a log item $logComment = wfMessage('deletedrevision', $oldimage)->inContentLanguage()->text(); if (trim($reason) != '') { $logComment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } $logtype = $suppress ? 'suppress' : 'delete'; $logEntry = new ManualLogEntry($logtype, 'delete'); $logEntry->setPerformer($user); $logEntry->setTarget($title); $logEntry->setComment($logComment); $logid = $logEntry->insert(); $logEntry->publish($logid); } } else { $status = Status::newFatal('cannotdelete', wfEscapeWikiText($title->getPrefixedText())); $page = WikiPage::factory($title); $dbw = wfGetDB(DB_MASTER); try { // delete the associated article first $error = ''; $deleteStatus = $page->doDeleteArticleReal($reason, $suppress, 0, false, $error, $user); // doDeleteArticleReal() returns a non-fatal error status if the page // or revision is missing, so check for isOK() rather than isGood() if ($deleteStatus->isOK()) { $status = $file->delete($reason, $suppress); if ($status->isOK()) { $dbw->commit(__METHOD__); } else { $dbw->rollback(__METHOD__); } } } catch (MWException $e) { // rollback before returning to prevent UI from displaying incorrect "View or restore N deleted edits?" $dbw->rollback(__METHOD__); throw $e; } } if ($status->isOK()) { wfRunHooks('FileDeleteComplete', array(&$file, &$oldimage, &$page, &$user, &$reason)); } return $status; }
/** * Actually attempt the history move * * @todo if all versions of page A are moved to B and then a user * tries to do a reverse-merge via the "unmerge" log link, then page * A will still be a redirect (as it was after the original merge), * though it will have the old revisions back from before (as expected). * The user may have to "undo" the redirect manually to finish the "unmerge". * Maybe this should delete redirects at the source page of merges? * * @param User $user * @param string $reason * @return Status status of the history merge */ public function merge(User $user, $reason = '') { $status = new Status(); // Check validity and permissions required for merge $validCheck = $this->isValidMerge(); // Check this first to check for null pages if (!$validCheck->isOK()) { return $validCheck; } $permCheck = $this->checkPermissions($user, $reason); if (!$permCheck->isOK()) { return $permCheck; } $this->dbw->update('revision', array('rev_page' => $this->dest->getArticleID()), array('rev_page' => $this->source->getArticleID(), $this->timeWhere), __METHOD__); // Check if this did anything $this->revisionsMerged = $this->dbw->affectedRows(); if ($this->revisionsMerged < 1) { $status->fatal('mergehistory-fail-no-change'); return $status; } // Make the source page a redirect if no revisions are left $haveRevisions = $this->dbw->selectField('revision', 'rev_timestamp', array('rev_page' => $this->source->getArticleID()), __METHOD__, array('FOR UPDATE')); if (!$haveRevisions) { if ($reason) { $reason = wfMessage('mergehistory-comment', $this->source->getPrefixedText(), $this->dest->getPrefixedText(), $reason)->inContentLanguage()->text(); } else { $reason = wfMessage('mergehistory-autocomment', $this->source->getPrefixedText(), $this->dest->getPrefixedText())->inContentLanguage()->text(); } $contentHandler = ContentHandler::getForTitle($this->source); $redirectContent = $contentHandler->makeRedirectContent($this->dest, wfMessage('mergehistory-redirect-text')->inContentLanguage()->plain()); if ($redirectContent) { $redirectPage = WikiPage::factory($this->source); $redirectRevision = new Revision(array('title' => $this->source, 'page' => $this->source->getArticleID(), 'comment' => $reason, 'content' => $redirectContent)); $redirectRevision->insertOn($this->dbw); $redirectPage->updateRevisionOn($this->dbw, $redirectRevision); // Now, we record the link from the redirect to the new title. // It should have no other outgoing links... $this->dbw->delete('pagelinks', array('pl_from' => $this->dest->getArticleID()), __METHOD__); $this->dbw->insert('pagelinks', array('pl_from' => $this->dest->getArticleID(), 'pl_from_namespace' => $this->dest->getNamespace(), 'pl_namespace' => $this->dest->getNamespace(), 'pl_title' => $this->dest->getDBkey()), __METHOD__); } else { // Warning if we couldn't create the redirect $status->warning('mergehistory-warning-redirect-not-created'); } } else { $this->source->invalidateCache(); // update histories } $this->dest->invalidateCache(); // update histories // Update our logs $logEntry = new ManualLogEntry('merge', 'merge'); $logEntry->setPerformer($user); $logEntry->setComment($reason); $logEntry->setTarget($this->source); $logEntry->setParameters(array('4::dest' => $this->dest->getPrefixedText(), '5::mergepoint' => $this->timestampLimit->getTimestamp(TS_MW))); $logId = $logEntry->insert(); $logEntry->publish($logId); Hooks::run('ArticleMergeComplete', array($this->source, $this->dest)); return $status; }
/** * Restore the given (or all) text and file revisions for the page. * Once restored, the items will be removed from the archive tables. * The deletion log will be updated with an undeletion notice. * * @param array $timestamps Pass an empty array to restore all revisions, * otherwise list the ones to undelete. * @param string $comment * @param array $fileVersions * @param bool $unsuppress * @param User $user User performing the action, or null to use $wgUser * @return array(number of file revisions restored, number of image revisions * restored, log message) on success, false on failure. */ function undelete($timestamps, $comment = '', $fileVersions = array(), $unsuppress = false, User $user = null) { // If both the set of text revisions and file revisions are empty, // restore everything. Otherwise, just restore the requested items. $restoreAll = empty($timestamps) && empty($fileVersions); $restoreText = $restoreAll || !empty($timestamps); $restoreFiles = $restoreAll || !empty($fileVersions); if ($restoreFiles && $this->title->getNamespace() == NS_FILE) { $img = wfLocalFile($this->title); $img->load(File::READ_LATEST); $this->fileStatus = $img->restore($fileVersions, $unsuppress); if (!$this->fileStatus->isOK()) { return false; } $filesRestored = $this->fileStatus->successCount; } else { $filesRestored = 0; } if ($restoreText) { $this->revisionStatus = $this->undeleteRevisions($timestamps, $unsuppress, $comment); if (!$this->revisionStatus->isOK()) { return false; } $textRestored = $this->revisionStatus->getValue(); } else { $textRestored = 0; } // Touch the log! if ($textRestored && $filesRestored) { $reason = wfMessage('undeletedrevisions-files')->numParams($textRestored, $filesRestored)->inContentLanguage()->text(); } elseif ($textRestored) { $reason = wfMessage('undeletedrevisions')->numParams($textRestored)->inContentLanguage()->text(); } elseif ($filesRestored) { $reason = wfMessage('undeletedfiles')->numParams($filesRestored)->inContentLanguage()->text(); } else { wfDebug("Undelete: nothing undeleted...\n"); return false; } if (trim($comment) != '') { $reason .= wfMessage('colon-separator')->inContentLanguage()->text() . $comment; } if ($user === null) { global $wgUser; $user = $wgUser; } $logEntry = new ManualLogEntry('delete', 'restore'); $logEntry->setPerformer($user); $logEntry->setTarget($this->title); $logEntry->setComment($reason); Hooks::run('ArticleUndeleteLogEntry', array($this, &$logEntry, $user)); $logid = $logEntry->insert(); $logEntry->publish($logid); return array($textRestored, $filesRestored, $reason); }
/** * Given the form data, actually implement a block. This is also called from ApiBlock. * * @param array $data * @param IContextSource $context * @return bool|string */ public static function processForm(array $data, IContextSource $context) { global $wgBlockAllowsUTEdit, $wgHideUserContribLimit, $wgContLang; $performer = $context->getUser(); // Handled by field validator callback // self::validateTargetField( $data['Target'] ); # This might have been a hidden field or a checkbox, so interesting data # can come from it $data['Confirm'] = !in_array($data['Confirm'], array('', '0', null, false), true); /** @var User $target */ list($target, $type) = self::getTargetAndType($data['Target']); if ($type == Block::TYPE_USER) { $user = $target; $target = $user->getName(); $userId = $user->getId(); # Give admins a heads-up before they go and block themselves. Much messier # to do this for IPs, but it's pretty unlikely they'd ever get the 'block' # permission anyway, although the code does allow for it. # Note: Important to use $target instead of $data['Target'] # since both $data['PreviousTarget'] and $target are normalized # but $data['target'] gets overridden by (non-normalized) request variable # from previous request. if ($target === $performer->getName() && ($data['PreviousTarget'] !== $target || !$data['Confirm'])) { return array('ipb-blockingself', 'ipb-confirmaction'); } } elseif ($type == Block::TYPE_RANGE) { $userId = 0; } elseif ($type == Block::TYPE_IP) { $target = $target->getName(); $userId = 0; } else { # This should have been caught in the form field validation return array('badipaddress'); } if (strlen($data['Expiry']) == 0 || strlen($data['Expiry']) > 50 || !self::parseExpiryInput($data['Expiry'])) { return array('ipb_expiry_invalid'); } if (!isset($data['DisableEmail'])) { $data['DisableEmail'] = false; } # If the user has done the form 'properly', they won't even have been given the # option to suppress-block unless they have the 'hideuser' permission if (!isset($data['HideUser'])) { $data['HideUser'] = false; } if ($data['HideUser']) { if (!$performer->isAllowed('hideuser')) { # this codepath is unreachable except by a malicious user spoofing forms, # or by race conditions (user has hideuser and block rights, loads block form, # and loses hideuser rights before submission); so need to fail completely # rather than just silently disable hiding return array('badaccess-group0'); } # Recheck params here... if ($type != Block::TYPE_USER) { $data['HideUser'] = false; # IP users should not be hidden } elseif (!wfIsInfinity($data['Expiry'])) { # Bad expiry. return array('ipb_expiry_temp'); } elseif ($wgHideUserContribLimit !== false && $user->getEditCount() > $wgHideUserContribLimit) { # Typically, the user should have a handful of edits. # Disallow hiding users with many edits for performance. return array(array('ipb_hide_invalid', Message::numParam($wgHideUserContribLimit))); } elseif (!$data['Confirm']) { return array('ipb-confirmhideuser', 'ipb-confirmaction'); } } # Create block object. $block = new Block(); $block->setTarget($target); $block->setBlocker($performer); # Truncate reason for whole multibyte characters $block->mReason = $wgContLang->truncate($data['Reason'][0], 255); $block->mExpiry = self::parseExpiryInput($data['Expiry']); $block->prevents('createaccount', $data['CreateAccount']); $block->prevents('editownusertalk', !$wgBlockAllowsUTEdit || $data['DisableUTEdit']); $block->prevents('sendemail', $data['DisableEmail']); $block->isHardblock($data['HardBlock']); $block->isAutoblocking($data['AutoBlock']); $block->mHideName = $data['HideUser']; $reason = array('hookaborted'); if (!Hooks::run('BlockIp', array(&$block, &$performer, &$reason))) { return $reason; } # Try to insert block. Is there a conflicting block? $status = $block->insert(); if (!$status) { # Indicates whether the user is confirming the block and is aware of # the conflict (did not change the block target in the meantime) $blockNotConfirmed = !$data['Confirm'] || array_key_exists('PreviousTarget', $data) && $data['PreviousTarget'] !== $target; # Special case for API - bug 32434 $reblockNotAllowed = array_key_exists('Reblock', $data) && !$data['Reblock']; # Show form unless the user is already aware of this... if ($blockNotConfirmed || $reblockNotAllowed) { return array(array('ipb_already_blocked', $block->getTarget())); # Otherwise, try to update the block... } else { # This returns direct blocks before autoblocks/rangeblocks, since we should # be sure the user is blocked by now it should work for our purposes $currentBlock = Block::newFromTarget($target); if ($block->equals($currentBlock)) { return array(array('ipb_already_blocked', $block->getTarget())); } # If the name was hidden and the blocking user cannot hide # names, then don't allow any block changes... if ($currentBlock->mHideName && !$performer->isAllowed('hideuser')) { return array('cant-see-hidden-user'); } $currentBlock->isHardblock($block->isHardblock()); $currentBlock->prevents('createaccount', $block->prevents('createaccount')); $currentBlock->mExpiry = $block->mExpiry; $currentBlock->isAutoblocking($block->isAutoblocking()); $currentBlock->mHideName = $block->mHideName; $currentBlock->prevents('sendemail', $block->prevents('sendemail')); $currentBlock->prevents('editownusertalk', $block->prevents('editownusertalk')); $currentBlock->mReason = $block->mReason; $status = $currentBlock->update(); $logaction = 'reblock'; # Unset _deleted fields if requested if ($currentBlock->mHideName && !$data['HideUser']) { RevisionDeleteUser::unsuppressUserName($target, $userId); } # If hiding/unhiding a name, this should go in the private logs if ((bool) $currentBlock->mHideName) { $data['HideUser'] = true; } } } else { $logaction = 'block'; } Hooks::run('BlockIpComplete', array($block, $performer)); # Set *_deleted fields if requested if ($data['HideUser']) { RevisionDeleteUser::suppressUserName($target, $userId); } # Can't watch a rangeblock if ($type != Block::TYPE_RANGE && $data['Watch']) { WatchAction::doWatch(Title::makeTitle(NS_USER, $target), $performer, WatchedItem::IGNORE_USER_RIGHTS); } # Block constructor sanitizes certain block options on insert $data['BlockEmail'] = $block->prevents('sendemail'); $data['AutoBlock'] = $block->isAutoblocking(); # Prepare log parameters $logParams = array(); $logParams['5::duration'] = $data['Expiry']; $logParams['6::flags'] = self::blockLogFlags($data, $type); # Make log entry, if the name is hidden, put it in the suppression log $log_type = $data['HideUser'] ? 'suppress' : 'block'; $logEntry = new ManualLogEntry($log_type, $logaction); $logEntry->setTarget(Title::makeTitle(NS_USER, $target)); $logEntry->setComment($data['Reason'][0]); $logEntry->setPerformer($performer); $logEntry->setParameters($logParams); # Relate log ID to block IDs (bug 25763) $blockIds = array_merge(array($status['id']), $status['autoIds']); $logEntry->setRelations(array('ipb_id' => $blockIds)); $logId = $logEntry->insert(); $logEntry->publish($logId); # Report to the user return true; }
/** * 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 $suppress boolean suppress all revisions and log the deletion in * the suppression log instead of the deletion log * @param int $id article ID * @param $commit boolean defaults to true, triggers transaction end * @param &$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 ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) { if ( $status->isOK() ) { // Hook aborted but didn't set a fatal status $status->fatal( 'delete-hook-aborted' ); } return $status; } if ( $id == 0 ) { $this->loadPageData( 'forupdate' ); $id = $this->getID(); if ( $id == 0 ) { $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) ); return $status; } } // 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'; } // we need to remember the old content so we can use it to generate all deletion updates. $content = $this->getContent( Revision::RAW ); $dbw = wfGetDB( DB_MASTER ); $dbw->begin( __METHOD__ ); // 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' => '\'\'', // Be explicit to appease 'ar_flags' => '\'\'', // MySQL's "strict mode"... '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__ ); } $this->doDeleteUpdates( $id, $content ); // Log the deletion, if the page was suppressed, log it at Oversight instead $logtype = $suppress ? 'suppress' : 'delete'; $logEntry = new ManualLogEntry( $logtype, 'delete' ); $logEntry->setPerformer( $user ); $logEntry->setTarget( $this->mTitle ); $logEntry->setComment( $reason ); $logid = $logEntry->insert(); $logEntry->publish( $logid ); if ( $commit ) { $dbw->commit( __METHOD__ ); } wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) ); $status->value = $logid; return $status; }
/** * @param Title $title * @param ForeignTitle $foreignTitle * @param int $revisionCount * @param int $successCount * @param array $pageInfo * @return void */ function reportPage($title, $foreignTitle, $revisionCount, $successCount, $pageInfo) { $args = func_get_args(); call_user_func_array($this->mOriginalPageOutCallback, $args); if ($title === null) { # Invalid or non-importable title; a notice is already displayed return; } $this->mPageCount++; if ($successCount > 0) { $this->getOutput()->addHTML("<li>" . Linker::linkKnown($title) . " " . $this->msg('import-revision-count')->numParams($successCount)->escaped() . "</li>\n"); if ($this->mIsUpload) { $detail = $this->msg('import-logentry-upload-detail')->numParams($successCount)->inContentLanguage()->text(); if ($this->reason) { $detail .= $this->msg('colon-separator')->inContentLanguage()->text() . $this->reason; } $action = 'upload'; } else { $interwiki = '[[:' . $this->mInterwiki . ':' . $foreignTitle->getFullText() . ']]'; $detail = $this->msg('import-logentry-interwiki-detail')->numParams($successCount)->params($interwiki)->inContentLanguage()->text(); if ($this->reason) { $detail .= $this->msg('colon-separator')->inContentLanguage()->text() . $this->reason; } $action = 'interwiki'; } $logEntry = new ManualLogEntry('import', $action); $logEntry->setTarget($title); $logEntry->setComment($detail); $logEntry->setPerformer($this->getUser()); $logid = $logEntry->insert(); $logEntry->publish($logid); $comment = $detail; // quick $dbw = wfGetDB(DB_MASTER); $latest = $title->getLatestRevID(); $nullRevision = Revision::newNullRevision($dbw, $title->getArticleID(), $comment, true, $this->getUser()); if (!is_null($nullRevision)) { $nullRevision->insertOn($dbw); $page = WikiPage::factory($title); # Update page record $page->updateRevisionOn($dbw, $nullRevision); Hooks::run('NewRevisionFromEditComplete', array($page, $nullRevision, $latest, $this->getUser())); } } else { $this->getOutput()->addHTML("<li>" . Linker::linkKnown($title) . " " . $this->msg('import-nonewrevisions')->escaped() . "</li>\n"); } }