public function loadPageData($data = 'fromdb') { $ret = parent::loadPageData($data); // get return code just in case. // At this point, we have a valid 'page id' in a local variable. // Use it to query the database for 'categorylinks' $dbr =& wfGetDB(DB_SLAVE); $page = $dbr->tableName('page'); $catlinks = $dbr->tableName('categorylinks'); $id = $this->getID(); $query = "SELECT cl_to FROM {$catlinks} WHERE {$catlinks}.cl_from={$id}"; $results = $dbr->query($query); $count = $dbr->numRows($results); if ($count >= 1) { while ($row = $dbr->fetchObject($results)) { $this->categories[] = $row->cl_to; } } $dbr->freeResult($results); return $ret; }
static function bulkLoad($rows) { // Preload subthreads $top_thread_ids = array(); $all_thread_rows = $rows; $pageIds = array(); $linkBatch = new LinkBatch(); $userIds = array(); $loadEditorsFor = array(); $dbr = wfGetDB(DB_SLAVE); if (!is_array(self::$replyCacheById)) { self::$replyCacheById = array(); } // Build a list of threads for which to pull replies, and page IDs to pull data for. // Also, pre-initialise the reply cache. foreach ($rows as $row) { if ($row->thread_ancestor) { $top_thread_ids[] = $row->thread_ancestor; } else { $top_thread_ids[] = $row->thread_id; } // Grab page data while we're here. if ($row->thread_root) { $pageIds[] = $row->thread_root; } if ($row->thread_summary_page) { $pageIds[] = $row->thread_summary_page; } if (!isset(self::$replyCacheById[$row->thread_id])) { self::$replyCacheById[$row->thread_id] = array(); } } $all_thread_ids = $top_thread_ids; // Pull replies to the threads provided, and as above, pull page IDs to pull data for, // pre-initialise the reply cache, and stash the row object for later use. if (count($top_thread_ids)) { $res = $dbr->select('thread', '*', array('thread_ancestor' => $top_thread_ids, 'thread_type != ' . $dbr->addQuotes(Threads::TYPE_DELETED)), __METHOD__); foreach ($res as $row) { // Grab page data while we're here. if ($row->thread_root) { $pageIds[] = $row->thread_root; } if ($row->thread_summary_page) { $pageIds[] = $row->thread_summary_page; } $all_thread_rows[] = $row; $all_thread_ids[$row->thread_id] = $row->thread_id; } } // Pull thread reactions if (count($all_thread_ids)) { $res = $dbr->select('thread_reaction', '*', array('tr_thread' => $all_thread_ids), __METHOD__); foreach ($res as $row) { $thread_id = $row->tr_thread; $user = $row->tr_user_text; $info = array('type' => $row->tr_type, 'user-id' => $row->tr_user, 'user-name' => $row->tr_user_text, 'value' => $row->tr_value); $type = $info['type']; $user = $info['user-name']; if (!isset(self::$reactionCacheById[$thread_id])) { self::$reactionCacheById[$thread_id] = array(); } if (!isset(self::$reactionCacheById[$thread_id][$type])) { self::$reactionCacheById[$thread_id][$type] = array(); } self::$reactionCacheById[$thread_id][$type][$user] = $info; } } // Preload page data (restrictions, and preload Article object with everything from // the page table. Also, precache the title and article objects for pulling later. $articlesById = array(); if (count($pageIds)) { // Pull restriction info. Needs to come first because otherwise it's done per // page by loadPageData. $restrictionRows = array_fill_keys($pageIds, array()); $res = $dbr->select('page_restrictions', '*', array('pr_page' => $pageIds), __METHOD__); foreach ($res as $row) { $restrictionRows[$row->pr_page][] = $row; } $res = $dbr->select('page', '*', array('page_id' => $pageIds), __METHOD__); foreach ($res as $row) { $t = Title::newFromRow($row); if (isset($restrictionRows[$t->getArticleId()])) { $t->loadRestrictionsFromRows($restrictionRows[$t->getArticleId()], $row->page_restrictions); } $article = new Article($t); $article->loadPageData($row); self::$titleCacheById[$t->getArticleId()] = $t; $articlesById[$article->getId()] = $article; if (count(self::$titleCacheById) > 10000) { self::$titleCacheById = array(); } } } // For every thread we have a row object for, load a Thread object, add the user and // user talk pages to a link batch, cache the relevant user id/name pair, and // populate the reply cache. foreach ($all_thread_rows as $row) { $thread = Thread::newFromRow($row, null); if (isset($articlesById[$thread->rootId])) { $thread->root = $articlesById[$thread->rootId]; } // User cache data $t = Title::makeTitleSafe(NS_USER, $row->thread_author_name); $linkBatch->addObj($t); $t = Title::makeTitleSafe(NS_USER_TALK, $row->thread_author_name); $linkBatch->addObj($t); User::$idCacheByName[$row->thread_author_name] = $row->thread_author_id; $userIds[$row->thread_author_id] = true; if ($row->thread_editedness > Threads::EDITED_BY_AUTHOR) { $loadEditorsFor[$row->thread_root] = $thread; $thread->setEditors(array()); } } // Pull list of users who have edited if (count($loadEditorsFor)) { $res = $dbr->select('revision', array('rev_user_text', 'rev_page'), array('rev_page' => array_keys($loadEditorsFor), 'rev_parent_id != ' . $dbr->addQuotes(0)), __METHOD__); foreach ($res as $row) { $pageid = $row->rev_page; $editor = $row->rev_user_text; $t = $loadEditorsFor[$pageid]; $t->addEditor($editor); } } // Pull link batch data. $linkBatch->execute(); $threads = array(); // Fill and return an array with the threads that were actually requested. foreach ($rows as $row) { $threads[$row->thread_id] = Threads::$cache_by_id[$row->thread_id]; } return $threads; }
/** * Attempt submission (no UI) * * @param array $result Array to add statuses to, currently with the * possible keys: * - spam (string): Spam string from content if any spam is detected by * matchSpamRegex. * - sectionanchor (string): Section anchor for a section save. * - nullEdit (boolean): Set if doEditContent is OK. True if null edit, * false otherwise. * - redirect (bool): Set if doEditContent is OK. True if resulting * revision is a redirect. * @param bool $bot True if edit is being made under the bot right. * * @return Status Status object, possibly with a message, but always with * one of the AS_* constants in $status->value, * * @todo FIXME: This interface is TERRIBLE, but hard to get rid of due to * various error display idiosyncrasies. There are also lots of cases * where error metadata is set in the object and retrieved later instead * of being returned, e.g. AS_CONTENT_TOO_BIG and * AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some * time. */ function internalAttemptSave(&$result, $bot = false) { global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize; $status = Status::newGood(); wfProfileIn(__METHOD__); wfProfileIn(__METHOD__ . '-checks'); if (!wfRunHooks('EditPage::attemptSave', array($this))) { wfDebug("Hook 'EditPage::attemptSave' aborted article saving\n"); $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } $spam = $wgRequest->getText('wpAntispam'); if ($spam !== '') { wfDebugLog('SimpleAntiSpam', $wgUser->getName() . ' editing "' . $this->mTitle->getPrefixedText() . '" submitted bogus field "' . $spam . '"'); $status->fatal('spamprotectionmatch', false); $status->value = self::AS_SPAM_ERROR; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } try { # Construct Content object $textbox_content = $this->toEditContent($this->textbox1); } catch (MWContentSerializationException $ex) { $status->fatal('content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage()); $status->value = self::AS_PARSE_ERROR; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } # Check image redirect if ($this->mTitle->getNamespace() == NS_FILE && $textbox_content->isRedirect() && !$wgUser->isAllowed('upload')) { $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; $status->setResult(false, $code); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } # Check for spam $match = self::matchSummarySpamRegex($this->summary); if ($match === false && $this->section == 'new') { # $wgSpamRegex is enforced on this new heading/summary because, unlike # regular summaries, it is added to the actual wikitext. if ($this->sectiontitle !== '') { # This branch is taken when the API is used with the 'sectiontitle' parameter. $match = self::matchSpamRegex($this->sectiontitle); } else { # This branch is taken when the "Add Topic" user interface is used, or the API # is used with the 'summary' parameter. $match = self::matchSpamRegex($this->summary); } } if ($match === false) { $match = self::matchSpamRegex($this->textbox1); } if ($match !== false) { $result['spam'] = $match; $ip = $wgRequest->getIP(); $pdbk = $this->mTitle->getPrefixedDBkey(); $match = str_replace("\n", '', $match); wfDebugLog('SpamRegex', "{$ip} spam regex hit [[{$pdbk}]]: \"{$match}\""); $status->fatal('spamprotectionmatch', $match); $status->value = self::AS_SPAM_ERROR; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if (!wfRunHooks('EditFilter', array($this, $this->textbox1, $this->section, &$this->hookError, $this->summary))) { # Error messages etc. could be handled within the hook... $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } elseif ($this->hookError != '') { # ...or the hook could be expecting us to produce an error $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR_EXPECTED; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if ($wgUser->isBlockedFrom($this->mTitle, false)) { // Auto-block user's IP if the account was "hard" blocked $wgUser->spreadAnyEditBlock(); # Check block state against master, thus 'false'. $status->setResult(false, self::AS_BLOCKED_PAGE_FOR_USER); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } $this->kblength = (int) (strlen($this->textbox1) / 1024); if ($this->kblength > $wgMaxArticleSize) { // Error will be displayed by showEditForm() $this->tooBig = true; $status->setResult(false, self::AS_CONTENT_TOO_BIG); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if (!$wgUser->isAllowed('edit')) { if ($wgUser->isAnon()) { $status->setResult(false, self::AS_READ_ONLY_PAGE_ANON); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } else { $status->fatal('readonlytext'); $status->value = self::AS_READ_ONLY_PAGE_LOGGED; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } } if ($this->contentModel !== $this->mTitle->getContentModel() && !$wgUser->isAllowed('editcontentmodel')) { $status->setResult(false, self::AS_NO_CHANGE_CONTENT_MODEL); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if (wfReadOnly()) { $status->fatal('readonlytext'); $status->value = self::AS_READ_ONLY_PAGE; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if ($wgUser->pingLimiter() || $wgUser->pingLimiter('linkpurge', 0)) { $status->fatal('actionthrottledtext'); $status->value = self::AS_RATE_LIMITED; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } # If the article has been deleted while editing, don't save it without # confirmation if ($this->wasDeletedSinceLastEdit() && !$this->recreate) { $status->setResult(false, self::AS_ARTICLE_WAS_DELETED); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } wfProfileOut(__METHOD__ . '-checks'); # Load the page data from the master. If anything changes in the meantime, # we detect it by using page_latest like a token in a 1 try compare-and-swap. $this->mArticle->loadPageData('fromdbmaster'); $new = !$this->mArticle->exists(); if ($new) { // Late check for create permission, just in case *PARANOIA* if (!$this->mTitle->userCan('create', $wgUser)) { $status->fatal('nocreatetext'); $status->value = self::AS_NO_CREATE_PERMISSION; wfDebug(__METHOD__ . ": no create permission\n"); wfProfileOut(__METHOD__); return $status; } // Don't save a new page if it's blank or if it's a MediaWiki: // message with content equivalent to default (allow empty pages // in this case to disable messages, see bug 50124) $defaultMessageText = $this->mTitle->getDefaultMessageText(); if ($this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false) { $defaultText = $defaultMessageText; } else { $defaultText = ''; } if (!$this->allowBlankArticle && $this->textbox1 === $defaultText) { $this->blankArticle = true; $status->fatal('blankarticle'); $status->setResult(false, self::AS_BLANK_ARTICLE); wfProfileOut(__METHOD__); return $status; } if (!$this->runPostMergeFilters($textbox_content, $status, $wgUser)) { wfProfileOut(__METHOD__); return $status; } $content = $textbox_content; $result['sectionanchor'] = ''; if ($this->section == 'new') { if ($this->sectiontitle !== '') { // Insert the section title above the content. $content = $content->addSectionHeader($this->sectiontitle); } elseif ($this->summary !== '') { // Insert the section title above the content. $content = $content->addSectionHeader($this->summary); } $this->summary = $this->newSectionSummary($result['sectionanchor']); } $status->value = self::AS_SUCCESS_NEW_ARTICLE; } else { # not $new # Article exists. Check for edit conflict. $this->mArticle->clear(); # Force reload of dates, etc. $timestamp = $this->mArticle->getTimestamp(); wfDebug("timestamp: {$timestamp}, edittime: {$this->edittime}\n"); if ($timestamp != $this->edittime) { $this->isConflict = true; if ($this->section == 'new') { if ($this->mArticle->getUserText() == $wgUser->getName() && $this->mArticle->getComment() == $this->newSectionSummary()) { // Probably a duplicate submission of a new comment. // This can happen when squid resends a request after // a timeout but the first one actually went through. wfDebug(__METHOD__ . ": duplicate new section submission; trigger edit conflict!\n"); } else { // New comment; suppress conflict. $this->isConflict = false; wfDebug(__METHOD__ . ": conflict suppressed; new section\n"); } } elseif ($this->section == '' && Revision::userWasLastToEdit(DB_MASTER, $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime)) { # Suppress edit conflict with self, except for section edits where merging is required. wfDebug(__METHOD__ . ": Suppressing edit conflict, same user.\n"); $this->isConflict = false; } } // If sectiontitle is set, use it, otherwise use the summary as the section title. if ($this->sectiontitle !== '') { $sectionTitle = $this->sectiontitle; } else { $sectionTitle = $this->summary; } $content = null; if ($this->isConflict) { wfDebug(__METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'" . " (article time '{$timestamp}')\n"); $content = $this->mArticle->replaceSectionContent($this->section, $textbox_content, $sectionTitle, $this->edittime); } else { wfDebug(__METHOD__ . ": getting section '{$this->section}'\n"); $content = $this->mArticle->replaceSectionContent($this->section, $textbox_content, $sectionTitle); } if (is_null($content)) { wfDebug(__METHOD__ . ": activating conflict; section replace failed.\n"); $this->isConflict = true; $content = $textbox_content; // do not try to merge here! } elseif ($this->isConflict) { # Attempt merge if ($this->mergeChangesIntoContent($content)) { // Successful merge! Maybe we should tell the user the good news? $this->isConflict = false; wfDebug(__METHOD__ . ": Suppressing edit conflict, successful merge.\n"); } else { $this->section = ''; $this->textbox1 = ContentHandler::getContentText($content); wfDebug(__METHOD__ . ": Keeping edit conflict, failed merge.\n"); } } if ($this->isConflict) { $status->setResult(false, self::AS_CONFLICT_DETECTED); wfProfileOut(__METHOD__); return $status; } if (!$this->runPostMergeFilters($content, $status, $wgUser)) { wfProfileOut(__METHOD__); return $status; } if ($this->section == 'new') { // Handle the user preference to force summaries here if (!$this->allowBlankSummary && trim($this->summary) == '') { $this->missingSummary = true; $status->fatal('missingsummary'); // or 'missingcommentheader' if $section == 'new'. Blegh $status->value = self::AS_SUMMARY_NEEDED; wfProfileOut(__METHOD__); return $status; } // Do not allow the user to post an empty comment if ($this->textbox1 == '') { $this->missingComment = true; $status->fatal('missingcommenttext'); $status->value = self::AS_TEXTBOX_EMPTY; wfProfileOut(__METHOD__); return $status; } } elseif (!$this->allowBlankSummary && !$content->equals($this->getOriginalContent($wgUser)) && !$content->isRedirect() && md5($this->summary) == $this->autoSumm) { $this->missingSummary = true; $status->fatal('missingsummary'); $status->value = self::AS_SUMMARY_NEEDED; wfProfileOut(__METHOD__); return $status; } # All's well wfProfileIn(__METHOD__ . '-sectionanchor'); $sectionanchor = ''; if ($this->section == 'new') { $this->summary = $this->newSectionSummary($sectionanchor); } elseif ($this->section != '') { # Try to get a section anchor from the section source, redirect # to edited section if header found. # XXX: Might be better to integrate this into Article::replaceSection # for duplicate heading checking and maybe parsing. $hasmatch = preg_match("/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches); # We can't deal with anchors, includes, html etc in the header for now, # headline would need to be parsed to improve this. if ($hasmatch && strlen($matches[2]) > 0) { $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText($matches[2]); } } $result['sectionanchor'] = $sectionanchor; wfProfileOut(__METHOD__ . '-sectionanchor'); // Save errors may fall down to the edit form, but we've now // merged the section into full text. Clear the section field // so that later submission of conflict forms won't try to // replace that into a duplicated mess. $this->textbox1 = $this->toEditText($content); $this->section = ''; $status->value = self::AS_SUCCESS_UPDATE; } // Check for length errors again now that the section is merged in $this->kblength = (int) (strlen($this->toEditText($content)) / 1024); if ($this->kblength > $wgMaxArticleSize) { $this->tooBig = true; $status->setResult(false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED); wfProfileOut(__METHOD__); return $status; } $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | ($new ? EDIT_NEW : EDIT_UPDATE) | ($this->minoredit && !$this->isNew ? EDIT_MINOR : 0) | ($bot ? EDIT_FORCE_BOT : 0); $doEditStatus = $this->mArticle->doEditContent($content, $this->summary, $flags, false, null, $content->getDefaultFormat()); if (!$doEditStatus->isOK()) { // Failure from doEdit() // Show the edit conflict page for certain recognized errors from doEdit(), // but don't show it for errors from extension hooks $errors = $doEditStatus->getErrorsArray(); if (in_array($errors[0][0], array('edit-gone-missing', 'edit-conflict', 'edit-already-exists'))) { $this->isConflict = true; // Destroys data doEdit() put in $status->value but who cares $doEditStatus->value = self::AS_END; } wfProfileOut(__METHOD__); return $doEditStatus; } $result['nullEdit'] = $doEditStatus->hasMessage('edit-no-change'); if ($result['nullEdit']) { // We don't know if it was a null edit until now, so increment here $wgUser->pingLimiter('linkpurge'); } $result['redirect'] = $content->isRedirect(); $this->updateWatchlist(); wfProfileOut(__METHOD__); return $status; }
/** * The main function */ function execute($par) { global $wgRequest, $wgUser, $wgOut, $wgImportVideoSources; if ($wgUser->isBlocked()) { $wgOut->blockedPage(); return; } wfLoadExtensionMessages('Importvideo'); if ($wgRequest->getVal('popup') == 'true') { $wgOut->setArticleBodyOnly(true); } $this->setHeaders(); $source = $this->mSource = $wgRequest->getVal('source', 'youtube'); $target = isset($par) ? $par : $wgRequest->getVal('target'); $query = $wgRequest->getVal('q'); $me = Title::makeTitle(NS_SPECIAL, "Importvideo"); $wasnew = $this->getRequest()->getVal('wasnew'); // some sanity checks on the target if ($target && !$wasnew) { $title = Title::newFromURL($target); if (!$title || !$title->exists()) { $wgOut->addHTML("Error: target article does not exist."); return; } else { $article = new Article($title); $article->loadPageData(); if ($article->mIsRedirect) { $wgOut->addHTML("Error: target article is a redirect."); return; } } } $wgOut->addHTML("<div id='importvideo'>"); $wgOut->addHTML("<h2>" . wfMsg('add_a_video') . "</h2>"); # changing target article feature $search = $wgRequest->getVal("dosearch", null); if ($search != null) { $this->doSearch($target, $orderby, $query, $search); return; } $sp = null; switch ($source) { case 'howcast': $sp = new ImportvideoHowcast($source); break; case 'youtube': default: $sp = new ImportvideoYoutube($source); break; } // handle special cases where user is adding a video to a new article or by category if ($wgRequest->getVal('new') || $wgRequest->getVal('wasnew')) { if ($wgRequest->getVal('new')) { $t = $this->getNewArticleWithoutVideo(); $target = $t->getText(); } else { $t = Title::newFromText($target); } $wgRequest->setVal('target', $target); } else { if ($wgRequest->getVal('category') && $target == '') { $t = $this->getTitleFromCategory($wgRequest->getVal('category')); $target = $t->getText(); $wgRequest->setVal('target', $target); } } // construct base url to switch between sources $url = $me->getFullURL() . "?target=" . urlencode($target) . "&q=" . urlencode($query) . $this->getURLExtras() . "&source="; $title = Title::newFromText($target); if (!trim($target)) { $wgOut->addHTML("Error: no target specified."); return; } $target = $title->getText(); //get the steps and intro to show to the user $r = Revision::newFromTitle($title); $text = ""; if ($r) { $text = $r->getText(); } $article = new Article($title); $extra = $article->getSection($text, 0); $steps = ""; for ($i = 1; $i < 3; $i++) { $xx = $article->getSection($text, $i); if (preg_match("/^==[ ]+" . wfMsg('steps') . "/", $xx)) { $steps = $xx; break; } } $extra = preg_replace("/{{[^}]*}}/", "", $extra); $extra = $wgOut->parse($extra); $steps = $wgOut->parse($steps); $cancel = ""; $nextlink = "/Special:Importvideo?new=1&skip={$title->getArticleID()}"; if ($wgRequest->getVal('category')) { $nextlink = "/Special:Importvideo?category=" . urlencode($wgRequest->getVal('category')); } if ($wgRequest->getVal('popup') != 'true') { $wgOut->addHTML("<div class='article_title'>\n\t\t\t\t" . wfMsg('importvideo_article') . "- <a href='{$title->getFullURL()}' target='new'>" . wfMsg('howto', $title->getText()) . "</a>"); $wgOut->addHTML("<spanid='showhide' style='font-size: 80%; text-align:right; font-weight: normal;'>\n\t\t\t\t\t(<a href='{$nextlink}' accesskey='s'>next article</a> |\n\t\t\t\t\t<a href='{$url}&dosearch=1' accesskey='s'>" . wfMsg('importvideo_searchforarticle') . "</a> {$cancel} )\n\t\t\t\t</span>"); if ($wgRequest->getVal('category')) { $wgOut->addHTML("You are adding videos to articles from the \"{$wgRequest->getVal('category')}\" category.\n\t\t\t\t\t(<a href='#'>change</a>)"); } $wgOut->addHTML("</div>"); $wgOut->addHTML("<div class='video_related wh_block'>\n\t\t\t\t\t<h2>Introduction</h2>\n\t\t\t\t\t{$extra}\n\t\t\t\t\t<br clear='all'/>\n\t\t\t\t\t<div id='showhide' style='font-size: 80%; text-align:right;'>\n\t\t\t\t\t\t<span id='showsteps'><a href='#' onclick='javascript:showhidesteps(); return false;'>" . wfMsg('importvideo_showsteps') . "</a></span>\n\t\t\t\t\t\t<span id='hidesteps' style='display: none;'><a href='#' onclick='javascript:showhidesteps(); return false;'>" . wfMsg('importvideo_hidesteps') . "</a></span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div id='stepsarea' style='display: none;'>\n\t\t\t\t\t{$steps}\n\t\t\t\t\t</div>\n\t\t\t\t\t<br clear='all'/>\n\t\t\t\t</div>\n\t\t\t"); } $wgOut->addHTML("<script type='text/javascript' src='" . wfGetPad('/extensions/min/f/extensions/wikihow/video/importvideo.js?rev=') . WH_SITEREV . "'> </script>\t"); $wgOut->addHTML("<link rel='stylesheet' type='text/css' href='" . wfGetPad('/extensions/min/f/extensions/wikihow/video/importvideo.css?rev=') . WH_SITEREV . "' />"); $wgOut->addHTML("<script type='text/javascript'>\n\t\t\tvar isPopUp = " . ($wgRequest->getVal('popup') ? "true" : "false") . ";\n\t\t\t</script>"); if (!$wgRequest->wasPosted()) { $wgOut->addHTML(wfMsgWikiHtml('add_video_info')); # HEADER for import page $url = $me->getFullURL() . "?target=" . urlencode($target) . "&q=" . urlencode($query) . $this->getURLExtras() . "&source="; // refine form $orderby = $wgRequest->getVal('orderby', 'relevance'); $wgOut->addHTML($this->refineForm($me, $target, $wgRequest->getVal('popup') == 'true', $query, $orderby)); // sources tab $wgOut->addHTML("<ul id='importvideo_search_tabs'>"); foreach ($wgImportVideoSources as $s) { $selected = $s == $source ? ' class="iv_selected"' : ''; $wgOut->addHTML("<li{$selected}><a href='{$url}{$s}'>" . wfMsg('importvideo_source_' . $s) . "</a></li>"); } $wgOut->addHTML("</ul>"); $vt = Title::makeTitle(NS_VIDEO, $target); if ($vt->getArticleID() > 0 && $wgRequest->getVal('popup') != 'true') { $wgOut->addHTML("<div class='wh_block importvideo_main'>" . wfMsgExt('importvideo_videoexists', 'parse', $vt->getFullText()) . "</div>"); } } //special class just for pop-ups if ($wgRequest->getVal('popup')) { $pop_class = 'importvideo_pop'; } $wgOut->addHTML("<div class='wh_block importvideo_main {$pop_class}'>"); $sp->execute($par); $wgOut->addHTML("</div>"); //Bebeth: took out extra closing div $wgOut->addHTML("</div>"); //Scott: put a brand new extra closing div in (take that, Bebeth!) }
/** * This is the meaty bit -- restores archived revisions of the given page * to the cur/old tables. If the page currently exists, all revisions will * be stuffed into old, otherwise the most recent will go into cur. * * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete. * @param $comment String * @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs * * @return Mixed: number of revisions restored or false on failure */ private function undeleteRevisions($timestamps, $unsuppress = false, $comment = '') { if (wfReadOnly()) { return false; } $restoreAll = empty($timestamps); $dbw = wfGetDB(DB_MASTER); # Does this page already exist? We'll have to update it... $article = new Article($this->title); # Load latest data for the current page (bug 31179) $article->loadPageData('fromdbmaster'); $oldcountable = $article->isCountable(); $options = 'FOR UPDATE'; // lock page $page = $dbw->selectRow('page', array('page_id', 'page_latest'), array('page_namespace' => $this->title->getNamespace(), 'page_title' => $this->title->getDBkey()), __METHOD__, $options); if ($page) { $makepage = false; # Page already exists. Import the history, and if necessary # we'll update the latest revision field in the record. $newid = 0; $pageId = $page->page_id; $previousRevId = $page->page_latest; # Get the time span of this page $previousTimestamp = $dbw->selectField('revision', 'rev_timestamp', array('rev_id' => $previousRevId), __METHOD__); if ($previousTimestamp === false) { wfDebug(__METHOD__ . ": existing page refers to a page_latest that does not exist\n"); return 0; } } else { # Have to create a new article... $makepage = true; $previousRevId = 0; $previousTimestamp = 0; } if ($restoreAll) { $oldones = '1 = 1'; # All revisions... } else { $oldts = implode(',', array_map(array(&$dbw, 'addQuotes'), array_map(array(&$dbw, 'timestamp'), $timestamps))); $oldones = "ar_timestamp IN ( {$oldts} )"; } /** * Select each archived revision... */ $result = $dbw->select('archive', array('ar_rev_id', 'ar_text', 'ar_comment', 'ar_user', 'ar_user_text', 'ar_timestamp', 'ar_minor_edit', 'ar_flags', 'ar_text_id', 'ar_deleted', 'ar_page_id', 'ar_len'), array('ar_namespace' => $this->title->getNamespace(), 'ar_title' => $this->title->getDBkey(), $oldones), __METHOD__, array('ORDER BY' => 'ar_timestamp')); $ret = $dbw->resultObject($result); $rev_count = $dbw->numRows($result); if (!$rev_count) { wfDebug(__METHOD__ . ": no revisions to restore\n"); return false; // ??? } $ret->seek($rev_count - 1); // move to last $row = $ret->fetchObject(); // get newest archived rev $ret->seek(0); // move back if ($makepage) { // Check the state of the newest to-be version... if (!$unsuppress && $row->ar_deleted & Revision::DELETED_TEXT) { return false; // we can't leave the current revision like this! } // Safe to insert now... $newid = $article->insertOn($dbw); $pageId = $newid; } else { // Check if a deleted revision will become the current revision... if ($row->ar_timestamp > $previousTimestamp) { // Check the state of the newest to-be version... if (!$unsuppress && $row->ar_deleted & Revision::DELETED_TEXT) { return false; // we can't leave the current revision like this! } } } $revision = null; $restored = 0; foreach ($ret as $row) { // Check for key dupes due to shitty archive integrity. if ($row->ar_rev_id) { $exists = $dbw->selectField('revision', '1', array('rev_id' => $row->ar_rev_id), __METHOD__); if ($exists) { continue; // don't throw DB errors } } // Insert one revision at a time...maintaining deletion status // unless we are specifically removing all restrictions... $revision = Revision::newFromArchiveRow($row, array('page' => $pageId, 'deleted' => $unsuppress ? 0 : $row->ar_deleted)); $revision->insertOn($dbw); $restored++; wfRunHooks('ArticleRevisionUndeleted', array(&$this->title, $revision, $row->ar_page_id)); } # Now that it's safely stored, take it out of the archive $dbw->delete('archive', array('ar_namespace' => $this->title->getNamespace(), 'ar_title' => $this->title->getDBkey(), $oldones), __METHOD__); // Was anything restored at all? if ($restored == 0) { return 0; } $created = (bool) $newid; // Attach the latest revision to the page... $wasnew = $article->updateIfNewerOn($dbw, $revision, $previousRevId); if ($created || $wasnew) { // Update site stats, link tables, etc $user = User::newFromName($revision->getRawUserText(), false); $article->doEditUpdates($revision, $user, array('created' => $created, 'oldcountable' => $oldcountable)); } wfRunHooks('ArticleUndelete', array(&$this->title, $created, $comment)); if ($this->title->getNamespace() == NS_FILE) { $update = new HTMLCacheUpdate($this->title, 'imagelinks'); $update->doUpdate(); } return $restored; }
function loadPageData($data = 'fromdb') { Article::loadPageData($data); $this->mIsRedirect = true; }
/** * Attempt submission (no UI) * * @param $result * @param $bot bool * * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value, * * FIXME: This interface is TERRIBLE, but hard to get rid of due to various error display idiosyncrasies. There are * also lots of cases where error metadata is set in the object and retrieved later instead of being returned, e.g. * AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time. */ function internalAttemptSave(&$result, $bot = false) { global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize; $status = Status::newGood(); wfProfileIn(__METHOD__); wfProfileIn(__METHOD__ . '-checks'); if (!wfRunHooks('EditPage::attemptSave', array($this))) { wfDebug("Hook 'EditPage::attemptSave' aborted article saving\n"); $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } # Check image redirect if ($this->mTitle->getNamespace() == NS_FILE && Title::newFromRedirect($this->textbox1) instanceof Title && !$wgUser->isAllowed('upload')) { $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; $status->setResult(false, $code); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } # Check for spam $match = self::matchSummarySpamRegex($this->summary); if ($match === false) { $match = self::matchSpamRegex($this->textbox1); } if ($match !== false) { $result['spam'] = $match; $ip = $wgRequest->getIP(); $pdbk = $this->mTitle->getPrefixedDBkey(); $match = str_replace("\n", '', $match); wfDebugLog('SpamRegex', "{$ip} spam regex hit [[{$pdbk}]]: \"{$match}\""); $status->fatal('spamprotectionmatch', $match); $status->value = self::AS_SPAM_ERROR; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if (!wfRunHooks('EditFilter', array($this, $this->textbox1, $this->section, &$this->hookError, $this->summary))) { # Error messages etc. could be handled within the hook... $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } elseif ($this->hookError != '') { # ...or the hook could be expecting us to produce an error $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR_EXPECTED; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if ($wgUser->isBlockedFrom($this->mTitle, false)) { // Auto-block user's IP if the account was "hard" blocked $wgUser->spreadAnyEditBlock(); # Check block state against master, thus 'false'. $status->setResult(false, self::AS_BLOCKED_PAGE_FOR_USER); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } $this->kblength = (int) (strlen($this->textbox1) / 1024); if ($this->kblength > $wgMaxArticleSize) { // Error will be displayed by showEditForm() $this->tooBig = true; $status->setResult(false, self::AS_CONTENT_TOO_BIG); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if (!$wgUser->isAllowed('edit')) { if ($wgUser->isAnon()) { $status->setResult(false, self::AS_READ_ONLY_PAGE_ANON); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } else { $status->fatal('readonlytext'); $status->value = self::AS_READ_ONLY_PAGE_LOGGED; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } } if (wfReadOnly()) { $status->fatal('readonlytext'); $status->value = self::AS_READ_ONLY_PAGE; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } if ($wgUser->pingLimiter()) { $status->fatal('actionthrottledtext'); $status->value = self::AS_RATE_LIMITED; wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } # If the article has been deleted while editing, don't save it without # confirmation if ($this->wasDeletedSinceLastEdit() && !$this->recreate) { $status->setResult(false, self::AS_ARTICLE_WAS_DELETED); wfProfileOut(__METHOD__ . '-checks'); wfProfileOut(__METHOD__); return $status; } wfProfileOut(__METHOD__ . '-checks'); # Load the page data from the master. If anything changes in the meantime, # we detect it by using page_latest like a token in a 1 try compare-and-swap. $this->mArticle->loadPageData('fromdbmaster'); $new = !$this->mArticle->exists(); if ($new) { // Late check for create permission, just in case *PARANOIA* if (!$this->mTitle->userCan('create')) { $status->fatal('nocreatetext'); $status->value = self::AS_NO_CREATE_PERMISSION; wfDebug(__METHOD__ . ": no create permission\n"); wfProfileOut(__METHOD__); return $status; } # Don't save a new article if it's blank. if ($this->textbox1 == '') { $status->setResult(false, self::AS_BLANK_ARTICLE); wfProfileOut(__METHOD__); return $status; } // Run post-section-merge edit filter if (!wfRunHooks('EditFilterMerged', array($this, $this->textbox1, &$this->hookError, $this->summary))) { # Error messages etc. could be handled within the hook... $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; wfProfileOut(__METHOD__); return $status; } elseif ($this->hookError != '') { # ...or the hook could be expecting us to produce an error $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR_EXPECTED; wfProfileOut(__METHOD__); return $status; } $text = $this->textbox1; $result['sectionanchor'] = ''; if ($this->section == 'new') { if ($this->sectiontitle !== '') { // Insert the section title above the content. $text = wfMessage('newsectionheaderdefaultlevel', $this->sectiontitle)->inContentLanguage()->text() . "\n\n" . $text; // Jump to the new section $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText($this->sectiontitle); // If no edit summary was specified, create one automatically from the section // title and have it link to the new section. Otherwise, respect the summary as // passed. if ($this->summary === '') { $cleanSectionTitle = $wgParser->stripSectionName($this->sectiontitle); $this->summary = wfMessage('newsectionsummary')->rawParams($cleanSectionTitle)->inContentLanguage()->text(); } } elseif ($this->summary !== '') { // Insert the section title above the content. $text = wfMessage('newsectionheaderdefaultlevel', $this->summary)->inContentLanguage()->text() . "\n\n" . $text; // Jump to the new section $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText($this->summary); // Create a link to the new section from the edit summary. $cleanSummary = $wgParser->stripSectionName($this->summary); $this->summary = wfMessage('newsectionsummary')->rawParams($cleanSummary)->inContentLanguage()->text(); } } $status->value = self::AS_SUCCESS_NEW_ARTICLE; } else { # Article exists. Check for edit conflict. $timestamp = $this->mArticle->getTimestamp(); wfDebug("timestamp: {$timestamp}, edittime: {$this->edittime}\n"); if ($timestamp != $this->edittime) { $this->isConflict = true; if ($this->section == 'new') { if ($this->mArticle->getUserText() == $wgUser->getName() && $this->mArticle->getComment() == $this->summary) { // Probably a duplicate submission of a new comment. // This can happen when squid resends a request after // a timeout but the first one actually went through. wfDebug(__METHOD__ . ": duplicate new section submission; trigger edit conflict!\n"); } else { // New comment; suppress conflict. $this->isConflict = false; wfDebug(__METHOD__ . ": conflict suppressed; new section\n"); } } elseif ($this->section == '' && Revision::userWasLastToEdit(DB_MASTER, $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime)) { # Suppress edit conflict with self, except for section edits where merging is required. wfDebug(__METHOD__ . ": Suppressing edit conflict, same user.\n"); $this->isConflict = false; } } // If sectiontitle is set, use it, otherwise use the summary as the section title (for // backwards compatibility with old forms/bots). if ($this->sectiontitle !== '') { $sectionTitle = $this->sectiontitle; } else { $sectionTitle = $this->summary; } if ($this->isConflict) { wfDebug(__METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}' (article time '{$timestamp}')\n"); $text = $this->mArticle->replaceSection($this->section, $this->textbox1, $sectionTitle, $this->edittime); } else { wfDebug(__METHOD__ . ": getting section '{$this->section}'\n"); $text = $this->mArticle->replaceSection($this->section, $this->textbox1, $sectionTitle); } if (is_null($text)) { wfDebug(__METHOD__ . ": activating conflict; section replace failed.\n"); $this->isConflict = true; $text = $this->textbox1; // do not try to merge here! } elseif ($this->isConflict) { # Attempt merge if ($this->mergeChangesInto($text)) { // Successful merge! Maybe we should tell the user the good news? $this->isConflict = false; wfDebug(__METHOD__ . ": Suppressing edit conflict, successful merge.\n"); } else { $this->section = ''; $this->textbox1 = $text; wfDebug(__METHOD__ . ": Keeping edit conflict, failed merge.\n"); } } if ($this->isConflict) { $status->setResult(false, self::AS_CONFLICT_DETECTED); wfProfileOut(__METHOD__); return $status; } // Run post-section-merge edit filter if (!wfRunHooks('EditFilterMerged', array($this, $text, &$this->hookError, $this->summary))) { # Error messages etc. could be handled within the hook... $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR; wfProfileOut(__METHOD__); return $status; } elseif ($this->hookError != '') { # ...or the hook could be expecting us to produce an error $status->fatal('hookaborted'); $status->value = self::AS_HOOK_ERROR_EXPECTED; wfProfileOut(__METHOD__); return $status; } # Handle the user preference to force summaries here, but not for null edits if ($this->section != 'new' && !$this->allowBlankSummary && $this->getOriginalContent() != $text && !Title::newFromRedirect($text)) { if (md5($this->summary) == $this->autoSumm) { $this->missingSummary = true; $status->fatal('missingsummary'); $status->value = self::AS_SUMMARY_NEEDED; wfProfileOut(__METHOD__); return $status; } } # And a similar thing for new sections if ($this->section == 'new' && !$this->allowBlankSummary) { if (trim($this->summary) == '') { $this->missingSummary = true; $status->fatal('missingsummary'); // or 'missingcommentheader' if $section == 'new'. Blegh $status->value = self::AS_SUMMARY_NEEDED; wfProfileOut(__METHOD__); return $status; } } # All's well wfProfileIn(__METHOD__ . '-sectionanchor'); $sectionanchor = ''; if ($this->section == 'new') { if ($this->textbox1 == '') { $this->missingComment = true; $status->fatal('missingcommenttext'); $status->value = self::AS_TEXTBOX_EMPTY; wfProfileOut(__METHOD__ . '-sectionanchor'); wfProfileOut(__METHOD__); return $status; } if ($this->sectiontitle !== '') { $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText($this->sectiontitle); // If no edit summary was specified, create one automatically from the section // title and have it link to the new section. Otherwise, respect the summary as // passed. if ($this->summary === '') { $cleanSectionTitle = $wgParser->stripSectionName($this->sectiontitle); $this->summary = wfMessage('newsectionsummary')->rawParams($cleanSectionTitle)->inContentLanguage()->text(); } } elseif ($this->summary !== '') { $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText($this->summary); # This is a new section, so create a link to the new section # in the revision summary. $cleanSummary = $wgParser->stripSectionName($this->summary); $this->summary = wfMessage('newsectionsummary')->rawParams($cleanSummary)->inContentLanguage()->text(); } } elseif ($this->section != '') { # Try to get a section anchor from the section source, redirect to edited section if header found # XXX: might be better to integrate this into Article::replaceSection # for duplicate heading checking and maybe parsing $hasmatch = preg_match("/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches); # we can't deal with anchors, includes, html etc in the header for now, # headline would need to be parsed to improve this if ($hasmatch && strlen($matches[2]) > 0) { $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText($matches[2]); } } $result['sectionanchor'] = $sectionanchor; wfProfileOut(__METHOD__ . '-sectionanchor'); // Save errors may fall down to the edit form, but we've now // merged the section into full text. Clear the section field // so that later submission of conflict forms won't try to // replace that into a duplicated mess. $this->textbox1 = $text; $this->section = ''; $status->value = self::AS_SUCCESS_UPDATE; } // Check for length errors again now that the section is merged in $this->kblength = (int) (strlen($text) / 1024); if ($this->kblength > $wgMaxArticleSize) { $this->tooBig = true; $status->setResult(false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED); wfProfileOut(__METHOD__); return $status; } $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | ($new ? EDIT_NEW : EDIT_UPDATE) | ($this->minoredit && !$this->isNew ? EDIT_MINOR : 0) | ($bot ? EDIT_FORCE_BOT : 0); $doEditStatus = $this->mArticle->doEdit($text, $this->summary, $flags); if ($doEditStatus->isOK()) { $result['redirect'] = Title::newFromRedirect($text) !== null; $this->commitWatch(); wfProfileOut(__METHOD__); return $status; } else { // Failure from doEdit() // Show the edit conflict page for certain recognized errors from doEdit(), // but don't show it for errors from extension hooks $errors = $doEditStatus->getErrorsArray(); if (in_array($errors[0][0], array('edit-gone-missing', 'edit-conflict', 'edit-already-exists'))) { $this->isConflict = true; // Destroys data doEdit() put in $status->value but who cares $doEditStatus->value = self::AS_END; } wfProfileOut(__METHOD__); return $doEditStatus; } }