/** * @dataProvider dataGetPageLanguage */ public function testGetPageLanguage($title, $expected) { if (is_string($title)) { $title = Title::newFromText($title); } $expected = wfGetLangObj($expected); $handler = ContentHandler::getForTitle($title); $lang = $handler->getPageLanguage($title); $this->assertEquals($expected->getCode(), $lang->getCode()); }
/** * Checks, if the given title supports the use of SpecialMobileHistory. * * @param Title $title The title to check * @return boolean True, if SpecialMobileHistory can be used, false otherwise */ public static function shouldUseSpecialHistory(Title $title) { $contentHandler = ContentHandler::getForTitle($title); $actionOverrides = $contentHandler->getActionOverrides(); // if history is overwritten, assume, that SpecialMobileHistory can't handle them if (isset($actionOverrides['history'])) { // and return false return false; } return true; }
/** * @return String */ function getFormat() { if (is_null($this->model)) { $this->format = ContentHandler::getForTitle($this->getTitle())->getDefaultFormat(); } return $this->format; }
function doSubmit() { $user = $this->getUser(); if ($user->pingLimiter('move')) { throw new ThrottledError(); } $ot = $this->oldTitle; $nt = $this->newTitle; # don't allow moving to pages with # in if (!$nt || $nt->hasFragment()) { $this->showForm(array(array('badtitletext'))); return; } # Show a warning if the target file exists on a shared repo if ($nt->getNamespace() == NS_FILE && !($this->moveOverShared && $user->isAllowed('reupload-shared')) && !RepoGroup::singleton()->getLocalRepo()->findFile($nt) && wfFindFile($nt)) { $this->showForm(array(array('file-exists-sharedrepo'))); return; } # Delete to make way if requested if ($this->deleteAndMove) { $permErrors = $nt->getUserPermissionsErrors('delete', $user); if (count($permErrors)) { # Only show the first error $this->showForm($permErrors); return; } $reason = $this->msg('delete_and_move_reason', $ot)->inContentLanguage()->text(); // Delete an associated image if there is if ($nt->getNamespace() == NS_FILE) { $file = wfLocalFile($nt); $file->load(File::READ_LATEST); if ($file->exists()) { $file->delete($reason, false, $user); } } $error = ''; // passed by ref $page = WikiPage::factory($nt); $deleteStatus = $page->doDeleteArticleReal($reason, false, 0, true, $error, $user); if (!$deleteStatus->isGood()) { $this->showForm($deleteStatus->getErrorsArray()); return; } } $handler = ContentHandler::getForTitle($ot); if (!$handler->supportsRedirects()) { $createRedirect = false; } elseif ($user->isAllowed('suppressredirect')) { $createRedirect = $this->leaveRedirect; } else { $createRedirect = true; } # Do the actual move. $mp = new MovePage($ot, $nt); $valid = $mp->isValidMove(); if (!$valid->isOK()) { $this->showForm($valid->getErrorsArray()); return; } $permStatus = $mp->checkPermissions($user, $this->reason); if (!$permStatus->isOK()) { $this->showForm($permStatus->getErrorsArray()); return; } $status = $mp->move($user, $this->reason, $createRedirect); if (!$status->isOK()) { $this->showForm($status->getErrorsArray()); return; } if ($this->getConfig()->get('FixDoubleRedirects') && $this->fixRedirects) { DoubleRedirectJob::fixRedirects('move', $ot, $nt); } $out = $this->getOutput(); $out->setPageTitle($this->msg('pagemovedsub')); $oldLink = Linker::link($ot, null, array('id' => 'movepage-oldlink'), array('redirect' => 'no')); $newLink = Linker::linkKnown($nt, null, array('id' => 'movepage-newlink')); $oldText = $ot->getPrefixedText(); $newText = $nt->getPrefixedText(); if ($ot->exists()) { // NOTE: we assume that if the old title exists, it's because it was re-created as // a redirect to the new title. This is not safe, but what we did before was // even worse: we just determined whether a redirect should have been created, // and reported that it was created if it should have, without any checks. // Also note that isRedirect() is unreliable because of bug 37209. $msgName = 'movepage-moved-redirect'; } else { $msgName = 'movepage-moved-noredirect'; } $out->addHTML($this->msg('movepage-moved')->rawParams($oldLink, $newLink)->params($oldText, $newText)->parseAsBlock()); $out->addWikiMsg($msgName); Hooks::run('SpecialMovepageAfterMove', array(&$this, &$ot, &$nt)); # Now we move extra pages we've been asked to move: subpages and talk # pages. First, if the old page or the new page is a talk page, we # can't move any talk pages: cancel that. if ($ot->isTalkPage() || $nt->isTalkPage()) { $this->moveTalk = false; } if (count($ot->getUserPermissionsErrors('move-subpages', $user))) { $this->moveSubpages = false; } # Next make a list of id's. This might be marginally less efficient # than a more direct method, but this is not a highly performance-cri- # tical code path and readable code is more important here. # # Note: this query works nicely on MySQL 5, but the optimizer in MySQL # 4 might get confused. If so, consider rewriting as a UNION. # # If the target namespace doesn't allow subpages, moving with subpages # would mean that you couldn't move them back in one operation, which # is bad. # @todo FIXME: A specific error message should be given in this case. // @todo FIXME: Use Title::moveSubpages() here $dbr = wfGetDB(DB_MASTER); if ($this->moveSubpages && (MWNamespace::hasSubpages($nt->getNamespace()) || $this->moveTalk && MWNamespace::hasSubpages($nt->getTalkPage()->getNamespace()))) { $conds = array('page_title' . $dbr->buildLike($ot->getDBkey() . '/', $dbr->anyString()) . ' OR page_title = ' . $dbr->addQuotes($ot->getDBkey())); $conds['page_namespace'] = array(); if (MWNamespace::hasSubpages($nt->getNamespace())) { $conds['page_namespace'][] = $ot->getNamespace(); } if ($this->moveTalk && MWNamespace::hasSubpages($nt->getTalkPage()->getNamespace())) { $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace(); } } elseif ($this->moveTalk) { $conds = array('page_namespace' => $ot->getTalkPage()->getNamespace(), 'page_title' => $ot->getDBkey()); } else { # Skip the query $conds = null; } $extraPages = array(); if (!is_null($conds)) { $extraPages = TitleArray::newFromResult($dbr->select('page', array('page_id', 'page_namespace', 'page_title'), $conds, __METHOD__)); } $extraOutput = array(); $count = 1; foreach ($extraPages as $oldSubpage) { if ($ot->equals($oldSubpage) || $nt->equals($oldSubpage)) { # Already did this one. continue; } $newPageName = preg_replace('#^' . preg_quote($ot->getDBkey(), '#') . '#', StringUtils::escapeRegexReplacement($nt->getDBkey()), $oldSubpage->getDBkey()); if ($oldSubpage->isSubpage() && ($ot->isTalkPage() xor $nt->isTalkPage())) { // Moving a subpage from a subject namespace to a talk namespace or vice-versa $newNs = $nt->getNamespace(); } elseif ($oldSubpage->isTalkPage()) { $newNs = $nt->getTalkPage()->getNamespace(); } else { $newNs = $nt->getSubjectPage()->getNamespace(); } # Bug 14385: we need makeTitleSafe because the new page names may # be longer than 255 characters. $newSubpage = Title::makeTitleSafe($newNs, $newPageName); if (!$newSubpage) { $oldLink = Linker::linkKnown($oldSubpage); $extraOutput[] = $this->msg('movepage-page-unmoved')->rawParams($oldLink)->params(Title::makeName($newNs, $newPageName))->escaped(); continue; } # This was copy-pasted from Renameuser, bleh. if ($newSubpage->exists() && !$oldSubpage->isValidMoveTarget($newSubpage)) { $link = Linker::linkKnown($newSubpage); $extraOutput[] = $this->msg('movepage-page-exists')->rawParams($link)->escaped(); } else { $success = $oldSubpage->moveTo($newSubpage, true, $this->reason, $createRedirect); if ($success === true) { if ($this->fixRedirects) { DoubleRedirectJob::fixRedirects('move', $oldSubpage, $newSubpage); } $oldLink = Linker::link($oldSubpage, null, array(), array('redirect' => 'no')); $newLink = Linker::linkKnown($newSubpage); $extraOutput[] = $this->msg('movepage-page-moved')->rawParams($oldLink, $newLink)->escaped(); ++$count; $maximumMovedPages = $this->getConfig()->get('MaximumMovedPages'); if ($count >= $maximumMovedPages) { $extraOutput[] = $this->msg('movepage-max-pages')->numParams($maximumMovedPages)->escaped(); break; } } else { $oldLink = Linker::linkKnown($oldSubpage); $newLink = Linker::link($newSubpage); $extraOutput[] = $this->msg('movepage-page-unmoved')->rawParams($oldLink, $newLink)->escaped(); } } } if ($extraOutput !== array()) { $out->addHTML("<ul>\n<li>" . implode("</li>\n<li>", $extraOutput) . "</li>\n</ul>"); } # Deal with watches (we don't watch subpages) WatchAction::doWatchOrUnwatch($this->watch, $ot, $user); WatchAction::doWatchOrUnwatch($this->watch, $nt, $user); }
/** * Initialize the main Article object for "standard" actions (view, etc) * Create an Article object for the page, following redirects if needed. * * @return Article|string An Article, or a string to redirect to another URL */ private function initializeArticle() { $title = $this->context->getTitle(); if ($this->context->canUseWikiPage()) { // Try to use request context wiki page, as there // is already data from db saved in per process // cache there from this->getAction() call. $page = $this->context->getWikiPage(); } else { // This case should not happen, but just in case. // @TODO: remove this or use an exception $page = WikiPage::factory($title); $this->context->setWikiPage($page); wfWarn("RequestContext::canUseWikiPage() returned false"); } // Make GUI wrapper for the WikiPage $article = Article::newFromWikiPage($page, $this->context); // Skip some unnecessary code if the content model doesn't support redirects if (!ContentHandler::getForTitle($title)->supportsRedirects()) { return $article; } $request = $this->context->getRequest(); // Namespace might change when using redirects // Check for redirects ... $action = $request->getVal('action', 'view'); $file = $page instanceof WikiFilePage ? $page->getFile() : null; if (($action == 'view' || $action == 'render') && !$request->getVal('oldid') && !$request->getVal('diff') && $request->getVal('redirect') != 'no' && !(is_object($file) && $file->exists() && !$file->getRedirected())) { // Give extensions a change to ignore/handle redirects as needed $ignoreRedirect = $target = false; Hooks::run('InitializeArticleMaybeRedirect', [&$title, &$request, &$ignoreRedirect, &$target, &$article]); $page = $article->getPage(); // reflect any hook changes // Follow redirects only for... redirects. // If $target is set, then a hook wanted to redirect. if (!$ignoreRedirect && ($target || $page->isRedirect())) { // Is the target already set by an extension? $target = $target ? $target : $page->followRedirect(); if (is_string($target)) { if (!$this->config->get('DisableHardRedirects')) { // we'll need to redirect return $target; } } if (is_object($target)) { // Rewrite environment to redirected article $rpage = WikiPage::factory($target); $rpage->loadPageData(); if ($rpage->exists() || is_object($file) && !$file->isLocal()) { $rarticle = Article::newFromWikiPage($rpage, $this->context); $rarticle->setRedirectedFrom($title); $article = $rarticle; $this->context->setTitle($target); $this->context->setWikiPage($article->getPage()); } } } else { // Article may have been changed by hook $this->context->setTitle($article->getTitle()); $this->context->setWikiPage($article->getPage()); } } return $article; }
/** * @param Title $title * @return null|string */ protected function getContent($title) { $handler = ContentHandler::getForTitle($title); if ($handler->isSupportedFormat(CONTENT_FORMAT_CSS)) { $format = CONTENT_FORMAT_CSS; } elseif ($handler->isSupportedFormat(CONTENT_FORMAT_JAVASCRIPT)) { $format = CONTENT_FORMAT_JAVASCRIPT; } else { return null; } $revision = Revision::newFromTitle($title, false, Revision::READ_NORMAL); if (!$revision) { return null; } $content = $revision->getContent(Revision::RAW); if (!$content) { wfDebugLog('resourceloader', __METHOD__ . ': failed to load content of JS/CSS page!'); return null; } return $content->serialize($format); }
/** * @param Skin $skin * @param object $result Result row * @return string */ function formatResult($skin, $result) { $titleA = Title::makeTitle($result->namespace, $result->title); // If only titleA is in the query, it means this came from // querycache (which only saves 3 columns). // That does save the bulk of the query cost, but now we need to // get a little more detail about each individual entry quickly // using the filter of reallyGetQueryInfo. if ($result && !isset($result->nsb)) { $dbr = wfGetDB(DB_SLAVE); $qi = $this->reallyGetQueryInfo($result->namespace, $result->title); $res = $dbr->select($qi['tables'], $qi['fields'], $qi['conds'], __METHOD__); if ($res) { $result = $dbr->fetchObject($res); } } if (!$result) { return '<del>' . Linker::link($titleA, null, [], ['redirect' => 'no']) . '</del>'; } $titleB = Title::makeTitle($result->nsb, $result->tb); $titleC = Title::makeTitle($result->nsc, $result->tc, '', $result->iwc); $linkA = Linker::linkKnown($titleA, null, [], ['redirect' => 'no']); // if the page is editable, add an edit link if ($this->getUser()->isAllowed('edit') && ContentHandler::getForTitle($titleA)->supportsDirectEditing()) { $edit = Linker::linkKnown($titleA, $this->msg('parentheses', $this->msg('editlink')->text())->escaped(), [], ['action' => 'edit']); } else { $edit = ''; } $linkB = Linker::linkKnown($titleB, null, [], ['redirect' => 'no']); $linkC = Linker::linkKnown($titleC); $lang = $this->getLanguage(); $arr = $lang->getArrow() . $lang->getDirMark(); return "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}"; }
/** * Get the language in which the content of this page is written when * viewed by user. Defaults to $wgContLang, but in certain cases it can be * e.g. $wgLang (such as special pages, which are in the user language). * * @since 1.20 * @return Language */ public function getPageViewLanguage() { global $wgLang; if ($this->isSpecialPage()) { // If the user chooses a variant, the content is actually // in a language whose code is the variant code. $variant = $wgLang->getPreferredVariant(); if ($wgLang->getCode() !== $variant) { return Language::factory($variant); } return $wgLang; } // @note Can't be cached persistently, depends on user settings. // @note ContentHandler::getPageViewLanguage() may need to load the // content to determine the page language! $contentHandler = ContentHandler::getForTitle($this); $pageLang = $contentHandler->getPageViewLanguage($this); return $pageLang; }
protected function populateRevisionOrArchive(DatabaseBase $dbw, $table, $ns) { $prefix = $table === 'archive' ? 'ar' : 'rev'; $model_column = "{$prefix}_content_model"; $format_column = "{$prefix}_content_format"; $key = "{$prefix}_id"; if ($table === 'archive') { $selectTables = 'archive'; $fields = array('ar_namespace', 'ar_title'); $join_conds = array(); $where = $ns === 'all' ? array() : array('ar_namespace' => $ns); } else { // revision $selectTables = array('revision', 'page'); $fields = array('page_title', 'page_namespace'); $join_conds = array('page' => array('INNER JOIN', 'rev_page=page_id')); $where = $ns === 'all' ? array() : array('page_namespace' => $ns); } $toSave = array(); $lastId = 0; do { $rows = $dbw->select($selectTables, array_merge($fields, array($model_column, $format_column, $key)), array($model_column => null, "{$key} > " . $dbw->addQuotes($lastId)) + $where, __METHOD__, array('LIMIT' => $this->mBatchSize, 'ORDER BY' => "{$key} ASC"), $join_conds); $this->output("Fetched {$rows->numRows()} rows.\n"); foreach ($rows as $row) { if ($table === 'archive') { $title = Title::makeTitle($row->ar_namespace, $row->ar_title); } else { $title = Title::newFromRow($row); } $lastId = $row->{$key}; try { $handler = ContentHandler::getForTitle($title); } catch (MWException $e) { $this->error("Invalid content model for {$title}"); continue; } $defaultModel = $handler->getModelID(); $defaultFormat = $handler->getDefaultFormat(); $dbModel = $row->{$model_column}; $dbFormat = $row->{$format_column}; $id = $row->{$key}; if ($dbModel === null && $dbFormat === null) { // Set the defaults $toSave[$defaultModel][] = $row->{$key}; } else { // $dbModel === null, $dbFormat set. if ($dbFormat === $defaultFormat) { $toSave[$defaultModel][] = $row->{$key}; } else { // non-default format, just update now $this->output("Updating model to match format for {$table} {$id} of {$title}... "); $dbw->update($table, array($model_column => $defaultModel), array($key => $id), __METHOD__); wfWaitForSlaves(); $this->output("done.\n"); continue; } } if (count($toSave[$defaultModel]) >= $this->mBatchSize) { $this->updateRevisionOrArchiveRows($dbw, $toSave[$defaultModel], $defaultModel, $table); unset($toSave[$defaultModel]); } } } while ($rows->numRows() >= $this->mBatchSize); foreach ($toSave as $model => $ids) { $this->updateRevisionOrArchiveRows($dbw, $ids, $model, $table); } }
/** * 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 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(User $user, &$nt, $reason = '', $createRedirect = true) { global $wgContLang; if ($nt->exists()) { $moveOverRedirect = true; $logType = 'move_redir'; } else { $moveOverRedirect = false; $logType = 'move'; } 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(array('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); 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, $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', 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->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', array($newpage, $nullRevision, $nullRevision->getParentId(), $user)); $newpage->doEditUpdates($nullRevision, $user, array('changed' => false, 'moved' => true, 'oldcountable' => $oldcountable)); // If the default content model changes, we need to populate rev_content_model if ($defaultContentModelChanging) { $dbw->update('revision', array('rev_content_model' => $contentModel), array('rev_page' => $nt->getArticleID(), 'rev_content_model IS NULL'), __METHOD__); } if (!$moveOverRedirect) { 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(array('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', array($redirectArticle, $redirectRevision, false, $user)); $redirectArticle->doEditUpdates($redirectRevision, $user, array('created' => true)); } } # Log the move $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * 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 target page of merges? * * @return bool Success */ function merge() { # Get the titles directly from the IDs, in case the target page params # were spoofed. The queries are done based on the IDs, so it's best to # keep it consistent... $targetTitle = Title::newFromID($this->mTargetID); $destTitle = Title::newFromID($this->mDestID); if (is_null($targetTitle) || is_null($destTitle)) { return false; // validate these } if ($targetTitle->getArticleID() == $destTitle->getArticleID()) { return false; } # Verify that this timestamp is valid # Must be older than the destination page $dbw = wfGetDB(DB_MASTER); # Get timestamp into DB format $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp($this->mTimestamp) : ''; # Max timestamp should be min of destination page $maxtimestamp = $dbw->selectField('revision', 'MIN(rev_timestamp)', array('rev_page' => $this->mDestID), __METHOD__); # Destination page must exist with revisions if (!$maxtimestamp) { $this->getOutput()->addWikiMsg('mergehistory-fail'); return false; } # Get the latest timestamp of the source $lasttimestamp = $dbw->selectField(array('page', 'revision'), 'rev_timestamp', array('page_id' => $this->mTargetID, 'page_latest = rev_id'), __METHOD__); # $this->mTimestamp must be older than $maxtimestamp if ($this->mTimestamp >= $maxtimestamp) { $this->getOutput()->addWikiMsg('mergehistory-fail'); return false; } # Get the timestamp pivot condition if ($this->mTimestamp) { $timewhere = "rev_timestamp <= {$this->mTimestamp}"; $timestampLimit = wfTimestamp(TS_MW, $this->mTimestamp); } else { $timewhere = "rev_timestamp <= {$maxtimestamp}"; $timestampLimit = wfTimestamp(TS_MW, $lasttimestamp); } # Check that there are not too many revisions to move $limit = 5000; // avoid too much slave lag $count = $dbw->selectRowCount('revision', '1', array('rev_page' => $this->mTargetID, $timewhere), __METHOD__, array('LIMIT' => $limit + 1)); if ($count > $limit) { $this->getOutput()->addWikiMsg('mergehistory-fail-toobig'); return false; } # Do the moving... $dbw->update('revision', array('rev_page' => $this->mDestID), array('rev_page' => $this->mTargetID, $timewhere), __METHOD__); $count = $dbw->affectedRows(); # Make the source page a redirect if no revisions are left $haveRevisions = $dbw->selectField('revision', 'rev_timestamp', array('rev_page' => $this->mTargetID), __METHOD__, array('FOR UPDATE')); if (!$haveRevisions) { if ($this->mComment) { $comment = $this->msg('mergehistory-comment', $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $this->mComment)->inContentLanguage()->text(); } else { $comment = $this->msg('mergehistory-autocomment', $targetTitle->getPrefixedText(), $destTitle->getPrefixedText())->inContentLanguage()->text(); } $contentHandler = ContentHandler::getForTitle($targetTitle); $redirectContent = $contentHandler->makeRedirectContent($destTitle); if ($redirectContent) { $redirectPage = WikiPage::factory($targetTitle); $redirectRevision = new Revision(array('title' => $targetTitle, 'page' => $this->mTargetID, 'comment' => $comment, 'content' => $redirectContent)); $redirectRevision->insertOn($dbw); $redirectPage->updateRevisionOn($dbw, $redirectRevision); # Now, we record the link from the redirect to the new title. # It should have no other outgoing links... $dbw->delete('pagelinks', array('pl_from' => $this->mDestID), __METHOD__); $dbw->insert('pagelinks', array('pl_from' => $this->mDestID, 'pl_from_namespace' => $destTitle->getNamespace(), 'pl_namespace' => $destTitle->getNamespace(), 'pl_title' => $destTitle->getDBkey()), __METHOD__); } else { // would be nice to show a warning if we couldn't create a redirect } } else { $targetTitle->invalidateCache(); // update histories } $destTitle->invalidateCache(); // update histories # Check if this did anything if (!$count) { $this->getOutput()->addWikiMsg('mergehistory-fail'); return false; } # Update our logs $log = new LogPage('merge'); $log->addEntry('merge', $targetTitle, $this->mComment, array($destTitle->getPrefixedText(), $timestampLimit), $this->getUser()); # @todo message should use redirect=no $this->getOutput()->addWikiMsg('mergehistory-success', $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count); wfRunHooks('ArticleMergeComplete', array($targetTitle, $destTitle)); return true; }
/** * Parse the Content object and generate a ParserOutput from the result. * * @param $title Title The page title to use as a context for rendering * @param $revId null|int The revision being rendered (optional) * @param $options null|ParserOptions Any parser options * @param $generateHtml boolean Whether to generate HTML (default: true). * @param &$output ParserOutput representing the HTML form of the text. * @return ParserOutput */ protected function fillParserOutput(Title $title, $revId = null, ParserOptions $options = null, $generateHtml = true, ParserOutput &$output) { global $wgParser, $wgScribuntoUseGeSHi, $wgUseSiteCss; $text = $this->getNativeData(); $output = null; // Get documentation, if any $output = new ParserOutput(); $doc = Scribunto::getDocPage($title); if ($doc) { $msg = wfMessage($doc->exists() ? 'scribunto-doc-page-show' : 'scribunto-doc-page-does-not-exist', $doc->getPrefixedText())->inContentLanguage(); if (!$msg->isDisabled()) { // We need the ParserOutput for categories and such, so we // can't use $msg->parse(). $docViewLang = $doc->getPageViewLanguage(); $dir = $docViewLang->getDir(); // Code is forced to be ltr, but the documentation can be rtl. // Correct direction class is needed for correct formatting. // The possible classes are // mw-content-ltr or mw-content-rtl $dirClass = "mw-content-{$dir}"; $docWikitext = Html::rawElement('div', array('lang' => $docViewLang->getHtmlCode(), 'dir' => $dir, 'class' => $dirClass), "\n" . $msg->plain() . "\n"); if (!$options) { // NOTE: use canonical options per default to produce cacheable output $options = ContentHandler::getForTitle($doc)->makeParserOptions('canonical'); } else { if ($options->getTargetLanguage() === null) { $options->setTargetLanguage($doc->getPageLanguage()); } } $output = $wgParser->parse($docWikitext, $title, $options, true, true, $revId); } // Mark the doc page as a transclusion, so we get purged when it // changes. $output->addTemplate($doc, $doc->getArticleID(), $doc->getLatestRevID()); } // Validate the script, and include an error message and tracking // category if it's invalid $engine = Scribunto::newDefaultEngine(); $engine->setTitle($title); $status = $engine->validate($text, $title->getPrefixedDBkey()); if (!$status->isOK()) { $output->setText($output->getText() . Html::rawElement('div', array('class' => 'errorbox'), $status->getHTML('scribunto-error-short', 'scribunto-error-long'))); $catmsg = wfMessage('scribunto-module-with-errors-category')->title($title)->inContentLanguage(); if (!$catmsg->isDisabled()) { $cat = Title::makeTitleSafe(NS_CATEGORY, $catmsg->text()); if ($cat) { $sort = (string) $output->getProperty('defaultsort'); $output->addCategory($cat->getDBkey(), $sort); } else { wfDebug(__METHOD__ . ": [[MediaWiki:scribunto-module-with-errors-category]] " . "is not a valid title!\n"); } } } if (!$generateHtml) { // We don't need the actual HTML $output->setText(''); return $output; } // Add HTML for the actual script $language = $engine->getGeSHiLanguage(); if ($wgScribuntoUseGeSHi && $language) { $geshi = SyntaxHighlight_GeSHi::prepare($text, $language); $geshi->set_language($language); if ($geshi instanceof GeSHi && !$geshi->error()) { $code = $geshi->parse_code(); if ($code) { // @todo Once we drop support for old versions of // Extension:SyntaxHighlight_GeSHi, drop the ugly test and // the BC case. global $wgAutoloadClasses; if (isset($wgAutoloadClasses['ResourceLoaderGeSHiModule'])) { $output->addModuleStyles("ext.geshi.language.{$language}"); } else { // Backwards compatibility $output->addHeadItem(SyntaxHighlight_GeSHi::buildHeadItem($geshi), "source-{$language}"); } if ($wgUseSiteCss) { $output->addModuleStyles('ext.geshi.local'); } $output->setText($output->getText() . $code); return $output; } } } // No GeSHi, or GeSHi can't parse it, use plain <pre> $output->setText($output->getText() . "<pre class='mw-code mw-script' dir='ltr'>\n" . htmlspecialchars($text) . "\n</pre>\n"); return $output; }
/** * Returns whether section editing is supported for the current page. * Subclasses may override this to replace the default behavior, which is * to check ContentHandler::supportsSections. * * @return bool True if this edit page supports sections, false otherwise. */ protected function isSectionEditSupported() { $contentHandler = ContentHandler::getForTitle($this->mTitle); return $contentHandler->supportsSections(); }
/** * Really format a diff for the newsfeed * * @param $title Title object * @param $oldid Integer: old revision's id * @param $newid Integer: new revision's id * @param $timestamp Integer: new revision's timestamp * @param $comment String: new revision's comment * @param $actiontext String: text of the action; in case of log event * @return String */ public static function formatDiffRow($title, $oldid, $newid, $timestamp, $comment, $actiontext = '') { global $wgFeedDiffCutoff, $wgLang; wfProfileIn(__METHOD__); # log enties $completeText = '<p>' . implode(' ', array_filter(array($actiontext, Linker::formatComment($comment)))) . "</p>\n"; // NOTE: Check permissions for anonymous users, not current user. // No "privileged" version should end up in the cache. // Most feed readers will not log in anway. $anon = new User(); $accErrors = $title->getUserPermissionsErrors('read', $anon, true); // Can't diff special pages, unreadable pages or pages with no new revision // to compare against: just return the text. if ($title->getNamespace() < 0 || $accErrors || !$newid) { wfProfileOut(__METHOD__); return $completeText; } if ($oldid) { wfProfileIn(__METHOD__ . "-dodiff"); #$diffText = $de->getDiff( wfMessage( 'revisionasof', # $wgLang->timeanddate( $timestamp ), # $wgLang->date( $timestamp ), # $wgLang->time( $timestamp ) )->text(), # wfMessage( 'currentrev' )->text() ); $diffText = ''; // Don't bother generating the diff if we won't be able to show it if ($wgFeedDiffCutoff > 0) { $rev = Revision::newFromId($oldid); if (!$rev) { $diffText = false; } else { $context = clone RequestContext::getMain(); $context->setTitle($title); $contentHandler = $rev->getContentHandler(); $de = $contentHandler->createDifferenceEngine($context, $oldid, $newid); $diffText = $de->getDiff(wfMessage('previousrevision')->text(), wfMessage('revisionasof', $wgLang->timeanddate($timestamp), $wgLang->date($timestamp), $wgLang->time($timestamp))->text()); } } if ($wgFeedDiffCutoff <= 0 || strlen($diffText) > $wgFeedDiffCutoff) { // Omit large diffs $diffText = self::getDiffLink($title, $newid, $oldid); } elseif ($diffText === false) { // Error in diff engine, probably a missing revision $diffText = "<p>Can't load revision {$newid}</p>"; } else { // Diff output fine, clean up any illegal UTF-8 $diffText = UtfNormal::cleanUp($diffText); $diffText = self::applyDiffStyle($diffText); } wfProfileOut(__METHOD__ . "-dodiff"); } else { $rev = Revision::newFromId($newid); if ($wgFeedDiffCutoff <= 0 || is_null($rev)) { $newContent = ContentHandler::getForTitle($title)->makeEmptyContent(); } else { $newContent = $rev->getContent(); } if ($newContent instanceof TextContent) { // only textual content has a "source view". $text = $newContent->getNativeData(); if ($wgFeedDiffCutoff <= 0 || strlen($text) > $wgFeedDiffCutoff) { $html = null; } else { $html = nl2br(htmlspecialchars($text)); } } else { //XXX: we could get an HTML representation of the content via getParserOutput, but that may // contain JS magic and generally may not be suitable for inclusion in a feed. // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method. //Compare also ApiFeedContributions::feedItemDesc $html = null; } if ($html === null) { // Omit large new page diffs, bug 29110 // Also use diff link for non-textual content $diffText = self::getDiffLink($title, $newid); } else { $diffText = '<p><b>' . wfMessage('newpage')->text() . '</b></p>' . '<div>' . $html . '</div>'; } } $completeText .= $diffText; wfProfileOut(__METHOD__); return $completeText; }
private function cleanupArticle($id, $domain) { $title = Title::newFromID($id); if (!$title) { $this->error("Internal error: no page for ID {$id}"); return; } $this->output($title->getPrefixedDBkey() . " ..."); $rev = Revision::newFromTitle($title); $currentRevId = $rev->getId(); while ($rev && ($rev->isDeleted(Revision::DELETED_TEXT) || LinkFilter::matchEntry($rev->getContent(Revision::RAW), $domain))) { $rev = $rev->getPrevious(); } if ($rev && $rev->getId() == $currentRevId) { // The regex didn't match the current article text // This happens e.g. when a link comes from a template rather than the page itself $this->output("False match\n"); } else { $dbw = wfGetDB(DB_MASTER); $dbw->begin(__METHOD__); $page = WikiPage::factory($title); if ($rev) { // Revert to this revision $content = $rev->getContent(Revision::RAW); $this->output("reverting\n"); $page->doEditContent($content, wfMessage('spam_reverting', $domain)->inContentLanguage()->text(), EDIT_UPDATE, $rev->getId()); } elseif ($this->hasOption('delete')) { // Didn't find a non-spammy revision, blank the page $this->output("deleting\n"); $page->doDeleteArticle(wfMessage('spam_deleting', $domain)->inContentLanguage()->text()); } else { // Didn't find a non-spammy revision, blank the page $handler = ContentHandler::getForTitle($title); $content = $handler->makeEmptyContent(); $this->output("blanking\n"); $page->doEditContent($content, wfMessage('spam_blanking', $domain)->inContentLanguage()->text()); } $dbw->commit(__METHOD__); } }
/** * Get the contents to be preloaded into the box, either set by * an earlier setPreloadText() or by loading the given page. * * @param string $preload Representing the title to preload from. * @param array $params Parameters to use (interface-message style) in the preloaded text * * @return Content * * @since 1.21 */ protected function getPreloadedContent($preload, $params = array()) { global $wgUser; if (!empty($this->mPreloadContent)) { return $this->mPreloadContent; } $handler = ContentHandler::getForTitle($this->getTitle()); if ($preload === '') { return $handler->makeEmptyContent(); } $title = Title::newFromText($preload); # Check for existence to avoid getting MediaWiki:Noarticletext if ($title === null || !$title->exists() || !$title->userCan('read', $wgUser)) { //TODO: somehow show a warning to the user! return $handler->makeEmptyContent(); } $page = WikiPage::factory($title); if ($page->isRedirect()) { $title = $page->getRedirectTarget(); # Same as before if ($title === null || !$title->exists() || !$title->userCan('read', $wgUser)) { //TODO: somehow show a warning to the user! return $handler->makeEmptyContent(); } $page = WikiPage::factory($title); } $parserOptions = ParserOptions::newFromUser($wgUser); $content = $page->getContent(Revision::RAW); if (!$content) { //TODO: somehow show a warning to the user! return $handler->makeEmptyContent(); } if ($content->getModel() !== $handler->getModelID()) { $converted = $content->convert($handler->getModelID()); if (!$converted) { //TODO: somehow show a warning to the user! wfDebug("Attempt to preload incompatible content: " . "can't convert " . $content->getModel() . " to " . $handler->getModelID()); return $handler->makeEmptyContent(); } $content = $converted; } return $content->preloadTransform($title, $parserOptions, $params); }
protected function wlhLink(Title $target, $text, $editText) { static $title = null; if ($title === null) { $title = $this->getPageTitle(); } // always show a "<- Links" link $links = ['links' => Linker::linkKnown($title, $text, [], ['target' => $target->getPrefixedText()])]; // if the page is editable, add an edit link if ($this->getUser()->isAllowed('edit') && ContentHandler::getForTitle($target)->supportsDirectEditing()) { $links['edit'] = Linker::linkKnown($target, $editText, [], ['action' => 'edit']); } // build the links html return $this->getLanguage()->pipeList($links); }
/** * @param bool $hasHistory * @return mixed */ public function generateReason(&$hasHistory) { $title = $this->mPage->getTitle(); $handler = ContentHandler::getForTitle($title); return $handler->getAutoDeleteReason($title, $hasHistory); }
/** * 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; }
/** * @param Skin $skin * @param object $result Result row * @return string */ function formatResult($skin, $result) { $fromObj = Title::makeTitle($result->namespace, $result->title); if (isset($result->rd_title)) { $toObj = Title::makeTitle($result->rd_namespace, $result->rd_title); } else { $blinks = $fromObj->getBrokenLinksFrom(); # TODO: check for redirect, not for links if ($blinks) { $toObj = $blinks[0]; } else { $toObj = false; } } // $toObj may very easily be false if the $result list is cached if (!is_object($toObj)) { return '<del>' . Linker::link($fromObj) . '</del>'; } $from = Linker::linkKnown($fromObj, null, [], ['redirect' => 'no']); $links = []; // if the page is editable, add an edit link if ($this->getUser()->isAllowed('edit') && ContentHandler::getForTitle($fromObj)->supportsDirectEditing()) { $links[] = Linker::linkKnown($fromObj, $this->msg('brokenredirects-edit')->escaped(), [], ['action' => 'edit']); } $to = Linker::link($toObj, null, [], [], ['broken']); $arr = $this->getLanguage()->getArrow(); $out = $from . $this->msg('word-separator')->escaped(); if ($this->getUser()->isAllowed('delete')) { $links[] = Linker::linkKnown($fromObj, $this->msg('brokenredirects-delete')->escaped(), [], ['action' => 'delete']); } if ($links) { $out .= $this->msg('parentheses')->rawParams($this->getLanguage()->pipeList($links))->escaped(); } $out .= " {$arr} {$to}"; return $out; }
/** * Get the contents to be preloaded into the box, either set by * an earlier setPreloadText() or by loading the given page. * * @param $preload String: representing the title to preload from. * * @return Content * * @since 1.21 */ protected function getPreloadedContent($preload) { global $wgUser; if (!empty($this->mPreloadContent)) { return $this->mPreloadContent; } $handler = ContentHandler::getForTitle($this->getTitle()); if ($preload === '') { return $handler->makeEmptyContent(); } $title = Title::newFromText($preload); # Check for existence to avoid getting MediaWiki:Noarticletext if ($title === null || !$title->exists() || !$title->userCan('read', $wgUser)) { return $handler->makeEmptyContent(); } $page = WikiPage::factory($title); if ($page->isRedirect()) { $title = $page->getRedirectTarget(); # Same as before if ($title === null || !$title->exists() || !$title->userCan('read', $wgUser)) { return $handler->makeEmptyContent(); } $page = WikiPage::factory($title); } $parserOptions = ParserOptions::newFromUser($wgUser); $content = $page->getContent(Revision::RAW); if (!$content) { return $handler->makeEmptyContent(); } return $content->preloadTransform($title, $parserOptions); }