function execute($par) { $out = $this->getOutput(); $this->setHeaders(); $this->outputHeader(); $opts = new FormOptions(); $opts->add('target', ''); $opts->add('namespace', '', FormOptions::INTNULL); $opts->add('limit', 50); $opts->add('from', 0); $opts->add('back', 0); $opts->add('hideredirs', false); $opts->add('hidetrans', false); $opts->add('hidelinks', false); $opts->add('hideimages', false); $opts->fetchValuesFromRequest($this->getRequest()); $opts->validateIntBounds('limit', 0, 5000); // Give precedence to subpage syntax if (isset($par)) { $opts->setValue('target', $par); } // Bind to member variable $this->opts = $opts; $this->target = Title::newFromURL($opts->getValue('target')); if (!$this->target) { $out->addHTML($this->whatlinkshereForm()); return; } $this->getSkin()->setRelevantTitle($this->target); $this->selfTitle = $this->getTitle($this->target->getPrefixedDBkey()); $out->setPageTitle(wfMsg('whatlinkshere-title', $this->target->getPrefixedText())); $out->setSubtitle(wfMsg('whatlinkshere-backlink', Linker::link($this->target, $this->target->getPrefixedText(), array(), array('redirect' => 'no')))); $this->showIndirectLinks(0, $this->target, $opts->getValue('limit'), $opts->getValue('from'), $opts->getValue('back')); }
/** * @return string */ public function toString() { $paramString = ''; if ($this->params) { foreach ($this->params as $key => $value) { if ($paramString != '') { $paramString .= ' '; } if (is_array($value)) { $value = "array(" . count($value) . ")"; } elseif (is_object($value) && !method_exists($value, '__toString')) { $value = "object(" . get_class($value) . ")"; } $value = (string) $value; if (mb_strlen($value) > 1024) { $value = "string(" . mb_strlen($value) . ")"; } $paramString .= "{$key}={$value}"; } } if (is_object($this->title)) { $s = "{$this->command} " . $this->title->getPrefixedDBkey(); if ($paramString !== '') { $s .= ' ' . $paramString; } return $s; } else { return "{$this->command} {$paramString}"; } }
/** * Converts html to wikitext * * @param Title $title * @param string $html * @return string wikitext */ protected function convertHtmlToWikitext(Title $title, $html) { $wikitext = $this->requestParsoid('POST', 'transform/html/to/wikitext/' . urlencode($title->getPrefixedDBkey()), array('html' => $html, 'scrubWikitext' => 1)); if ($wikitext === false) { $this->dieUsage('Error contacting the Parsoid server', 'parsoidserver'); } return $wikitext; }
/** * @param Title|string $title Title object or prefixed DB key string * @param string $action * @throws MWException */ public function __construct($title, $action) { $allowedTypes = self::cacheablePageActions(); if (!in_array($action, $allowedTypes)) { throw new MWException('Invalid file cache type given.'); } $this->mKey = $title instanceof Title ? $title->getPrefixedDBkey() : (string) $title; $this->mType = (string) $action; $this->mExt = 'html'; }
/** * @return string */ public function toString() { $truncFunc = function ($value) { $value = (string) $value; if (mb_strlen($value) > 1024) { $value = "string(" . mb_strlen($value) . ")"; } return $value; }; $paramString = ''; if ($this->params) { foreach ($this->params as $key => $value) { if ($paramString != '') { $paramString .= ' '; } if (is_array($value)) { $filteredValue = array(); foreach ($value as $k => $v) { if (is_scalar($v)) { $filteredValue[$k] = $truncFunc($v); } else { $filteredValue = null; break; } } if ($filteredValue && count($filteredValue) < 10) { $value = FormatJson::encode($filteredValue); } else { $value = "array(" . count($value) . ")"; } } elseif (is_object($value) && !method_exists($value, '__toString')) { $value = "object(" . get_class($value) . ")"; } $paramString .= "{$key}={$truncFunc($value)}"; } } $metaString = ''; foreach ($this->metadata as $key => $value) { if (is_scalar($value) && mb_strlen($value) < 1024) { $metaString .= $metaString ? ",{$key}={$value}" : "{$key}={$value}"; } } $s = $this->command; if (is_object($this->title)) { $s .= " {$this->title->getPrefixedDBkey()}"; } if ($paramString != '') { $s .= " {$paramString}"; } if ($metaString != '') { $s .= " ({$metaString})"; } return $s; }
/** * Construct an ObjectFileCache from a Title and an action * @param Title|string $title Title object or prefixed DB key string * @param string $action * @throws MWException * @return HTMLFileCache */ public static function newFromTitle($title, $action) { $cache = new self(); $allowedTypes = self::cacheablePageActions(); if (!in_array($action, $allowedTypes)) { throw new MWException("Invalid filecache type given."); } $cache->mKey = $title instanceof Title ? $title->getPrefixedDBkey() : (string) $title; $cache->mType = (string) $action; $cache->mExt = 'html'; return $cache; }
/** * @return string */ public function toString() { $paramString = ''; if ($this->params) { foreach ($this->params as $key => $value) { if ($paramString != '') { $paramString .= ' '; } if (is_array($value)) { $filteredValue = []; foreach ($value as $k => $v) { $json = FormatJson::encode($v); if ($json === false || mb_strlen($json) > 512) { $filteredValue[$k] = gettype($v) . '(...)'; } else { $filteredValue[$k] = $v; } } if (count($filteredValue) <= 10) { $value = FormatJson::encode($filteredValue); } else { $value = "array(" . count($value) . ")"; } } elseif (is_object($value) && !method_exists($value, '__toString')) { $value = "object(" . get_class($value) . ")"; } $flatValue = (string) $value; if (mb_strlen($value) > 1024) { $flatValue = "string(" . mb_strlen($value) . ")"; } $paramString .= "{$key}={$flatValue}"; } } $metaString = ''; foreach ($this->metadata as $key => $value) { if (is_scalar($value) && mb_strlen($value) < 1024) { $metaString .= $metaString ? ",{$key}={$value}" : "{$key}={$value}"; } } $s = $this->command; if (is_object($this->title)) { $s .= " {$this->title->getPrefixedDBkey()}"; } if ($paramString != '') { $s .= " {$paramString}"; } if ($metaString != '') { $s .= " ({$metaString})"; } return $s; }
function execute($par) { $out = $this->getOutput(); $this->setHeaders(); $this->outputHeader(); $this->addHelpLink('Help:What links here'); $opts = new FormOptions(); $opts->add('target', ''); $opts->add('namespace', '', FormOptions::INTNULL); $opts->add('limit', $this->getConfig()->get('QueryPageDefaultLimit')); $opts->add('from', 0); $opts->add('back', 0); $opts->add('hideredirs', false); $opts->add('hidetrans', false); $opts->add('hidelinks', false); $opts->add('hideimages', false); $opts->add('invert', false); $opts->fetchValuesFromRequest($this->getRequest()); $opts->validateIntBounds('limit', 0, 5000); // Give precedence to subpage syntax if ($par !== null) { $opts->setValue('target', $par); } // Bind to member variable $this->opts = $opts; $this->target = Title::newFromText($opts->getValue('target')); if (!$this->target) { if (!$this->including()) { $out->addHTML($this->whatlinkshereForm()); } return; } $this->getSkin()->setRelevantTitle($this->target); $this->selfTitle = $this->getPageTitle($this->target->getPrefixedDBkey()); $out->setPageTitle($this->msg('whatlinkshere-title', $this->target->getPrefixedText())); $out->addBacklinkSubtitle($this->target); $this->showIndirectLinks(0, $this->target, $opts->getValue('limit'), $opts->getValue('from'), $opts->getValue('back')); }
static function isHubsPage(Title &$title) { global $wgHubsPages, $wgContLanguageCode; wfProfileIn(__METHOD__); if (empty($wgHubsPages[$wgContLanguageCode])) { return false; } $dbKey = strtolower($title->getPrefixedDBkey()); foreach ($wgHubsPages[$wgContLanguageCode] as $key => $value) { if (is_array($key)) { $key = $key['name']; } if ($dbKey == strtolower($key)) { wfProfileOut(__METHOD__); return true; } } wfProfileOut(__METHOD__); return false; }
/** * @return string */ public function toString() { $paramString = ''; if ($this->params) { foreach ($this->params as $key => $value) { if ($paramString != '') { $paramString .= ' '; } $paramString .= "{$key}={$value}"; } } if (is_object($this->title)) { $s = "{$this->command} " . $this->title->getPrefixedDBkey(); if ($paramString !== '') { $s .= ' ' . $paramString; } return $s; } else { return "{$this->command} {$paramString}"; } }
/** * Checks if $this can be moved to a given Title * - Selects for update, so don't call it unless you mean business * * @since 1.25 * @return bool */ protected function isValidMoveTarget() { # Is it an existing file? if ($this->newTitle->inNamespace(NS_FILE)) { $file = wfLocalFile($this->newTitle); $file->load(File::READ_LATEST); if ($file->exists()) { wfDebug(__METHOD__ . ": file exists\n"); return false; } } # Is it a redirect with no history? if (!$this->newTitle->isSingleRevRedirect()) { wfDebug(__METHOD__ . ": not a one-rev redirect\n"); return false; } # Get the article text $rev = Revision::newFromTitle($this->newTitle, false, Revision::READ_LATEST); if (!is_object($rev)) { return false; } $content = $rev->getContent(); # Does the redirect point to the source? # Or is it a broken self-redirect, usually caused by namespace collisions? $redirTitle = $content ? $content->getRedirectTarget() : null; if ($redirTitle) { if ($redirTitle->getPrefixedDBkey() !== $this->oldTitle->getPrefixedDBkey() && $redirTitle->getPrefixedDBkey() !== $this->newTitle->getPrefixedDBkey()) { wfDebug(__METHOD__ . ": redirect points to other page\n"); return false; } else { return true; } } else { # Fail safe (not a redirect after all. strange.) wfDebug(__METHOD__ . ": failsafe: database says " . $this->newTitle->getPrefixedDBkey() . " is a redirect, but it doesn't contain a valid redirect.\n"); return false; } }
/** * Extract information from a Title object for return to Lua * * This also records a link to this title in the current ParserOutput * and caches the title for repeated lookups. The caller should call * incrementExpensiveFunctionCount() if necessary. * * @param $title Title Title to return * @return array Lua data */ private function returnTitleToLua(Title $title) { // Cache it $this->titleCache[$title->getPrefixedDBkey()] = $title; if ($title->getArticleID() > 0) { $this->idCache[$title->getArticleID()] = $title; } // Record a link if ($this->getParser() && !$title->equals($this->getTitle())) { $this->getParser()->getOutput()->addLink($title); } $ns = $title->getNamespace(); $ret = array('isLocal' => (bool) $title->isLocal(), 'isRedirect' => (bool) $title->isRedirect(), 'interwiki' => $title->getInterwiki(), 'namespace' => $ns, 'nsText' => $title->getNsText(), 'text' => $title->getText(), 'id' => $title->getArticleID(), 'fragment' => $title->getFragment(), 'thePartialUrl' => $title->getPartialURL()); if ($ns === NS_SPECIAL) { $ret['exists'] = (bool) SpecialPageFactory::exists($title->getDBkey()); } else { $ret['exists'] = $ret['id'] > 0; } if ($ns !== NS_FILE && $ns !== NS_MEDIA) { $ret['fileExists'] = false; } return $ret; }
/** * Insert jobs into the job queue to fix redirects to the given title * @param string $type The reason for the fix, see message double-redirect-fixed-<reason> * @param Title $redirTitle The title which has changed, redirects pointing to this title are fixed */ public static function fixRedirects($reason, $redirTitle, $destTitle = false) { # Need to use the master to get the redirect table updated in the same transaction $dbw = wfGetDB(DB_MASTER); $res = $dbw->select(array('redirect', 'page'), array('page_namespace', 'page_title'), array('page_id = rd_from', 'rd_namespace' => $redirTitle->getNamespace(), 'rd_title' => $redirTitle->getDBkey()), __METHOD__); if (!$res->numRows()) { return; } $jobs = array(); foreach ($res as $row) { $title = Title::makeTitle($row->page_namespace, $row->page_title); if (!$title) { continue; } $jobs[] = new self($title, array('reason' => $reason, 'redirTitle' => $redirTitle->getPrefixedDBkey())); # Avoid excessive memory usage if (count($jobs) > 10000) { Job::batchInsert($jobs); $jobs = array(); } } Job::batchInsert($jobs); }
/** * Do standard deferred updates after page edit. * Update links tables, site stats, search index and message cache. * Purges pages that include this page if the text was changed here. * Every 100th edit, prune the recent changes table. * * @param Revision $revision * @param User $user User object that did the revision * @param array $options Array of options, following indexes are used: * - changed: boolean, whether the revision changed the content (default true) * - created: boolean, whether the revision created the page (default false) * - moved: boolean, whether the page was moved (default false) * - restored: boolean, whether the page was undeleted (default false) * - oldrevision: Revision object for the pre-update revision (default null) * - oldcountable: boolean, null, or string 'no-change' (default null): * - boolean: whether the page was counted as an article before that * revision, only used in changed is true and created is false * - null: if created is false, don't update the article count; if created * is true, do update the article count * - 'no-change': don't update the article count, ever */ public function doEditUpdates(Revision $revision, User $user, array $options = []) { global $wgRCWatchCategoryMembership, $wgContLang; $options += ['changed' => true, 'created' => false, 'moved' => false, 'restored' => false, 'oldrevision' => null, 'oldcountable' => null]; $content = $revision->getContent(); $logger = LoggerFactory::getInstance('SaveParse'); // See if the parser output before $revision was inserted is still valid $editInfo = false; if (!$this->mPreparedEdit) { $logger->debug(__METHOD__ . ": No prepared edit...\n"); } elseif ($this->mPreparedEdit->output->getFlag('vary-revision')) { $logger->info(__METHOD__ . ": Prepared edit has vary-revision...\n"); } elseif ($this->mPreparedEdit->output->getFlag('vary-revision-id') && $this->mPreparedEdit->output->getSpeculativeRevIdUsed() !== $revision->getId()) { $logger->info(__METHOD__ . ": Prepared edit has vary-revision-id with wrong ID...\n"); } elseif ($this->mPreparedEdit->output->getFlag('vary-user') && !$options['changed']) { $logger->info(__METHOD__ . ": Prepared edit has vary-user and is null...\n"); } else { wfDebug(__METHOD__ . ": Using prepared edit...\n"); $editInfo = $this->mPreparedEdit; } if (!$editInfo) { // Parse the text again if needed. Be careful not to do pre-save transform twice: // $text is usually already pre-save transformed once. Avoid using the edit stash // as any prepared content from there or in doEditContent() was already rejected. $editInfo = $this->prepareContentForEdit($content, $revision, $user, null, false); } // Save it to the parser cache. // Make sure the cache time matches page_touched to avoid double parsing. ParserCache::singleton()->save($editInfo->output, $this, $editInfo->popts, $revision->getTimestamp(), $editInfo->revid); // Update the links tables and other secondary data if ($content) { $recursive = $options['changed']; // bug 50785 $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, $recursive, $editInfo->output); foreach ($updates as $update) { if ($update instanceof LinksUpdate) { $update->setRevision($revision); $update->setTriggeringUser($user); } DeferredUpdates::addUpdate($update); } if ($wgRCWatchCategoryMembership && $this->getContentHandler()->supportsCategories() === true && ($options['changed'] || $options['created']) && !$options['restored']) { // Note: jobs are pushed after deferred updates, so the job should be able to see // the recent change entry (also done via deferred updates) and carry over any // bot/deletion/IP flags, ect. JobQueueGroup::singleton()->lazyPush(new CategoryMembershipChangeJob($this->getTitle(), ['pageId' => $this->getId(), 'revTimestamp' => $revision->getTimestamp()])); } } Hooks::run('ArticleEditUpdates', [&$this, &$editInfo, $options['changed']]); if (Hooks::run('ArticleEditUpdatesDeleteFromRecentchanges', [&$this])) { // Flush old entries from the `recentchanges` table if (mt_rand(0, 9) == 0) { JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newPurgeJob()); } } if (!$this->exists()) { return; } $id = $this->getId(); $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey(); if ($options['oldcountable'] === 'no-change' || !$options['changed'] && !$options['moved']) { $good = 0; } elseif ($options['created']) { $good = (int) $this->isCountable($editInfo); } elseif ($options['oldcountable'] !== null) { $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable']; } else { $good = 0; } $edits = $options['changed'] ? 1 : 0; $total = $options['created'] ? 1 : 0; DeferredUpdates::addUpdate(new SiteStatsUpdate(0, $edits, $good, $total)); DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content)); // If this is another user's talk page, update newtalk. // Don't do this if $options['changed'] = false (null-edits) nor if // it's a minor edit and the user doesn't want notifications for those. if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) { $recipient = User::newFromName($shortTitle, false); if (!$recipient) { wfDebug(__METHOD__ . ": invalid username\n"); } else { // Allow extensions to prevent user notification // when a new message is added to their talk page if (Hooks::run('ArticleEditUpdateNewTalk', [&$this, $recipient])) { if (User::isIP($shortTitle)) { // An anonymous user $recipient->setNewtalk(true, $revision); } elseif ($recipient->isLoggedIn()) { $recipient->setNewtalk(true, $revision); } else { wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n"); } } } } if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) { // XXX: could skip pseudo-messages like js/css here, based on content model. $msgtext = $content ? $content->getWikitextForTransclusion() : null; if ($msgtext === false || $msgtext === null) { $msgtext = ''; } MessageCache::singleton()->replace($shortTitle, $msgtext); if ($wgContLang->hasVariants()) { $wgContLang->updateConversionTable($this->mTitle); } } if ($options['created']) { self::onArticleCreate($this->mTitle); } elseif ($options['changed']) { // bug 50785 self::onArticleEdit($this->mTitle, $revision); } ResourceLoaderWikiModule::invalidateModuleCache($this->mTitle, $options['oldrevision'], $revision, wfWikiID()); }
/** * This function accomplishes several tasks: * 1) Auto-number headings if that option is enabled * 2) Add an [edit] link to sections for users who have enabled the option and can edit the page * 3) Add a Table of contents on the top for users who have enabled the option * 4) Auto-anchor headings * * It loops through all headlines, collects the necessary data, then splits up the * string and re-inserts the newly formatted headlines. * * @param $text String * @param string $origText original, untouched wikitext * @param $isMain Boolean * @return mixed|string * @private */ function formatHeadings($text, $origText, $isMain = true) { global $wgMaxTocLevel, $wgExperimentalHtmlIds; # Inhibit editsection links if requested in the page if (isset($this->mDoubleUnderscores['noeditsection'])) { $maybeShowEditLink = $showEditLink = false; } else { $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */ $showEditLink = $this->mOptions->getEditSection(); } if ($showEditLink) { $this->mOutput->setEditSectionTokens(true); } # Get all headlines for numbering them and adding funky stuff like [edit] # links - this is for later, but we need the number of headlines right now $matches = array(); $numMatches = preg_match_all('/<H(?P<level>[1-6])(?P<attrib>.*?' . '>)\\s*(?P<header>[\\s\\S]*?)\\s*<\\/H[1-6] *>/i', $text, $matches); # if there are fewer than 4 headlines in the article, do not show TOC # unless it's been explicitly enabled. $enoughToc = $this->mShowToc && ($numMatches >= 4 || $this->mForceTocPosition); # Allow user to stipulate that a page should have a "new section" # link added via __NEWSECTIONLINK__ if (isset($this->mDoubleUnderscores['newsectionlink'])) { $this->mOutput->setNewSection(true); } # Allow user to remove the "new section" # link via __NONEWSECTIONLINK__ if (isset($this->mDoubleUnderscores['nonewsectionlink'])) { $this->mOutput->hideNewSection(true); } # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, # override above conditions and always show TOC above first header if (isset($this->mDoubleUnderscores['forcetoc'])) { $this->mShowToc = true; $enoughToc = true; } # headline counter $headlineCount = 0; $numVisible = 0; # Ugh .. the TOC should have neat indentation levels which can be # passed to the skin functions. These are determined here $toc = ''; $full = ''; $head = array(); $sublevelCount = array(); $levelCount = array(); $level = 0; $prevlevel = 0; $toclevel = 0; $prevtoclevel = 0; $markerRegex = "{$this->mUniqPrefix}-h-(\\d+)-" . self::MARKER_SUFFIX; $baseTitleText = $this->mTitle->getPrefixedDBkey(); $oldType = $this->mOutputType; $this->setOutputType(self::OT_WIKI); $frame = $this->getPreprocessor()->newFrame(); $root = $this->preprocessToDom($origText); $node = $root->getFirstChild(); $byteOffset = 0; $tocraw = array(); $refers = array(); foreach ($matches[3] as $headline) { $isTemplate = false; $titleText = false; $sectionIndex = false; $numbering = ''; $markerMatches = array(); if (preg_match("/^{$markerRegex}/", $headline, $markerMatches)) { $serial = $markerMatches[1]; list($titleText, $sectionIndex) = $this->mHeadings[$serial]; $isTemplate = $titleText != $baseTitleText; $headline = preg_replace("/^{$markerRegex}\\s*/", "", $headline); } if ($toclevel) { $prevlevel = $level; } $level = $matches[1][$headlineCount]; if ($level > $prevlevel) { # Increase TOC level $toclevel++; $sublevelCount[$toclevel] = 0; if ($toclevel < $wgMaxTocLevel) { $prevtoclevel = $toclevel; $toc .= Linker::tocIndent(); $numVisible++; } } elseif ($level < $prevlevel && $toclevel > 1) { # Decrease TOC level, find level to jump to for ($i = $toclevel; $i > 0; $i--) { if ($levelCount[$i] == $level) { # Found last matching level $toclevel = $i; break; } elseif ($levelCount[$i] < $level) { # Found first matching level below current level $toclevel = $i + 1; break; } } if ($i == 0) { $toclevel = 1; } if ($toclevel < $wgMaxTocLevel) { if ($prevtoclevel < $wgMaxTocLevel) { # Unindent only if the previous toc level was shown :p $toc .= Linker::tocUnindent($prevtoclevel - $toclevel); $prevtoclevel = $toclevel; } else { $toc .= Linker::tocLineEnd(); } } } else { # No change in level, end TOC line if ($toclevel < $wgMaxTocLevel) { $toc .= Linker::tocLineEnd(); } } $levelCount[$toclevel] = $level; # count number of headlines for each level $sublevelCount[$toclevel]++; $dot = 0; for ($i = 1; $i <= $toclevel; $i++) { if (!empty($sublevelCount[$i])) { if ($dot) { $numbering .= '.'; } $numbering .= $this->getTargetLanguage()->formatNum($sublevelCount[$i]); $dot = 1; } } # The safe header is a version of the header text safe to use for links # Remove link placeholders by the link text. # <!--LINK number--> # turns into # link text with suffix # Do this before unstrip since link text can contain strip markers $safeHeadline = $this->replaceLinkHoldersText($headline); # Avoid insertion of weird stuff like <math> by expanding the relevant sections $safeHeadline = $this->mStripState->unstripBoth($safeHeadline); # Strip out HTML (first regex removes any tag not allowed) # Allowed tags are: # * <sup> and <sub> (bug 8393) # * <i> (bug 26375) # * <b> (r105284) # * <span dir="rtl"> and <span dir="ltr"> (bug 35167) # # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>, # to allow setting directionality in toc items. $tocline = preg_replace(array('#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#', '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#'), array('', '<$1>'), $safeHeadline); $tocline = trim($tocline); # For the anchor, strip out HTML-y stuff period $safeHeadline = preg_replace('/<.*?' . '>/', '', $safeHeadline); $safeHeadline = Sanitizer::normalizeSectionNameWhitespace($safeHeadline); # Save headline for section edit hint before it's escaped $headlineHint = $safeHeadline; if ($wgExperimentalHtmlIds) { # For reverse compatibility, provide an id that's # HTML4-compatible, like we used to. # # It may be worth noting, academically, that it's possible for # the legacy anchor to conflict with a non-legacy headline # anchor on the page. In this case likely the "correct" thing # would be to either drop the legacy anchors or make sure # they're numbered first. However, this would require people # to type in section names like "abc_.D7.93.D7.90.D7.A4" # manually, so let's not bother worrying about it. $legacyHeadline = Sanitizer::escapeId($safeHeadline, array('noninitial', 'legacy')); $safeHeadline = Sanitizer::escapeId($safeHeadline); if ($legacyHeadline == $safeHeadline) { # No reason to have both (in fact, we can't) $legacyHeadline = false; } } else { $legacyHeadline = false; $safeHeadline = Sanitizer::escapeId($safeHeadline, 'noninitial'); } # HTML names must be case-insensitively unique (bug 10721). # This does not apply to Unicode characters per # http://dev.w3.org/html5/spec/infrastructure.html#case-sensitivity-and-string-comparison # @todo FIXME: We may be changing them depending on the current locale. $arrayKey = strtolower($safeHeadline); if ($legacyHeadline === false) { $legacyArrayKey = false; } else { $legacyArrayKey = strtolower($legacyHeadline); } # count how many in assoc. array so we can track dupes in anchors if (isset($refers[$arrayKey])) { $refers[$arrayKey]++; } else { $refers[$arrayKey] = 1; } if (isset($refers[$legacyArrayKey])) { $refers[$legacyArrayKey]++; } else { $refers[$legacyArrayKey] = 1; } # Don't number the heading if it is the only one (looks silly) if (count($matches[3]) > 1 && $this->mOptions->getNumberHeadings()) { # the two are different if the line contains a link $headline = Html::element('span', array('class' => 'mw-headline-number'), $numbering) . ' ' . $headline; } # Create the anchor for linking from the TOC to the section $anchor = $safeHeadline; $legacyAnchor = $legacyHeadline; if ($refers[$arrayKey] > 1) { $anchor .= '_' . $refers[$arrayKey]; } if ($legacyHeadline !== false && $refers[$legacyArrayKey] > 1) { $legacyAnchor .= '_' . $refers[$legacyArrayKey]; } if ($enoughToc && (!isset($wgMaxTocLevel) || $toclevel < $wgMaxTocLevel)) { $toc .= Linker::tocLine($anchor, $tocline, $numbering, $toclevel, $isTemplate ? false : $sectionIndex); } # Add the section to the section tree # Find the DOM node for this header $noOffset = $isTemplate || $sectionIndex === false; while ($node && !$noOffset) { if ($node->getName() === 'h') { $bits = $node->splitHeading(); if ($bits['i'] == $sectionIndex) { break; } } $byteOffset += mb_strlen($this->mStripState->unstripBoth($frame->expand($node, PPFrame::RECOVER_ORIG))); $node = $node->getNextSibling(); } $tocraw[] = array('toclevel' => $toclevel, 'level' => $level, 'line' => $tocline, 'number' => $numbering, 'index' => ($isTemplate ? 'T-' : '') . $sectionIndex, 'fromtitle' => $titleText, 'byteoffset' => $noOffset ? null : $byteOffset, 'anchor' => $anchor); # give headline the correct <h#> tag if ($maybeShowEditLink && $sectionIndex !== false) { // Output edit section links as markers with styles that can be customized by skins if ($isTemplate) { # Put a T flag in the section identifier, to indicate to extractSections() # that sections inside <includeonly> should be counted. $editlinkArgs = array($titleText, "T-{$sectionIndex}"); } else { $editlinkArgs = array($this->mTitle->getPrefixedText(), $sectionIndex, $headlineHint); } // We use a bit of pesudo-xml for editsection markers. The language converter is run later on // Using a UNIQ style marker leads to the converter screwing up the tokens when it converts stuff // And trying to insert strip tags fails too. At this point all real inputted tags have already been escaped // so we don't have to worry about a user trying to input one of these markers directly. // We use a page and section attribute to stop the language converter from converting these important bits // of data, but put the headline hint inside a content block because the language converter is supposed to // be able to convert that piece of data. $editlink = '<mw:editsection page="' . htmlspecialchars($editlinkArgs[0]); $editlink .= '" section="' . htmlspecialchars($editlinkArgs[1]) . '"'; if (isset($editlinkArgs[2])) { $editlink .= '>' . $editlinkArgs[2] . '</mw:editsection>'; } else { $editlink .= '/>'; } } else { $editlink = ''; } $head[$headlineCount] = Linker::makeHeadline($level, $matches['attrib'][$headlineCount], $anchor, $headline, $editlink, $legacyAnchor); $headlineCount++; } $this->setOutputType($oldType); # Never ever show TOC if no headers if ($numVisible < 1) { $enoughToc = false; } if ($enoughToc) { if ($prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel) { $toc .= Linker::tocUnindent($prevtoclevel - 1); } $toc = Linker::tocList($toc, $this->mOptions->getUserLangObj()); $this->mOutput->setTOCHTML($toc); $toc = self::TOC_START . $toc . self::TOC_END; } if ($isMain) { $this->mOutput->setSections($tocraw); } # split up and insert constructed headlines $blocks = preg_split('/<H[1-6].*?' . '>[\\s\\S]*?<\\/H[1-6]>/i', $text); $i = 0; // build an array of document sections $sections = array(); foreach ($blocks as $block) { // $head is zero-based, sections aren't. if (empty($head[$i - 1])) { $sections[$i] = $block; } else { $sections[$i] = $head[$i - 1] . $block; } /** * Send a hook, one per section. * The idea here is to be able to make section-level DIVs, but to do so in a * lower-impact, more correct way than r50769 * * $this : caller * $section : the section number * &$sectionContent : ref to the content of the section * $showEditLinks : boolean describing whether this section has an edit link */ wfRunHooks('ParserSectionCreate', array($this, $i, &$sections[$i], $showEditLink)); $i++; } if ($enoughToc && $isMain && !$this->mForceTocPosition) { // append the TOC at the beginning // Top anchor now in skin $sections[0] = $sections[0] . $toc . "\n"; } $full .= join('', $sections); if ($this->mForceTocPosition) { return str_replace('<!--MWTOC-->', $toc, $full); } else { return $full; } }
/** * @param object $row * @param Title $title */ protected function moveInconsistentPage($row, $title) { if ($title->exists() || $title->getInterwiki() || !$title->canExist()) { if ($title->getInterwiki() || !$title->canExist()) { $prior = $title->getPrefixedDBkey(); } else { $prior = $title->getDBkey(); } # Old cleanupTitles could move articles there. See bug 23147. $ns = $row->page_namespace; if ($ns < 0) { $ns = 0; } # Namespace which no longer exists. Put the page in the main namespace # since we don't have any idea of the old namespace name. See bug 68501. if (!MWNamespace::exists($ns)) { $ns = 0; } $clean = 'Broken/' . $prior; $verified = Title::makeTitleSafe($ns, $clean); if (!$verified || $verified->exists()) { $blah = "Broken/id:" . $row->page_id; $this->output("Couldn't legalize; form '{$clean}' exists; using '{$blah}'\n"); $verified = Title::makeTitleSafe($ns, $blah); } $title = $verified; } if (is_null($title)) { $this->error("Something awry; empty title.", true); } $ns = $title->getNamespace(); $dest = $title->getDBkey(); if ($this->dryrun) { $this->output("DRY RUN: would rename {$row->page_id} ({$row->page_namespace}," . "'{$row->page_title}') to ({$ns},'{$dest}')\n"); } else { $this->output("renaming {$row->page_id} ({$row->page_namespace}," . "'{$row->page_title}') to ({$ns},'{$dest}')\n"); $dbw = wfGetDB(DB_MASTER); $dbw->update('page', array('page_namespace' => $ns, 'page_title' => $dest), array('page_id' => $row->page_id), __METHOD__); LinkCache::singleton()->clear(); } }
/** * Determine if an image exists on the 'bad image list'. * * The format of MediaWiki:Bad_image_list is as follows: * * Only list items (lines starting with "*") are considered * * The first link on a line must be a link to a bad image * * Any subsequent links on the same line are considered to be exceptions, * i.e. articles where the image may occur inline. * * @param string $name The image name to check * @param Title|bool $contextTitle The page on which the image occurs, if known * @param string $blacklist Wikitext of a file blacklist * @return bool */ function wfIsBadImage($name, $contextTitle = false, $blacklist = null) { static $badImageCache = null; // based on bad_image_list msg # Handle redirects $redirectTitle = RepoGroup::singleton()->checkRedirect(Title::makeTitle(NS_FILE, $name)); if ($redirectTitle) { $name = $redirectTitle->getDBkey(); } # Run the extension hook $bad = false; if (!Hooks::run('BadImage', array($name, &$bad))) { return $bad; } $cacheable = $blacklist === null; if ($cacheable && $badImageCache !== null) { $badImages = $badImageCache; } else { // cache miss if ($blacklist === null) { $blacklist = wfMessage('bad_image_list')->inContentLanguage()->plain(); // site list } # Build the list now $badImages = array(); $lines = explode("\n", $blacklist); foreach ($lines as $line) { # List items only if (substr($line, 0, 1) !== '*') { continue; } # Find all links $m = array(); if (!preg_match_all('/\\[\\[:?(.*?)\\]\\]/', $line, $m)) { continue; } $exceptions = array(); $imageDBkey = false; foreach ($m[1] as $i => $titleText) { $title = Title::newFromText($titleText); if (!is_null($title)) { if ($i == 0) { $imageDBkey = $title->getDBkey(); } else { $exceptions[$title->getPrefixedDBkey()] = true; } } } if ($imageDBkey !== false) { $badImages[$imageDBkey] = $exceptions; } } if ($cacheable) { $badImageCache = $badImages; } } $contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false; $bad = isset($badImages[$name]) && !isset($badImages[$name][$contextKey]); return $bad; }
private function formatFileRow($row) { $file = ArchivedFile::newFromRow($row); $ts = wfTimestamp(TS_MW, $row->fa_timestamp); $user = $this->getUser(); $checkBox = ''; if ($this->mCanView && $row->fa_storage_key) { if ($this->mAllowed) { $checkBox = Xml::check('fileid' . $row->fa_id); } $key = urlencode($row->fa_storage_key); $pageLink = $this->getFileLink($file, $this->getPageTitle(), $ts, $key); } else { $pageLink = $this->getLanguage()->userTimeAndDate($ts, $user); } $userLink = $this->getFileUser($file); $data = $this->msg('widthheight')->numParams($row->fa_width, $row->fa_height)->text(); $bytes = $this->msg('parentheses')->rawParams($this->msg('nbytes')->numParams($row->fa_size)->text())->plain(); $data = htmlspecialchars($data . ' ' . $bytes); $comment = $this->getFileComment($file); // Add show/hide deletion links if available $canHide = $this->isAllowed('deleterevision'); if ($canHide || $file->getVisibility() && $this->isAllowed('deletedhistory')) { if (!$file->userCan(File::DELETED_RESTRICTED, $user)) { // Revision was hidden from sysops $revdlink = Linker::revDeleteLinkDisabled($canHide); } else { $query = array('type' => 'filearchive', 'target' => $this->mTargetObj->getPrefixedDBkey(), 'ids' => $row->fa_id); $revdlink = Linker::revDeleteLink($query, $file->isDeleted(File::DELETED_RESTRICTED), $canHide); } } else { $revdlink = ''; } return "<li>{$checkBox} {$revdlink} {$pageLink} . . {$userLink} {$data} {$comment}</li>\n"; }
/** * Get a revision-deletion link, or disabled link, or nothing, depending * on user permissions & the settings on the revision. * * Will use forward-compatible revision ID in the Special:RevDelete link * if possible, otherwise the timestamp-based ID which may break after * undeletion. * * @param User $user * @param Revision $rev * @param Revision $title * @return string HTML fragment */ public static function getRevDeleteLink(User $user, Revision $rev, Title $title) { $canHide = $user->isAllowed('deleterevision'); if (!$canHide && !($rev->getVisibility() && $user->isAllowed('deletedhistory'))) { return ''; } if (!$rev->userCan(Revision::DELETED_RESTRICTED, $user)) { return Linker::revDeleteLinkDisabled($canHide); // revision was hidden from sysops } else { if ($rev->getId()) { // RevDelete links using revision ID are stable across // page deletion and undeletion; use when possible. $query = array('type' => 'revision', 'target' => $title->getPrefixedDBkey(), 'ids' => $rev->getId()); } else { // Older deleted entries didn't save a revision ID. // We have to refer to these by timestamp, ick! $query = array('type' => 'archive', 'target' => $title->getPrefixedDBkey(), 'ids' => $rev->getTimestamp()); } return Linker::revDeleteLink($query, $rev->isDeleted(Revision::DELETED_RESTRICTED), $canHide); } }
/** * Given parameters derived from [[Image:Foo|options...]], generate the * HTML that that syntax inserts in the page. * * @param $title Title object * @param $file File object, or false if it doesn't exist * @param $frameParams Array: associative array of parameters external to the media handler. * Boolean parameters are indicated by presence or absence, the value is arbitrary and * will often be false. * thumbnail If present, downscale and frame * manualthumb Image name to use as a thumbnail, instead of automatic scaling * framed Shows image in original size in a frame * frameless Downscale but don't frame * upright If present, tweak default sizes for portrait orientation * upright_factor Fudge factor for "upright" tweak (default 0.75) * border If present, show a border around the image * align Horizontal alignment (left, right, center, none) * valign Vertical alignment (baseline, sub, super, top, text-top, middle, * bottom, text-bottom) * alt Alternate text for image (i.e. alt attribute). Plain text. * caption HTML for image caption. * link-url URL to link to * link-title Title object to link to * link-target Value for the target attribue, only with link-url * no-link Boolean, suppress description link * * @param $handlerParams Array: associative array of media handler parameters, to be passed * to transform(). Typical keys are "width" and "page". * @param $time String: timestamp of the file, set as false for current * @param $query String: query params for desc url * @param $widthOption: Used by the parser to remember the user preference thumbnailsize * @return String: HTML for an image, with links, wrappers, etc. */ static function makeImageLink2(Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "", $widthOption = null) { $res = null; $dummy = new DummyLinker(); if (!wfRunHooks('ImageBeforeProduceHTML', array(&$dummy, &$title, &$file, &$frameParams, &$handlerParams, &$time, &$res))) { return $res; } if ($file && !$file->allowInlineDisplay()) { wfDebug(__METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n"); return self::link($title); } // Shortcuts $fp =& $frameParams; $hp =& $handlerParams; // Clean up parameters $page = isset($hp['page']) ? $hp['page'] : false; if (!isset($fp['align'])) { $fp['align'] = ''; } if (!isset($fp['alt'])) { $fp['alt'] = ''; } if (!isset($fp['title'])) { $fp['title'] = ''; } $prefix = $postfix = ''; if ('center' == $fp['align']) { $prefix = '<div class="center">'; $postfix = '</div>'; $fp['align'] = 'none'; } if ($file && !isset($hp['width'])) { if (isset($hp['height']) && $file->isVectorized()) { // If its a vector image, and user only specifies height // we don't want it to be limited by its "normal" width. global $wgSVGMaxSize; $hp['width'] = $wgSVGMaxSize; } else { $hp['width'] = $file->getWidth($page); } if (isset($fp['thumbnail']) || isset($fp['framed']) || isset($fp['frameless']) || !$hp['width']) { global $wgThumbLimits, $wgThumbUpright; if (!isset($widthOption) || !isset($wgThumbLimits[$widthOption])) { $widthOption = User::getDefaultOption('thumbsize'); } // Reduce width for upright images when parameter 'upright' is used if (isset($fp['upright']) && $fp['upright'] == 0) { $fp['upright'] = $wgThumbUpright; } // For caching health: If width scaled down due to upright parameter, round to full __0 pixel to avoid the creation of a lot of odd thumbs $prefWidth = isset($fp['upright']) ? round($wgThumbLimits[$widthOption] * $fp['upright'], -1) : $wgThumbLimits[$widthOption]; // Use width which is smaller: real image width or user preference width // Unless image is scalable vector. if (!isset($hp['height']) && ($hp['width'] <= 0 || $prefWidth < $hp['width'] || $file->isVectorized())) { $hp['width'] = $prefWidth; } } } if (isset($fp['thumbnail']) || isset($fp['manualthumb']) || isset($fp['framed'])) { global $wgContLang; # Create a thumbnail. Alignment depends on language # writing direction, # right aligned for left-to-right- # languages ("Western languages"), left-aligned # for right-to-left-languages ("Semitic languages") # # If thumbnail width has not been provided, it is set # to the default user option as specified in Language*.php if ($fp['align'] == '') { $fp['align'] = $wgContLang->alignEnd(); } return $prefix . self::makeThumbLink2($title, $file, $fp, $hp, $time, $query) . $postfix; } if ($file && isset($fp['frameless'])) { $srcWidth = $file->getWidth($page); # For "frameless" option: do not present an image bigger than the source (for bitmap-style images) # This is the same behaviour as the "thumb" option does it already. if ($srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth) { $hp['width'] = $srcWidth; } } if ($file && isset($hp['width'])) { # Create a resized image, without the additional thumbnail features $thumb = $file->transform($hp); } else { $thumb = false; } if (!$thumb) { $s = self::makeBrokenImageLinkObj($title, $fp['title'], '', '', '', $time == true); } else { $params = array('alt' => $fp['alt'], 'title' => $fp['title'], 'valign' => isset($fp['valign']) ? $fp['valign'] : false, 'img-class' => isset($fp['border']) ? 'thumbborder' : false); $params = self::getImageLinkMTOParams($fp, $query) + $params; $s = $thumb->toHtml($params); } if ($fp['align'] != '') { $s = "<div class=\"float{$fp['align']}\">{$s}</div>"; } return str_replace("\n", ' ', $prefix . $s . $postfix); }
/** * Get an array containing the variables to be set in mw.config in JavaScript. * * Do not add things here which can be evaluated in ResourceLoaderStartUpModule * - in other words, page-independent/site-wide variables (without state). * You will only be adding bloat to the html page and causing page caches to * have to be purged on configuration changes. * @return array */ public function getJSVars() { global $wgContLang; $curRevisionId = 0; $articleId = 0; $canonicalSpecialPageName = false; # bug 21115 $title = $this->getTitle(); $ns = $title->getNamespace(); $canonicalNamespace = MWNamespace::exists($ns) ? MWNamespace::getCanonicalName($ns) : $title->getNsText(); $sk = $this->getSkin(); // Get the relevant title so that AJAX features can use the correct page name // when making API requests from certain special pages (bug 34972). $relevantTitle = $sk->getRelevantTitle(); $relevantUser = $sk->getRelevantUser(); if ($ns == NS_SPECIAL) { list($canonicalSpecialPageName, ) = SpecialPageFactory::resolveAlias($title->getDBkey()); } elseif ($this->canUseWikiPage()) { $wikiPage = $this->getWikiPage(); $curRevisionId = $wikiPage->getLatest(); $articleId = $wikiPage->getId(); } $lang = $title->getPageLanguage(); // Pre-process information $separatorTransTable = $lang->separatorTransformTable(); $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); $compactSeparatorTransTable = array(implode("\t", array_keys($separatorTransTable)), implode("\t", $separatorTransTable)); $digitTransTable = $lang->digitTransformTable(); $digitTransTable = $digitTransTable ? $digitTransTable : array(); $compactDigitTransTable = array(implode("\t", array_keys($digitTransTable)), implode("\t", $digitTransTable)); $user = $this->getUser(); $vars = array('wgCanonicalNamespace' => $canonicalNamespace, 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName, 'wgNamespaceNumber' => $title->getNamespace(), 'wgPageName' => $title->getPrefixedDBkey(), 'wgTitle' => $title->getText(), 'wgCurRevisionId' => $curRevisionId, 'wgRevisionId' => (int) $this->getRevisionId(), 'wgArticleId' => $articleId, 'wgIsArticle' => $this->isArticle(), 'wgIsRedirect' => $title->isRedirect(), 'wgAction' => Action::getActionName($this->getContext()), 'wgUserName' => $user->isAnon() ? null : $user->getName(), 'wgUserGroups' => $user->getEffectiveGroups(), 'wgCategories' => $this->getCategories(), 'wgBreakFrames' => $this->getFrameOptions() == 'DENY', 'wgPageContentLanguage' => $lang->getCode(), 'wgPageContentModel' => $title->getContentModel(), 'wgSeparatorTransformTable' => $compactSeparatorTransTable, 'wgDigitTransformTable' => $compactDigitTransTable, 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(), 'wgMonthNames' => $lang->getMonthNamesArray(), 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(), 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(), 'wgRelevantArticleId' => $relevantTitle->getArticleId()); if ($user->isLoggedIn()) { $vars['wgUserId'] = $user->getId(); $vars['wgUserEditCount'] = $user->getEditCount(); $userReg = wfTimestampOrNull(TS_UNIX, $user->getRegistration()); $vars['wgUserRegistration'] = $userReg !== null ? $userReg * 1000 : null; // Get the revision ID of the oldest new message on the user's talk // page. This can be used for constructing new message alerts on // the client side. $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId(); } if ($wgContLang->hasVariants()) { $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); } // Same test as SkinTemplate $vars['wgIsProbablyEditable'] = $title->quickUserCan('edit', $user) && ($title->exists() || $title->quickUserCan('create', $user)); foreach ($title->getRestrictionTypes() as $type) { $vars['wgRestriction' . ucfirst($type)] = $title->getRestrictions($type); } if ($title->isMainPage()) { $vars['wgIsMainPage'] = true; } if ($this->mRedirectedFrom) { $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey(); } if ($relevantUser) { $vars['wgRelevantUserName'] = $relevantUser->getName(); } // Allow extensions to add their custom variables to the mw.config map. // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not // page-dependant but site-wide (without state). // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. Hooks::run('MakeGlobalVariablesScript', array(&$vars, $this)); // Merge in variables from addJsConfigVars last return array_merge($vars, $this->getJsConfigVars()); }
/** * @param Preprocessor $preprocessor * @param bool|PPFrame_DOM $parent * @param array $numberedArgs * @param array $namedArgs * @param bool|Title $title */ public function __construct($preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false) { parent::__construct($preprocessor); $this->parent = $parent; $this->numberedArgs = $numberedArgs; $this->namedArgs = $namedArgs; $this->title = $title; $pdbk = $title ? $title->getPrefixedDBkey() : false; $this->titleCache = $parent->titleCache; $this->titleCache[] = $pdbk; $this->loopCheckHash = $parent->loopCheckHash; if ($pdbk !== false) { $this->loopCheckHash[$pdbk] = true; } $this->depth = $parent->depth + 1; $this->numberedExpansionCache = $this->namedExpansionCache = array(); }
/** * Create a new BacklinkCache or reuse any existing one. * Currently, only one cache instance can exist; callers that * need multiple backlink cache objects should keep them in scope. * * @param Title $title : Title object to get a backlink cache for * @return BacklinkCache */ public static function get(Title $title) { if (!self::$cache) { // init cache self::$cache = new ProcessCacheLRU(1); } $dbKey = $title->getPrefixedDBkey(); if (!self::$cache->has($dbKey, 'obj')) { self::$cache->set($dbKey, 'obj', new self($title)); } return self::$cache->get($dbKey, 'obj'); }
/** * Determine if an image exists on the 'bad image list'. * * The format of MediaWiki:Bad_image_list is as follows: * * Only list items (lines starting with "*") are considered * * The first link on a line must be a link to a bad image * * Any subsequent links on the same line are considered to be exceptions, * i.e. articles where the image may occur inline. * * @param string $name The image name to check * @param Title|bool $contextTitle The page on which the image occurs, if known * @param string $blacklist Wikitext of a file blacklist * @return bool */ function wfIsBadImage($name, $contextTitle = false, $blacklist = null) { # Handle redirects; callers almost always hit wfFindFile() anyway, # so just use that method because it has a fast process cache. $file = wfFindFile($name); // get the final name $name = $file ? $file->getTitle()->getDBkey() : $name; # Run the extension hook $bad = false; if (!Hooks::run('BadImage', array($name, &$bad))) { return $bad; } $cache = ObjectCache::newAccelerator('hash'); $key = wfMemcKey('bad-image-list', $blacklist === null ? 'default' : md5($blacklist)); $badImages = $cache->get($key); if ($badImages === false) { // cache miss if ($blacklist === null) { $blacklist = wfMessage('bad_image_list')->inContentLanguage()->plain(); // site list } # Build the list now $badImages = array(); $lines = explode("\n", $blacklist); foreach ($lines as $line) { # List items only if (substr($line, 0, 1) !== '*') { continue; } # Find all links $m = array(); if (!preg_match_all('/\\[\\[:?(.*?)\\]\\]/', $line, $m)) { continue; } $exceptions = array(); $imageDBkey = false; foreach ($m[1] as $i => $titleText) { $title = Title::newFromText($titleText); if (!is_null($title)) { if ($i == 0) { $imageDBkey = $title->getDBkey(); } else { $exceptions[$title->getPrefixedDBkey()] = true; } } } if ($imageDBkey !== false) { $badImages[$imageDBkey] = $exceptions; } } $cache->set($key, $badImages, 60); } $contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false; $bad = isset($badImages[$name]) && !isset($badImages[$name][$contextKey]); return $bad; }
/** * Add a title to the link cache, return the page_id or zero if non-existent * * @param Title $nt Title object to add * @return int Page ID or zero */ public function addLinkObj(Title $nt) { global $wgContentHandlerUseDB; $key = $nt->getPrefixedDBkey(); if ($this->isBadLink($key) || $nt->isExternal()) { return 0; } $id = $this->getGoodLinkID($key); if ($id != 0) { return $id; } if ($key === '') { return 0; } // Some fields heavily used for linking... $db = $this->mForUpdate ? wfGetDB(DB_MASTER) : wfGetDB(DB_SLAVE); $fields = array('page_id', 'page_len', 'page_is_redirect', 'page_latest'); if ($wgContentHandlerUseDB) { $fields[] = 'page_content_model'; } $row = $db->selectRow('page', $fields, array('page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey()), __METHOD__); if ($row !== false) { $this->addGoodLinkObjFromRow($nt, $row); $id = intval($row->page_id); } else { $this->addBadLinkObj($nt); $id = 0; } return $id; }
/** * Do standard deferred updates after page edit. * Update links tables, site stats, search index and message cache. * Purges pages that include this page if the text was changed here. * Every 100th edit, prune the recent changes table. * * @param $revision Revision object * @param $user User object that did the revision * @param $options Array of options, following indexes are used: * - changed: boolean, whether the revision changed the content (default true) * - created: boolean, whether the revision created the page (default false) * - oldcountable: boolean or null (default null): * - boolean: whether the page was counted as an article before that * revision, only used in changed is true and created is false * - null: don't change the article count */ public function doEditUpdates(Revision $revision, User $user, array $options = array()) { global $wgEnableParserCache; wfProfileIn(__METHOD__); $options += array('changed' => true, 'created' => false, 'oldcountable' => null); $content = $revision->getContent(); # Parse the text # Be careful not to double-PST: $text is usually already PST-ed once if (!$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag('vary-revision')) { wfDebug(__METHOD__ . ": No prepared edit or vary-revision is set...\n"); $editInfo = $this->prepareContentForEdit($content, $revision->getId(), $user); } else { wfDebug(__METHOD__ . ": No vary-revision, using prepared edit...\n"); $editInfo = $this->mPreparedEdit; } # Save it to the parser cache if ($wgEnableParserCache) { $parserCache = ParserCache::singleton(); $parserCache->save($editInfo->output, $this, $editInfo->popts); } # Update the links tables and other secondary data $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, true, $editInfo->output); DataUpdate::runUpdates($updates); wfRunHooks('ArticleEditUpdates', array(&$this, &$editInfo, $options['changed'])); if (wfRunHooks('ArticleEditUpdatesDeleteFromRecentchanges', array(&$this))) { if (0 == mt_rand(0, 99)) { // Flush old entries from the `recentchanges` table; we do this on // random requests so as to avoid an increase in writes for no good reason global $wgRCMaxAge; $dbw = wfGetDB(DB_MASTER); $cutoff = $dbw->timestamp(time() - $wgRCMaxAge); $dbw->delete('recentchanges', array("rc_timestamp < '{$cutoff}'"), __METHOD__); } } if (!$this->mTitle->exists()) { wfProfileOut(__METHOD__); return; } $id = $this->getId(); $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey(); if (!$options['changed']) { $good = 0; $total = 0; } elseif ($options['created']) { $good = (int) $this->isCountable($editInfo); $total = 1; } elseif ($options['oldcountable'] !== null) { $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable']; $total = 0; } else { $good = 0; $total = 0; } DeferredUpdates::addUpdate(new SiteStatsUpdate(0, 1, $good, $total)); DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content->getTextForSearchIndex())); #@TODO: let the search engine decide what to do with the content object # If this is another user's talk page, update newtalk. # Don't do this if $options['changed'] = false (null-edits) nor if # it's a minor edit and the user doesn't want notifications for those. if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) { if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this))) { $other = User::newFromName($shortTitle, false); if (!$other) { wfDebug(__METHOD__ . ": invalid username\n"); } elseif (User::isIP($shortTitle)) { // An anonymous user $other->setNewtalk(true, $revision); } elseif ($other->isLoggedIn()) { $other->setNewtalk(true, $revision); } else { wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n"); } } } if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) { #XXX: could skip pseudo-messages like js/css here, based on content model. $msgtext = $content->getWikitextForTransclusion(); if ($msgtext === false || $msgtext === null) { $msgtext = ''; } MessageCache::singleton()->replace($shortTitle, $msgtext); } if ($options['created']) { self::onArticleCreate($this->mTitle); } else { self::onArticleEdit($this->mTitle); } wfProfileOut(__METHOD__); }
/** * Do standard deferred updates after page edit. * Update links tables, site stats, search index and message cache. * Purges pages that include this page if the text was changed here. * Every 100th edit, prune the recent changes table. * * @param $revision Revision object * @param $user User object that did the revision * @param array $options of options, following indexes are used: * - changed: boolean, whether the revision changed the content (default true) * - created: boolean, whether the revision created the page (default false) * - oldcountable: boolean or null (default null): * - boolean: whether the page was counted as an article before that * revision, only used in changed is true and created is false * - null: don't change the article count */ public function doEditUpdates( Revision $revision, User $user, array $options = array() ) { global $wgEnableParserCache; wfProfileIn( __METHOD__ ); $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null ); $content = $revision->getContent(); // Parse the text // Be careful not to do pre-save transform twice: $text is usually // already pre-save transformed once. if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) { wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" ); $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user ); } else { wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" ); $editInfo = $this->mPreparedEdit; } // Save it to the parser cache if ( $wgEnableParserCache ) { $parserCache = ParserCache::singleton(); $parserCache->save( $editInfo->output, $this, $editInfo->popts ); } // Update the links tables and other secondary data if ( $content ) { $recursive = $options['changed']; // bug 50785 $updates = $content->getSecondaryDataUpdates( $this->getTitle(), null, $recursive, $editInfo->output ); DataUpdate::runUpdates( $updates ); } wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) ); if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) { if ( 0 == mt_rand( 0, 99 ) ) { // Flush old entries from the `recentchanges` table; we do this on // random requests so as to avoid an increase in writes for no good reason RecentChange::purgeExpiredChanges(); } } if ( !$this->exists() ) { wfProfileOut( __METHOD__ ); return; } $id = $this->getId(); $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey(); if ( !$options['changed'] ) { $good = 0; $total = 0; } elseif ( $options['created'] ) { $good = (int)$this->isCountable( $editInfo ); $total = 1; } elseif ( $options['oldcountable'] !== null ) { $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable']; $total = 0; } else { $good = 0; $total = 0; } DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) ); DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) ); // If this is another user's talk page, update newtalk. // Don't do this if $options['changed'] = false (null-edits) nor if // it's a minor edit and the user doesn't want notifications for those. if ( $options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) ) ) { $recipient = User::newFromName( $shortTitle, false ); if ( !$recipient ) { wfDebug( __METHOD__ . ": invalid username\n" ); } else { // Allow extensions to prevent user notification when a new message is added to their talk page if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) { if ( User::isIP( $shortTitle ) ) { // An anonymous user $recipient->setNewtalk( true, $revision ); } elseif ( $recipient->isLoggedIn() ) { $recipient->setNewtalk( true, $revision ); } else { wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" ); } } } } if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { // XXX: could skip pseudo-messages like js/css here, based on content model. $msgtext = $content ? $content->getWikitextForTransclusion() : null; if ( $msgtext === false || $msgtext === null ) { $msgtext = ''; } MessageCache::singleton()->replace( $shortTitle, $msgtext ); } if ( $options['created'] ) { self::onArticleCreate( $this->mTitle ); } else { self::onArticleEdit( $this->mTitle ); } wfProfileOut( __METHOD__ ); }
/** * Show log extract. Either with text and a box (set $msgKey) or without (don't set $msgKey) * * @param OutputPage|string $out By-reference * @param string|array $types Log types to show * @param string|Title $page The page title to show log entries for * @param string $user The user who made the log entries * @param array $param Associative Array with the following additional options: * - lim Integer Limit of items to show, default is 50 * - conds Array Extra conditions for the query (e.g. "log_action != 'revision'") * - showIfEmpty boolean Set to false if you don't want any output in case the loglist is empty * if set to true (default), "No matching items in log" is displayed if loglist is empty * - msgKey Array If you want a nice box with a message, set this to the key of the message. * First element is the message key, additional optional elements are parameters for the key * that are processed with wfMessage * - offset Set to overwrite offset parameter in WebRequest * set to '' to unset offset * - wrap String Wrap the message in html (usually something like "<div ...>$1</div>"). * - flags Integer display flags (NO_ACTION_LINK,NO_EXTRA_USER_LINKS) * - useRequestParams boolean Set true to use Pager-related parameters in the WebRequest * - useMaster boolean Use master DB * @return int Number of total log items (not limited by $lim) */ public static function showLogExtract(&$out, $types = array(), $page = '', $user = '', $param = array()) { $defaultParameters = array('lim' => 25, 'conds' => array(), 'showIfEmpty' => true, 'msgKey' => array(''), 'wrap' => "\$1", 'flags' => 0, 'useRequestParams' => false, 'useMaster' => false); # The + operator appends elements of remaining keys from the right # handed array to the left handed, whereas duplicated keys are NOT overwritten. $param += $defaultParameters; # Convert $param array to individual variables $lim = $param['lim']; $conds = $param['conds']; $showIfEmpty = $param['showIfEmpty']; $msgKey = $param['msgKey']; $wrap = $param['wrap']; $flags = $param['flags']; $useRequestParams = $param['useRequestParams']; if (!is_array($msgKey)) { $msgKey = array($msgKey); } if ($out instanceof OutputPage) { $context = $out->getContext(); } else { $context = RequestContext::getMain(); } # Insert list of top 50 (or top $lim) items $loglist = new LogEventsList($context, null, $flags); $pager = new LogPager($loglist, $types, $user, $page, '', $conds); if (!$useRequestParams) { # Reset vars that may have been taken from the request $pager->mLimit = 50; $pager->mDefaultLimit = 50; $pager->mOffset = ""; $pager->mIsBackwards = false; } if ($param['useMaster']) { $pager->mDb = wfGetDB(DB_MASTER); } if (isset($param['offset'])) { # Tell pager to ignore WebRequest offset $pager->setOffset($param['offset']); } if ($lim > 0) { $pager->mLimit = $lim; } // Fetch the log rows and build the HTML if needed $logBody = $pager->getBody(); $numRows = $pager->getNumRows(); $s = ''; if ($logBody) { if ($msgKey[0]) { $dir = $context->getLanguage()->getDir(); $lang = $context->getLanguage()->getHtmlCode(); $s = Xml::openElement('div', array('class' => "mw-warning-with-logexcerpt mw-content-{$dir}", 'dir' => $dir, 'lang' => $lang)); if (count($msgKey) == 1) { $s .= $context->msg($msgKey[0])->parseAsBlock(); } else { // Process additional arguments $args = $msgKey; array_shift($args); $s .= $context->msg($msgKey[0], $args)->parseAsBlock(); } } $s .= $loglist->beginLogEventsList() . $logBody . $loglist->endLogEventsList(); } elseif ($showIfEmpty) { $s = Html::rawElement('div', array('class' => 'mw-warning-logempty'), $context->msg('logempty')->parse()); } if ($numRows > $pager->mLimit) { # Show "Full log" link $urlParam = array(); if ($page instanceof Title) { $urlParam['page'] = $page->getPrefixedDBkey(); } elseif ($page != '') { $urlParam['page'] = $page; } if ($user != '') { $urlParam['user'] = $user; } if (!is_array($types)) { # Make it an array, if it isn't $types = array($types); } # If there is exactly one log type, we can link to Special:Log?type=foo if (count($types) == 1) { $urlParam['type'] = $types[0]; } $s .= Linker::link(SpecialPage::getTitleFor('Log'), $context->msg('log-fulllog')->escaped(), array(), $urlParam); } if ($logBody && $msgKey[0]) { $s .= '</div>'; } if ($wrap != '') { // Wrap message in html $s = str_replace('$1', $s, $wrap); } /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */ if (Hooks::run('LogEventsListShowLogExtract', array(&$s, $types, $page, $user, $param))) { // $out can be either an OutputPage object or a String-by-reference if ($out instanceof OutputPage) { $out->addHTML($s); } else { $out = $s; } } return $numRows; }
/** * Constructs a RecentChange object for the given categorization * This does not call save() on the object and thus does not write to the db * * @since 1.27 * * @param string $timestamp Timestamp of the recent change to occur * @param Title $categoryTitle Title of the category a page is being added to or removed from * @param User $user User object of the user that made the change * @param string $comment Change summary * @param Title $pageTitle Title of the page that is being added or removed * @param int $oldRevId Parent revision ID of this change * @param int $newRevId Revision ID of this change * @param string $lastTimestamp Parent revision timestamp of this change * @param bool $bot true, if the change was made by a bot * @param string $ip IP address of the user, if the change was made anonymously * @param int $deleted Indicates whether the change has been deleted * * @return RecentChange */ public static function newForCategorization($timestamp, Title $categoryTitle, User $user = null, $comment, Title $pageTitle, $oldRevId, $newRevId, $lastTimestamp, $bot, $ip = '', $deleted = 0) { $rc = new RecentChange(); $rc->mTitle = $categoryTitle; $rc->mPerformer = $user; $rc->mAttribs = array('rc_timestamp' => $timestamp, 'rc_namespace' => $categoryTitle->getNamespace(), 'rc_title' => $categoryTitle->getDBkey(), 'rc_type' => RC_CATEGORIZE, 'rc_source' => self::SRC_CATEGORIZE, 'rc_minor' => 0, 'rc_cur_id' => $pageTitle->getArticleID(), 'rc_user' => $user ? $user->getId() : 0, 'rc_user_text' => $user ? $user->getName() : '', 'rc_comment' => $comment, 'rc_this_oldid' => $newRevId, 'rc_last_oldid' => $oldRevId, 'rc_bot' => $bot ? 1 : 0, 'rc_ip' => self::checkIPAddress($ip), 'rc_patrolled' => 1, 'rc_new' => 0, 'rc_old_len' => 0, 'rc_new_len' => 0, 'rc_deleted' => $deleted, 'rc_logid' => 0, 'rc_log_type' => null, 'rc_log_action' => '', 'rc_params' => ''); $rc->mExtra = array('prefixedDBkey' => $categoryTitle->getPrefixedDBkey(), 'lastTimestamp' => $lastTimestamp, 'oldSize' => 0, 'newSize' => 0, 'pageStatus' => 'changed'); return $rc; }
protected function getParsoidURL(\Title $title, $prev = false) { global $wgVisualEditorParsoidURL; $oldid = $prev ? $title->getPreviousRevisionID($title->getLatestRevID()) : $title->getLatestRevID(); return $wgVisualEditorParsoidURL . '/' . wfExpandUrl(wfScript('api')) . '/' . wfUrlencode($title->getPrefixedDBkey()) . '?oldid=' . $oldid; }