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'));
 }
Example #2
0
 /**
  * @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;
 }
Example #4
0
 /**
  * @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';
 }
Example #5
0
 /**
  * @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;
 }
Example #7
0
 /**
  * @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;
 }
Example #10
0
 /**
  * @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}";
     }
 }
Example #11
0
 /**
  * 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;
     }
 }
Example #12
0
 /**
  * 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;
 }
Example #13
0
 /** 
  * 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);
 }
Example #14
0
 /**
  * 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());
 }
Example #15
0
 /**
  * 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;
     }
 }
Example #16
0
 /**
  * @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();
     }
 }
Example #17
0
/**
 * 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";
 }
Example #19
0
 /**
  * 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);
     }
 }
Example #20
0
 /**
  * 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);
 }
Example #21
0
 /**
  * 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());
 }
Example #22
0
 /**
  * @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');
 }
Example #24
0
/**
 * 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;
}
Example #25
0
 /**
  * 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;
 }
Example #26
0
 /**
  * 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__);
 }
Example #27
0
	/**
	 * 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__ );
	}
Example #28
0
 /**
  * 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;
 }