/** * @param stdClass $row A single row from the result set * @return string Formatted HTML list item */ public function logLine($row) { $entry = DatabaseLogEntry::newFromRow($row); $formatter = LogFormatter::newFromEntry($entry); $formatter->setContext($this->getContext()); $formatter->setShowUserToolLinks(!($this->flags & self::NO_EXTRA_USER_LINKS)); $time = htmlspecialchars($this->getLanguage()->userTimeAndDate($entry->getTimestamp(), $this->getUser())); $action = $formatter->getActionText(); if ($this->flags & self::NO_ACTION_LINK) { $revert = ''; } else { $revert = $formatter->getActionLinks(); if ($revert != '') { $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>'; } } $comment = $formatter->getComment(); // Some user can hide log items and have review links $del = $this->getShowHideLinks($row); // Any tags... list($tagDisplay, $newClasses) = ChangeTags::formatSummaryRow($row->ts_tags, 'logevent', $this->getContext()); $classes = array_merge(array('mw-logline-' . $entry->getType()), $newClasses); return Html::rawElement('li', array('class' => $classes), "{$del} {$time} {$action} {$comment} {$revert} {$tagDisplay}") . "\n"; }
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; }
private function extractRowInfo($row) { $logEntry = DatabaseLogEntry::newFromRow($row); $vals = array(ApiResult::META_TYPE => 'assoc'); $anyHidden = false; $user = $this->getUser(); if ($this->fld_ids) { $vals['logid'] = intval($row->log_id); } if ($this->fld_title || $this->fld_parsedcomment) { $title = Title::makeTitle($row->log_namespace, $row->log_title); } if ($this->fld_title || $this->fld_ids || $this->fld_details && $row->log_params !== '') { if (LogEventsList::isDeleted($row, LogPage::DELETED_ACTION)) { $vals['actionhidden'] = true; $anyHidden = true; } if (LogEventsList::userCan($row, LogPage::DELETED_ACTION, $user)) { if ($this->fld_title) { ApiQueryBase::addTitleInfo($vals, $title); } if ($this->fld_ids) { $vals['pageid'] = intval($row->page_id); $vals['logpage'] = intval($row->log_page); } if ($this->fld_details) { $vals['params'] = LogFormatter::newFromEntry($logEntry)->formatParametersForApi(); } } } if ($this->fld_type) { $vals['type'] = $row->log_type; $vals['action'] = $row->log_action; } if ($this->fld_user || $this->fld_userid) { if (LogEventsList::isDeleted($row, LogPage::DELETED_USER)) { $vals['userhidden'] = true; $anyHidden = true; } if (LogEventsList::userCan($row, LogPage::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $row->user_name === null ? $row->log_user_text : $row->user_name; } if ($this->fld_userid) { $vals['userid'] = intval($row->log_user); } if (!$row->log_user) { $vals['anon'] = true; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->log_timestamp); } if (($this->fld_comment || $this->fld_parsedcomment) && isset($row->log_comment)) { if (LogEventsList::isDeleted($row, LogPage::DELETED_COMMENT)) { $vals['commenthidden'] = true; $anyHidden = true; } if (LogEventsList::userCan($row, LogPage::DELETED_COMMENT, $user)) { if ($this->fld_comment) { $vals['comment'] = $row->log_comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($row->log_comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } if ($anyHidden && LogEventsList::isDeleted($row, LogPage::DELETED_RESTRICTED)) { $vals['suppressed'] = true; } return $vals; }
/** * Add a log entry * * @param string $action One of '', 'block', 'protect', 'rights', 'delete', * 'upload', 'move', 'move_redir' * @param Title $target Title object * @param string $comment Description associated * @param array $params Parameters passed later to wfMessage function * @param null|int|User $doer The user doing the action. null for $wgUser * * @return int The log_id of the inserted log entry */ public function addEntry($action, $target, $comment, $params = array(), $doer = null) { global $wgContLang; if (!is_array($params)) { $params = array($params); } if ($comment === null) { $comment = ''; } # Trim spaces on user supplied text $comment = trim($comment); # Truncate for whole multibyte characters. $comment = $wgContLang->truncate($comment, 255); $this->action = $action; $this->target = $target; $this->comment = $comment; $this->params = LogPage::makeParamBlob($params); if ($doer === null) { global $wgUser; $doer = $wgUser; } elseif (!is_object($doer)) { $doer = User::newFromId($doer); } $this->doer = $doer; $logEntry = new ManualLogEntry($this->type, $action); $logEntry->setTarget($target); $logEntry->setPerformer($doer); $logEntry->setParameters($params); // All log entries using the LogPage to insert into the logging table // are using the old logging system and therefore the legacy flag is // needed to say the LogFormatter the parameters have numeric keys $logEntry->setLegacy(true); $formatter = LogFormatter::newFromEntry($logEntry); $context = RequestContext::newExtraneousContext($target); $formatter->setContext($context); $this->actionText = $formatter->getPlainActionText(); $this->ircActionText = $formatter->getIRCActionText(); return $this->saveContent(); }
/** * 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; }
/** * @param $row Row: a single row from the result set * @return String: Formatted HTML list item */ public function logLine($row) { # start wikia change $this->fixUserName($row); # end wikia change $entry = DatabaseLogEntry::newFromRow($row); $formatter = LogFormatter::newFromEntry($entry); $formatter->setShowUserToolLinks(!($this->flags & self::NO_EXTRA_USER_LINKS)); $action = $formatter->getActionText(); $comment = $formatter->getComment(); $classes = array('mw-logline-' . $entry->getType()); $title = $entry->getTarget(); $time = $this->logTimestamp($entry); // Extract extra parameters $paramArray = LogPage::extractParams($row->log_params); // Add review/revert links and such... $revert = $this->logActionLinks($row, $title, $paramArray, $comment); // Some user can hide log items and have review links $del = $this->getShowHideLinks($row); if ($del != '') { $del .= ' '; } // Any tags... list($tagDisplay, $newClasses) = ChangeTags::formatSummaryRow($row->ts_tags, 'logevent'); $classes = array_merge($classes, $newClasses); return Xml::tags('li', array("class" => implode(' ', $classes)), $del . "{$time} {$action} {$comment} {$revert} {$tagDisplay}") . "\n"; }
/** * 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); }
/** * @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); }
/** * 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); }
/** * Publishes the log entry. * @param int $newId id of the log entry. * @param string $to rcandudp (default), rc, udp */ public function publish($newId, $to = 'rcandudp') { $log = new LogPage($this->getType()); if ($log->isRestricted()) { return; } $formatter = LogFormatter::newFromEntry($this); $context = RequestContext::newExtraneousContext($this->getTarget()); $formatter->setContext($context); $logpage = SpecialPage::getTitleFor('Log', $this->getType()); $user = $this->getPerformer(); $ip = ""; if ($user->isAnon()) { /* * "MediaWiki default" and friends may have * no IP address in their name */ if (IP::isIPAddress($user->getName())) { $ip = $user->getName(); } } $rc = RecentChange::newLogEntry($this->getTimestamp(), $logpage, $user, $formatter->getPlainActionText(), $ip, $this->getType(), $this->getSubtype(), $this->getTarget(), $this->getComment(), serialize((array) $this->getParameters()), $newId, $formatter->getIRCActionComment()); if ($to === 'rc' || $to === 'rcandudp') { $rc->save('pleasedontudp'); } if ($to === 'udp' || $to === 'rcandudp') { $rc->notifyRC2UDP(); } }
/** * Get a RecentChanges object for the log entry * @param int $newId * @return RecentChange * @since 1.23 */ public function getRecentChange($newId = 0) { $formatter = LogFormatter::newFromEntry($this); $context = RequestContext::newExtraneousContext($this->getTarget()); $formatter->setContext($context); $logpage = SpecialPage::getTitleFor('Log', $this->getType()); $user = $this->getPerformer(); $ip = ""; if ($user->isAnon()) { /* * "MediaWiki default" and friends may have * no IP address in their name */ if (IP::isIPAddress($user->getName())) { $ip = $user->getName(); } } return RecentChange::newLogEntry($this->getTimestamp(), $logpage, $user, $formatter->getPlainActionText(), $ip, $this->getType(), $this->getSubtype(), $this->getTarget(), $this->getComment(), LogEntryBase::makeParamBlob($this->getParameters()), $newId, $formatter->getIRCActionComment()); }
public function getApiData(ApiResult $result) { $logEntry = DatabaseLogEntry::newFromRow($this->row); $user = $this->list->getUser(); $ret = array('id' => $logEntry->getId(), 'type' => $logEntry->getType(), 'action' => $logEntry->getSubtype()); $ret += $logEntry->isDeleted(LogPage::DELETED_USER) ? array('userhidden' => '') : array(); $ret += $logEntry->isDeleted(LogPage::DELETED_COMMENT) ? array('commenthidden' => '') : array(); $ret += $logEntry->isDeleted(LogPage::DELETED_ACTION) ? array('actionhidden' => '') : array(); if (LogEventsList::userCan($this->row, LogPage::DELETED_ACTION, $user)) { $ret['params'] = LogFormatter::newFromEntry($logEntry)->formatParametersForApi(); } if (LogEventsList::userCan($this->row, LogPage::DELETED_USER, $user)) { $ret += array('userid' => $this->row->log_user, 'user' => $this->row->log_user_text); } if (LogEventsList::userCan($this->row, LogPage::DELETED_COMMENT, $user)) { $ret += array('comment' => $this->row->log_comment); } return $ret; }
/** * Publishes the log entry. * @param $newId int id of the log entry. * @param $to string: rcandudp (default), rc, udp */ public function publish($newId, $to = 'rcandudp') { $log = new LogPage($this->getType()); if ($log->isRestricted()) { return; } $formatter = LogFormatter::newFromEntry($this); $context = RequestContext::newExtraneousContext($this->getTarget()); $formatter->setContext($context); $logpage = SpecialPage::getTitleFor('Log', $this->getType()); $user = $this->getPerformer(); $rc = RecentChange::newLogEntry($this->getTimestamp(), $logpage, $user, $formatter->getPlainActionText(), $user->isAnon() ? $user->getName() : '', $this->getType(), $this->getSubtype(), $this->getTarget(), $this->getComment(), serialize((array) $this->getParameters()), $newId, $formatter->getIRCActionComment()); if ($to === 'rc' || $to === 'rcandudp') { $rc->save('pleasedontudp'); } if ($to === 'udp' || $to === 'rcandudp') { $rc->notifyRC2UDP(); } }
/** * @covers LogFormatter::newFromEntry * @covers LogFormatter::getComment */ public function testLogComment() { $entry = $this->newLogEntry('test', array()); $formatter = LogFormatter::newFromEntry($entry); $formatter->setContext($this->context); $comment = ltrim(Linker::commentBlock($entry->getComment())); $this->assertEquals($comment, $formatter->getComment()); }
/** * 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 log entry * * @param $action String: one of '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'move_redir' * @param $target Title object * @param $comment String: description associated * @param $params Array: parameters passed later to wfMessage function * @param $doer User object: the user doing the action * * @return int log_id of the inserted log entry */ public function addEntry($action, $target, $comment, $params = array(), $doer = null) { global $wgContLang; if (!is_array($params)) { $params = array($params); } if ($comment === null) { $comment = ''; } # Truncate for whole multibyte characters. $comment = $wgContLang->truncate($comment, 255); $this->action = $action; $this->target = $target; $this->comment = $comment; $this->params = LogPage::makeParamBlob($params); if ($doer === null) { global $wgUser; $doer = $wgUser; } elseif (!is_object($doer)) { $doer = User::newFromId($doer); } $this->doer = $doer; $logEntry = new ManualLogEntry($this->type, $action); $logEntry->setTarget($target); $logEntry->setPerformer($doer); $logEntry->setParameters($params); $formatter = LogFormatter::newFromEntry($logEntry); $context = RequestContext::newExtraneousContext($target); $formatter->setContext($context); $this->actionText = $formatter->getPlainActionText(); $this->ircActionText = $formatter->getIRCActionText(); return $this->saveContent(); }
/** * @dataProvider provideApiParamFormatting * @covers LogFormatter::formatParametersForApi * @covers LogFormatter::formatParameterValueForApi */ public function testApiParamFormatting($key, $value, $expected) { $entry = $this->newLogEntry('param', array($key => $value)); $formatter = LogFormatter::newFromEntry($entry); $formatter->setContext($this->context); ApiResult::setIndexedTagName($expected, 'param'); ApiResult::setArrayType($expected, 'assoc'); $this->assertEquals($expected, $formatter->formatParametersForApi()); }
/** * Move page to a title which is either a redirect to the * source page or nonexistent * * @fixme This was basically directly moved from Title, it should be split into smaller functions * @param User $user the User doing the move * @param Title $nt The page to move to, which should be a redirect or non-existent * @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 * @return Revision the revision created by the move * @throws MWException */ private function moveToInternal(User $user, &$nt, $reason = '', $createRedirect = true) { global $wgContLang; if ($nt->exists()) { $moveOverRedirect = true; $logType = 'move_redir'; } else { $moveOverRedirect = false; $logType = 'move'; } if ($moveOverRedirect) { $overwriteMessage = wfMessage('delete_and_move_reason', $this->oldTitle->getPrefixedText())->text(); $newpage = WikiPage::factory($nt); $errs = []; $status = $newpage->doDeleteArticleReal($overwriteMessage, false, $nt->getArticleID(), false, $errs, $user); if (!$status->isGood()) { throw new MWException('Failed to delete page-move revision: ' . $status); } $nt->resetArticleID(false); } if ($createRedirect) { if ($this->oldTitle->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->oldTitle); $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; } // Figure out whether the content model is no longer the default $oldDefault = ContentHandler::getDefaultModelFor($this->oldTitle); $contentModel = $this->oldTitle->getContentModel(); $newDefault = ContentHandler::getDefaultModelFor($nt); $defaultContentModelChanging = $oldDefault !== $newDefault && $oldDefault === $contentModel; // bug 57084: log_page should be the ID of the *moved* page $oldid = $this->oldTitle->getArticleID(); $logTitle = clone $this->oldTitle; $logEntry = new ManualLogEntry('move', $logType); $logEntry->setPerformer($user); $logEntry->setTarget($logTitle); $logEntry->setComment($reason); $logEntry->setParameters(['4::target' => $nt->getPrefixedText(), '5::noredir' => $redirectContent ? '0' : '1']); $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext(RequestContext::newExtraneousContext($this->oldTitle)); $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); $oldpage = WikiPage::factory($this->oldTitle); $oldcountable = $oldpage->isCountable(); $newpage = WikiPage::factory($nt); # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true, $user); 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', ['page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey()], ['page_id' => $oldid], __METHOD__); if (!$redirectContent) { // Clean up the old title *before* reset article id - bug 45348 WikiPage::onArticleDelete($this->oldTitle); } $this->oldTitle->resetArticleID(0); // 0 == non existing $nt->resetArticleID($oldid); $newpage->loadPageData(WikiPage::READ_LOCKING); // bug 46397 $newpage->updateRevisionOn($dbw, $nullRevision); Hooks::run('NewRevisionFromEditComplete', [$newpage, $nullRevision, $nullRevision->getParentId(), $user]); $newpage->doEditUpdates($nullRevision, $user, ['changed' => false, 'moved' => true, 'oldcountable' => $oldcountable]); // If the default content model changes, we need to populate rev_content_model if ($defaultContentModelChanging) { $dbw->update('revision', ['rev_content_model' => $contentModel], ['rev_page' => $nt->getArticleID(), 'rev_content_model IS NULL'], __METHOD__); } WikiPage::onArticleCreate($nt); # Recreate the redirect, this time in the other direction. if ($redirectContent) { $redirectArticle = WikiPage::factory($this->oldTitle); $redirectArticle->loadFromRow(false, WikiPage::READ_LOCKING); // bug 46397 $newid = $redirectArticle->insertOn($dbw); if ($newid) { // sanity $this->oldTitle->resetArticleID($newid); $redirectRevision = new Revision(['title' => $this->oldTitle, 'page' => $newid, 'user_text' => $user->getName(), 'user' => $user->getId(), 'comment' => $comment, 'content' => $redirectContent]); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); Hooks::run('NewRevisionFromEditComplete', [$redirectArticle, $redirectRevision, false, $user]); $redirectArticle->doEditUpdates($redirectRevision, $user, ['created' => true]); } } # Log the move $logid = $logEntry->insert(); $logEntry->publish($logid); return $nullRevision; }