/** * 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 $wgFilterCallback, $wgUser, $wgRequest, $wgParser; global $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 ($wgFilterCallback && is_callable($wgFilterCallback) && $wgFilterCallback($this->mTitle, $this->textbox1, $this->section, $this->hookError, $this->summary)) { # Error messages or other handling should be performed by the filter function $status->setResult(false, self::AS_FILTERING); 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'); # If article is new, insert it. $aid = $this->mTitle->getArticleID(Title::GAID_FOR_UPDATE); $new = $aid == 0; 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; } # Handle the user preference to force summaries here. Check if it's not a redirect. if (!$this->allowBlankSummary && !Title::newFromRedirect($this->textbox1)) { if (md5($this->summary) == $this->autoSumm) { $this->missingSummary = true; $status->fatal('missingsummary'); // or 'missingcommentheader' if $section == 'new'. Blegh $status->value = self::AS_SUMMARY_NEEDED; wfProfileOut(__METHOD__); return $status; } } $text = $this->textbox1; $result['sectionanchor'] = ''; if ($this->section == 'new') { if ($this->sectiontitle !== '') { // Insert the section title above the content. $text = wfMsgForContent('newsectionheaderdefaultlevel', $this->sectiontitle) . "\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 = wfMsgForContent('newsectionsummary', $cleanSectionTitle); } } elseif ($this->summary !== '') { // Insert the section title above the content. $text = wfMsgForContent('newsectionheaderdefaultlevel', $this->summary) . "\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 = wfMsgForContent('newsectionsummary', $cleanSummary); } } $status->value = self::AS_SUCCESS_NEW_ARTICLE; } else { # 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->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 == '' && $this->userWasLastToEdit($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 = wfMsgForContent('newsectionsummary', $cleanSectionTitle); } } 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 = wfMsgForContent('newsectionsummary', $cleanSummary); } } 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 { $this->isConflict = true; $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares wfProfileOut(__METHOD__); return $doEditStatus; } }
/** * Collects metadata and additional resources for this page * @param Title $oTitle * @param DOMDocument $oPageDOM * @param array $aParams * @return array array( 'meta' => ..., 'resources' => ...); */ private static function collectData($oTitle, $oPageDOM, $aParams) { $aMeta = array(); $aResources = array('ATTACHMENT' => array(), 'STYLESHEET' => array(), 'IMAGE' => array()); // TODO RBV (01.02.12 13:51): Handle oldid $aCategories = array(); if ($oTitle->exists()) { // TODO RBV (27.06.12 11:47): Throws an exception. Maybe better use try ... catch instead of $oTitle->exists() $aAPIParams = new FauxRequest(array('action' => 'parse', 'page' => $oTitle->getPrefixedText(), 'prop' => 'images|categories|links')); $oAPI = new ApiMain($aAPIParams); $oAPI->execute(); if (defined('ApiResult::META_CONTENT')) { $aResult = $oAPI->getResult()->getResultData(null, array('BC' => array(), 'Types' => array(), 'Strip' => 'all')); } else { $aResult = $oAPI->getResultData(); } foreach ($aResult['parse']['categories'] as $aCat) { $aCategories[] = $aCat['*']; } } /* //For future use... foreach($aResult['parse']['images'] as $sFileName ) { $oImage = RepoGroup::singleton()->getLocalRepo()->newFile( Title::newFromText( $sFileName, NS_FILE ) ); if( $oImage->exists() ) { $sAbsoluteFileSystemPath = $oImage->getFullPath(); } } */ //Dublin Core: $aMeta['DC.title'] = $oTitle->getPrefixedText(); $aMeta['DC.date'] = wfTimestamp(TS_ISO_8601); // TODO RBV (14.12.10 14:01): Check for conformity. Maybe there is a better way to acquire than wfTimestamp()? //Custom global $wgLang; $sCurrentTS = $wgLang->userAdjust(wfTimestampNow()); $aMeta['title'] = $oTitle->getPrefixedText(); $aMeta['exportdate'] = $wgLang->sprintfDate('d.m.Y', $sCurrentTS); $aMeta['exporttime'] = $wgLang->sprintfDate('H:i', $sCurrentTS); $aMeta['exporttimeexact'] = $wgLang->sprintfDate('H:i:s', $sCurrentTS); //Custom - Categories->Keywords $aMeta['keywords'] = implode(', ', $aCategories); $oDOMXPath = new DOMXPath($oPageDOM); $oMetadataElements = $oDOMXPath->query("//div[@class='bs-universalexport-meta']"); foreach ($oMetadataElements as $oMetadataElement) { if ($oMetadataElement->hasAttributes()) { foreach ($oMetadataElement->attributes as $oAttribute) { if ($oAttribute->name !== 'class') { $aMeta[$oAttribute->name] = $oAttribute->value; } } } $oMetadataElement->parentNode->removeChild($oMetadataElement); } //If it's a normal article if (!in_array($oTitle->getNamespace(), array(NS_SPECIAL, NS_IMAGE, NS_CATEGORY))) { $oArticle = new Article($oTitle); $aMeta['author'] = $oArticle->getUserText(); // TODO RBV (14.12.10 12:19): Realname/Username -> DisplayName $aMeta['date'] = $wgLang->sprintfDate('d.m.Y', $oArticle->getTouched()); } wfRunHooks('BSUEModulePDFcollectMetaData', array($oTitle, $oPageDOM, &$aParams, $oDOMXPath, &$aMeta)); $aMetaDataOverrides = json_decode(BsConfig::get('MW::UniversalExport::MetadataOverrides'), true); $aMeta = array_merge($aMeta, $aMetaDataOverrides); return array('meta' => $aMeta, 'resources' => $aResources); }
static function gatherImageCredits($parsed, &$credits) { foreach ($parsed as $k => $v) { if (!is_array($v) && ($k == "url" || $k == "html")) { if (strpos(strtolower($v), 'jpg') === FALSE && strpos(strtolower($v), 'png') === FALSE) { continue; } if ($k == "html") { $gotImage = true; $pqDoc = PHPQuery::newDocument($v); $v = pq("img")->attr("src"); } elseif ($k == "url") { $gotImage = true; } } if ($gotImage) { $imageName = explode("/", $v); $imageName = $imageName[count($imageName) - 2]; $titleName = "Image:" . $imageName; $title = Title::newFromText($imageName, NS_IMAGE); if ($title && $title->isKnown()) { $article = new Article($title); if ($article) { $user = $article->getUserText(); if ($user) { $user .= ' (wikiHow)'; } $wikitext = $article->getContent(); if (preg_match('@{{(cc-by[^}]+)}}@', $wikitext, $m)) { $license = $m[1]; // From http://creativecommons.org/licenses/ $licenseTexts = array('cc-by-sa-nc-2.5-self' => 'Creative Commons Share-Alike Non-Commercial Attribution'); if (isset($licenseTexts[$license])) { $license = $licenseTexts[$license]; } } elseif (preg_match('@{{flickr[^}]+\\|([^|}]+)}}@', $wikitext, $m)) { $user = $m[1] . ' (Flickr)'; } } } if ($user) { $credits['uploaders'][] = $user; } if ($license) { $credits['licenses'][] = $license; } } elseif (is_array($v)) { self::gatherImageCredits($v, $credits); } } }
/** * 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; }
/** * Constructor for a single item in the feed. Requires the URI of the item. */ public function __construct(Title $t, $c, $d) { $this->title = $t; $this->uri = $t->getFullURL(); $this->label = $t->getText(); $article = null; if (count($c) == 0) { $article = new Article($t); $this->creator = array(); $this->creator[] = $article->getUserText(); } else { $this->creator = $c; } $this->date = array(); if (count($d) == 0) { if (is_null($article)) { $article = new Article($t); } $this->date[] = date("c", strtotime($article->getTimestamp())); } else { foreach ($d as $date) { $this->date[] = $date; } } // get content if ($t->getNamespace() == NS_MAIN) { $this->articlename = ':' . $t->getDBkey(); } else { $this->articlename = $t->getPrefixedDBKey(); } }