diff options
Diffstat (limited to 'includes')
534 files changed, 37258 insertions, 22541 deletions
diff --git a/includes/Action.php b/includes/Action.php index 2e0c88ba..4b6e4468 100644 --- a/includes/Action.php +++ b/includes/Action.php @@ -59,7 +59,7 @@ abstract class Action { * the action is disabled, or null if it's not recognised * @param $action String * @param $overrides Array - * @return bool|null|string + * @return bool|null|string|callable */ final private static function getClass( $action, array $overrides ) { global $wgActions; @@ -89,12 +89,18 @@ abstract class Action { * if it is not recognised */ final public static function factory( $action, Page $page, IContextSource $context = null ) { - $class = self::getClass( $action, $page->getActionOverrides() ); - if ( $class ) { - $obj = new $class( $page, $context ); + $classOrCallable = self::getClass( $action, $page->getActionOverrides() ); + + if ( is_string( $classOrCallable ) ) { + $obj = new $classOrCallable( $page, $context ); return $obj; } - return $class; + + if ( is_callable( $classOrCallable ) ) { + return call_user_func_array( $classOrCallable, array( $page, $context ) ); + } + + return $classOrCallable; } /** @@ -136,7 +142,7 @@ abstract class Action { return 'view'; } - $action = Action::factory( $actionName, $context->getWikiPage() ); + $action = Action::factory( $actionName, $context->getWikiPage(), $context ); if ( $action instanceof Action ) { return $action->getName(); } @@ -161,8 +167,14 @@ abstract class Action { final public function getContext() { if ( $this->context instanceof IContextSource ) { return $this->context; + } else if ( $this->page instanceof Article ) { + // NOTE: $this->page can be a WikiPage, which does not have a context. + wfDebug( __METHOD__ . ': no context known, falling back to Article\'s context.' ); + return $this->page->getContext(); } - return $this->page->getContext(); + + wfWarn( __METHOD__ . ': no context known, falling back to RequestContext::getMain().' ); + return RequestContext::getMain(); } /** @@ -213,7 +225,7 @@ abstract class Action { /** * Shortcut to get the user Language being used for this instance * - * @deprecated 1.19 Use getLanguage instead + * @deprecated since 1.19 Use getLanguage instead * @return Language */ final public function getLang() { @@ -241,12 +253,20 @@ abstract class Action { } /** - * Protected constructor: use Action::factory( $action, $page ) to actually build - * these things in the real world + * Constructor. + * + * Only public since 1.21 + * * @param $page Page * @param $context IContextSource */ - protected function __construct( Page $page, IContextSource $context = null ) { + public function __construct( Page $page, IContextSource $context = null ) { + if ( $context === null ) { + wfWarn( __METHOD__ . ' called without providing a Context object.' ); + // NOTE: We could try to initialize $context using $page->getContext(), + // if $page is an Article. That however seems to not work seamlessly. + } + $this->page = $page; $this->context = $context; } @@ -374,18 +394,23 @@ abstract class FormAction extends Action { * Add pre- or post-text to the form * @return String HTML which will be sent to $form->addPreText() */ - protected function preText() { return ''; } + protected function preText() { + return ''; + } /** * @return string */ - protected function postText() { return ''; } + protected function postText() { + return ''; + } /** * Play with the HTMLForm if you need to more substantially * @param $form HTMLForm */ - protected function alterForm( HTMLForm $form ) {} + protected function alterForm( HTMLForm $form ) { + } /** * Get the HTMLForm to control behavior @@ -464,7 +489,7 @@ abstract class FormAction extends Action { public function execute( array $data = null, $captureErrors = true ) { try { // Set a new context so output doesn't leak. - $this->context = clone $this->page->getContext(); + $this->context = clone $this->getContext(); // This will throw exceptions if there's a problem $this->checkCanExecute( $this->getUser() ); @@ -553,7 +578,7 @@ abstract class FormlessAction extends Action { public function execute( array $data = null, $captureErrors = true ) { try { // Set a new context so output doesn't leak. - $this->context = clone $this->page->getContext(); + $this->context = clone $this->getContext(); if ( is_array( $data ) ) { $this->context->setRequest( new FauxRequest( $data, false ) ); } diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index b00cf309..c9ca1283 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -63,7 +63,7 @@ class AjaxDispatcher { $this->mode = "post"; } - switch( $this->mode ) { + switch ( $this->mode ) { case 'get': $this->func_name = isset( $_GET["rs"] ) ? $_GET["rs"] : ''; if ( ! empty( $_GET["rsargs"] ) ) { @@ -111,15 +111,13 @@ class AjaxDispatcher { wfHttpError( 400, 'Bad Request', - "unknown function " . (string) $this->func_name + "unknown function " . $this->func_name ); - } elseif ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) - && !$wgUser->isAllowed( 'read' ) ) - { + } elseif ( !User::isEveryoneAllowed( 'read' ) && !$wgUser->isAllowed( 'read' ) ) { wfHttpError( 403, 'Forbidden', - 'You must log in to view pages.' ); + 'You are not allowed to view pages.' ); } else { wfDebug( __METHOD__ . ' dispatching ' . $this->func_name . "\n" ); diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php index 138f808a..d5536529 100644 --- a/includes/AjaxResponse.php +++ b/includes/AjaxResponse.php @@ -210,7 +210,7 @@ class AjaxResponse { * @param $timestamp string * @return bool Returns true if the response code was set to 304 Not Modified. */ - function checkLastModified ( $timestamp ) { + function checkLastModified( $timestamp ) { global $wgCachePages, $wgCacheEpoch, $wgUser; $fname = 'AjaxResponse::checkLastModified'; diff --git a/includes/ArrayUtils.php b/includes/ArrayUtils.php index 0b74f06a..985271f7 100644 --- a/includes/ArrayUtils.php +++ b/includes/ArrayUtils.php @@ -39,7 +39,7 @@ class ArrayUtils { * * @return bool|int|string */ - public static function pickRandom( $weights ){ + public static function pickRandom( $weights ) { if ( !is_array( $weights ) || count( $weights ) == 0 ) { return false; } diff --git a/includes/Article.php b/includes/Article.php index 9b4afe44..0b18221a 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -25,7 +25,7 @@ * * This maintains WikiPage functions for backwards compatibility. * - * @todo move and rewrite code to an Action class + * @todo Move and rewrite code to an Action class * * See design.txt for an overview. * Note: edit user interface and cache support functions have been @@ -160,7 +160,7 @@ class Article implements Page { $page = null; wfRunHooks( 'ArticleFromTitle', array( &$title, &$page ) ); if ( !$page ) { - switch( $title->getNamespace() ) { + switch ( $title->getNamespace() ) { case NS_FILE: $page = new ImagePage( $title ); break; @@ -385,7 +385,8 @@ class Article implements Page { $content = $this->fetchContentObject(); - $this->mContent = ContentHandler::getContentText( $content ); #@todo: get rid of mContent everywhere! + // @todo Get rid of mContent everywhere! + $this->mContent = ContentHandler::getContentText( $content ); ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); wfProfileOut( __METHOD__ ); @@ -609,7 +610,7 @@ class Article implements Page { $this->mParserOutput = false; while ( !$outputDone && ++$pass ) { - switch( $pass ) { + switch ( $pass ) { case 1: wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) ); break; @@ -673,12 +674,12 @@ class Article implements Page { wfDebug( __METHOD__ . ": showing CSS/JS source\n" ); $this->showCssOrJsPage(); $outputDone = true; - } elseif( !wfRunHooks( 'ArticleContentViewCustom', + } elseif ( !wfRunHooks( 'ArticleContentViewCustom', array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { # Allow extensions do their own custom view for certain pages $outputDone = true; - } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', + } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) { # Allow extensions do their own custom view for certain pages @@ -787,7 +788,7 @@ class Article implements Page { * Show a diff page according to current request variables. For use within * Article::view() only, other callers should use the DifferenceEngine class. * - * @todo: make protected + * @todo Make protected */ public function showDiffPage() { $request = $this->getContext()->getRequest(); @@ -854,11 +855,11 @@ class Article implements Page { /** * Get the robot policy to be used for the current view * @param string $action the action= GET parameter - * @param $pOutput ParserOutput + * @param $pOutput ParserOutput|null * @return Array the policy that should be set * TODO: actions other than 'view' */ - public function getRobotPolicy( $action, $pOutput ) { + public function getRobotPolicy( $action, $pOutput = null ) { global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy; $ns = $this->getTitle()->getNamespace(); @@ -875,7 +876,7 @@ class Article implements Page { } if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) { return array( - 'index' => 'noindex', + 'index' => 'noindex', 'follow' => 'nofollow' ); } @@ -884,19 +885,19 @@ class Article implements Page { if ( $this->mPage->getID() === 0 || $this->getOldID() ) { # Non-articles (special pages etc), and old revisions return array( - 'index' => 'noindex', + 'index' => 'noindex', 'follow' => 'nofollow' ); } elseif ( $this->getContext()->getOutput()->isPrintable() ) { # Discourage indexing of printable versions, but encourage following return array( - 'index' => 'noindex', + 'index' => 'noindex', 'follow' => 'follow' ); } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) { # For ?curid=x urls, disallow indexing return array( - 'index' => 'noindex', + 'index' => 'noindex', 'follow' => 'follow' ); } @@ -988,8 +989,9 @@ class Article implements Page { // Set the fragment if one was specified in the redirect if ( strval( $this->getTitle()->getFragment() ) != '' ) { - $fragment = Xml::escapeJsString( $this->getTitle()->getFragmentForURL() ); - $outputPage->addInlineScript( "redirectToFragment(\"$fragment\");" ); + $outputPage->addInlineScript( Xml::encodeJsCall( + 'redirectToFragment', array( $this->getTitle()->getFragmentForURL() ) + ) ); } // Add a <link rel="canonical"> tag @@ -1035,11 +1037,10 @@ class Article implements Page { $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' ); } - # If we have been passed an &rcid= parameter, we want to give the user a - # chance to mark this new article as patrolled. - $this->showPatrolFooter(); + // Show a footer allowing the user to patrol the shown revision or page if possible + $patrolFooterShown = $this->showPatrolFooter(); - wfRunHooks( 'ArticleViewFooter', array( $this ) ); + wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) ); } @@ -1049,21 +1050,92 @@ class Article implements Page { * desired, does nothing. * Side effect: When the patrol link is build, this method will call * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax. + * + * @return bool */ public function showPatrolFooter() { - $request = $this->getContext()->getRequest(); + global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI; + $outputPage = $this->getContext()->getOutput(); $user = $this->getContext()->getUser(); - $rcid = $request->getVal( 'rcid' ); + $cache = wfGetMainCache(); + $rc = false; - if ( !$rcid || !$this->getTitle()->quickUserCan( 'patrol', $user ) ) { - return; + if ( !$this->getTitle()->quickUserCan( 'patrol', $user ) || !( $wgUseRCPatrol || $wgUseNPPatrol ) ) { + // Patrolling is disabled or the user isn't allowed to + return false; + } + + wfProfileIn( __METHOD__ ); + + // New page patrol: Get the timestamp of the oldest revison which + // the revision table holds for the given page. Then we look + // whether it's within the RC lifespan and if it is, we try + // to get the recentchanges row belonging to that entry + // (with rc_new = 1). + + // Check for cached results + if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) { + wfProfileOut( __METHOD__ ); + return false; } + if ( $this->mRevision && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 ) ) { + // The current revision is already older than what could be in the RC table + // 6h tolerance because the RC might not be cleaned out regularly + wfProfileOut( __METHOD__ ); + return false; + } + + $dbr = wfGetDB( DB_SLAVE ); + $oldestRevisionTimestamp = $dbr->selectField( + 'revision', + 'MIN( rev_timestamp )', + array( 'rev_page' => $this->getTitle()->getArticleID() ), + __METHOD__ + ); + + if ( $oldestRevisionTimestamp && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 ) ) { + // 6h tolerance because the RC might not be cleaned out regularly + $rc = RecentChange::newFromConds( + array( + 'rc_new' => 1, + 'rc_timestamp' => $oldestRevisionTimestamp, + 'rc_namespace' => $this->getTitle()->getNamespace(), + 'rc_cur_id' => $this->getTitle()->getArticleID(), + 'rc_patrolled' => 0 + ), + __METHOD__, + array( 'USE INDEX' => 'new_name_timestamp' ) + ); + } + + if ( !$rc ) { + // No RC entry around + + // Cache the information we gathered above in case we can't patrol + // Don't cache in case we can patrol as this could change + $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' ); + + wfProfileOut( __METHOD__ ); + return false; + } + + if ( $rc->getPerformer()->getName() == $user->getName() ) { + // Don't show a patrol link for own creations. If the user could + // patrol them, they already would be patrolled + wfProfileOut( __METHOD__ ); + return false; + } + + $rcid = $rc->getAttribute( 'rc_id' ); + $token = $user->getEditToken( $rcid ); $outputPage->preventClickjacking(); - $outputPage->addModules( 'mediawiki.page.patrol.ajax' ); + if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) { + $outputPage->addModules( 'mediawiki.page.patrol.ajax' ); + } $link = Linker::linkKnown( $this->getTitle(), @@ -1081,6 +1153,9 @@ class Article implements Page { wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() . '</div>' ); + + wfProfileOut( __METHOD__ ); + return true; } /** @@ -1090,6 +1165,7 @@ class Article implements Page { public function showMissingArticle() { global $wgSend404Code; $outputPage = $this->getContext()->getOutput(); + // Whether the page is a root user page of an existing user (but not a subpage) $validUserPage = false; # Show info in user (talk) namespace. Does the user exist? Is he blocked? @@ -1099,7 +1175,7 @@ class Article implements Page { $user = User::newFromName( $rootPart, false /* allow IP users*/ ); $ip = User::isIP( $rootPart ); - if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist + if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>", array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) ); } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked @@ -1117,9 +1193,9 @@ class Article implements Page { ) ) ); - $validUserPage = true; + $validUserPage = !$this->getTitle()->isSubpage(); } else { - $validUserPage = true; + $validUserPage = !$this->getTitle()->isSubpage(); } } @@ -1139,6 +1215,13 @@ class Article implements Page { $this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" ); } + if ( $validUserPage ) { + // Also apply the robot policy for nonexisting user pages (as those aren't served as 404) + $policy = $this->getRobotPolicy( 'view' ); + $outputPage->setIndexPolicy( $policy['index'] ); + $outputPage->setFollowPolicy( $policy['follow'] ); + } + $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) ); if ( ! $hookResult ) { @@ -1188,7 +1271,7 @@ class Article implements Page { } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) { # Give explanation and add a link to view the revision... $oldid = intval( $this->getOldID() ); - $link = $this->getTitle()->getFullUrl( "oldid={$oldid}&unhide=1" ); + $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" ); $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ? 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide'; $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", @@ -1470,13 +1553,7 @@ class Article implements Page { $this->doDelete( $reason, $suppress ); - if ( $user->isLoggedIn() && $request->getCheck( 'wpWatch' ) != $user->isWatched( $title ) ) { - if ( $request->getCheck( 'wpWatch' ) ) { - WatchAction::doWatch( $title, $user ); - } else { - WatchAction::doUnwatch( $title, $user ); - } - } + WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user ); return; } diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php index a4658176..84cf3d5e 100644 --- a/includes/AuthPlugin.php +++ b/includes/AuthPlugin.php @@ -213,6 +213,19 @@ class AuthPlugin { } /** + * Update user groups in the external authentication database. + * Return true if successful. + * + * @param $user User object. + * @param $addgroups Groups to add. + * @param $delgroups Groups to remove. + * @return Boolean + */ + public function updateExternalDBGroups( $user, $addgroups, $delgroups = array() ) { + return true; + } + + /** * Check to see if external accounts can be created. * Return true if external accounts can be created. * @return Boolean diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 3555e2c3..0706fe3f 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -55,7 +55,6 @@ $wgAutoloadLocalClasses = array( 'CdbWriter_DBA' => 'includes/Cdb.php', 'CdbWriter_PHP' => 'includes/Cdb_PHP.php', 'ChangesFeed' => 'includes/ChangesFeed.php', - 'ChangesList' => 'includes/ChangesList.php', 'ChangeTags' => 'includes/ChangeTags.php', 'ChannelFeed' => 'includes/Feed.php', 'Collation' => 'includes/Collation.php', @@ -65,10 +64,10 @@ $wgAutoloadLocalClasses = array( 'ConfEditorToken' => 'includes/ConfEditor.php', 'Cookie' => 'includes/Cookie.php', 'CookieJar' => 'includes/Cookie.php', - 'MWCryptRand' => 'includes/CryptRand.php', 'CurlHttpRequest' => 'includes/HttpFunctions.php', 'DeferrableUpdate' => 'includes/DeferredUpdates.php', 'DeferredUpdates' => 'includes/DeferredUpdates.php', + 'MWCallableUpdate' => 'includes/CallableUpdate.php', 'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php', 'DerivativeRequest' => 'includes/WebRequest.php', 'DiffHistoryBlob' => 'includes/HistoryBlob.php', @@ -87,16 +86,8 @@ $wgAutoloadLocalClasses = array( 'DumpPipeOutput' => 'includes/Export.php', 'EditPage' => 'includes/EditPage.php', 'EmailNotification' => 'includes/UserMailer.php', - 'EnhancedChangesList' => 'includes/ChangesList.php', 'ErrorPageError' => 'includes/Exception.php', 'ExplodeIterator' => 'includes/StringUtils.php', - 'ExternalEdit' => 'includes/ExternalEdit.php', - 'ExternalStore' => 'includes/externalstore/ExternalStore.php', - 'ExternalStoreDB' => 'includes/externalstore/ExternalStoreDB.php', - 'ExternalStoreHttp' => 'includes/externalstore/ExternalStoreHttp.php', - 'ExternalStoreMedium' => 'includes/externalstore/ExternalStoreMedium.php', - 'ExternalStoreMwstore' => 'includes/externalstore/ExternalStoreMwstore.php', - 'ExternalUser' => 'includes/ExternalUser.php', 'FakeTitle' => 'includes/FakeTitle.php', 'Fallback' => 'includes/Fallback.php', 'FatalError' => 'includes/Exception.php', @@ -111,22 +102,27 @@ $wgAutoloadLocalClasses = array( 'FormOptions' => 'includes/FormOptions.php', 'FormSpecialPage' => 'includes/SpecialPage.php', 'GitInfo' => 'includes/GitInfo.php', + 'HashRing' => 'includes/HashRing.php', 'HashtableReplacer' => 'includes/StringUtils.php', 'HistoryBlob' => 'includes/HistoryBlob.php', 'HistoryBlobCurStub' => 'includes/HistoryBlob.php', 'HistoryBlobStub' => 'includes/HistoryBlob.php', 'Hooks' => 'includes/Hooks.php', 'Html' => 'includes/Html.php', + 'HtmlFormatter' => 'includes/HtmlFormatter.php', 'HTMLApiField' => 'includes/HTMLForm.php', + 'HTMLButtonField' => 'includes/HTMLForm.php', 'HTMLCheckField' => 'includes/HTMLForm.php', 'HTMLCheckMatrix' => 'includes/HTMLForm.php', 'HTMLEditTools' => 'includes/HTMLForm.php', 'HTMLFloatField' => 'includes/HTMLForm.php', 'HTMLForm' => 'includes/HTMLForm.php', 'HTMLFormField' => 'includes/HTMLForm.php', + 'HTMLFormFieldRequiredOptionsException' => 'includes/HTMLForm.php', 'HTMLHiddenField' => 'includes/HTMLForm.php', 'HTMLInfoField' => 'includes/HTMLForm.php', 'HTMLIntField' => 'includes/HTMLForm.php', + 'HTMLNestedFilterable' => 'includes/HTMLForm.php', 'HTMLMultiSelectField' => 'includes/HTMLForm.php', 'HTMLRadioField' => 'includes/HTMLForm.php', 'HTMLSelectAndOtherField' => 'includes/HTMLForm.php', @@ -140,7 +136,6 @@ $wgAutoloadLocalClasses = array( 'ICacheHelper' => 'includes/CacheHelper.php', 'IcuCollation' => 'includes/Collation.php', 'IdentityCollation' => 'includes/Collation.php', - 'ImageGallery' => 'includes/ImageGallery.php', 'ImageHistoryList' => 'includes/ImagePage.php', 'ImageHistoryPseudoPager' => 'includes/ImagePage.php', 'ImagePage' => 'includes/ImagePage.php', @@ -156,7 +151,6 @@ $wgAutoloadLocalClasses = array( 'LCStore_CDB' => 'includes/cache/LocalisationCache.php', 'LCStore_DB' => 'includes/cache/LocalisationCache.php', 'LCStore_Null' => 'includes/cache/LocalisationCache.php', - 'LegacyTemplate' => 'includes/SkinLegacy.php', 'License' => 'includes/Licenses.php', 'Licenses' => 'includes/Licenses.php', 'Linker' => 'includes/Linker.php', @@ -174,6 +168,7 @@ $wgAutoloadLocalClasses = array( 'Message' => 'includes/Message.php', 'MessageBlobStore' => 'includes/MessageBlobStore.php', 'MimeMagic' => 'includes/MimeMagic.php', + 'MWCryptRand' => 'includes/MWCryptRand.php', 'MWException' => 'includes/Exception.php', 'MWExceptionHandler' => 'includes/Exception.php', 'MWFunction' => 'includes/MWFunction.php', @@ -181,7 +176,6 @@ $wgAutoloadLocalClasses = array( 'MWHttpRequest' => 'includes/HttpFunctions.php', 'MWInit' => 'includes/Init.php', 'MWNamespace' => 'includes/Namespace.php', - 'OldChangesList' => 'includes/ChangesList.php', 'OutputPage' => 'includes/OutputPage.php', 'Page' => 'includes/WikiPage.php', 'PageQueryPage' => 'includes/PageQueryPage.php', @@ -194,6 +188,7 @@ $wgAutoloadLocalClasses = array( 'PoolCounter' => 'includes/PoolCounter.php', 'PoolCounter_Stub' => 'includes/PoolCounter.php', 'PoolCounterWork' => 'includes/PoolCounter.php', + 'PoolCounterWorkViaCallback' => 'includes/PoolCounter.php', 'PoolWorkArticleView' => 'includes/WikiPage.php', 'Preferences' => 'includes/Preferences.php', 'PreferencesForm' => 'includes/Preferences.php', @@ -202,10 +197,8 @@ $wgAutoloadLocalClasses = array( 'QueryPage' => 'includes/QueryPage.php', 'QuickTemplate' => 'includes/SkinTemplate.php', 'RawMessage' => 'includes/Message.php', - 'RCCacheEntry' => 'includes/ChangesList.php', 'RdfMetaData' => 'includes/Metadata.php', 'ReadOnlyError' => 'includes/Exception.php', - 'RecentChange' => 'includes/RecentChange.php', 'RedirectSpecialArticle' => 'includes/SpecialPage.php', 'RedirectSpecialPage' => 'includes/SpecialPage.php', 'RegexlikeReplacer' => 'includes/StringUtils.php', @@ -228,7 +221,6 @@ $wgAutoloadLocalClasses = array( 'SiteStatsInit' => 'includes/SiteStats.php', 'SiteStatsUpdate' => 'includes/SiteStats.php', 'Skin' => 'includes/Skin.php', - 'SkinLegacy' => 'includes/SkinLegacy.php', 'SkinTemplate' => 'includes/SkinTemplate.php', 'SpecialCreateAccount' => 'includes/SpecialPage.php', 'SpecialListAdmins' => 'includes/SpecialPage.php', @@ -237,11 +229,13 @@ $wgAutoloadLocalClasses = array( 'SpecialMypage' => 'includes/SpecialPage.php', 'SpecialMytalk' => 'includes/SpecialPage.php', 'SpecialMyuploads' => 'includes/SpecialPage.php', + 'SpecialAllMyUploads' => 'includes/SpecialPage.php', 'SpecialPage' => 'includes/SpecialPage.php', 'SpecialPageFactory' => 'includes/SpecialPageFactory.php', 'SpecialRedirectToSpecial' => 'includes/SpecialPage.php', 'SquidPurgeClient' => 'includes/SquidPurgeClient.php', 'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php', + 'StatCounter' => 'includes/StatCounter.php', 'Status' => 'includes/Status.php', 'StreamFile' => 'includes/StreamFile.php', 'StringUtils' => 'includes/StringUtils.php', @@ -415,6 +409,7 @@ $wgAutoloadLocalClasses = array( 'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php', 'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php', 'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.php', + 'ApiQueryFileRepoInfo' => 'includes/api/ApiQueryFileRepoInfo.php', 'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php', 'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php', 'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php', @@ -457,6 +452,13 @@ $wgAutoloadLocalClasses = array( 'TitleDependency' => 'includes/cache/CacheDependency.php', 'TitleListDependency' => 'includes/cache/CacheDependency.php', + # includes/changes + 'ChangesList' => 'includes/changes/ChangesList.php', + 'EnhancedChangesList' => 'includes/changes/EnhancedChangesList.php', + 'OldChangesList' => 'includes/changes/OldChangesList.php', + 'RCCacheEntry' => 'includes/changes/RCCacheEntry.php', + 'RecentChange' => 'includes/changes/RecentChange.php', + # includes/clientpool 'RedisConnectionPool' => 'includes/clientpool/RedisConnectionPool.php', 'RedisConnRef' => 'includes/clientpool/RedisConnectionPool.php', @@ -473,11 +475,13 @@ $wgAutoloadLocalClasses = array( # includes/db 'Blob' => 'includes/db/DatabaseUtility.php', - 'ChronologyProtector' => 'includes/db/LBFactory.php', + 'ChronologyProtector' => 'includes/db/ChronologyProtector.php', 'CloneDatabase' => 'includes/db/CloneDatabase.php', 'DatabaseBase' => 'includes/db/Database.php', 'DatabaseMssql' => 'includes/db/DatabaseMssql.php', 'DatabaseMysql' => 'includes/db/DatabaseMysql.php', + 'DatabaseMysqlBase' => 'includes/db/DatabaseMysqlBase.php', + 'DatabaseMysqli' => 'includes/db/DatabaseMysqli.php', 'DatabaseOracle' => 'includes/db/DatabaseOracle.php', 'DatabasePostgres' => 'includes/db/DatabasePostgres.php', 'DatabaseSqlite' => 'includes/db/DatabaseSqlite.php', @@ -485,8 +489,10 @@ $wgAutoloadLocalClasses = array( 'DatabaseType' => 'includes/db/Database.php', 'DBAccessError' => 'includes/db/LBFactory.php', 'DBConnectionError' => 'includes/db/DatabaseError.php', + 'DBConnRef' => 'includes/db/LoadBalancer.php', 'DBError' => 'includes/db/DatabaseError.php', 'DBObject' => 'includes/db/DatabaseUtility.php', + 'IDatabase' => 'includes/db/Database.php', 'IORMRow' => 'includes/db/IORMRow.php', 'IORMTable' => 'includes/db/IORMTable.php', 'DBMasterPos' => 'includes/db/DatabaseUtility.php', @@ -507,8 +513,8 @@ $wgAutoloadLocalClasses = array( 'LoadMonitor_Null' => 'includes/db/LoadMonitor.php', 'MssqlField' => 'includes/db/DatabaseMssql.php', 'MssqlResult' => 'includes/db/DatabaseMssql.php', - 'MySQLField' => 'includes/db/DatabaseMysql.php', - 'MySQLMasterPos' => 'includes/db/DatabaseMysql.php', + 'MySQLField' => 'includes/db/DatabaseMysqlBase.php', + 'MySQLMasterPos' => 'includes/db/DatabaseMysqlBase.php', 'ORAField' => 'includes/db/DatabaseOracle.php', 'ORAResult' => 'includes/db/DatabaseOracle.php', 'ORMIterator' => 'includes/db/ORMIterator.php', @@ -543,14 +549,17 @@ $wgAutoloadLocalClasses = array( 'WikiDiff3' => 'includes/diff/WikiDiff3.php', 'WordLevelDiff' => 'includes/diff/DairikiDiff.php', - # includes/extauth - 'ExternalUser_Hardcoded' => 'includes/extauth/Hardcoded.php', - 'ExternalUser_MediaWiki' => 'includes/extauth/MediaWiki.php', - 'ExternalUser_vB' => 'includes/extauth/vB.php', + # includes/externalstore + 'ExternalStore' => 'includes/externalstore/ExternalStore.php', + 'ExternalStoreDB' => 'includes/externalstore/ExternalStoreDB.php', + 'ExternalStoreHttp' => 'includes/externalstore/ExternalStoreHttp.php', + 'ExternalStoreMedium' => 'includes/externalstore/ExternalStoreMedium.php', + 'ExternalStoreMwstore' => 'includes/externalstore/ExternalStoreMwstore.php', # includes/filebackend 'FileBackendGroup' => 'includes/filebackend/FileBackendGroup.php', 'FileBackend' => 'includes/filebackend/FileBackend.php', + 'FileBackendError' => 'includes/filebackend/FileBackend.php', 'FileBackendStore' => 'includes/filebackend/FileBackendStore.php', 'FileBackendStoreShardListIterator' => 'includes/filebackend/FileBackendStore.php', 'FileBackendStoreShardDirIterator' => 'includes/filebackend/FileBackendStore.php', @@ -582,6 +591,7 @@ $wgAutoloadLocalClasses = array( 'QuorumLockManager' => 'includes/filebackend/lockmanager/QuorumLockManager.php', 'MySqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php', 'PostgreSqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php', + 'RedisLockManager' => 'includes/filebackend/lockmanager/RedisLockManager.php', 'NullLockManager' => 'includes/filebackend/lockmanager/LockManager.php', 'FileOp' => 'includes/filebackend/FileOp.php', 'FileOpBatch' => 'includes/filebackend/FileOpBatch.php', @@ -657,11 +667,15 @@ $wgAutoloadLocalClasses = array( # includes/job 'Job' => 'includes/job/Job.php', 'JobQueue' => 'includes/job/JobQueue.php', - 'JobQueueAggregator' => 'includes/job/JobQueueAggregator.php', - 'JobQueueAggregatorMemc' => 'includes/job/JobQueueAggregatorMemc.php', - 'JobQueueAggregatorRedis' => 'includes/job/JobQueueAggregatorRedis.php', + 'JobQueueAggregator' => 'includes/job/aggregator/JobQueueAggregator.php', + 'JobQueueAggregatorMemc' => 'includes/job/aggregator/JobQueueAggregatorMemc.php', + 'JobQueueAggregatorRedis' => 'includes/job/aggregator/JobQueueAggregatorRedis.php', 'JobQueueDB' => 'includes/job/JobQueueDB.php', + 'JobQueueConnectionError' => 'includes/job/JobQueue.php', + 'JobQueueError' => 'includes/job/JobQueue.php', 'JobQueueGroup' => 'includes/job/JobQueueGroup.php', + 'JobQueueFederated' => 'includes/job/JobQueueFederated.php', + 'JobQueueRedis' => 'includes/job/JobQueueRedis.php', # includes/job/jobs 'DoubleRedirectJob' => 'includes/job/jobs/DoubleRedirectJob.php', @@ -678,8 +692,6 @@ $wgAutoloadLocalClasses = array( # includes/json 'FormatJson' => 'includes/json/FormatJson.php', - 'Services_JSON' => 'includes/json/Services_JSON.php', - 'Services_JSON_Error' => 'includes/json/Services_JSON.php', # includes/libs 'CSSJanus' => 'includes/libs/CSSJanus.php', @@ -697,9 +709,16 @@ $wgAutoloadLocalClasses = array( 'JSToken' => 'includes/libs/jsminplus.php', 'JSTokenizer' => 'includes/libs/jsminplus.php', + # includes/libs/lessphp + 'lessc' => 'includes/libs/lessc.inc.php', + 'lessc_parser' => 'includes/libs/lessc.inc.php', + 'lessc_formatter_classic' => 'includes/libs/lessc.inc.php', + 'lessc_formatter_compressed' => 'includes/libs/lessc.inc.php', + 'lessc_formatter_lessjs' => 'includes/libs/lessc.inc.php', + # includes/logging 'DatabaseLogEntry' => 'includes/logging/LogEntry.php', - 'DeleteLogFormatter' => 'includes/logging/LogFormatter.php', + 'DeleteLogFormatter' => 'includes/logging/DeleteLogFormatter.php', 'LegacyLogFormatter' => 'includes/logging/LogFormatter.php', 'LogEntry' => 'includes/logging/LogEntry.php', 'LogEventsList' => 'includes/logging/LogEventsList.php', @@ -708,12 +727,22 @@ $wgAutoloadLocalClasses = array( 'LogPage' => 'includes/logging/LogPage.php', 'LogPager' => 'includes/logging/LogPager.php', 'ManualLogEntry' => 'includes/logging/LogEntry.php', - 'MoveLogFormatter' => 'includes/logging/LogFormatter.php', - 'NewUsersLogFormatter' => 'includes/logging/LogFormatter.php', + 'MoveLogFormatter' => 'includes/logging/MoveLogFormatter.php', + 'NewUsersLogFormatter' => 'includes/logging/NewUsersLogFormatter.php', 'PatrolLog' => 'includes/logging/PatrolLog.php', - 'PatrolLogFormatter' => 'includes/logging/LogFormatter.php', + 'PatrolLogFormatter' => 'includes/logging/PatrolLogFormatter.php', 'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php', - 'RightsLogFormatter' => 'includes/logging/LogFormatter.php', + 'RightsLogFormatter' => 'includes/logging/RightsLogFormatter.php', + + # Image gallery + + 'ImageGallery' => 'includes/gallery/TraditionalImageGallery.php', + 'ImageGalleryBase' => 'includes/gallery/ImageGalleryBase.php', + 'NolinesImageGallery' => 'includes/gallery/NolinesImageGallery.php', + 'TraditionalImageGallery' => 'includes/gallery/TraditionalImageGallery.php', + 'PackedImageGallery' => 'includes/gallery/PackedImageGallery.php', + 'PackedHoverImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php', + 'PackedOverlayImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php', # includes/media 'BitmapHandler' => 'includes/media/Bitmap.php', @@ -775,12 +804,10 @@ $wgAutoloadLocalClasses = array( # includes/parser 'CacheTime' => 'includes/parser/CacheTime.php', - 'CoreLinkFunctions' => 'includes/parser/CoreLinkFunctions.php', 'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php', 'CoreTagHooks' => 'includes/parser/CoreTagHooks.php', 'DateFormatter' => 'includes/parser/DateFormatter.php', 'LinkHolderArray' => 'includes/parser/LinkHolderArray.php', - 'LinkMarkerReplacer' => 'includes/parser/Parser_LinkHooks.php', 'MWTidy' => 'includes/parser/Tidy.php', 'MWTidyWrapper' => 'includes/parser/Tidy.php', 'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php', @@ -808,7 +835,6 @@ $wgAutoloadLocalClasses = array( 'ParserOptions' => 'includes/parser/ParserOptions.php', 'ParserOutput' => 'includes/parser/ParserOutput.php', 'Parser_DiffTest' => 'includes/parser/Parser_DiffTest.php', - 'Parser_LinkHooks' => 'includes/parser/Parser_LinkHooks.php', 'Preprocessor' => 'includes/parser/Preprocessor.php', 'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php', 'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php', @@ -821,12 +847,22 @@ $wgAutoloadLocalClasses = array( 'ProfilerSimpleTrace' => 'includes/profiler/ProfilerSimpleTrace.php', 'ProfilerSimpleUDP' => 'includes/profiler/ProfilerSimpleUDP.php', 'ProfilerStub' => 'includes/profiler/ProfilerStub.php', + 'ProfileSection' => 'includes/profiler/Profiler.php', + + # includes/rcfeed + 'RCFeedEngine' => 'includes/rcfeed/RCFeedEngine.php', + 'RedisPubSubFeedEngine' => 'includes/rcfeed/RedisPubSubFeedEngine.php', + 'UDPRCFeedEngine' => 'includes/rcfeed/UDPRCFeedEngine.php', + 'RCFeedFormatter' => 'includes/rcfeed/RCFeedFormatter.php', + 'IRCColourfulRCFeedFormatter' => 'includes/rcfeed/IRCColourfulRCFeedFormatter.php', + 'JSONRCFeedFormatter' => 'includes/rcfeed/JSONRCFeedFormatter.php', # includes/resourceloader 'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php', 'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php', 'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php', 'ResourceLoaderFilePageModule' => 'includes/resourceloader/ResourceLoaderFilePageModule.php', + 'ResourceLoaderLESSFunctions' => 'includes/resourceloader/ResourceLoaderLESSFunctions.php', 'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php', 'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php', 'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php', @@ -874,7 +910,6 @@ $wgAutoloadLocalClasses = array( 'SearchResultTooMany' => 'includes/search/SearchEngine.php', 'SearchSqlite' => 'includes/search/SearchSqlite.php', 'SearchUpdate' => 'includes/search/SearchUpdate.php', - 'SearchUpdateMyISAM' => 'includes/search/SearchUpdate.php', 'SqliteSearchResultSet' => 'includes/search/SearchSqlite.php', 'SqlSearchResultSet' => 'includes/search/SearchEngine.php', @@ -899,7 +934,6 @@ $wgAutoloadLocalClasses = array( 'DeadendPagesPage' => 'includes/specials/SpecialDeadendpages.php', 'DeletedContribsPager' => 'includes/specials/SpecialDeletedContributions.php', 'DeletedContributionsPage' => 'includes/specials/SpecialDeletedContributions.php', - 'DisambiguationsPage' => 'includes/specials/SpecialDisambiguations.php', 'DoubleRedirectsPage' => 'includes/specials/SpecialDoubleRedirects.php', 'EditWatchlistCheckboxSeriesField' => 'includes/specials/SpecialEditWatchlist.php', 'EditWatchlistNormalHTMLForm' => 'includes/specials/SpecialEditWatchlist.php', @@ -940,7 +974,6 @@ $wgAutoloadLocalClasses = array( 'SpecialBlankpage' => 'includes/specials/SpecialBlankpage.php', 'SpecialBlock' => 'includes/specials/SpecialBlock.php', 'SpecialBlockList' => 'includes/specials/SpecialBlockList.php', - 'SpecialBlockme' => 'includes/specials/SpecialBlockme.php', 'SpecialBookSources' => 'includes/specials/SpecialBooksources.php', 'SpecialCachedPage' => 'includes/specials/SpecialCachedPage.php', 'SpecialCategories' => 'includes/specials/SpecialCategories.php', @@ -969,9 +1002,12 @@ $wgAutoloadLocalClasses = array( 'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php', 'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php', 'SpecialProtectedtitles' => 'includes/specials/SpecialProtectedtitles.php', + 'SpecialRandomInCategory' => 'includes/specials/SpecialRandomInCategory.php', 'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php', 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php', 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php', + 'SpecialRedirect' => 'includes/specials/SpecialRedirect.php', + 'SpecialResetTokens' => 'includes/specials/SpecialResetTokens.php', 'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php', 'SpecialSearch' => 'includes/specials/SpecialSearch.php', 'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php', @@ -1021,6 +1057,7 @@ $wgAutoloadLocalClasses = array( 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php', 'UploadStash' => 'includes/upload/UploadStash.php', 'UploadStashBadPathException' => 'includes/upload/UploadStash.php', + 'UploadStashException' => 'includes/upload/UploadStash.php', 'UploadStashFile' => 'includes/upload/UploadStash.php', 'UploadStashFileException' => 'includes/upload/UploadStash.php', 'UploadStashFileNotFoundException' => 'includes/upload/UploadStash.php', @@ -1091,17 +1128,10 @@ $wgAutoloadLocalClasses = array( 'CologneBlueTemplate' => 'skins/CologneBlue.php', 'ModernTemplate' => 'skins/Modern.php', 'MonoBookTemplate' => 'skins/MonoBook.php', - 'NostalgiaTemplate' => 'skins/Nostalgia.php', - 'SkinChick' => 'skins/Chick.php', 'SkinCologneBlue' => 'skins/CologneBlue.php', 'SkinModern' => 'skins/Modern.php', 'SkinMonoBook' => 'skins/MonoBook.php', - 'SkinMySkin' => 'skins/MySkin.php', - 'SkinNostalgia' => 'skins/Nostalgia.php', - 'SkinSimple' => 'skins/Simple.php', - 'SkinStandard' => 'skins/Standard.php', 'SkinVector' => 'skins/Vector.php', - 'StandardTemplate' => 'skins/Standard.php', 'VectorTemplate' => 'skins/Vector.php', ); @@ -1117,12 +1147,13 @@ class AutoLoader { static function autoload( $className ) { global $wgAutoloadClasses, $wgAutoloadLocalClasses; - // Workaround for PHP bug <https://bugs.php.net/bug.php?id=49143> (5.3.2. is broken, it's fixed in 5.3.6). - // Strip leading backslashes from class names. When namespaces are used, leading backslashes are used to indicate - // the top-level namespace, e.g. \foo\Bar. When used like this in the code, the leading backslash isn't passed to - // the auto-loader ($className would be 'foo\Bar'). However, if a class is accessed using a string instead of a - // class literal (e.g. $class = '\foo\Bar'; new $class()), then some versions of PHP do not strip the leading - // backlash in this case, causing autoloading to fail. + // Workaround for PHP bug <https://bugs.php.net/bug.php?id=49143> (5.3.2. is broken, it's + // fixed in 5.3.6). Strip leading backslashes from class names. When namespaces are used, + // leading backslashes are used to indicate the top-level namespace, e.g. \foo\Bar. When + // used like this in the code, the leading backslash isn't passed to the auto-loader + // ($className would be 'foo\Bar'). However, if a class is accessed using a string instead + // of a class literal (e.g. $class = '\foo\Bar'; new $class()), then some versions of PHP + // do not strip the leading backlash in this case, causing autoloading to fail. $className = ltrim( $className, '\\' ); if ( isset( $wgAutoloadLocalClasses[$className] ) ) { @@ -1157,7 +1188,7 @@ class AutoLoader { $filename = "$IP/$filename"; } - require( $filename ); + require $filename; return true; } @@ -1175,12 +1206,4 @@ class AutoLoader { } } -if ( function_exists( 'spl_autoload_register' ) ) { - spl_autoload_register( array( 'AutoLoader', 'autoload' ) ); -} else { - function __autoload( $class ) { - AutoLoader::autoload( $class ); - } - - ini_set( 'unserialize_callback_func', '__autoload' ); -} +spl_autoload_register( array( 'AutoLoader', 'autoload' ) ); diff --git a/includes/Autopromote.php b/includes/Autopromote.php index 604b9248..170d7abf 100644 --- a/includes/Autopromote.php +++ b/includes/Autopromote.php @@ -126,7 +126,8 @@ class Autopromote { return false; } elseif ( $cond[0] == '^' ) { // XOR (exactly one cond passes) if ( count( $cond ) > 3 ) { - wfWarn( 'recCheckCondition() given XOR ("^") condition on three or more conditions. Check your $wgAutopromote and $wgAutopromoteOnce settings.' ); + wfWarn( 'recCheckCondition() given XOR ("^") condition on three or more conditions.' . + ' Check your $wgAutopromote and $wgAutopromoteOnce settings.' ); } return self::recCheckCondition( $cond[1], $user ) xor self::recCheckCondition( $cond[2], $user ); @@ -165,7 +166,7 @@ class Autopromote { return false; } - switch( $cond[0] ) { + switch ( $cond[0] ) { case APCOND_EMAILCONFIRMED: if ( Sanitizer::validateEmail( $user->getEmail() ) ) { if ( $wgEmailAuthentication ) { diff --git a/includes/Block.php b/includes/Block.php index 7ee36ce9..34b89e73 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -65,11 +65,11 @@ class Block { $timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0, $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = '' ) { - if( $timestamp === 0 ) { + if ( $timestamp === 0 ) { $timestamp = wfTimestampNow(); } - if( count( func_get_args() ) > 0 ) { + if ( count( func_get_args() ) > 0 ) { # Soon... :D # wfDeprecated( __METHOD__ . " with arguments" ); } @@ -206,16 +206,16 @@ class Block { */ public function load( $address = '', $user = 0 ) { wfDeprecated( __METHOD__, '1.18' ); - if( $user ) { + if ( $user ) { $username = User::whoIs( $user ); $block = self::newFromTarget( $username, $address ); } else { $block = self::newFromTarget( null, $address ); } - if( $block instanceof Block ) { + if ( $block instanceof Block ) { # This is mildly evil, but hey, it's B/C :D - foreach( $block as $variable => $value ) { + foreach ( $block as $variable => $value ) { $this->$variable = $value; } return true; @@ -237,7 +237,7 @@ class Block { protected function newLoad( $vagueTarget = null ) { $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE ); - if( $this->type !== null ) { + if ( $this->type !== null ) { $conds = array( 'ipb_address' => array( (string)$this->target ), ); @@ -247,9 +247,9 @@ class Block { # Be aware that the != '' check is explicit, since empty values will be # passed by some callers (bug 29116) - if( $vagueTarget != '' ) { + if ( $vagueTarget != '' ) { list( $target, $type ) = self::parseTarget( $vagueTarget ); - switch( $type ) { + switch ( $type ) { case self::TYPE_USER: # Slightly weird, but who are we to argue? $conds['ipb_address'][] = (string)$target; @@ -285,20 +285,20 @@ class Block { # This is begging for $this = $bestBlock, but that's not allowed in PHP :( $bestBlockPreventsEdit = null; - foreach( $res as $row ) { + foreach ( $res as $row ) { $block = self::newFromRow( $row ); # Don't use expired blocks - if( $block->deleteIfExpired() ) { + if ( $block->deleteIfExpired() ) { continue; } # Don't use anon only blocks on users - if( $this->type == self::TYPE_USER && !$block->isHardblock() ) { + if ( $this->type == self::TYPE_USER && !$block->isHardblock() ) { continue; } - if( $block->getType() == self::TYPE_RANGE ) { + if ( $block->getType() == self::TYPE_RANGE ) { # This is the number of bits that are allowed to vary in the block, give # or take some floating point errors $end = wfBaseconvert( $block->getRangeEnd(), 16, 10 ); @@ -313,14 +313,14 @@ class Block { $score = $block->getType(); } - if( $score < $bestBlockScore ) { + if ( $score < $bestBlockScore ) { $bestBlockScore = $score; $bestRow = $row; $bestBlockPreventsEdit = $block->prevents( 'edit' ); } } - if( $bestRow !== null ) { + if ( $bestRow !== null ) { $this->initFromRow( $bestRow ); $this->prevents( 'edit', $bestBlockPreventsEdit ); return true; @@ -511,7 +511,7 @@ class Block { * @return Array */ protected function getDatabaseArray( $db = null ) { - if( !$db ) { + if ( !$db ) { $db = wfGetDB( DB_SLAVE ); } $expiry = $db->encodeExpiry( $this->mExpiry ); @@ -579,7 +579,7 @@ class Block { global $wgPutIPinRC; // No IPs are in recentchanges table, so nothing to select - if( !$wgPutIPinRC ) { + if ( !$wgPutIPinRC ) { return; } @@ -601,7 +601,9 @@ class Block { foreach ( $res as $row ) { if ( $row->rc_ip ) { $id = $block->doAutoblock( $row->rc_ip ); - if ( $id ) $blockIds[] = $id; + if ( $id ) { + $blockIds[] = $id; + } } } } @@ -681,7 +683,7 @@ class Block { if ( $ipblock ) { # Check if the block is an autoblock and would exceed the user block # if renewed. If so, do nothing, otherwise prolong the block time... - if ( $ipblock->mAuto && // @TODO: why not compare $ipblock->mExpiry? + if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry? $this->mExpiry > Block::getAutoblockExpiry( $ipblock->mTimestamp ) ) { # Reset block timestamp to now and its expiry to @@ -793,7 +795,7 @@ class Block { * @return String IP in Hex form */ public function getRangeStart() { - switch( $this->type ) { + switch ( $this->type ) { case self::TYPE_USER: return ''; case self::TYPE_IP: @@ -801,17 +803,18 @@ class Block { case self::TYPE_RANGE: list( $start, /*...*/ ) = IP::parseRange( $this->target ); return $start; - default: throw new MWException( "Block with invalid type" ); + default: + throw new MWException( "Block with invalid type" ); } } /** - * Get the IP address at the start of the range in Hex form + * Get the IP address at the end of the range in Hex form * @throws MWException * @return String IP in Hex form */ public function getRangeEnd() { - switch( $this->type ) { + switch ( $this->type ) { case self::TYPE_USER: return ''; case self::TYPE_IP: @@ -819,7 +822,8 @@ class Block { case self::TYPE_RANGE: list( /*...*/, $end ) = IP::parseRange( $this->target ); return $end; - default: throw new MWException( "Block with invalid type" ); + default: + throw new MWException( "Block with invalid type" ); } } @@ -907,7 +911,7 @@ class Block { * @return Bool */ public function prevents( $action, $x = null ) { - switch( $action ) { + switch ( $action ) { case 'edit': # For now... <evil laugh> return true; @@ -997,11 +1001,16 @@ class Block { * Purge expired blocks from the ipblocks table */ public static function purgeExpired() { - if ( !wfReadOnly() ) { - $dbw = wfGetDB( DB_MASTER ); - $dbw->delete( 'ipblocks', - array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ ); + if ( wfReadOnly() ) { + return; } + + $method = __METHOD__; + $dbw = wfGetDB( DB_MASTER ); + $dbw->onTransactionIdle( function() use ( $dbw, $method ) { + $dbw->delete( 'ipblocks', + array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), $method ); + } ); } /** @@ -1050,30 +1059,216 @@ class Block { public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) { list( $target, $type ) = self::parseTarget( $specificTarget ); - if( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ) { + if ( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ) { return Block::newFromID( $target ); - } elseif( $target === null && $vagueTarget == '' ) { + } elseif ( $target === null && $vagueTarget == '' ) { # We're not going to find anything useful here # Be aware that the == '' check is explicit, since empty values will be # passed by some callers (bug 29116) return null; - } elseif( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) ) { + } elseif ( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) ) { $block = new Block(); $block->fromMaster( $fromMaster ); - if( $type !== null ) { + if ( $type !== null ) { $block->setTarget( $target ); } - if( $block->newLoad( $vagueTarget ) ) { + if ( $block->newLoad( $vagueTarget ) ) { return $block; } } return null; } + + /** + * Get all blocks that match any IP from an array of IP addresses + * + * @param Array $ipChain list of IPs (strings), usually retrieved from the + * X-Forwarded-For header of the request + * @param Bool $isAnon Exclude anonymous-only blocks if false + * @param Bool $fromMaster Whether to query the master or slave database + * @return Array of Blocks + * @since 1.22 + */ + public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) { + if ( !count( $ipChain ) ) { + return array(); + } + + wfProfileIn( __METHOD__ ); + $conds = array(); + foreach ( array_unique( $ipChain ) as $ipaddr ) { + # Discard invalid IP addresses. Since XFF can be spoofed and we do not + # necessarily trust the header given to us, make sure that we are only + # checking for blocks on well-formatted IP addresses (IPv4 and IPv6). + # Do not treat private IP spaces as special as it may be desirable for wikis + # to block those IP ranges in order to stop misbehaving proxies that spoof XFF. + if ( !IP::isValid( $ipaddr ) ) { + continue; + } + # Don't check trusted IPs (includes local squids which will be in every request) + if ( wfIsTrustedProxy( $ipaddr ) ) { + continue; + } + # Check both the original IP (to check against single blocks), as well as build + # the clause to check for rangeblocks for the given IP. + $conds['ipb_address'][] = $ipaddr; + $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) ); + } + + if ( !count( $conds ) ) { + wfProfileOut( __METHOD__ ); + return array(); + } + + if ( $fromMaster ) { + $db = wfGetDB( DB_MASTER ); + } else { + $db = wfGetDB( DB_SLAVE ); + } + $conds = $db->makeList( $conds, LIST_OR ); + if ( !$isAnon ) { + $conds = array( $conds, 'ipb_anon_only' => 0 ); + } + $selectFields = array_merge( + array( 'ipb_range_start', 'ipb_range_end' ), + Block::selectFields() + ); + $rows = $db->select( 'ipblocks', + $selectFields, + $conds, + __METHOD__ + ); + + $blocks = array(); + foreach ( $rows as $row ) { + $block = self::newFromRow( $row ); + if ( !$block->deleteIfExpired() ) { + $blocks[] = $block; + } + } + + wfProfileOut( __METHOD__ ); + return $blocks; + } + + /** + * From a list of multiple blocks, find the most exact and strongest Block. + * The logic for finding the "best" block is: + * - Blocks that match the block's target IP are preferred over ones in a range + * - Hardblocks are chosen over softblocks that prevent account creation + * - Softblocks that prevent account creation are chosen over other softblocks + * - Other softblocks are chosen over autoblocks + * - If there are multiple exact or range blocks at the same level, the one chosen + * is random + + * @param Array $ipChain list of IPs (strings). This is used to determine how "close" + * a block is to the server, and if a block matches exactly, or is in a range. + * The order is furthest from the server to nearest e.g., (Browser, proxy1, proxy2, + * local-squid, ...) + * @param Array $block Array of blocks + * @return Block|null the "best" block from the list + */ + public static function chooseBlock( array $blocks, array $ipChain ) { + if ( !count( $blocks ) ) { + return null; + } elseif ( count( $blocks ) == 1 ) { + return $blocks[0]; + } + + wfProfileIn( __METHOD__ ); + + // Sort hard blocks before soft ones and secondarily sort blocks + // that disable account creation before those that don't. + usort( $blocks, function( Block $a, Block $b ) { + $aWeight = (int)$a->isHardblock() . (int)$a->prevents( 'createaccount' ); + $bWeight = (int)$b->isHardblock() . (int)$b->prevents( 'createaccount' ); + return strcmp( $bWeight, $aWeight ); // highest weight first + } ); + + $blocksListExact = array( + 'hard' => false, + 'disable_create' => false, + 'other' => false, + 'auto' => false + ); + $blocksListRange = array( + 'hard' => false, + 'disable_create' => false, + 'other' => false, + 'auto' => false + ); + $ipChain = array_reverse( $ipChain ); + + foreach ( $blocks as $block ) { + // Stop searching if we have already have a "better" block. This + // is why the order of the blocks matters + if ( !$block->isHardblock() && $blocksListExact['hard'] ) { + break; + } elseif ( !$block->prevents( 'createaccount' ) && $blocksListExact['disable_create'] ) { + break; + } + + foreach ( $ipChain as $checkip ) { + $checkipHex = IP::toHex( $checkip ); + if ( (string)$block->getTarget() === $checkip ) { + if ( $block->isHardblock() ) { + $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block; + } elseif ( $block->prevents( 'createaccount' ) ) { + $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block; + } elseif ( $block->mAuto ) { + $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block; + } else { + $blocksListExact['other'] = $blocksListExact['other'] ?: $block; + } + // We found closest exact match in the ip list, so go to the next Block + break; + } elseif ( array_filter( $blocksListExact ) == array() + && $block->getRangeStart() <= $checkipHex + && $block->getRangeEnd() >= $checkipHex + ) { + if ( $block->isHardblock() ) { + $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block; + } elseif ( $block->prevents( 'createaccount' ) ) { + $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block; + } elseif ( $block->mAuto ) { + $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block; + } else { + $blocksListRange['other'] = $blocksListRange['other'] ?: $block; + } + break; + } + } + } + + if ( array_filter( $blocksListExact ) == array() ) { + $blocksList = &$blocksListRange; + } else { + $blocksList = &$blocksListExact; + } + + $chosenBlock = null; + if ( $blocksList['hard'] ) { + $chosenBlock = $blocksList['hard']; + } elseif ( $blocksList['disable_create'] ) { + $chosenBlock = $blocksList['disable_create']; + } elseif ( $blocksList['other'] ) { + $chosenBlock = $blocksList['other']; + } elseif ( $blocksList['auto'] ) { + $chosenBlock = $blocksList['auto']; + } else { + wfProfileOut( __METHOD__ ); + throw new MWException( "Proxy block found, but couldn't be classified." ); + } + + wfProfileOut( __METHOD__ ); + return $chosenBlock; + } + /** * From an existing Block, get the target and the type of target. * Note that, except for null, it is always safe to treat the target @@ -1085,13 +1280,13 @@ class Block { */ public static function parseTarget( $target ) { # We may have been through this before - if( $target instanceof User ) { - if( IP::isValid( $target->getName() ) ) { + if ( $target instanceof User ) { + if ( IP::isValid( $target->getName() ) ) { return array( $target, self::TYPE_IP ); } else { return array( $target, self::TYPE_USER ); } - } elseif( $target === null ) { + } elseif ( $target === null ) { return array( null, null ); } @@ -1112,7 +1307,7 @@ class Block { # Consider the possibility that this is not a username at all # but actually an old subpage (bug #29797) - if( strpos( $target, '/' ) !== false ) { + if ( strpos( $target, '/' ) !== false ) { # An old subpage, drill down to the user behind it $parts = explode( '/', $target ); $target = $parts[0]; @@ -1198,4 +1393,43 @@ class Block { public function setBlocker( $user ) { $this->blocker = $user; } + + /** + * Get the key and parameters for the corresponding error message. + * + * @since 1.22 + * @param IContextSource $context + * @return array + */ + public function getPermissionsError( IContextSource $context ) { + $blocker = $this->getBlocker(); + if ( $blocker instanceof User ) { // local user + $blockerUserpage = $blocker->getUserPage(); + $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]"; + } else { // foreign user + $link = $blocker; + } + + $reason = $this->mReason; + if ( $reason == '' ) { + $reason = $context->msg( 'blockednoreason' )->text(); + } + + /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked. + * This could be a username, an IP range, or a single IP. */ + $intended = $this->getTarget(); + + $lang = $context->getLanguage(); + return array( + $this->mAuto ? 'autoblockedtext' : 'blockedtext', + $link, + $reason, + $context->getRequest()->getIP(), + $this->getByName(), + $this->getId(), + $lang->formatExpiry( $this->mExpiry ), + (string)$intended, + $lang->timeanddate( wfTimestamp( TS_MW, $this->mTimestamp ), true ), + ); + } } diff --git a/includes/CallableUpdate.php b/includes/CallableUpdate.php new file mode 100644 index 00000000..6eb55413 --- /dev/null +++ b/includes/CallableUpdate.php @@ -0,0 +1,30 @@ +<?php + +/** + * Deferrable Update for closure/callback + */ +class MWCallableUpdate implements DeferrableUpdate { + + /** + * @var closure/callabck + */ + private $callback; + + /** + * @param callable $callback + */ + public function __construct( $callback ) { + if ( !is_callable( $callback ) ) { + throw new MWException( 'Not a valid callback/closure!' ); + } + $this->callback = $callback; + } + + /** + * Run the update + */ + public function doUpdate() { + call_user_func( $this->callback ); + } + +} diff --git a/includes/Category.php b/includes/Category.php index 868d6c46..126b8fee 100644 --- a/includes/Category.php +++ b/includes/Category.php @@ -40,7 +40,8 @@ class Category { /** Counts of membership (cat_pages, cat_subcats, cat_files) */ private $mPages = null, $mSubcats = null, $mFiles = null; - private function __construct() { } + private function __construct() { + } /** * Set up all member variables using a database query. diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php index 43ab4dbd..ba71aa01 100644 --- a/includes/CategoryPage.php +++ b/includes/CategoryPage.php @@ -106,7 +106,13 @@ class CategoryPage extends Article { unset( $reqArray["from"] ); unset( $reqArray["to"] ); - $viewer = new $this->mCategoryViewerClass( $this->getContext()->getTitle(), $this->getContext(), $from, $until, $reqArray ); + $viewer = new $this->mCategoryViewerClass( + $this->getContext()->getTitle(), + $this->getContext(), + $from, + $until, + $reqArray + ); $this->getContext()->getOutput()->addHTML( $viewer->getHTML() ); } } diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index 970adb53..55d9c1e5 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -141,8 +141,17 @@ class CategoryViewer extends ContextSource { $this->children = array(); $this->children_start_char = array(); if ( $this->showGallery ) { - $this->gallery = new ImageGallery(); + // Note that null for mode is taken to mean use default. + $mode = $this->getRequest()->getVal( 'gallerymode', null ); + try { + $this->gallery = ImageGalleryBase::factory( $mode ); + } catch ( MWException $e ) { + // User specified something invalid, fallback to default. + $this->gallery = ImageGalleryBase::factory(); + } + $this->gallery->setHideBadImages(); + $this->gallery->setContext( $this->getContext() ); } else { $this->imgsNoGallery = array(); $this->imgsNoGallery_start_char = array(); @@ -526,7 +535,10 @@ class CategoryViewer extends ContextSource { $first = true; foreach ( $colContents as $char => $articles ) { - $ret .= '<h3>' . htmlspecialchars( $char ); + # Change space to non-breaking space to keep headers aligned + $h3char = $char === ' ' ? ' ' : htmlspecialchars( $char ); + + $ret .= '<h3>' . $h3char; if ( $first && $char === $prevchar ) { # We're continuing a previous chunk at the top of a new # column, so add " cont." after the letter. @@ -645,28 +657,23 @@ class CategoryViewer extends ContextSource { * returned? This function says what. Each type is considered independently * of the other types. * - * Note for grepping: uses the messages category-article-count, - * category-article-count-limited, category-subcat-count, - * category-subcat-count-limited, category-file-count, - * category-file-count-limited. - * * @param int $rescnt The number of items returned by our database query. * @param int $dbcnt The number of items according to the category table. * @param string $type 'subcat', 'article', or 'file' - * @return String: A message giving the number of items, to output to HTML. + * @return string: A message giving the number of items, to output to HTML. */ private function getCountMessage( $rescnt, $dbcnt, $type ) { - # There are three cases: - # 1) The category table figure seems sane. It might be wrong, but - # we can't do anything about it if we don't recalculate it on ev- - # ery category view. - # 2) The category table figure isn't sane, like it's smaller than the - # number of actual results, *but* the number of results is less - # than $this->limit and there's no offset. In this case we still - # know the right figure. - # 3) We have no idea. - - # Check if there's a "from" or "until" for anything + // There are three cases: + // 1) The category table figure seems sane. It might be wrong, but + // we can't do anything about it if we don't recalculate it on ev- + // ery category view. + // 2) The category table figure isn't sane, like it's smaller than the + // number of actual results, *but* the number of results is less + // than $this->limit and there's no offset. In this case we still + // know the right figure. + // 3) We have no idea. + + // Check if there's a "from" or "until" for anything // This is a little ugly, but we seem to use different names // for the paging types then for the messages. @@ -686,19 +693,22 @@ class CategoryViewer extends ContextSource { if ( $dbcnt == $rescnt || ( ( $rescnt == $this->limit || $fromOrUntil ) && $dbcnt > $rescnt ) ) { - # Case 1: seems sane. + // Case 1: seems sane. $totalcnt = $dbcnt; } elseif ( $rescnt < $this->limit && !$fromOrUntil ) { - # Case 2: not sane, but salvageable. Use the number of results. - # Since there are fewer than 200, we can also take this opportunity - # to refresh the incorrect category table entry -- which should be - # quick due to the small number of entries. + // Case 2: not sane, but salvageable. Use the number of results. + // Since there are fewer than 200, we can also take this opportunity + // to refresh the incorrect category table entry -- which should be + // quick due to the small number of entries. $totalcnt = $rescnt; $this->cat->refreshCounts(); } else { - # Case 3: hopeless. Don't give a total count at all. + // Case 3: hopeless. Don't give a total count at all. + // Messages: category-subcat-count-limited, category-article-count-limited, + // category-file-count-limited return $this->msg( "category-$type-count-limited" )->numParams( $rescnt )->parseAsBlock(); } + // Messages: category-subcat-count, category-article-count, category-file-count return $this->msg( "category-$type-count" )->numParams( $rescnt, $totalcnt )->parseAsBlock(); } } diff --git a/includes/Cdb.php b/includes/Cdb.php index a142c7cb..81c0afe1 100644 --- a/includes/Cdb.php +++ b/includes/Cdb.php @@ -133,7 +133,7 @@ class CdbReader_DBA { } function close() { - if( isset( $this->handle ) ) { + if ( isset( $this->handle ) ) { dba_close( $this->handle ); } unset( $this->handle ); @@ -164,7 +164,7 @@ class CdbWriter_DBA { } function close() { - if( isset( $this->handle ) ) { + if ( isset( $this->handle ) ) { dba_close( $this->handle ); } if ( wfIsWindows() ) { diff --git a/includes/Cdb_PHP.php b/includes/Cdb_PHP.php index 71b55f87..a38b9a86 100644 --- a/includes/Cdb_PHP.php +++ b/includes/Cdb_PHP.php @@ -73,10 +73,10 @@ class CdbFunctions { public static function hash( $s ) { $h = 5381; for ( $i = 0; $i < strlen( $s ); $i++ ) { - $h5 = ($h << 5) & 0xffffffff; + $h5 = ( $h << 5 ) & 0xffffffff; // Do a 32-bit sum // Inlined here for speed - $sum = ($h & 0x3fffffff) + ($h5 & 0x3fffffff); + $sum = ( $h & 0x3fffffff ) + ( $h5 & 0x3fffffff ); $h = ( ( $sum & 0x40000000 ? 1 : 0 ) @@ -138,7 +138,7 @@ class CdbReader_PHP extends CdbReader { } function close() { - if( isset( $this->handle ) ) { + if ( isset( $this->handle ) ) { fclose( $this->handle ); } unset( $this->handle ); @@ -332,7 +332,7 @@ class CdbWriter_PHP extends CdbWriter { */ public function close() { $this->finish(); - if( isset( $this->handle ) ) { + if ( isset( $this->handle ) ) { fclose( $this->handle ); } if ( wfIsWindows() && file_exists( $this->realFileName ) ) { @@ -411,7 +411,7 @@ class CdbWriter_PHP extends CdbWriter { // Calculate the number of items that will be in each hashtable $counts = array_fill( 0, 256, 0 ); foreach ( $this->hplist as $item ) { - ++ $counts[ 255 & $item['h'] ]; + ++ $counts[255 & $item['h']]; } // Fill in $starts with the *end* indexes @@ -450,9 +450,11 @@ class CdbWriter_PHP extends CdbWriter { $hp = $packedTables[$starts[$i] + $u]; $where = CdbFunctions::unsignedMod( CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len ); - while ( $hashtable[$where]['p'] ) - if ( ++$where == $len ) + while ( $hashtable[$where]['p'] ) { + if ( ++$where == $len ) { $where = 0; + } + } $hashtable[$where] = $hp; } diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php index 3adf58f8..3fc27f9a 100644 --- a/includes/ChangeTags.php +++ b/includes/ChangeTags.php @@ -34,17 +34,18 @@ class ChangeTags { * - classes: Array of strings: CSS classes used in the generated html, one class for each tag * */ - static function formatSummaryRow( $tags, $page ) { + public static function formatSummaryRow( $tags, $page ) { global $wgLang; - if( !$tags ) + if ( !$tags ) { return array( '', array() ); + } $classes = array(); $tags = explode( ',', $tags ); $displayTags = array(); - foreach( $tags as $tag ) { + foreach ( $tags as $tag ) { $displayTags[] = Xml::tags( 'span', array( 'class' => 'mw-tag-marker ' . @@ -53,7 +54,10 @@ class ChangeTags { ); $classes[] = Sanitizer::escapeClass( "mw-tag-$tag" ); } - $markers = wfMessage( 'parentheses' )->rawParams( $wgLang->commaList( $displayTags ) )->text(); + $markers = wfMessage( 'tag-list-wrapper' ) + ->numParams( count( $displayTags ) ) + ->rawParams( $wgLang->commaList( $displayTags ) ) + ->parse(); $markers = Xml::tags( 'span', array( 'class' => 'mw-tag-markers' ), $markers ); return array( $markers, $classes ); @@ -67,7 +71,7 @@ class ChangeTags { * @return String: Short description of the tag from "mediawiki:tag-$tag" if this message exists, * html-escaped version of $tag otherwise */ - static function tagDescription( $tag ) { + public static function tagDescription( $tag ) { $msg = wfMessage( "tag-$tag" ); return $msg->exists() ? $msg->parse() : htmlspecialchars( $tag ); } @@ -86,28 +90,29 @@ class ChangeTags { * * @exception MWException when $rc_id, $rev_id and $log_id are all null */ - static function addTags( $tags, $rc_id = null, $rev_id = null, $log_id = null, $params = null ) { + public static function addTags( $tags, $rc_id = null, $rev_id = null, $log_id = null, $params = null ) { if ( !is_array( $tags ) ) { $tags = array( $tags ); } $tags = array_filter( $tags ); // Make sure we're submitting all tags... - if( !$rc_id && !$rev_id && !$log_id ) { - throw new MWException( "At least one of: RCID, revision ID, and log ID MUST be specified when adding a tag to a change!" ); + if ( !$rc_id && !$rev_id && !$log_id ) { + throw new MWException( 'At least one of: RCID, revision ID, and log ID MUST be ' . + 'specified when adding a tag to a change!' ); } $dbr = wfGetDB( DB_SLAVE ); // Might as well look for rcids and so on. - if( !$rc_id ) { + if ( !$rc_id ) { $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave. - if( $log_id ) { + if ( $log_id ) { $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_logid' => $log_id ), __METHOD__ ); - } elseif( $rev_id ) { + } elseif ( $rev_id ) { $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_this_oldid' => $rev_id ), __METHOD__ ); } - } elseif( !$log_id && !$rev_id ) { + } elseif ( !$log_id && !$rev_id ) { $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave. $log_id = $dbr->selectField( 'recentchanges', 'rc_logid', array( 'rc_id' => $rc_id ), __METHOD__ ); $rev_id = $dbr->selectField( 'recentchanges', 'rc_this_oldid', array( 'rc_id' => $rc_id ), __METHOD__ ); @@ -138,7 +143,7 @@ class ChangeTags { // Insert the tags rows. $tagsRows = array(); - foreach( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally. + foreach ( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally. $tagsRows[] = array_filter( array( 'ct_tag' => $tag, @@ -169,18 +174,18 @@ class ChangeTags { * * @throws MWException When unable to determine appropriate JOIN condition for tagging */ - static function modifyDisplayQuery( &$tables, &$fields, &$conds, + public static function modifyDisplayQuery( &$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag = false ) { global $wgRequest, $wgUseTagFilter; - if( $filter_tag === false ) { + if ( $filter_tag === false ) { $filter_tag = $wgRequest->getVal( 'tagfilter' ); } // Figure out which conditions can be done. if ( in_array( 'recentchanges', $tables ) ) { $join_cond = 'rc_id'; - } elseif( in_array( 'logging', $tables ) ) { + } elseif ( in_array( 'logging', $tables ) ) { $join_cond = 'log_id'; } elseif ( in_array( 'revision', $tables ) ) { $join_cond = 'rev_id'; @@ -193,14 +198,12 @@ class ChangeTags { $join_conds['tag_summary'] = array( 'LEFT JOIN', "ts_$join_cond=$join_cond" ); $fields[] = 'ts_tags'; - if( $wgUseTagFilter && $filter_tag ) { + if ( $wgUseTagFilter && $filter_tag ) { // Somebody wants to filter on a tag. // Add an INNER JOIN on change_tag // FORCE INDEX -- change_tags will almost ALWAYS be the correct query plan. - global $wgOldChangeTagsIndex; - $index = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id'; - $options['USE INDEX'] = array( 'change_tag' => $index ); + $options['USE INDEX'] = array( 'change_tag' => 'change_tag_tag_id' ); unset( $options['FORCE INDEX'] ); $tables[] = 'change_tag'; $join_conds['change_tag'] = array( 'INNER JOIN', "ct_$join_cond=$join_cond" ); @@ -252,7 +255,7 @@ class ChangeTags { * * @return Array of strings: tags */ - static function listDefinedTags() { + public static function listDefinedTags() { // Caching... global $wgMemc; $key = wfMemcKey( 'valid-tags' ); @@ -278,4 +281,34 @@ class ChangeTags { $wgMemc->set( $key, $emptyTags, 300 ); return $emptyTags; } + + /** + * Returns a map of any tags used on the wiki to number of edits + * tagged with them, ordered descending by the hitcount. + * + * @return array Array of string => int + */ + public static function tagUsageStatistics() { + $out = array(); + + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( + 'change_tag', + array( 'ct_tag', 'hitcount' => 'count(*)' ), + array(), + __METHOD__, + array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) + ); + + foreach ( $res as $row ) { + $out[$row->ct_tag] = $row->hitcount; + } + foreach ( self::listDefinedTags() as $tag ) { + if ( !isset( $out[$tag] ) ) { + $out[$tag] = 0; + } + } + + return $out; + } } diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php index 476de5bd..0736c507 100644 --- a/includes/ChangesFeed.php +++ b/includes/ChangesFeed.php @@ -54,7 +54,7 @@ class ChangesFeed { return false; } - if( !array_key_exists( $this->format, $wgFeedClasses ) ) { + if ( !array_key_exists( $this->format, $wgFeedClasses ) ) { // falling back to atom $this->format = 'atom'; } @@ -92,7 +92,7 @@ class ChangesFeed { * gets it quick too. */ $cachedFeed = $this->loadFromCache( $lastmod, $timekey, $key ); - if( is_string( $cachedFeed ) ) { + if ( is_string( $cachedFeed ) ) { wfDebug( "RC: Outputting cached feed\n" ); $feed->httpHeaders(); echo $cachedFeed; @@ -134,7 +134,7 @@ class ChangesFeed { $feedLastmod = $messageMemc->get( $timekey ); - if( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) { + if ( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) { /** * If the cached feed was rendered very recently, we may * go ahead and use it even if there have been edits made @@ -146,7 +146,7 @@ class ChangesFeed { $feedLastmodUnix = wfTimestamp( TS_UNIX, $feedLastmod ); $lastmodUnix = wfTimestamp( TS_UNIX, $lastmod ); - if( $feedAge < $wgFeedCacheTimeout || $feedLastmodUnix > $lastmodUnix) { + if ( $feedAge < $wgFeedCacheTimeout || $feedLastmodUnix > $lastmodUnix ) { wfDebug( "RC: loading feed from cache ($key; $feedLastmod; $lastmod)...\n" ); if ( $feedLastmodUnix < $lastmodUnix ) { $wgOut->setLastModified( $feedLastmod ); // bug 21916 @@ -172,30 +172,32 @@ class ChangesFeed { # Merge adjacent edits by one user $sorted = array(); $n = 0; - foreach( $rows as $obj ) { - if( $n > 0 && + foreach ( $rows as $obj ) { + if ( $n > 0 && $obj->rc_type == RC_EDIT && $obj->rc_namespace >= 0 && - $obj->rc_cur_id == $sorted[$n-1]->rc_cur_id && - $obj->rc_user_text == $sorted[$n-1]->rc_user_text ) { - $sorted[$n-1]->rc_last_oldid = $obj->rc_last_oldid; + $obj->rc_cur_id == $sorted[$n - 1]->rc_cur_id && + $obj->rc_user_text == $sorted[$n - 1]->rc_user_text ) { + $sorted[$n - 1]->rc_last_oldid = $obj->rc_last_oldid; } else { $sorted[$n] = $obj; $n++; } } - foreach( $sorted as $obj ) { + foreach ( $sorted as $obj ) { $title = Title::makeTitle( $obj->rc_namespace, $obj->rc_title ); - $talkpage = MWNamespace::canTalk( $obj->rc_namespace ) ? $title->getTalkPage()->getFullUrl() : ''; + $talkpage = MWNamespace::canTalk( $obj->rc_namespace ) ? $title->getTalkPage()->getFullURL() : ''; // Skip items with deleted content (avoids partially complete/inconsistent output) - if( $obj->rc_deleted ) continue; + if ( $obj->rc_deleted ) { + continue; + } if ( $obj->rc_this_oldid ) { - $url = $title->getFullURL( - 'diff=' . $obj->rc_this_oldid . - '&oldid=' . $obj->rc_last_oldid - ); + $url = $title->getFullURL( array( + 'diff' => $obj->rc_this_oldid, + 'oldid' => $obj->rc_last_oldid, + ) ); } else { // log entry or something like that. $url = $title->getFullURL(); @@ -206,7 +208,8 @@ class ChangesFeed { FeedUtils::formatDiff( $obj ), $url, $obj->rc_timestamp, - ( $obj->rc_deleted & Revision::DELETED_USER ) ? wfMessage( 'rev-deleted-user' )->escaped() : $obj->rc_user_text, + ( $obj->rc_deleted & Revision::DELETED_USER ) + ? wfMessage( 'rev-deleted-user' )->escaped() : $obj->rc_user_text, $talkpage ); $feed->outItem( $item ); diff --git a/includes/ChangesList.php b/includes/ChangesList.php deleted file mode 100644 index 8461001e..00000000 --- a/includes/ChangesList.php +++ /dev/null @@ -1,1304 +0,0 @@ -<?php -/** - * Classes to show lists of changes. - * - * These can be: - * - watchlist - * - related changes - * - recent changes - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - */ - -/** - * @todo document - */ -class RCCacheEntry extends RecentChange { - var $secureName, $link; - var $curlink, $difflink, $lastlink, $usertalklink, $versionlink; - var $userlink, $timestamp, $watched; - - /** - * @param $rc RecentChange - * @return RCCacheEntry - */ - static function newFromParent( $rc ) { - $rc2 = new RCCacheEntry; - $rc2->mAttribs = $rc->mAttribs; - $rc2->mExtra = $rc->mExtra; - return $rc2; - } -} - -/** - * Base class for all changes lists - */ -class ChangesList extends ContextSource { - - /** - * @var Skin - */ - public $skin; - - protected $watchlist = false; - - protected $message; - - /** - * Changeslist constructor - * - * @param $obj Skin or IContextSource - */ - public function __construct( $obj ) { - if ( $obj instanceof IContextSource ) { - $this->setContext( $obj ); - $this->skin = $obj->getSkin(); - } else { - $this->setContext( $obj->getContext() ); - $this->skin = $obj; - } - $this->preCacheMessages(); - } - - /** - * Fetch an appropriate changes list class for the main context - * This first argument used to be an User object. - * - * @deprecated in 1.18; use newFromContext() instead - * @param string|User $unused Unused - * @return ChangesList|EnhancedChangesList|OldChangesList derivative - */ - public static function newFromUser( $unused ) { - wfDeprecated( __METHOD__, '1.18' ); - return self::newFromContext( RequestContext::getMain() ); - } - - /** - * Fetch an appropriate changes list class for the specified context - * Some users might want to use an enhanced list format, for instance - * - * @param $context IContextSource to use - * @return ChangesList|EnhancedChangesList|OldChangesList derivative - */ - public static function newFromContext( IContextSource $context ) { - $user = $context->getUser(); - $sk = $context->getSkin(); - $list = null; - if( wfRunHooks( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) { - $new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) ); - return $new ? new EnhancedChangesList( $context ) : new OldChangesList( $context ); - } else { - return $list; - } - } - - /** - * Sets the list to use a "<li class='watchlist-(namespace)-(page)'>" tag - * @param $value Boolean - */ - public function setWatchlistDivs( $value = true ) { - $this->watchlist = $value; - } - - /** - * As we use the same small set of messages in various methods and that - * they are called often, we call them once and save them in $this->message - */ - private function preCacheMessages() { - if( !isset( $this->message ) ) { - foreach ( explode( ' ', 'cur diff hist last blocklink history ' . - 'semicolon-separator pipe-separator' ) as $msg ) { - $this->message[$msg] = $this->msg( $msg )->escaped(); - } - } - } - - /** - * Returns the appropriate flags for new page, minor change and patrolling - * @param array $flags Associative array of 'flag' => Bool - * @param string $nothing to use for empty space - * @return String - */ - protected function recentChangesFlags( $flags, $nothing = ' ' ) { - $f = ''; - foreach( array( 'newpage', 'minor', 'bot', 'unpatrolled' ) as $flag ) { - $f .= isset( $flags[$flag] ) && $flags[$flag] - ? self::flag( $flag ) - : $nothing; - } - return $f; - } - - /** - * Provide the "<abbr>" element appropriate to a given abbreviated flag, - * namely the flag indicating a new page, a minor edit, a bot edit, or an - * unpatrolled edit. By default in English it will contain "N", "m", "b", - * "!" respectively, plus it will have an appropriate title and class. - * - * @param string $flag 'newpage', 'unpatrolled', 'minor', or 'bot' - * @return String: Raw HTML - */ - public static function flag( $flag ) { - static $messages = null; - if ( is_null( $messages ) ) { - $messages = array( - 'newpage' => array( 'newpageletter', 'recentchanges-label-newpage' ), - 'minoredit' => array( 'minoreditletter', 'recentchanges-label-minor' ), - 'botedit' => array( 'boteditletter', 'recentchanges-label-bot' ), - 'unpatrolled' => array( 'unpatrolledletter', 'recentchanges-label-unpatrolled' ), - ); - foreach( $messages as &$value ) { - $value[0] = wfMessage( $value[0] )->escaped(); - $value[1] = wfMessage( $value[1] )->escaped(); - } - } - - # Inconsistent naming, bleh - $map = array( - 'newpage' => 'newpage', - 'minor' => 'minoredit', - 'bot' => 'botedit', - 'unpatrolled' => 'unpatrolled', - 'minoredit' => 'minoredit', - 'botedit' => 'botedit', - ); - $flag = $map[$flag]; - - return "<abbr class='$flag' title='" . $messages[$flag][1] . "'>" . $messages[$flag][0] . '</abbr>'; - } - - /** - * Returns text for the start of the tabular part of RC - * @return String - */ - public function beginRecentChangesList() { - $this->rc_cache = array(); - $this->rcMoveIndex = 0; - $this->rcCacheIndex = 0; - $this->lastdate = ''; - $this->rclistOpen = false; - $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' ); - return ''; - } - - /** - * Show formatted char difference - * @param $old Integer: bytes - * @param $new Integer: bytes - * @param $context IContextSource context to use - * @return String - */ - public static function showCharacterDifference( $old, $new, IContextSource $context = null ) { - global $wgRCChangedSizeThreshold, $wgMiserMode; - - if ( !$context ) { - $context = RequestContext::getMain(); - } - - $new = (int)$new; - $old = (int)$old; - $szdiff = $new - $old; - - $lang = $context->getLanguage(); - $code = $lang->getCode(); - static $fastCharDiff = array(); - if ( !isset( $fastCharDiff[$code] ) ) { - $fastCharDiff[$code] = $wgMiserMode || $context->msg( 'rc-change-size' )->plain() === '$1'; - } - - $formattedSize = $lang->formatNum( $szdiff ); - - if ( !$fastCharDiff[$code] ) { - $formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text(); - } - - if( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) { - $tag = 'strong'; - } else { - $tag = 'span'; - } - - if ( $szdiff === 0 ) { - $formattedSizeClass = 'mw-plusminus-null'; - } - if ( $szdiff > 0 ) { - $formattedSize = '+' . $formattedSize; - $formattedSizeClass = 'mw-plusminus-pos'; - } - if ( $szdiff < 0 ) { - $formattedSizeClass = 'mw-plusminus-neg'; - } - - $formattedTotalSize = $context->msg( 'rc-change-size-new' )->numParams( $new )->text(); - - return Html::element( $tag, - array( 'dir' => 'ltr', 'class' => $formattedSizeClass, 'title' => $formattedTotalSize ), - $context->msg( 'parentheses', $formattedSize )->plain() ) . $lang->getDirMark(); - } - - /** - * Format the character difference of one or several changes. - * - * @param $old RecentChange - * @param $new RecentChange last change to use, if not provided, $old will be used - * @return string HTML fragment - */ - public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) { - $oldlen = $old->mAttribs['rc_old_len']; - - if ( $new ) { - $newlen = $new->mAttribs['rc_new_len']; - } else { - $newlen = $old->mAttribs['rc_new_len']; - } - - if( $oldlen === null || $newlen === null ) { - return ''; - } - - return self::showCharacterDifference( $oldlen, $newlen, $this->getContext() ); - } - - /** - * Returns text for the end of RC - * @return String - */ - public function endRecentChangesList() { - if( $this->rclistOpen ) { - return "</ul>\n"; - } else { - return ''; - } - } - - /** - * @param string $s HTML to update - * @param $rc_timestamp mixed - */ - public function insertDateHeader( &$s, $rc_timestamp ) { - # Make date header if necessary - $date = $this->getLanguage()->userDate( $rc_timestamp, $this->getUser() ); - if( $date != $this->lastdate ) { - if( $this->lastdate != '' ) { - $s .= "</ul>\n"; - } - $s .= Xml::element( 'h4', null, $date ) . "\n<ul class=\"special\">"; - $this->lastdate = $date; - $this->rclistOpen = true; - } - } - - /** - * @param string $s HTML to update - * @param $title Title - * @param $logtype string - */ - public function insertLog( &$s, $title, $logtype ) { - $page = new LogPage( $logtype ); - $logname = $page->getName()->escaped(); - $s .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $title, $logname ) )->escaped(); - } - - /** - * @param string $s HTML to update - * @param $rc RecentChange - * @param $unpatrolled - */ - public function insertDiffHist( &$s, &$rc, $unpatrolled ) { - # Diff link - if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) { - $diffLink = $this->message['diff']; - } elseif ( !self::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) { - $diffLink = $this->message['diff']; - } else { - $query = array( - 'curid' => $rc->mAttribs['rc_cur_id'], - 'diff' => $rc->mAttribs['rc_this_oldid'], - 'oldid' => $rc->mAttribs['rc_last_oldid'] - ); - - if( $unpatrolled ) { - $query['rcid'] = $rc->mAttribs['rc_id']; - }; - - $diffLink = Linker::linkKnown( - $rc->getTitle(), - $this->message['diff'], - array( 'tabindex' => $rc->counter ), - $query - ); - } - $diffhist = $diffLink . $this->message['pipe-separator']; - # History link - $diffhist .= Linker::linkKnown( - $rc->getTitle(), - $this->message['hist'], - array(), - array( - 'curid' => $rc->mAttribs['rc_cur_id'], - 'action' => 'history' - ) - ); - $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() . ' <span class="mw-changeslist-separator">. .</span> '; - } - - /** - * @param string $s HTML to update - * @param $rc RecentChange - * @param $unpatrolled - * @param $watched - */ - public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) { - # If it's a new article, there is no diff link, but if it hasn't been - # patrolled yet, we need to give users a way to do so - $params = array(); - - if ( $unpatrolled && $rc->mAttribs['rc_type'] == RC_NEW ) { - $params['rcid'] = $rc->mAttribs['rc_id']; - } - - $articlelink = Linker::linkKnown( - $rc->getTitle(), - null, - array( 'class' => 'mw-changeslist-title' ), - $params - ); - if( $this->isDeleted( $rc, Revision::DELETED_TEXT ) ) { - $articlelink = '<span class="history-deleted">' . $articlelink . '</span>'; - } - # To allow for boldening pages watched by this user - $articlelink = "<span class=\"mw-title\">{$articlelink}</span>"; - # RTL/LTR marker - $articlelink .= $this->getLanguage()->getDirMark(); - - wfRunHooks( 'ChangesListInsertArticleLink', - array( &$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched ) ); - - $s .= " $articlelink"; - } - - /** - * Get the timestamp from $rc formatted with current user's settings - * and a separator - * - * @param $rc RecentChange - * @return string HTML fragment - */ - public function getTimestamp( $rc ) { - return $this->message['semicolon-separator'] . '<span class="mw-changeslist-date">' . - $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() ) . '</span> <span class="mw-changeslist-separator">. .</span> '; - } - - /** - * Insert time timestamp string from $rc into $s - * - * @param string $s HTML to update - * @param $rc RecentChange - */ - public function insertTimestamp( &$s, $rc ) { - $s .= $this->getTimestamp( $rc ); - } - - /** - * Insert links to user page, user talk page and eventually a blocking link - * - * @param &$s String HTML to update - * @param &$rc RecentChange - */ - public function insertUserRelatedLinks( &$s, &$rc ) { - if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) { - $s .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>'; - } else { - $s .= $this->getLanguage()->getDirMark() . Linker::userLink( $rc->mAttribs['rc_user'], - $rc->mAttribs['rc_user_text'] ); - $s .= Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); - } - } - - /** - * Insert a formatted action - * - * @param $rc RecentChange - * @return string - */ - public function insertLogEntry( $rc ) { - $formatter = LogFormatter::newFromRow( $rc->mAttribs ); - $formatter->setContext( $this->getContext() ); - $formatter->setShowUserToolLinks( true ); - $mark = $this->getLanguage()->getDirMark(); - return $formatter->getActionText() . " $mark" . $formatter->getComment(); - } - - /** - * Insert a formatted comment - * @param $rc RecentChange - * @return string - */ - public function insertComment( $rc ) { - if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) { - if( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) { - return ' <span class="history-deleted">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>'; - } else { - return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() ); - } - } - return ''; - } - - /** - * Check whether to enable recent changes patrol features - * @return Boolean - */ - public static function usePatrol() { - global $wgUser; - return $wgUser->useRCPatrol(); - } - - /** - * Returns the string which indicates the number of watching users - * @return string - */ - protected function numberofWatchingusers( $count ) { - static $cache = array(); - if( $count > 0 ) { - if( !isset( $cache[$count] ) ) { - $cache[$count] = $this->msg( 'number_of_watching_users_RCview' )->numParams( $count )->escaped(); - } - return $cache[$count]; - } else { - return ''; - } - } - - /** - * Determine if said field of a revision is hidden - * @param $rc RCCacheEntry - * @param $field Integer: one of DELETED_* bitfield constants - * @return Boolean - */ - public static function isDeleted( $rc, $field ) { - return ( $rc->mAttribs['rc_deleted'] & $field ) == $field; - } - - /** - * Determine if the current user is allowed to view a particular - * field of this revision, if it's marked as deleted. - * @param $rc RCCacheEntry - * @param $field Integer - * @param $user User object to check, or null to use $wgUser - * @return Boolean - */ - public static function userCan( $rc, $field, User $user = null ) { - if( $rc->mAttribs['rc_type'] == RC_LOG ) { - return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user ); - } else { - return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user ); - } - } - - /** - * @param $link string - * @param $watched bool - * @return string - */ - protected function maybeWatchedLink( $link, $watched = false ) { - if( $watched ) { - return '<strong class="mw-watched">' . $link . '</strong>'; - } else { - return '<span class="mw-rc-unwatched">' . $link . '</span>'; - } - } - - /** Inserts a rollback link - * - * @param $s string - * @param $rc RecentChange - */ - public function insertRollback( &$s, &$rc ) { - if( $rc->mAttribs['rc_type'] != RC_NEW && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) { - $page = $rc->getTitle(); - /** Check for rollback and edit permissions, disallow special pages, and only - * show a link on the top-most revision */ - if ( $this->getUser()->isAllowed( 'rollback' ) && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] ) - { - $rev = new Revision( array( - 'title' => $page, - 'id' => $rc->mAttribs['rc_this_oldid'], - 'user' => $rc->mAttribs['rc_user'], - 'user_text' => $rc->mAttribs['rc_user_text'], - 'deleted' => $rc->mAttribs['rc_deleted'] - ) ); - $s .= ' '.Linker::generateRollback( $rev, $this->getContext() ); - } - } - } - - /** - * @param $s string - * @param $rc RecentChange - * @param $classes - */ - public function insertTags( &$s, &$rc, &$classes ) { - if ( empty( $rc->mAttribs['ts_tags'] ) ) - return; - - list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' ); - $classes = array_merge( $classes, $newClasses ); - $s .= ' ' . $tagSummary; - } - - public function insertExtra( &$s, &$rc, &$classes ) { - // Empty, used for subclasses to add anything special. - } - - protected function showAsUnpatrolled( RecentChange $rc ) { - $unpatrolled = false; - if ( !$rc->mAttribs['rc_patrolled'] ) { - if ( $this->getUser()->useRCPatrol() ) { - $unpatrolled = true; - } elseif ( $this->getUser()->useNPPatrol() && $rc->mAttribs['rc_type'] == RC_NEW ) { - $unpatrolled = true; - } - } - return $unpatrolled; - } -} - -/** - * Generate a list of changes using the good old system (no javascript) - */ -class OldChangesList extends ChangesList { - /** - * Format a line using the old system (aka without any javascript). - * - * @param $rc RecentChange, passed by reference - * @param bool $watched (default false) - * @param int $linenumber (default null) - * - * @return string|bool - */ - public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) { - global $wgRCShowChangedSize; - wfProfileIn( __METHOD__ ); - - # Should patrol-related stuff be shown? - $unpatrolled = $this->showAsUnpatrolled( $rc ); - - $dateheader = ''; // $s now contains only <li>...</li>, for hooks' convenience. - $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] ); - - $s = ''; - $classes = array(); - // use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468) - if( $linenumber ) { - if( $linenumber & 1 ) { - $classes[] = 'mw-line-odd'; - } - else { - $classes[] = 'mw-line-even'; - } - } - - // Indicate watched status on the line to allow for more - // comprehensive styling. - $classes[] = $watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; - - // Moved pages (very very old, not supported anymore) - if( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) { - // Log entries - } elseif( $rc->mAttribs['rc_log_type'] ) { - $logtitle = SpecialPage::getTitleFor( 'Log', $rc->mAttribs['rc_log_type'] ); - $this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] ); - // Log entries (old format) or log targets, and special pages - } elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) { - list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] ); - if( $name == 'Log' ) { - $this->insertLog( $s, $rc->getTitle(), $subpage ); - } - // Regular entries - } else { - $this->insertDiffHist( $s, $rc, $unpatrolled ); - # M, N, b and ! (minor, new, bot and unpatrolled) - $s .= $this->recentChangesFlags( - array( - 'newpage' => $rc->mAttribs['rc_type'] == RC_NEW, - 'minor' => $rc->mAttribs['rc_minor'], - 'unpatrolled' => $unpatrolled, - 'bot' => $rc->mAttribs['rc_bot'] - ), - '' - ); - $this->insertArticleLink( $s, $rc, $unpatrolled, $watched ); - } - # Edit/log timestamp - $this->insertTimestamp( $s, $rc ); - # Bytes added or removed - if ( $wgRCShowChangedSize ) { - $cd = $this->formatCharacterDifference( $rc ); - if ( $cd !== '' ) { - $s .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; - } - } - - if ( $rc->mAttribs['rc_type'] == RC_LOG ) { - $s .= $this->insertLogEntry( $rc ); - } else { - # User tool links - $this->insertUserRelatedLinks( $s, $rc ); - # LTR/RTL direction mark - $s .= $this->getLanguage()->getDirMark(); - $s .= $this->insertComment( $rc ); - } - - # Tags - $this->insertTags( $s, $rc, $classes ); - # Rollback - $this->insertRollback( $s, $rc ); - # For subclasses - $this->insertExtra( $s, $rc, $classes ); - - # How many users watch this page - if( $rc->numberofWatchingusers > 0 ) { - $s .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers ); - } - - if( $this->watchlist ) { - $classes[] = Sanitizer::escapeClass( 'watchlist-' . $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] ); - } - - if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc, &$classes ) ) ) { - wfProfileOut( __METHOD__ ); - return false; - } - - wfProfileOut( __METHOD__ ); - return "$dateheader<li class=\"" . implode( ' ', $classes ) . "\">" . $s . "</li>\n"; - } -} - -/** - * Generate a list of changes using an Enhanced system (uses javascript). - */ -class EnhancedChangesList extends ChangesList { - - protected $rc_cache; - - /** - * Add the JavaScript file for enhanced changeslist - * @return String - */ - public function beginRecentChangesList() { - $this->rc_cache = array(); - $this->rcMoveIndex = 0; - $this->rcCacheIndex = 0; - $this->lastdate = ''; - $this->rclistOpen = false; - $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' ); - return ''; - } - /** - * Format a line for enhanced recentchange (aka with javascript and block of lines). - * - * @param $baseRC RecentChange - * @param $watched bool - * - * @return string - */ - public function recentChangesLine( &$baseRC, $watched = false ) { - wfProfileIn( __METHOD__ ); - - # Create a specialised object - $rc = RCCacheEntry::newFromParent( $baseRC ); - - $curIdEq = array( 'curid' => $rc->mAttribs['rc_cur_id'] ); - - # If it's a new day, add the headline and flush the cache - $date = $this->getLanguage()->userDate( $rc->mAttribs['rc_timestamp'], $this->getUser() ); - $ret = ''; - if( $date != $this->lastdate ) { - # Process current cache - $ret = $this->recentChangesBlock(); - $this->rc_cache = array(); - $ret .= Xml::element( 'h4', null, $date ) . "\n"; - $this->lastdate = $date; - } - - # Should patrol-related stuff be shown? - $rc->unpatrolled = $this->showAsUnpatrolled( $rc ); - - $showdifflinks = true; - # Make article link - $type = $rc->mAttribs['rc_type']; - $logType = $rc->mAttribs['rc_log_type']; - // Page moves, very old style, not supported anymore - if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) { - // New unpatrolled pages - } elseif( $rc->unpatrolled && $type == RC_NEW ) { - $clink = Linker::linkKnown( $rc->getTitle(), null, array(), - array( 'rcid' => $rc->mAttribs['rc_id'] ) ); - // Log entries - } elseif( $type == RC_LOG ) { - if( $logType ) { - $logtitle = SpecialPage::getTitleFor( 'Log', $logType ); - $logpage = new LogPage( $logType ); - $logname = $logpage->getName()->escaped(); - $clink = $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logtitle, $logname ) )->escaped(); - } else { - $clink = Linker::link( $rc->getTitle() ); - } - $watched = false; - // Log entries (old format) and special pages - } elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) { - wfDebug( "Unexpected special page in recentchanges\n" ); - $clink = ''; - // Edits - } else { - $clink = Linker::linkKnown( $rc->getTitle() ); - } - - # Don't show unusable diff links - if ( !ChangesList::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) { - $showdifflinks = false; - } - - $time = $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() ); - $rc->watched = $watched; - $rc->link = $clink; - $rc->timestamp = $time; - $rc->numberofWatchingusers = $baseRC->numberofWatchingusers; - - # Make "cur" and "diff" links. Do not use link(), it is too slow if - # called too many times (50% of CPU time on RecentChanges!). - $thisOldid = $rc->mAttribs['rc_this_oldid']; - $lastOldid = $rc->mAttribs['rc_last_oldid']; - if( $rc->unpatrolled ) { - $rcIdQuery = array( 'rcid' => $rc->mAttribs['rc_id'] ); - } else { - $rcIdQuery = array(); - } - $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $thisOldid ); - $querydiff = $curIdEq + array( 'diff' => $thisOldid, 'oldid' => - $lastOldid ) + $rcIdQuery; - - if( !$showdifflinks ) { - $curLink = $this->message['cur']; - $diffLink = $this->message['diff']; - } elseif( in_array( $type, array( RC_NEW, RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) { - if ( $type != RC_NEW ) { - $curLink = $this->message['cur']; - } else { - $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) ); - $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>"; - } - $diffLink = $this->message['diff']; - } else { - $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querydiff ) ); - $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) ); - $diffLink = "<a href=\"$diffUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['diff']}</a>"; - $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>"; - } - - # Make "last" link - if( !$showdifflinks || !$lastOldid ) { - $lastLink = $this->message['last']; - } elseif( in_array( $type, array( RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) { - $lastLink = $this->message['last']; - } else { - $lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'], - array(), $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid ) + $rcIdQuery ); - } - - # Make user links - if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) { - $rc->userlink = ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>'; - } else { - $rc->userlink = Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); - $rc->usertalklink = Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); - } - - $rc->lastlink = $lastLink; - $rc->curlink = $curLink; - $rc->difflink = $diffLink; - - # Put accumulated information into the cache, for later display - # Page moves go on their own line - $title = $rc->getTitle(); - $secureName = $title->getPrefixedDBkey(); - if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) { - # Use an @ character to prevent collision with page names - $this->rc_cache['@@' . ($this->rcMoveIndex++)] = array( $rc ); - } else { - # Logs are grouped by type - if( $type == RC_LOG ) { - $secureName = SpecialPage::getTitleFor( 'Log', $logType )->getPrefixedDBkey(); - } - if( !isset( $this->rc_cache[$secureName] ) ) { - $this->rc_cache[$secureName] = array(); - } - - array_push( $this->rc_cache[$secureName], $rc ); - } - - wfProfileOut( __METHOD__ ); - - return $ret; - } - - /** - * Enhanced RC group - * @return string - */ - protected function recentChangesBlockGroup( $block ) { - global $wgRCShowChangedSize; - - wfProfileIn( __METHOD__ ); - - # Add the namespace and title of the block as part of the class - $classes = array( 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' ); - if ( $block[0]->mAttribs['rc_log_type'] ) { - # Log entry - $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' - . $block[0]->mAttribs['rc_log_type'] . '-' . $block[0]->mAttribs['rc_title'] ); - } else { - $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' - . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] ); - } - $classes[] = $block[0]->watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; - $r = Html::openElement( 'table', array( 'class' => $classes ) ) . - Html::openElement( 'tr' ); - - # Collate list of users - $userlinks = array(); - # Other properties - $unpatrolled = false; - $isnew = false; - $allBots = true; - $allMinors = true; - $curId = $currentRevision = 0; - # Some catalyst variables... - $namehidden = true; - $allLogs = true; - foreach( $block as $rcObj ) { - $oldid = $rcObj->mAttribs['rc_last_oldid']; - if( $rcObj->mAttribs['rc_type'] == RC_NEW ) { - $isnew = true; - } - // If all log actions to this page were hidden, then don't - // give the name of the affected page for this block! - if( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) { - $namehidden = false; - } - $u = $rcObj->userlink; - if( !isset( $userlinks[$u] ) ) { - $userlinks[$u] = 0; - } - if( $rcObj->unpatrolled ) { - $unpatrolled = true; - } - if( $rcObj->mAttribs['rc_type'] != RC_LOG ) { - $allLogs = false; - } - # Get the latest entry with a page_id and oldid - # since logs may not have these. - if( !$curId && $rcObj->mAttribs['rc_cur_id'] ) { - $curId = $rcObj->mAttribs['rc_cur_id']; - } - if( !$currentRevision && $rcObj->mAttribs['rc_this_oldid'] ) { - $currentRevision = $rcObj->mAttribs['rc_this_oldid']; - } - - if( !$rcObj->mAttribs['rc_bot'] ) { - $allBots = false; - } - if( !$rcObj->mAttribs['rc_minor'] ) { - $allMinors = false; - } - - $userlinks[$u]++; - } - - # Sort the list and convert to text - krsort( $userlinks ); - asort( $userlinks ); - $users = array(); - foreach( $userlinks as $userlink => $count) { - $text = $userlink; - $text .= $this->getLanguage()->getDirMark(); - if( $count > 1 ) { - $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->formatNum( $count ) . '×' )->escaped(); - } - array_push( $users, $text ); - } - - $users = ' <span class="changedby">' - . $this->msg( 'brackets' )->rawParams( - implode( $this->message['semicolon-separator'], $users ) - )->escaped() . '</span>'; - - $tl = '<span class="mw-collapsible-toggle mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>'; - $r .= "<td>$tl</td>"; - - # Main line - $r .= '<td class="mw-enhanced-rc">' . $this->recentChangesFlags( array( - 'newpage' => $isnew, # show, when one have this flag - 'minor' => $allMinors, # show only, when all have this flag - 'unpatrolled' => $unpatrolled, # show, when one have this flag - 'bot' => $allBots, # show only, when all have this flag - ) ); - - # Timestamp - $r .= ' ' . $block[0]->timestamp . ' </td><td>'; - - # Article link - if( $namehidden ) { - $r .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-event' )->escaped() . '</span>'; - } elseif( $allLogs ) { - $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched ); - } else { - $this->insertArticleLink( $r, $block[0], $block[0]->unpatrolled, $block[0]->watched ); - } - - $r .= $this->getLanguage()->getDirMark(); - - $queryParams['curid'] = $curId; - # Changes message - $n = count( $block ); - static $nchanges = array(); - if ( !isset( $nchanges[$n] ) ) { - $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped(); - } - # Total change link - $r .= ' '; - $logtext = ''; - if( !$allLogs ) { - if( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) { - $logtext .= $nchanges[$n]; - } elseif( $isnew ) { - $logtext .= $nchanges[$n]; - } else { - $params = $queryParams; - $params['diff'] = $currentRevision; - $params['oldid'] = $oldid; - - $logtext .= Linker::link( - $block[0]->getTitle(), - $nchanges[$n], - array(), - $params, - array( 'known', 'noclasses' ) - ); - } - } - - # History - if( $allLogs ) { - // don't show history link for logs - } elseif( $namehidden || !$block[0]->getTitle()->exists() ) { - $logtext .= $this->message['pipe-separator'] . $this->message['hist']; - } else { - $params = $queryParams; - $params['action'] = 'history'; - - $logtext .= $this->message['pipe-separator'] . - Linker::linkKnown( - $block[0]->getTitle(), - $this->message['hist'], - array(), - $params - ); - } - - if( $logtext !== '' ) { - $r .= $this->msg( 'parentheses' )->rawParams( $logtext )->escaped(); - } - - $r .= ' <span class="mw-changeslist-separator">. .</span> '; - - # Character difference (does not apply if only log items) - if( $wgRCShowChangedSize && !$allLogs ) { - $last = 0; - $first = count( $block ) - 1; - # Some events (like logs) have an "empty" size, so we need to skip those... - while( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) { - $last++; - } - while( $first > $last && $block[$first]->mAttribs['rc_old_len'] === null ) { - $first--; - } - # Get net change - $chardiff = $this->formatCharacterDifference( $block[$first], $block[$last] ); - - if( $chardiff == '' ) { - $r .= ' '; - } else { - $r .= ' ' . $chardiff. ' <span class="mw-changeslist-separator">. .</span> '; - } - } - - $r .= $users; - $r .= $this->numberofWatchingusers( $block[0]->numberofWatchingusers ); - - # Sub-entries - foreach( $block as $rcObj ) { - # Classes to apply -- TODO implement - $classes = array(); - $type = $rcObj->mAttribs['rc_type']; - - $r .= '<tr><td></td><td class="mw-enhanced-rc">'; - $r .= $this->recentChangesFlags( array( - 'newpage' => $type == RC_NEW, - 'minor' => $rcObj->mAttribs['rc_minor'], - 'unpatrolled' => $rcObj->unpatrolled, - 'bot' => $rcObj->mAttribs['rc_bot'], - ) ); - $r .= ' </td><td class="mw-enhanced-rc-nested"><span class="mw-enhanced-rc-time">'; - - $params = $queryParams; - - if( $rcObj->mAttribs['rc_this_oldid'] != 0 ) { - $params['oldid'] = $rcObj->mAttribs['rc_this_oldid']; - } - - # Log timestamp - if( $type == RC_LOG ) { - $link = $rcObj->timestamp; - # Revision link - } elseif( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) { - $link = '<span class="history-deleted">' . $rcObj->timestamp . '</span> '; - } else { - if ( $rcObj->unpatrolled && $type == RC_NEW) { - $params['rcid'] = $rcObj->mAttribs['rc_id']; - } - - $link = Linker::linkKnown( - $rcObj->getTitle(), - $rcObj->timestamp, - array(), - $params - ); - if( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) { - $link = '<span class="history-deleted">' . $link . '</span> '; - } - } - $r .= $link . '</span>'; - - if ( !$type == RC_LOG || $type == RC_NEW ) { - $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->curlink . $this->message['pipe-separator'] . $rcObj->lastlink )->escaped(); - } - $r .= ' <span class="mw-changeslist-separator">. .</span> '; - - # Character diff - if ( $wgRCShowChangedSize ) { - $cd = $this->formatCharacterDifference( $rcObj ); - if ( $cd !== '' ) { - $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; - } - } - - if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) { - $r .= $this->insertLogEntry( $rcObj ); - } else { - # User links - $r .= $rcObj->userlink; - $r .= $rcObj->usertalklink; - $r .= $this->insertComment( $rcObj ); - } - - # Rollback - $this->insertRollback( $r, $rcObj ); - # Tags - $this->insertTags( $r, $rcObj, $classes ); - - $r .= "</td></tr>\n"; - } - $r .= "</table>\n"; - - $this->rcCacheIndex++; - - wfProfileOut( __METHOD__ ); - - return $r; - } - - /** - * Generate HTML for an arrow or placeholder graphic - * @param string $dir one of '', 'd', 'l', 'r' - * @param string $alt text - * @param string $title text - * @return String: HTML "<img>" tag - */ - protected function arrow( $dir, $alt = '', $title = '' ) { - global $wgStylePath; - $encUrl = htmlspecialchars( $wgStylePath . '/common/images/Arr_' . $dir . '.png' ); - $encAlt = htmlspecialchars( $alt ); - $encTitle = htmlspecialchars( $title ); - return "<img src=\"$encUrl\" width=\"12\" height=\"12\" alt=\"$encAlt\" title=\"$encTitle\" />"; - } - - /** - * Generate HTML for a right- or left-facing arrow, - * depending on language direction. - * @return String: HTML "<img>" tag - */ - protected function sideArrow() { - $dir = $this->getLanguage()->isRTL() ? 'l' : 'r'; - return $this->arrow( $dir, '+', $this->msg( 'rc-enhanced-expand' )->text() ); - } - - /** - * Generate HTML for a down-facing arrow - * depending on language direction. - * @return String: HTML "<img>" tag - */ - protected function downArrow() { - return $this->arrow( 'd', '-', $this->msg( 'rc-enhanced-hide' )->text() ); - } - - /** - * Generate HTML for a spacer image - * @return String: HTML "<img>" tag - */ - protected function spacerArrow() { - return $this->arrow( '', codepointToUtf8( 0xa0 ) ); // non-breaking space - } - - /** - * Enhanced RC ungrouped line. - * - * @param $rcObj RecentChange - * @return String: a HTML formatted line (generated using $r) - */ - protected function recentChangesBlockLine( $rcObj ) { - global $wgRCShowChangedSize; - - wfProfileIn( __METHOD__ ); - $query['curid'] = $rcObj->mAttribs['rc_cur_id']; - - $type = $rcObj->mAttribs['rc_type']; - $logType = $rcObj->mAttribs['rc_log_type']; - $classes = array( 'mw-enhanced-rc' ); - if( $logType ) { - # Log entry - $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' - . $logType . '-' . $rcObj->mAttribs['rc_title'] ); - } else { - $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' . - $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] ); - } - $classes[] = $rcObj->watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; - $r = Html::openElement( 'table', array( 'class' => $classes ) ) . - Html::openElement( 'tr' ); - - $r .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>'; - # Flag and Timestamp - if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) { - $r .= '    '; // 4 flags -> 4 spaces - } else { - $r .= $this->recentChangesFlags( array( - 'newpage' => $type == RC_NEW, - 'minor' => $rcObj->mAttribs['rc_minor'], - 'unpatrolled' => $rcObj->unpatrolled, - 'bot' => $rcObj->mAttribs['rc_bot'], - ) ); - } - $r .= ' ' . $rcObj->timestamp . ' </td><td>'; - # Article or log link - if( $logType ) { - $logPage = new LogPage( $logType ); - $logTitle = SpecialPage::getTitleFor( 'Log', $logType ); - $logName = $logPage->getName()->escaped(); - $r .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped(); - } else { - $this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched ); - } - # Diff and hist links - if ( $type != RC_LOG ) { - $query['action'] = 'history'; - $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown( - $rcObj->getTitle(), - $this->message['hist'], - array(), - $query - ) )->escaped(); - } - $r .= ' <span class="mw-changeslist-separator">. .</span> '; - # Character diff - if ( $wgRCShowChangedSize ) { - $cd = $this->formatCharacterDifference( $rcObj ); - if ( $cd !== '' ) { - $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; - } - } - - if ( $type == RC_LOG ) { - $r .= $this->insertLogEntry( $rcObj ); - } else { - $r .= ' ' . $rcObj->userlink . $rcObj->usertalklink; - $r .= $this->insertComment( $rcObj ); - $this->insertRollback( $r, $rcObj ); - } - - # Tags - $this->insertTags( $r, $rcObj, $classes ); - # Show how many people are watching this if enabled - $r .= $this->numberofWatchingusers( $rcObj->numberofWatchingusers ); - - $r .= "</td></tr></table>\n"; - - wfProfileOut( __METHOD__ ); - - return $r; - } - - /** - * If enhanced RC is in use, this function takes the previously cached - * RC lines, arranges them, and outputs the HTML - * - * @return string - */ - protected function recentChangesBlock() { - if( count ( $this->rc_cache ) == 0 ) { - return ''; - } - - wfProfileIn( __METHOD__ ); - - $blockOut = ''; - foreach( $this->rc_cache as $block ) { - if( count( $block ) < 2 ) { - $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) ); - } else { - $blockOut .= $this->recentChangesBlockGroup( $block ); - } - } - - wfProfileOut( __METHOD__ ); - - return '<div>' . $blockOut . '</div>'; - } - - /** - * Returns text for the end of RC - * If enhanced RC is in use, returns pretty much all the text - * @return string - */ - public function endRecentChangesList() { - return $this->recentChangesBlock() . parent::endRecentChangesList(); - } - -} diff --git a/includes/Collation.php b/includes/Collation.php index 6bba019b..b0252c70 100644 --- a/includes/Collation.php +++ b/includes/Collation.php @@ -40,7 +40,7 @@ abstract class Collation { * @return Collation */ static function factory( $collationName ) { - switch( $collationName ) { + switch ( $collationName ) { case 'uppercase': return new UppercaseCollation; case 'identity': @@ -49,7 +49,7 @@ abstract class Collation { return new IcuCollation( 'root' ); default: $match = array(); - if ( preg_match( '/^uca-([a-z-]+)$/', $collationName, $match ) ) { + if ( preg_match( '/^uca-([a-z@=-]+)$/', $collationName, $match ) ) { return new IcuCollation( $match[1] ); } @@ -62,7 +62,7 @@ abstract class Collation { } // If all else fails... - throw new MWException( __METHOD__.": unknown collation type \"$collationName\"" ); + throw new MWException( __METHOD__ . ": unknown collation type \"$collationName\"" ); } } @@ -214,6 +214,7 @@ class IcuCollation extends Collation { 'pt' => array(), 'ru' => array(), 'sv' => array( "Å", "Ä", "Ö" ), + 'sv@collation=standard' => array( "Å", "Ä", "Ö" ), 'uk' => array( "Ґ", "Ь" ), 'vi' => array( "Ă", "Â", "Đ", "Ê", "Ô", "Ơ", "Ư" ), // Not verified, but likely correct @@ -235,6 +236,7 @@ class IcuCollation extends Collation { 'es' => array( "Ñ" ), 'et' => array( "Š", "Ž", "Õ", "Ä", "Ö", "Ü" ), 'eu' => array( "Ñ" ), + 'fa' => array( "آ", "ء", "ه" ), 'fo' => array( "Á", "Ð", "Í", "Ó", "Ú", "Ý", "Æ", "Ø", "Å" ), 'fr' => array(), 'fur' => array( "À", "Á", "Â", "È", "Ì", "Ò", "Ù" ), @@ -349,21 +351,21 @@ class IcuCollation extends Collation { $cacheEntry = $cache->get( $cacheKey ); if ( $cacheEntry && isset( $cacheEntry['version'] ) - && $cacheEntry['version'] == self::FIRST_LETTER_VERSION ) - { + && $cacheEntry['version'] == self::FIRST_LETTER_VERSION + ) { $this->firstLetterData = $cacheEntry; return $this->firstLetterData; } // Generate data from serialized data file - if ( isset ( self::$tailoringFirstLetters[$this->locale] ) ) { + if ( isset( self::$tailoringFirstLetters[$this->locale] ) ) { $letters = wfGetPrecompiledData( "first-letters-root.ser" ); // Append additional characters $letters = array_merge( $letters, self::$tailoringFirstLetters[$this->locale] ); // Remove unnecessary ones, if any - if ( isset( self::$tailoringFirstLetters[ '-' . $this->locale ] ) ) { - $letters = array_diff( $letters, self::$tailoringFirstLetters[ '-' . $this->locale ] ); + if ( isset( self::$tailoringFirstLetters['-' . $this->locale] ) ) { + $letters = array_diff( $letters, self::$tailoringFirstLetters['-' . $this->locale] ); } } else { $letters = wfGetPrecompiledData( "first-letters-{$this->locale}.ser" ); @@ -430,7 +432,7 @@ class IcuCollation extends Collation { $prev = false; $duplicatePrefixes = array(); - foreach( $letterMap as $key => $value ) { + foreach ( $letterMap as $key => $value ) { // Remove terminator byte. Otherwise the prefix // comparison will get hung up on that. $trimmedKey = rtrim( $key, "\0" ); @@ -456,7 +458,7 @@ class IcuCollation extends Collation { } $prev = $trimmedKey; } - foreach( $duplicatePrefixes as $badKey ) { + foreach ( $duplicatePrefixes as $badKey ) { wfDebug( "Removing '{$letterMap[$badKey]}' from first letters." ); unset( $letterMap[$badKey] ); // This code assumes that unsetting does not change sort order. diff --git a/includes/ConfEditor.php b/includes/ConfEditor.php index 1d9ca921..67cb87db 100644 --- a/includes/ConfEditor.php +++ b/includes/ConfEditor.php @@ -278,19 +278,23 @@ class ConfEditor { function getVars() { $vars = array(); $this->parse(); - foreach( $this->pathInfo as $path => $data ) { - if ( $path[0] != '$' ) + foreach ( $this->pathInfo as $path => $data ) { + if ( $path[0] != '$' ) { continue; + } $trimmedPath = substr( $path, 1 ); $name = $data['name']; - if ( $name[0] == '@' ) + if ( $name[0] == '@' ) { continue; - if ( $name[0] == '$' ) + } + if ( $name[0] == '$' ) { $name = substr( $name, 1 ); + } $parentPath = substr( $trimmedPath, 0, strlen( $trimmedPath ) - strlen( $name ) ); - if( substr( $parentPath, -1 ) == '/' ) + if ( substr( $parentPath, -1 ) == '/' ) { $parentPath = substr( $parentPath, 0, -1 ); + } $value = substr( $this->text, $data['valueStartByte'], $data['valueEndByte'] - $data['valueStartByte'] @@ -315,13 +319,15 @@ class ConfEditor { $target =& $array; if ( $path !== '' ) { foreach ( $pathArr as $p ) { - if( !isset( $target[$p] ) ) + if ( !isset( $target[$p] ) ) { $target[$p] = array(); + } $target =& $target[$p]; } } - if ( !isset( $target[$key] ) ) + if ( !isset( $target[$key] ) ) { $target[$key] = $value; + } } /** @@ -329,25 +335,30 @@ class ConfEditor { * @return mixed Parsed value */ function parseScalar( $str ) { - if ( $str !== '' && $str[0] == '\'' ) + if ( $str !== '' && $str[0] == '\'' ) { // Single-quoted string // @todo FIXME: trim() call is due to mystery bug where whitespace gets // appended to the token; without it we ended up reading in the // extra quote on the end! return strtr( substr( trim( $str ), 1, -1 ), array( '\\\'' => '\'', '\\\\' => '\\' ) ); - if ( $str !== '' && $str[0] == '"' ) + } + if ( $str !== '' && $str[0] == '"' ) { // Double-quoted string // @todo FIXME: trim() call is due to mystery bug where whitespace gets // appended to the token; without it we ended up reading in the // extra quote on the end! return stripcslashes( substr( trim( $str ), 1, -1 ) ); - if ( substr( $str, 0, 4 ) == 'true' ) + } + if ( substr( $str, 0, 4 ) == 'true' ) { return true; - if ( substr( $str, 0, 5 ) == 'false' ) + } + if ( substr( $str, 0, 5 ) == 'false' ) { return false; - if ( substr( $str, 0, 4 ) == 'null' ) + } + if ( substr( $str, 0, 4 ) == 'null' ) { return null; + } // Must be some kind of numeric value, so let PHP's weak typing // be useful for a change return $str; @@ -537,7 +548,7 @@ class ConfEditor { */ function getIndent( $pos, $key = false, $arrowPos = false ) { $arrowIndent = ' '; - if ( $pos == 0 || $this->text[$pos-1] == "\n" ) { + if ( $pos == 0 || $this->text[$pos - 1] == "\n" ) { $indentLength = strspn( $this->text, " \t", $pos ); $indent = substr( $this->text, $pos, $indentLength ); } else { @@ -559,7 +570,7 @@ class ConfEditor { public function parse() { $this->initParse(); $this->pushState( 'file' ); - $this->pushPath( '@extra-' . ($this->serial++) ); + $this->pushPath( '@extra-' . ( $this->serial++ ) ); $token = $this->firstToken(); while ( !$token->isEnd() ) { @@ -617,19 +628,21 @@ class ConfEditor { $this->expect( '=' ); $this->skipSpace(); $this->startPathValue(); - if ( $arrayAssign ) + if ( $arrayAssign ) { $this->pushState( 'expression', 'array assign end' ); - else + } else { $this->pushState( 'expression', 'statement end' ); + } break; case 'array assign end': case 'statement end': $this->endPathValue(); - if ( $state == 'array assign end' ) + if ( $state == 'array assign end' ) { $this->popPath(); + } $this->skipSpace(); $this->expect( ';' ); - $this->nextPath( '@extra-' . ($this->serial++) ); + $this->nextPath( '@extra-' . ( $this->serial++ ) ); break; case 'expression': $token = $this->skipSpace(); @@ -649,7 +662,7 @@ class ConfEditor { $this->skipSpace(); $this->expect( '(' ); $this->skipSpace(); - $this->pushPath( '@extra-' . ($this->serial++) ); + $this->pushPath( '@extra-' . ( $this->serial++ ) ); if ( $this->isAhead( ')' ) ) { // Empty array $this->pushState( 'array end' ); @@ -681,7 +694,7 @@ class ConfEditor { $this->endPathValue(); $this->markComma(); $this->nextToken(); - $this->nextPath( '@extra-' . ($this->serial++) ); + $this->nextPath( '@extra-' . ( $this->serial++ ) ); // Look ahead to find ending bracket if ( $this->isAhead( ")" ) ) { // Found ending bracket, no continuation @@ -1056,7 +1069,7 @@ class ConfEditorParseError extends MWException { $lines = StringUtils::explode( "\n", $text ); foreach ( $lines as $lineNum => $line ) { if ( $lineNum == $this->lineNum - 1 ) { - return "$line\n" .str_repeat( ' ', $this->colNum - 1 ) . "^\n"; + return "$line\n" . str_repeat( ' ', $this->colNum - 1 ) . "^\n"; } } return ''; diff --git a/includes/Cookie.php b/includes/Cookie.php index 27a85072..ecf4667d 100644 --- a/includes/Cookie.php +++ b/includes/Cookie.php @@ -82,7 +82,8 @@ class Cookie { * http://publicsuffix.org/ * * @todo fixme fails to detect 3-letter top-level domains - * @todo fixme fails to detect 2-letter top-level domains for single-domain use (probably not a big problem in practice, but there are test cases) + * @todo fixme fails to detect 2-letter top-level domains for single-domain use (probably + * not a big problem in practice, but there are test cases) * * @param string $domain the domain to validate * @param string $originDomain (optional) the domain the cookie originates from @@ -197,7 +198,7 @@ class CookieJar { * Set a cookie in the cookie jar. Make sure only one cookie per-name exists. * @see Cookie::set() */ - public function setCookie ( $name, $value, $attr ) { + public function setCookie( $name, $value, $attr ) { /* cookies: case insensitive, so this should work. * We'll still send the cookies back in the same case we got them, though. */ @@ -235,7 +236,7 @@ class CookieJar { * @param string $domain cookie's domain * @return null */ - public function parseCookieResponseHeader ( $cookie, $domain ) { + public function parseCookieResponseHeader( $cookie, $domain ) { $len = strlen( 'Set-Cookie:' ); if ( substr_compare( 'Set-Cookie:', $cookie, 0, $len, true ) === 0 ) { diff --git a/includes/DataUpdate.php b/includes/DataUpdate.php index c1076b23..7b9ac281 100644 --- a/includes/DataUpdate.php +++ b/includes/DataUpdate.php @@ -78,7 +78,9 @@ abstract class DataUpdate implements DeferrableUpdate { * @throws Exception|null */ public static function runUpdates( $updates ) { - if ( empty( $updates ) ) return; # nothing to do + if ( empty( $updates ) ) { + return; # nothing to do + } $open_transactions = array(); $exception = null; diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index a56ef849..0fc59fb2 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -63,7 +63,7 @@ $wgConf = new SiteConfiguration; * MediaWiki version number * @since 1.2 */ -$wgVersion = '1.21.3'; +$wgVersion = '1.22.0'; /** * Name of the site. It must be changed in LocalSettings.php @@ -172,14 +172,6 @@ $wgScriptExtension = '.php'; $wgScript = false; /** - * The URL path to redirect.php. This is a script that is used by the Nostalgia - * skin. - * - * Defaults to "{$wgScriptPath}/redirect{$wgScriptExtension}". - */ -$wgRedirectScript = false; - -/** * The URL path to load.php. * * Defaults to "{$wgScriptPath}/load{$wgScriptExtension}". @@ -274,7 +266,6 @@ $wgAppleTouchIcon = false; * * @see wfTempDir() * @note Default changed to false in MediaWiki 1.20. - * */ $wgTmpDirectory = false; @@ -317,7 +308,9 @@ $wgActionPaths = array(); * @{ */ -/** Uploads have to be specially set up to be secure */ +/** + * Uploads have to be specially set up to be secure + */ $wgEnableUploads = false; /** @@ -325,24 +318,22 @@ $wgEnableUploads = false; */ $wgUploadStashMaxAge = 6 * 3600; // 6 hours -/** Allows to move images and other media files */ +/** + * Allows to move images and other media files + */ $wgAllowImageMoving = true; /** * Enable deferred upload tasks that use the job queue. * Only enable this if job runners are set up for both the * 'AssembleUploadChunks' and 'PublishStashedFile' job types. + * + * @note If you use suhosin, this setting is incompatible with + * suhosin.session.encrypt. */ $wgEnableAsyncUploads = false; /** - * Allow chunked uploads. This should only really be needed if you - * use the UploadWizard extension or allow huge file uploads. - * https://www.mediawiki.org/wiki/API:Upload#Chunked_uploading - */ -$wgAllowChunkedUploads = false; - -/** * These are additional characters that should be replaced with '-' in filenames */ $wgIllegalFileChars = ":"; @@ -456,7 +447,9 @@ $wgImgAuthPublicTest = true; */ $wgLocalFileRepo = false; -/** @see $wgLocalFileRepo */ +/** + * @see $wgLocalFileRepo + */ $wgForeignFileRepos = array(); /** @@ -512,11 +505,11 @@ $wgFileBackends = array(); $wgLockManagers = array(); /** - * Show EXIF data, on by default if available. - * Requires PHP's EXIF extension: http://www.php.net/manual/en/ref.exif.php + * Show Exif data, on by default if available. + * Requires PHP's Exif extension: http://www.php.net/manual/en/ref.exif.php * * @note FOR WINDOWS USERS: - * To enable EXIF functions, add the following lines to the "Windows + * To enable Exif functions, add the following lines to the "Windows * extensions" section of php.ini: * @code{.ini} * extension=extensions/php_mbstring.dll @@ -547,22 +540,36 @@ $wgUpdateCompatibleMetadata = false; */ $wgUseSharedUploads = false; -/** Full path on the web server where shared uploads can be found */ +/** + * Full path on the web server where shared uploads can be found + */ $wgSharedUploadPath = "http://commons.wikimedia.org/shared/images"; -/** Fetch commons image description pages and display them on the local wiki? */ +/** + * Fetch commons image description pages and display them on the local wiki? + */ $wgFetchCommonsDescriptions = false; -/** Path on the file system where shared uploads can be found. */ +/** + * Path on the file system where shared uploads can be found. + */ $wgSharedUploadDirectory = "/var/www/wiki3/images"; -/** DB name with metadata about shared directory. Set this to false if the uploads do not come from a wiki. */ +/** + * DB name with metadata about shared directory. + * Set this to false if the uploads do not come from a wiki. + */ $wgSharedUploadDBname = false; -/** Optional table prefix used in database. */ +/** + * Optional table prefix used in database. + */ $wgSharedUploadDBprefix = ''; -/** Cache shared metadata in memcached. Don't do this if the commons wiki is in a different memcached domain */ +/** + * Cache shared metadata in memcached. + * Don't do this if the commons wiki is in a different memcached domain + */ $wgCacheSharedUploads = true; /** @@ -599,6 +606,27 @@ $wgCopyUploadsFromSpecialUpload = false; $wgCopyUploadProxy = false; /** + * Different timeout for upload by url + * This could be useful since when fetching large files, you may want a + * timeout longer than the default $wgHTTPTimeout. False means fallback + * to default. + * + * @since 1.22 + */ +$wgCopyUploadTimeout = false; + +/** + * Different timeout for upload by url when run as a background job + * This could be useful since when fetching large files via job queue, + * you may want a different timeout, especially because there is no + * http request being kept alive. + * + * false means fallback to $wgCopyUploadTimeout. + * @since 1.22 + */ +$wgCopyUploadAsyncTimeout = false; + +/** * Max size for uploads, in bytes. If not set to an array, applies to all * uploads. If set to an array, per upload type maximums can be set, using the * file and url keys. If the * key is set this value will be used as maximum @@ -613,7 +641,6 @@ $wgCopyUploadProxy = false; * @endcode * Sets the maximum for all uploads to 250 kB except for upload-by-url, which * will have a maximum of 500 kB. - * */ $wgMaxUploadSize = 1024 * 1024 * 100; # 100MB @@ -648,6 +675,7 @@ $wgUploadMissingFileUrl = false; * @endcode */ $wgThumbnailScriptPath = false; + /** * @see $wgThumbnailScriptPath */ @@ -699,7 +727,7 @@ $wgFileExtensions = array( 'png', 'gif', 'jpg', 'jpeg' ); * Files with these extensions will never be allowed as uploads. * An array of file extensions to blacklist. You should append to this array * if you want to blacklist additional files. - * */ + */ $wgFileBlacklist = array( # HTML may contain cookie-stealing JavaScript and web bugs 'html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', @@ -826,15 +854,25 @@ $wgContentHandlers = array( * Use Image Magick instead of PHP builtin functions. */ $wgUseImageMagick = false; -/** The convert command shipped with ImageMagick */ + +/** + * The convert command shipped with ImageMagick + */ $wgImageMagickConvertCommand = '/usr/bin/convert'; -/** The identify command shipped with ImageMagick */ + +/** + * The identify command shipped with ImageMagick + */ $wgImageMagickIdentifyCommand = '/usr/bin/identify'; -/** Sharpening parameter to ImageMagick */ +/** + * Sharpening parameter to ImageMagick + */ $wgSharpenParameter = '0x0.4'; -/** Reduction in linear dimensions below which sharpening will be enabled */ +/** + * Reduction in linear dimensions below which sharpening will be enabled + */ $wgSharpenReductionThreshold = 0.85; /** @@ -857,15 +895,15 @@ $wgImageMagickTempDir = false; */ $wgCustomConvertCommand = false; -/** used for lossless jpeg rotation +/** + * used for lossless jpeg rotation * * @since 1.21 - * **/ + */ $wgJpegTran = '/usr/bin/jpegtran'; - /** - * Some tests and extensions use exiv2 to manipulate the EXIF metadata in some + * Some tests and extensions use exiv2 to manipulate the Exif metadata in some * image formats. */ $wgExiv2Command = '/usr/bin/exiv2'; @@ -889,16 +927,23 @@ $wgSVGConverters = array( 'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ), ); -/** Pick a converter defined in $wgSVGConverters */ +/** + * Pick a converter defined in $wgSVGConverters + */ $wgSVGConverter = 'ImageMagick'; -/** If not in the executable PATH, specify the SVG converter path. */ +/** + * If not in the executable PATH, specify the SVG converter path. + */ $wgSVGConverterPath = ''; -/** Don't scale a SVG larger than this */ +/** + * Don't scale a SVG larger than this + */ $wgSVGMaxSize = 2048; -/** Don't read SVG metadata beyond this point. +/** + * Don't read SVG metadata beyond this point. * Default is 1024*256 bytes */ $wgSVGMetadataCutoff = 262144; @@ -930,6 +975,7 @@ $wgAllowTitlesInSVG = false; * 12.5 million pixels or 3500x3500. */ $wgMaxImageArea = 1.25e7; + /** * Force thumbnailing of animated GIFs above this size to a single * frame instead of an animated thumbnail. As of MW 1.17 this limit @@ -937,6 +983,7 @@ $wgMaxImageArea = 1.25e7; * It probably makes sense to keep this equal to $wgMaxImageArea. */ $wgMaxAnimatedGifArea = 1.25e7; + /** * Browsers don't support TIFF inline generally... * For inline display, we need to convert to PNG or JPEG. @@ -987,7 +1034,9 @@ $wgGenerateThumbnailOnParse = true; */ $wgShowArchiveThumbnails = true; -/** Obsolete, always true, kept for compatibility with extensions */ +/** + * Obsolete, always true, kept for compatibility with extensions + */ $wgUseImageResize = true; /** @@ -1053,29 +1102,31 @@ $wgAntivirusSetup = array( ), ); -/** Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected. */ +/** + * Determines if a failed virus scan (AV_SCAN_FAILED) will cause the file to be rejected. + */ $wgAntivirusRequired = true; -/** Determines if the mime type of uploaded files should be checked */ +/** + * Determines if the mime type of uploaded files should be checked + */ $wgVerifyMimeType = true; -/** Sets the mime type definition file to use by MimeMagic.php. */ +/** + * Sets the mime type definition file to use by MimeMagic.php. + * Set to null, to use built-in defaults only. + * example: $wgMimeTypeFile = '/etc/mime.types'; + */ $wgMimeTypeFile = 'includes/mime.types'; -#$wgMimeTypeFile = '/etc/mime.types'; -#$wgMimeTypeFile = null; #use built-in defaults only. - -/** Sets the mime type info file to use by MimeMagic.php. */ -$wgMimeInfoFile = 'includes/mime.info'; -#$wgMimeInfoFile = null; #use built-in defaults only. /** - * Switch for loading the FileInfo extension by PECL at runtime. - * This should be used only if fileinfo is installed as a shared object - * or a dynamic library. + * Sets the mime type info file to use by MimeMagic.php. + * Set to null, to use built-in defaults only. */ -$wgLoadFileinfoExtension = false; +$wgMimeInfoFile = 'includes/mime.info'; -/** Sets an external mime detector program. The command must print only +/** + * Sets an external mime detector program. The command must print only * the mime type to standard output. * The name of the file to process will be appended to the command given here. * If not set or NULL, mime_content_type will be used if available. @@ -1145,6 +1196,7 @@ $wgGalleryOptions = array( 'imageHeight' => 120, // Height of the cells containing images in galleries (in "px") 'captionLength' => 25, // Length of caption to truncate (in characters) 'showBytes' => true, // Show the filesize in bytes in categories + 'mode' => 'traditional', ); /** @@ -1173,25 +1225,26 @@ $wgResponsiveImages = true; * @name DJVU settings * @{ */ + /** * Path of the djvudump executable * Enable this and $wgDjvuRenderer to enable djvu rendering + * example: $wgDjvuDump = 'djvudump'; */ -# $wgDjvuDump = 'djvudump'; $wgDjvuDump = null; /** * Path of the ddjvu DJVU renderer * Enable this and $wgDjvuDump to enable djvu rendering + * example: $wgDjvuRenderer = 'ddjvu'; */ -# $wgDjvuRenderer = 'ddjvu'; $wgDjvuRenderer = null; /** * Path of the djvutxt DJVU text extraction utility * Enable this and $wgDjvuDump to enable text layer extraction from djvu files + * example: $wgDjvuTxt = 'djvutxt'; */ -# $wgDjvuTxt = 'djvutxt'; $wgDjvuTxt = null; /** @@ -1216,10 +1269,12 @@ $wgDjvuToXML = null; * Set this to false to output the ppm file directly. */ $wgDjvuPostProcessor = 'pnmtojpeg'; + /** * File extension for the DJVU post processor output */ $wgDjvuOutputExtension = 'jpg'; + /** @} */ # end of DJvu } /** @} */ # end of file uploads } @@ -1334,7 +1389,8 @@ $wgAllowHTMLEmail = false; $wgEnotifFromEditor = false; // TODO move UPO to preferences probably ? -# If set to true, users get a corresponding option in their preferences and can choose to enable or disable at their discretion +# If set to true, users get a corresponding option in their preferences and can choose to +# enable or disable at their discretion # If set to false, the corresponding input form on the user preference page is suppressed # It call this to be a "user-preferences-option (UPO)" @@ -1400,33 +1456,61 @@ $wgEnotifUseRealName = false; */ $wgUsersNotifiedOnAllChanges = array(); - /** @} */ # end of email settings /************************************************************************//** * @name Database settings * @{ */ -/** Database host name or IP address */ + +/** + * Database host name or IP address + */ $wgDBserver = 'localhost'; -/** Database port number (for PostgreSQL) */ + +/** + * Database port number (for PostgreSQL) + */ $wgDBport = 5432; -/** Name of the database */ + +/** + * Name of the database + */ $wgDBname = 'my_wiki'; -/** Database username */ + +/** + * Database username + */ $wgDBuser = 'wikiuser'; -/** Database user's password */ + +/** + * Database user's password + */ $wgDBpassword = ''; -/** Database type */ + +/** + * Database type + */ $wgDBtype = 'mysql'; -/** Whether to use SSL in DB connection. */ + +/** + * Whether to use SSL in DB connection. + */ $wgDBssl = false; -/** Whether to use compression in DB connection. */ + +/** + * Whether to use compression in DB connection. + */ $wgDBcompress = false; -/** Separate username for maintenance tasks. Leave as null to use the default. */ +/** + * Separate username for maintenance tasks. Leave as null to use the default. + */ $wgDBadminuser = null; -/** Separate password for maintenance tasks. Leave as null to use the default. */ + +/** + * Separate password for maintenance tasks. Leave as null to use the default. + */ $wgDBadminpassword = null; /** @@ -1437,9 +1521,23 @@ $wgDBadminpassword = null; */ $wgSearchType = null; -/** Table name prefix */ +/** + * Alternative search types + * Sometimes you want to support multiple search engines for testing. This + * allows users to select their search engine of choice via url parameters + * to Special:Search and the action=search API. If using this, there's no + * need to add $wgSearchType to it, that is handled automatically. + */ +$wgSearchTypeAlternatives = null; + +/** + * Table name prefix + */ $wgDBprefix = ''; -/** MySQL table options to use during installation or update */ + +/** + * MySQL table options to use during installation or update + */ $wgDBTableOptions = 'ENGINE=InnoDB'; /** @@ -1450,10 +1548,14 @@ $wgDBTableOptions = 'ENGINE=InnoDB'; */ $wgSQLMode = ''; -/** Mediawiki schema */ +/** + * Mediawiki schema + */ $wgDBmwschema = 'mediawiki'; -/** To override default SQLite data directory ($docroot/../data) */ +/** + * To override default SQLite data directory ($docroot/../data) + */ $wgSQLiteDataDir = ''; /** @@ -1487,9 +1589,14 @@ $wgAllDBsAreLocalhost = false; */ $wgSharedDB = null; -/** @see $wgSharedDB */ +/** + * @see $wgSharedDB + */ $wgSharedPrefix = false; -/** @see $wgSharedDB */ + +/** + * @see $wgSharedDB + */ $wgSharedTables = array( 'user', 'user_properties' ); /** @@ -1509,11 +1616,11 @@ $wgSharedTables = array( 'user', 'user_properties' ); * - DBO_DEFAULT -- turns on DBO_TRX only if !$wgCommandLineMode (recommended) * - DBO_DEBUG -- equivalent of $wgDebugDumpSql * - DBO_TRX -- wrap entire request in a transaction - * - DBO_IGNORE -- ignore errors (not useful in LocalSettings.php) * - DBO_NOBUFFER -- turn off buffering (not useful in LocalSettings.php) * - DBO_PERSISTENT -- enables persistent database connections * - DBO_SSL -- uses SSL/TLS encryption in database connections, if available - * - DBO_COMPRESS -- uses internal compression in database connections, if available + * - DBO_COMPRESS -- uses internal compression in database connections, + * if available * * - max lag: (optional) Maximum replication lag before a slave will taken out of rotation * - max threads: (optional) Maximum number of running threads @@ -1552,10 +1659,14 @@ $wgDBservers = false; */ $wgLBFactoryConf = array( 'class' => 'LBFactory_Simple' ); -/** How long to wait for a slave to catch up to the master */ +/** + * How long to wait for a slave to catch up to the master + */ $wgMasterWaitTimeout = 10; -/** File to log database errors to */ +/** + * File to log database errors to + */ $wgDBerrorLog = false; /** @@ -1578,7 +1689,9 @@ $wgDBerrorLog = false; */ $wgDBerrorLogTZ = false; -/** When to give an error message */ +/** + * When to give an error message + */ $wgDBClusterTimeout = 10; /** @@ -1607,6 +1720,35 @@ $wgDBAvgStatusPoll = 2000; $wgDBmysql5 = false; /** + * Set true to enable Oracle DCRP (supported from 11gR1 onward) + * + * To use this feature set to true and use a datasource defined as + * POOLED (i.e. in tnsnames definition set server=pooled in connect_data + * block). + * + * Starting from 11gR1 you can use DCRP (Database Resident Connection + * Pool) that maintains established sessions and reuses them on new + * connections. + * + * Not completely tested, but it should fall back on normal connection + * in case the pool is full or the datasource is not configured as + * pooled. + * And the other way around; using oci_pconnect on a non pooled + * datasource should produce a normal connection. + * + * When it comes to frequent shortlived DB connections like with MW + * Oracle tends to s***. The problem is the driver connects to the + * database reasonably fast, but establishing a session takes time and + * resources. MW does not rely on session state (as it does not use + * features such as package variables) so establishing a valid session + * is in this case an unwanted overhead that just slows things down. + * + * @warning EXPERIMENTAL! + * + */ +$wgDBOracleDRCP = false; + +/** * Other wikis on this site, can be administered from a single developer * account. * Array numeric key => database name @@ -1619,13 +1761,11 @@ $wgLocalDatabases = array(); * show a more obvious warning. */ $wgSlaveLagWarning = 10; -/** @see $wgSlaveLagWarning */ -$wgSlaveLagCritical = 30; /** - * Use old names for change_tags indices. + * @see $wgSlaveLagWarning */ -$wgOldChangeTagsIndex = false; +$wgSlaveLagCritical = 30; /**@}*/ # End of DB settings } @@ -1637,8 +1777,8 @@ $wgOldChangeTagsIndex = false; /** * We can also compress text stored in the 'text' table. If this is set on, new * revisions will be compressed on page save if zlib support is available. Any - * compressed revisions will be decompressed on load regardless of this setting - * *but will not be readable at all* if zlib support is not available. + * compressed revisions will be decompressed on load regardless of this setting, + * but will not be readable at all* if zlib support is not available. */ $wgCompressRevisions = false; @@ -1701,23 +1841,36 @@ $wgRevisionCacheExpiry = 0; * @name Performance hacks and limits * @{ */ -/** Disable database-intensive features */ + +/** + * Disable database-intensive features + */ $wgMiserMode = false; -/** Disable all query pages if miser mode is on, not just some */ + +/** + * Disable all query pages if miser mode is on, not just some + */ $wgDisableQueryPages = false; -/** Number of rows to cache in 'querycache' table when miser mode is on */ + +/** + * Number of rows to cache in 'querycache' table when miser mode is on + */ $wgQueryCacheLimit = 1000; -/** Number of links to a page required before it is deemed "wanted" */ + +/** + * Number of links to a page required before it is deemed "wanted" + */ $wgWantedPagesThreshold = 1; -/** Enable slow parser functions */ + +/** + * Enable slow parser functions + */ $wgAllowSlowParserFunctions = false; -/** Allow schema updates */ -$wgAllowSchemaUpdates = true; /** - * Do DELETE/INSERT for link updates instead of incremental + * Allow schema updates */ -$wgUseDumbLinkUpdate = false; +$wgAllowSchemaUpdates = true; /** * Anti-lock flags - bitfield @@ -1886,11 +2039,15 @@ $wgObjectCacheSessionExpiry = 3600; */ $wgSessionHandler = null; -/** If enabled, will send MemCached debugging information to $wgDebugLogFile */ +/** + * If enabled, will send MemCached debugging information to $wgDebugLogFile + */ $wgMemCachedDebug = false; -/** The list of MemCached servers and port numbers */ -$wgMemCachedServers = array( '127.0.0.1:11000' ); +/** + * The list of MemCached servers and port numbers + */ +$wgMemCachedServers = array( '127.0.0.1:11211' ); /** * Use persistent connections to MemCached, which are shared across multiple @@ -1910,13 +2067,6 @@ $wgMemCachedTimeout = 500000; $wgUseLocalMessageCache = false; /** - * Defines format of local cache. - * - true: Serialized object - * - false: PHP source file (Warning - security risk) - */ -$wgLocalMessageCacheSerialized = true; - -/** * Instead of caching everything, only cache those messages which have * been customised in the site content language. This means that * MediaWiki:Foo/ja is ignored if MediaWiki:Foo doesn't exist. @@ -1952,7 +2102,9 @@ $wgLocalisationCacheConf = array( 'manualRecache' => false, ); -/** Allow client-side caching of pages */ +/** + * Allow client-side caching of pages + */ $wgCachePages = true; /** @@ -2038,7 +2190,8 @@ $wgUseGzip = false; */ $wgUseETag = false; -/** Clock skew or the one-second resolution of time() can occasionally cause cache +/** + * Clock skew or the one-second resolution of time() can occasionally cause cache * problems when the user requests two pages within a short period of time. This * variable adds a given number of seconds to vulnerable timestamps, thereby giving * a grace period. @@ -2077,13 +2230,18 @@ $wgInvalidateCacheOnLocalSettingsChange = true; */ $wgUseSquid = false; -/** If you run Squid3 with ESI support, enable this (default:false): */ +/** + * If you run Squid3 with ESI support, enable this (default:false): + */ $wgUseESI = false; -/** Send X-Vary-Options header for better caching (requires patched Squid) */ +/** + * Send X-Vary-Options header for better caching (requires patched Squid) + */ $wgUseXVO = false; -/** Add X-Forwarded-Proto to the Vary and X-Vary-Options headers for API +/** + * Add X-Forwarded-Proto to the Vary and X-Vary-Options headers for API * requests and RSS/Atom feeds. Use this if you have an SSL termination setup * and need to split the cache between HTTP and HTTPS for API requests, * feed requests and HTTP redirect responses in order to prevent cache @@ -2131,7 +2289,9 @@ $wgSquidServers = array(); */ $wgSquidServersNoPurge = array(); -/** Maximum number of titles to purge in any one client operation */ +/** + * Maximum number of titles to purge in any one client operation + */ $wgMaxSquidPurgeTitles = 400; /** @@ -2163,12 +2323,12 @@ $wgSquidPurgeUseHostHeader = true; * Each key in this array is a regular expression to match against the purged * URL, or an empty string to match all URLs. The purged URL is matched against * the regexes in the order specified, and the first rule whose regex matches - * is used. + * is used, all remaining rules will thus be ignored. * - * Example configuration to send purges for upload.wikimedia.org to one + * @par Example configuration to send purges for upload.wikimedia.org to one * multicast group and all other purges to another: * @code - * $wgHTCPMulticastRouting = array( + * $wgHTCPRouting = array( * '|^https?://upload\.wikimedia\.org|' => array( * 'host' => '239.128.0.113', * 'port' => 4827, @@ -2180,11 +2340,44 @@ $wgSquidPurgeUseHostHeader = true; * ); * @endcode * - * @since 1.20 + * You can also pass an array of hosts to send purges too. This is useful when + * you have several multicast groups or unicast address that should receive a + * given purge. Multiple hosts support was introduced in MediaWiki 1.22. + * + * @par Example of sending purges to multiple hosts: + * @code + * $wgHTCPRouting = array( + * '' => array( + * // Purges to text caches using multicast + * array( 'host' => '239.128.0.114', 'port' => '4827' ), + * // Purges to a hardcoded list of caches + * array( 'host' => '10.88.66.1', 'port' => '4827' ), + * array( 'host' => '10.88.66.2', 'port' => '4827' ), + * array( 'host' => '10.88.66.3', 'port' => '4827' ), + * ), + * ); + * @endcode + * + * @since 1.22 + * + * $wgHTCPRouting replaces $wgHTCPMulticastRouting that was introduced in 1.20. + * For back compatibility purposes, whenever its array is empty + * $wgHTCPMutlicastRouting will be used as a fallback if it not null. * * @see $wgHTCPMulticastTTL */ -$wgHTCPMulticastRouting = array(); +$wgHTCPRouting = array(); + +/** + * @deprecated since 1.22, please use $wgHTCPRouting instead. + * + * Whenever this is set and $wgHTCPRouting evaluates to false, $wgHTCPRouting + * will be set to this value. + * This is merely for back compatibility. + * + * @since 1.20 + */ +$wgHTCPMulticastRouting = null; /** * HTCP multicast address. Set this to a multicast IP address to enable HTCP. @@ -2192,29 +2385,34 @@ $wgHTCPMulticastRouting = array(); * Note that MediaWiki uses the old non-RFC compliant HTCP format, which was * present in the earliest Squid implementations of the protocol. * - * This setting is DEPRECATED in favor of $wgHTCPMulticastRouting , and kept - * for backwards compatibility only. If $wgHTCPMulticastRouting is set, this - * setting is ignored. If $wgHTCPMulticastRouting is not set and this setting - * is, it is used to populate $wgHTCPMulticastRouting. + * This setting is DEPRECATED in favor of $wgHTCPRouting , and kept for + * backwards compatibility only. If $wgHTCPRouting is set, this setting is + * ignored. If $wgHTCPRouting is not set and this setting is, it is used to + * populate $wgHTCPRouting. * - * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting + * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting and since 1.22 in + * favor of $wgHTCPRouting. */ $wgHTCPMulticastAddress = false; /** * HTCP multicast port. - * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting + * @deprecated since 1.20 in favor of $wgHTCPMulticastRouting and since 1.22 in + * favor of $wgHTCPRouting. + * * @see $wgHTCPMulticastAddress */ $wgHTCPPort = 4827; /** * HTCP multicast TTL. - * @see $wgHTCPMulticastRouting + * @see $wgHTCPRouting */ $wgHTCPMulticastTTL = 1; -/** Should forwarded Private IPs be accepted? */ +/** + * Should forwarded Private IPs be accepted? + */ $wgUsePrivateIPs = false; /** @} */ # end of HTTP proxy settings @@ -2258,13 +2456,19 @@ $wgLangObjCacheSize = 10; */ $wgGrammarForms = array(); -/** Treat language links as magic connectors, not inline links */ +/** + * Treat language links as magic connectors, not inline links + */ $wgInterwikiMagic = true; -/** Hide interlanguage links from the sidebar */ +/** + * Hide interlanguage links from the sidebar + */ $wgHideInterlanguageLinks = false; -/** List of language names or overrides for default names in Names.php */ +/** + * List of language names or overrides for default names in Names.php + */ $wgExtraLanguageNames = array(); /** @@ -2364,7 +2568,8 @@ $wgBrowserBlackList = array( '/^Mozilla\/4\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/', /** - * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, Þ to <THORN> and Ð to <ETH> + * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, + * Þ to <THORN> and Ð to <ETH> * * Known useragents: * - Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC) @@ -2372,7 +2577,7 @@ $wgBrowserBlackList = array( * - Mozilla/4.0 (compatible; MSIE 5.23; Mac_PowerPC) * - [...] * - * @link http://en.wikipedia.org/w/index.php?title=User%3A%C6var_Arnfj%F6r%F0_Bjarmason%2Ftestme&diff=12356041&oldid=12355864 + * @link http://en.wikipedia.org/w/index.php?diff=12356041&oldid=12355864 * @link http://en.wikipedia.org/wiki/Template%3AOS9 */ '/^Mozilla\/4\.0 \(compatible; MSIE \d+\.\d+; Mac_PowerPC\)/', @@ -2403,6 +2608,7 @@ $wgLegacySchemaConversion = false; * the interface is set to English. */ $wgAmericanDates = false; + /** * For Hindi and Arabic use local numerals instead of Western style (0-9) * numerals in interface. @@ -2425,16 +2631,24 @@ $wgMsgCacheExpiry = 86400; */ $wgMaxMsgCacheEntrySize = 10000; -/** Whether to enable language variant conversion. */ +/** + * Whether to enable language variant conversion. + */ $wgDisableLangConversion = false; -/** Whether to enable language variant conversion for links. */ +/** + * Whether to enable language variant conversion for links. + */ $wgDisableTitleConversion = false; -/** Whether to enable canonical language links in meta data. */ +/** + * Whether to enable canonical language links in meta data. + */ $wgCanonicalLanguageLinks = true; -/** Default variant code, if false, the default will be the language code */ +/** + * Default variant code, if false, the default will be the language code + */ $wgDefaultLanguageVariant = false; /** @@ -2538,52 +2752,44 @@ $wgLocalTZoffset = null; * @{ */ -/** The default Content-Type header. */ -$wgMimeType = 'text/html'; - -/** - * The content type used in script tags. This is mostly going to be ignored if - * $wgHtml5 is true, at least for actual HTML output, since HTML5 doesn't - * require a MIME type for JavaScript or CSS (those are the default script and - * style languages). - */ -$wgJsMimeType = 'text/javascript'; - /** - * The HTML document type. Ignored if $wgHtml5 is true, since <!DOCTYPE html> - * doesn't actually have a doctype part to put this variable's contents in. + * The default Content-Type header. */ -$wgDocType = '-//W3C//DTD XHTML 1.0 Transitional//EN'; +$wgMimeType = 'text/html'; /** - * The URL of the document type declaration. Ignored if $wgHtml5 is true, - * since HTML5 has no DTD, and <!DOCTYPE html> doesn't actually have a DTD part - * to put this variable's contents in. + * Previously used as content type in HTML script tags. This is now ignored since + * HTML5 doesn't require a MIME type for script tags (javascript is the default). + * It was also previously used by RawAction to determine the ctype query parameter + * value that will result in a javascript response. + * @deprecated since 1.22 */ -$wgDTD = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'; +$wgJsMimeType = null; /** - * The default xmlns attribute. Ignored if $wgHtml5 is true (or it's supposed - * to be), since we don't currently support XHTML5, and in HTML5 (i.e., served - * as text/html) the attribute has no effect, so why bother? + * The default xmlns attribute. The option to define this has been removed. + * The value of this variable is no longer used by core and is set to a fixed + * value in Setup.php for compatibility with extensions that depend on the value + * of this variable being set. Such a dependency however is deprecated. + * @deprecated since 1.22 */ -$wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml'; +$wgXhtmlDefaultNamespace = null; /** - * Should we output an HTML5 doctype? If false, use XHTML 1.0 Transitional - * instead, and disable HTML5 features. This may eventually be removed and set - * to always true. If it's true, a number of other settings will be irrelevant - * and have no effect. + * Previously used to determine if we should output an HTML5 doctype. + * This is no longer used as we always output HTML5 now. For compatibility with + * extensions that still check the value of this config it's value is now forced + * to true by Setup.php. + * @deprecated since 1.22 */ $wgHtml5 = true; /** * Defines the value of the version attribute in the <html> tag, if any. - * This is ignored if $wgHtml5 is false. If $wgAllowRdfaAttributes and - * $wgHtml5 are both true, and this evaluates to boolean false (like if it's - * left at the default null value), it will be auto-initialized to the correct - * value for RDFa+HTML5. As such, you should have no reason to ever actually - * set this to anything. + * If $wgAllowRdfaAttributes is true, and this evaluates to boolean false + * (like if it's left at the default null value), it will be auto-initialized + * to the correct value for RDFa+HTML5. As such, you should have no reason to + * ever actually set this to anything. */ $wgHtml5Version = null; @@ -2594,7 +2800,7 @@ $wgHtml5Version = null; $wgAllowRdfaAttributes = false; /** - * Enabled HTML5 microdata attributes for use in wikitext, if $wgHtml5 is also true. + * Enabled HTML5 microdata attributes for use in wikitext. */ $wgAllowMicrodataAttributes = false; @@ -2628,8 +2834,7 @@ $wgWellFormedXml = true; * Normally we wouldn't have to define this in the root "<html>" * element, but IE needs it there in some circumstances. * - * This is ignored if $wgHtml5 is true, for the same reason as - * $wgXhtmlDefaultNamespace. + * This is ignored if $wgMimeType is set to a non-XML mimetype. */ $wgXhtmlNamespaces = array(); @@ -2679,34 +2884,11 @@ $wgDefaultSkin = 'vector'; * remove from the .../skins/ directory */ $wgSkipSkin = ''; -/** Array for more like $wgSkipSkin. */ -$wgSkipSkins = array(); /** - * Optionally, we can specify a stylesheet to use for media="handheld". - * This is recognized by some, but not all, handheld/mobile/PDA browsers. - * If left empty, compliant handheld browsers won't pick up the skin - * stylesheet, which is specified for 'screen' media. - * - * Can be a complete URL, base-relative path, or $wgStylePath-relative path. - * Try 'chick/main.css' to apply the Chick styles to the MonoBook HTML. - * - * Will also be switched in when 'handheld=yes' is added to the URL, like - * the 'printable=yes' mode for print media. + * Array for more like $wgSkipSkin. */ -$wgHandheldStyle = false; - -/** - * If set, 'screen' and 'handheld' media specifiers for stylesheets are - * transformed such that they apply to the iPhone/iPod Touch Mobile Safari, - * which doesn't recognize 'handheld' but does support media queries on its - * screen size. - * - * Consider only using this if you have a *really good* handheld stylesheet, - * as iPhone users won't have any way to disable it and use the "grown-up" - * styles instead. - */ -$wgHandheldForIPhone = false; +$wgSkipSkins = array(); /** * Allow user Javascript page? @@ -2729,10 +2911,14 @@ $wgAllowUserCss = false; */ $wgAllowUserCssPrefs = true; -/** Use the site's Javascript page? */ +/** + * Use the site's Javascript page? + */ $wgUseSiteJs = true; -/** Use the site's Cascading Style Sheets (CSS)? */ +/** + * Use the site's Cascading Style Sheets (CSS)? + */ $wgUseSiteCss = true; /** @@ -2771,7 +2957,6 @@ $wgEditPageFrameOptions = 'DENY'; * - 'SAMEORIGIN': Allow framing by pages on the same domain. * - false: Allow all framing. */ - $wgApiFrameOptions = 'DENY'; /** @@ -2807,9 +2992,10 @@ $wgExperimentalHtmlIds = false; * The value should be either a string or an array. If it is a string it will be output * directly as html, however some skins may choose to ignore it. An array is the preferred format * for the icon, the following keys are used: - * - src: An absolute url to the image to use for the icon, this is recommended + * - src: An absolute url to the image to use for the icon, this is recommended * but not required, however some skins will ignore icons without an image - * - url: The url to use in the a element around the text or icon, if not set an a element will not be outputted + * - url: The url to use in the a element around the text or icon, if not set an a element will + * not be outputted * - alt: This is the text form of the icon, it will be displayed without an image in * skins like Modern or if src is not set, and will otherwise be used as * the alt="" for the image. This key is required. @@ -2851,7 +3037,7 @@ $wgVectorUseSimpleSearch = true; * - true = use an icon watch/unwatch button * - false = use watch/unwatch text link */ -$wgVectorUseIconWatch = false; +$wgVectorUseIconWatch = true; /** * Display user edit counts in various prominent places. @@ -3052,8 +3238,10 @@ $wgLegacyJavaScriptGlobals = true; * * If set to a negative number, ResourceLoader will assume there is no query * string length limit. + * + * Defaults to a value based on php configuration. */ -$wgResourceLoaderMaxQueryLength = -1; +$wgResourceLoaderMaxQueryLength = false; /** * If set to true, JavaScript modules loaded from wiki pages will be parsed @@ -3081,6 +3269,59 @@ $wgResourceLoaderValidateStaticJS = false; */ $wgResourceLoaderExperimentalAsyncLoading = false; +/** + * Global LESS variables. An associative array binding variable names to CSS + * string values. + * + * Because the hashed contents of this array are used to construct the cache key + * that ResourceLoader uses to look up LESS compilation results, updating this + * array can be used to deliberately invalidate the set of cached results. + * + * @par Example: + * @code + * $wgResourceLoaderLESSVars = array( + * 'baseFontSize' => '1em', + * 'smallFontSize' => '0.75em', + * 'WikimediaBlue' => '#006699', + * ); + * @endcode + * @since 1.22 + */ +$wgResourceLoaderLESSVars = array(); + +/** + * Custom LESS functions. An associative array mapping function name to PHP + * callable. + * + * Changes to LESS functions do not trigger cache invalidation. If you update + * the behavior of a LESS function and need to invalidate stale compilation + * results, you can touch one of values in $wgResourceLoaderLESSVars, as + * documented above. + * + * @since 1.22 + */ +$wgResourceLoaderLESSFunctions = array( + 'embeddable' => 'ResourceLoaderLESSFunctions::embeddable', + 'embed' => 'ResourceLoaderLESSFunctions::embed', +); + +/** + * Default import paths for LESS modules. LESS files referenced in @import + * statements will be looked up here first, and relative to the importing file + * second. To avoid collisions, it's important for the LESS files in these + * directories to have a common, predictable file name prefix. + * + * Extensions need not (and should not) register paths in + * $wgResourceLoaderLESSImportPaths. The import path includes the path of the + * currently compiling LESS file, which allows each extension to freely import + * files from its own tree. + * + * @since 1.22 + */ +$wgResourceLoaderLESSImportPaths = array( + "$IP/resources/mediawiki.less/", +); + /** @} */ # End of resource loader settings } /*************************************************************************//** @@ -3167,7 +3408,8 @@ $wgNamespaceAliases = array(); * - []{}|# Are needed for link syntax, never enable these * - <> Causes problems with HTML escaping, don't use * - % Enabled by default, minor problems with path to query rewrite rules, see below - * - + Enabled by default, but doesn't work with path to query rewrite rules, corrupted by apache + * - + Enabled by default, but doesn't work with path to query rewrite rules, + * corrupted by apache * - ? Enabled by default, but doesn't work with path to PATH_INFO rewrites * * All three of these punctuation problems can be avoided by using an alias, @@ -3201,6 +3443,7 @@ $wgInterwikiExpiry = 10800; * @name Interwiki caching settings. * @{ */ + /** *$wgInterwikiCache specifies path to constant database file. * @@ -3215,6 +3458,7 @@ $wgInterwikiExpiry = 10800; * data layout. */ $wgInterwikiCache = false; + /** * Specify number of domains to check for messages. * - 1: Just wiki(db)-level @@ -3222,10 +3466,12 @@ $wgInterwikiCache = false; * - 3: site levels */ $wgInterwikiScopes = 3; + /** - * $wgInterwikiFallbackSite - if unable to resolve from cache + * Fallback site, if unable to resolve from cache */ $wgInterwikiFallbackSite = 'wiki'; + /** @} */ # end of Interwiki caching settings. /** @@ -3270,7 +3516,8 @@ $wgCapitalLinks = true; */ $wgCapitalLinkOverrides = array(); -/** Which namespaces should support subpages? +/** + * Which namespaces should support subpages? * See Language.php for a list of namespaces. */ $wgNamespacesWithSubpages = array( @@ -3313,7 +3560,7 @@ $wgMaxRedirects = 1; * As of now, this only checks special pages. Redirects to pages in * other namespaces cannot be invalidated by this variable. */ -$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' ); +$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk', 'Redirect' ); /** @} */ # End of title and interwiki settings } @@ -3349,7 +3596,9 @@ $wgParserConf = array( #'preprocessorClass' => 'Preprocessor_Hash', ); -/** Maximum indent level of toc. */ +/** + * Maximum indent level of toc. + */ $wgMaxTocLevel = 999; /** @@ -3377,25 +3626,41 @@ $wgMaxGeneratedPPNodeCount = 1000000; */ $wgMaxTemplateDepth = 40; -/** @see $wgMaxTemplateDepth */ +/** + * @see $wgMaxTemplateDepth + */ $wgMaxPPExpandDepth = 40; -/** The external URL protocols */ +/** + * The external URL protocols + */ $wgUrlProtocols = array( 'http://', 'https://', 'ftp://', + 'ftps://', // If we allow ftp:// we should allow the secure version. + 'ssh://', + 'sftp://', // SFTP > FTP 'irc://', 'ircs://', // @bug 28503 + 'xmpp:', // Another open communication protocol + 'sip:', + 'sips:', 'gopher://', 'telnet://', // Well if we're going to support the above.. -ævar 'nntp://', // @bug 3808 RFC 1738 'worldwind://', 'mailto:', + 'tel:', // If we can make emails linkable, why not phone numbers? + 'sms:', // Likewise this is standardized too 'news:', 'svn://', 'git://', 'mms://', + 'bitcoin:', // Even registerProtocolHandler whitelists this along with mailto: + 'magnet:', // No reason to reject torrents over magnet: when they're allowed over http:// + 'urn:', // Allow URNs to be used in Microdata/RDFa <link ... href="urn:...">s + 'geo:', // urls define geo locations, they're useful in Microdata/RDFa and for coordinates '//', // for protocol-relative URLs ); @@ -3404,7 +3669,9 @@ $wgUrlProtocols = array( */ $wgCleanSignatures = true; -/** Whether to allow inline image pointing to other websites */ +/** + * Whether to allow inline image pointing to other websites + */ $wgAllowExternalImages = false; /** @@ -3421,7 +3688,8 @@ $wgAllowExternalImages = false; */ $wgAllowExternalImagesFrom = ''; -/** If $wgAllowExternalImages is false, you can allow an on-wiki +/** + * If $wgAllowExternalImages is false, you can allow an on-wiki * whitelist of regular expression fragments to match the image URL * against. If the image matches one of the regular expression fragments, * The image will be displayed. @@ -3457,15 +3725,30 @@ $wgAllowImageTag = false; * 'extension=tidy.so' to php.ini. */ $wgUseTidy = false; -/** @see $wgUseTidy */ + +/** + * @see $wgUseTidy + */ $wgAlwaysUseTidy = false; -/** @see $wgUseTidy */ + +/** + * @see $wgUseTidy + */ $wgTidyBin = 'tidy'; -/** @see $wgUseTidy */ + +/** + * @see $wgUseTidy + */ $wgTidyConf = $IP . '/includes/tidy.conf'; -/** @see $wgUseTidy */ + +/** + * @see $wgUseTidy + */ $wgTidyOpts = ''; -/** @see $wgUseTidy */ + +/** + * @see $wgUseTidy + */ $wgTidyInternal = extension_loaded( 'tidy' ); /** @@ -3474,7 +3757,8 @@ $wgTidyInternal = extension_loaded( 'tidy' ); */ $wgDebugTidy = false; -/** Allow raw, unchecked HTML in "<html>...</html>" sections. +/** + * Allow raw, unchecked HTML in "<html>...</html>" sections. * THIS IS VERY DANGEROUS on a publicly editable site, so USE wgGroupPermissions * TO RESTRICT EDITING to only those that you trust */ @@ -3518,8 +3802,9 @@ $wgNoFollowDomainExceptions = array(); $wgAllowDisplayTitle = true; /** - * For consistency, restrict DISPLAYTITLE to titles that normalize to the same - * canonical DB key. + * For consistency, restrict DISPLAYTITLE to text that normalizes to the same + * canonical DB key. Also disallow some inline CSS rules like display: none; + * which can cause the text to be hidden or unselectable. */ $wgRestrictDisplayTitle = true; @@ -3536,12 +3821,13 @@ $wgExpensiveParserFunctionLimit = 100; $wgPreprocessorCacheThreshold = 1000; /** - * Enable interwiki transcluding. Only when iw_trans=1. + * Enable interwiki transcluding. Only when iw_trans=1 in the interwiki table. */ $wgEnableScaryTranscluding = false; /** - * (see next option $wgGlobalDatabase). + * Expiry time for transcluded templates cached in transcache database table. + * Only used $wgEnableInterwikiTranscluding is set to true. */ $wgTranscludeCacheExpiry = 3600; @@ -3591,7 +3877,8 @@ $wgHitcounterUpdateFreq = 1; /** * How many days user must be idle before he is considered inactive. Will affect - * the number shown on Special:Statistics and Special:ActiveUsers special page. + * the number shown on Special:Statistics, Special:ActiveUsers, and the + * {{NUMBEROFACTIVEUSERS}} magic word in wikitext. * You might want to leave this as the default value, to provide comparable * numbers between different wikis. */ @@ -3604,7 +3891,9 @@ $wgActiveUserDays = 30; * @{ */ -/** For compatibility with old installations set to false */ +/** + * For compatibility with old installations set to false + */ $wgPasswordSalt = true; /** @@ -3647,7 +3936,7 @@ $wgReservedUsernames = array( 'ScriptImporter', // Default user name used by maintenance/importSiteScripts.php 'msg:double-redirect-fixer', // Automatic double redirect fix 'msg:usermessage-editor', // Default user for leaving user messages - 'msg:proxyblocker', // For Special:Blockme + 'msg:proxyblocker', // For $wgProxyList and Special:Blockme (removed in 1.22) ); /** @@ -3655,7 +3944,6 @@ $wgReservedUsernames = array( * preferences used by anonymous visitors and newly created accounts. * For instance, to disable section editing links: * $wgDefaultUserOptions ['editsection'] = 0; - * */ $wgDefaultUserOptions = array( 'ccmeonemails' => 0, @@ -3673,8 +3961,6 @@ $wgDefaultUserOptions = array( 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 0, 'extendwatchlist' => 0, - 'externaldiff' => 0, - 'externaleditor' => 0, 'fancysig' => 0, 'forceeditsummary' => 0, 'gender' => 'unknown', @@ -3691,14 +3977,12 @@ $wgDefaultUserOptions = array( 'numberheadings' => 0, 'previewonfirst' => 0, 'previewontop' => 1, - 'quickbar' => 5, 'rcdays' => 7, 'rclimit' => 50, 'rememberpassword' => 0, 'rows' => 25, 'searchlimit' => 20, 'showhiddencats' => 0, - 'showjumplinks' => 1, 'shownumberswatching' => 1, 'showtoc' => 1, 'showtoolbar' => 1, @@ -3708,6 +3992,7 @@ $wgDefaultUserOptions = array( 'underline' => 2, 'uselivepreview' => 0, 'usenewrc' => 0, + 'vector-simplesearch' => 1, 'watchcreations' => 0, 'watchdefault' => 0, 'watchdeletion' => 0, @@ -3720,9 +4005,13 @@ $wgDefaultUserOptions = array( 'watchlisthidepatrolled' => 0, 'watchmoves' => 0, 'wllimit' => 250, + 'useeditwarning' => 1, + 'prefershttps' => 1, ); -/** An array of preferences to not show for the user */ +/** + * An array of preferences to not show for the user + */ $wgHiddenPrefs = array(); /** @@ -3744,63 +4033,6 @@ $wgInvalidUsernameCharacters = '@'; $wgUserrightsInterwikiDelimiter = '@'; /** - * Use some particular type of external authentication. The specific - * authentication module you use will normally require some extra settings to - * be specified. - * - * null indicates no external authentication is to be used. Otherwise, - * $wgExternalAuthType must be the name of a non-abstract class that extends - * ExternalUser. - * - * Core authentication modules can be found in includes/extauth/. - */ -$wgExternalAuthType = null; - -/** - * Configuration for the external authentication. This may include arbitrary - * keys that depend on the authentication mechanism. For instance, - * authentication against another web app might require that the database login - * info be provided. Check the file where your auth mechanism is defined for - * info on what to put here. - */ -$wgExternalAuthConf = array(); - -/** - * When should we automatically create local accounts when external accounts - * already exist, if using ExternalAuth? Can have three values: 'never', - * 'login', 'view'. 'view' requires the external database to support cookies, - * and implies 'login'. - * - * TODO: Implement 'view' (currently behaves like 'login'). - */ -$wgAutocreatePolicy = 'login'; - -/** - * Policies for how each preference is allowed to be changed, in the presence - * of external authentication. The keys are preference keys, e.g., 'password' - * or 'emailaddress' (see Preferences.php et al.). The value can be one of the - * following: - * - * - local: Allow changes to this pref through the wiki interface but only - * apply them locally (default). - * - semiglobal: Allow changes through the wiki interface and try to apply them - * to the foreign database, but continue on anyway if that fails. - * - global: Allow changes through the wiki interface, but only let them go - * through if they successfully update the foreign database. - * - message: Allow no local changes for linked accounts; replace the change - * form with a message provided by the auth plugin, telling the user how to - * change the setting externally (maybe providing a link, etc.). If the auth - * plugin provides no message for this preference, hide it entirely. - * - * Accounts that are not linked to an external account are never affected by - * this setting. You may want to look at $wgHiddenPrefs instead. - * $wgHiddenPrefs supersedes this option. - * - * TODO: Implement message, global. - */ -$wgAllowPrefChange = array(); - -/** * This is to let user authenticate using https when they come from http. * Based on an idea by George Herbert on wikitech-l: * http://lists.wikimedia.org/pipermail/wikitech-l/2010-October/050039.html @@ -3808,13 +4040,6 @@ $wgAllowPrefChange = array(); */ $wgSecureLogin = false; -/** - * By default, keep users logged in via HTTPS when $wgSecureLogin is also - * true. Users opt-out of HTTPS when they login by de-selecting the checkbox. - * @since 1.21 - */ -$wgSecureLoginDefaultHTTPS = true; - /** @} */ # end user accounts } /************************************************************************//** @@ -3832,7 +4057,9 @@ $wgAutoblockExpiry = 86400; */ $wgBlockAllowsUTEdit = false; -/** Allow sysops to ban users from accessing Emailuser */ +/** + * Allow sysops to ban users from accessing Emailuser + */ $wgSysopEmailBans = true; /** @@ -3950,6 +4177,13 @@ $wgGroupPermissions['*']['edit'] = true; $wgGroupPermissions['*']['createpage'] = true; $wgGroupPermissions['*']['createtalk'] = true; $wgGroupPermissions['*']['writeapi'] = true; +$wgGroupPermissions['*']['editmyusercss'] = true; +$wgGroupPermissions['*']['editmyuserjs'] = true; +$wgGroupPermissions['*']['viewmywatchlist'] = true; +$wgGroupPermissions['*']['editmywatchlist'] = true; +$wgGroupPermissions['*']['viewmyprivateinfo'] = true; +$wgGroupPermissions['*']['editmyprivateinfo'] = true; +$wgGroupPermissions['*']['editmyoptions'] = true; #$wgGroupPermissions['*']['patrolmarks'] = false; // let anons see what was patrolled // Implicit group for all logged-in accounts @@ -3971,25 +4205,29 @@ $wgGroupPermissions['user']['sendemail'] = true; // Implicit group for accounts that pass $wgAutoConfirmAge $wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true; +$wgGroupPermissions['autoconfirmed']['editsemiprotected'] = true; // Users with bot privilege can have their edits hidden // from various log pages by default $wgGroupPermissions['bot']['bot'] = true; $wgGroupPermissions['bot']['autoconfirmed'] = true; +$wgGroupPermissions['bot']['editsemiprotected'] = true; $wgGroupPermissions['bot']['nominornewtalk'] = true; $wgGroupPermissions['bot']['autopatrol'] = true; $wgGroupPermissions['bot']['suppressredirect'] = true; $wgGroupPermissions['bot']['apihighlimits'] = true; $wgGroupPermissions['bot']['writeapi'] = true; -#$wgGroupPermissions['bot']['editprotected'] = true; // can edit all protected pages without cascade protection enabled // Most extra permission abilities go to this group $wgGroupPermissions['sysop']['block'] = true; $wgGroupPermissions['sysop']['createaccount'] = true; $wgGroupPermissions['sysop']['delete'] = true; -$wgGroupPermissions['sysop']['bigdelete'] = true; // can be separately configured for pages with > $wgDeleteRevisionsLimit revs -$wgGroupPermissions['sysop']['deletedhistory'] = true; // can view deleted history entries, but not see or restore the text -$wgGroupPermissions['sysop']['deletedtext'] = true; // can view deleted revision text +// can be separately configured for pages with > $wgDeleteRevisionsLimit revs +$wgGroupPermissions['sysop']['bigdelete'] = true; +// can view deleted history entries, but not see or restore the text +$wgGroupPermissions['sysop']['deletedhistory'] = true; +// can view deleted revision text +$wgGroupPermissions['sysop']['deletedtext'] = true; $wgGroupPermissions['sysop']['undelete'] = true; $wgGroupPermissions['sysop']['editinterface'] = true; $wgGroupPermissions['sysop']['editusercss'] = true; @@ -4002,6 +4240,7 @@ $wgGroupPermissions['sysop']['move-rootuserpages'] = true; $wgGroupPermissions['sysop']['patrol'] = true; $wgGroupPermissions['sysop']['autopatrol'] = true; $wgGroupPermissions['sysop']['protect'] = true; +$wgGroupPermissions['sysop']['editprotected'] = true; $wgGroupPermissions['sysop']['proxyunbannable'] = true; $wgGroupPermissions['sysop']['rollback'] = true; $wgGroupPermissions['sysop']['upload'] = true; @@ -4009,6 +4248,7 @@ $wgGroupPermissions['sysop']['reupload'] = true; $wgGroupPermissions['sysop']['reupload-shared'] = true; $wgGroupPermissions['sysop']['unwatchedpages'] = true; $wgGroupPermissions['sysop']['autoconfirmed'] = true; +$wgGroupPermissions['sysop']['editsemiprotected'] = true; $wgGroupPermissions['sysop']['ipblock-exempt'] = true; $wgGroupPermissions['sysop']['blockemail'] = true; $wgGroupPermissions['sysop']['markbotedits'] = true; @@ -4084,11 +4324,12 @@ $wgImplicitGroups = array( '*', 'user', 'autoconfirmed' ); * @endcode * This allows users in the '*' group (i.e. any user) to remove themselves from * any group that they happen to be in. - * */ $wgGroupsAddToSelf = array(); -/** @see $wgGroupsAddToSelf */ +/** + * @see $wgGroupsAddToSelf + */ $wgGroupsRemoveFromSelf = array(); /** @@ -4108,11 +4349,37 @@ $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ); * dictates the order on the protection form's lists. * * - '' will be ignored (i.e. unprotected) - * - 'sysop' is quietly rewritten to 'protect' for backwards compatibility + * - 'autoconfirmed' is quietly rewritten to 'editsemiprotected' for backwards compatibility + * - 'sysop' is quietly rewritten to 'editprotected' for backwards compatibility */ $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ); /** + * Restriction levels that can be used with cascading protection + * + * A page can only be protected with cascading protection if the + * requested restriction level is included in this array. + * + * 'autoconfirmed' is quietly rewritten to 'editsemiprotected' for backwards compatibility. + * 'sysop' is quietly rewritten to 'editprotected' for backwards compatibility. + */ +$wgCascadingRestrictionLevels = array( 'sysop' ); + +/** + * Restriction levels that should be considered "semiprotected" + * + * Certain places in the interface recognize a dichotomy between "protected" + * and "semiprotected", without further distinguishing the specific levels. In + * general, if anyone can be eligible to edit a protection level merely by + * reaching some condition in $wgAutopromote, it should probably be considered + * "semiprotected". + * + * 'autoconfirmed' is quietly rewritten to 'editsemiprotected' for backwards compatibility. + * 'sysop' is not changed, since it really shouldn't be here. + */ +$wgSemiprotectedRestrictionLevels = array( 'autoconfirmed' ); + +/** * Set the minimum permissions required to edit pages in each * namespace. If you list more than one permission, a user must * have all of them to edit pages in that namespace. @@ -4248,7 +4515,10 @@ $wgAutopromoteOnceLogInRC = true; * @endcode */ $wgAddGroups = array(); -/** @see $wgAddGroups */ + +/** + * @see $wgAddGroups + */ $wgRemoveGroups = array(); /** @@ -4283,7 +4553,9 @@ $wgAccountCreationThrottle = 0; */ $wgSpamRegex = array(); -/** Same as the above except for edit summaries */ +/** + * Same as the above except for edit summaries + */ $wgSummarySpamRegex = array(); /** @@ -4337,6 +4609,13 @@ $wgSorbsUrl = array(); $wgProxyWhitelist = array(); /** + * Whether to look at the X-Forwarded-For header's list of (potentially spoofed) + * IPs and apply IP blocks to them. This allows for IP blocks to work with correctly-configured + * (transparent) proxies without needing to block the proxies themselves. + */ +$wgApplyIpBlocksToXff = false; + +/** * Simple rate limiter options to brake edit floods. * * Maximum number actions allowed in the given number of seconds; after that @@ -4362,7 +4641,7 @@ $wgRateLimits = array( 'user' => null, // for each logged-in user 'newbie' => null, // for each recent (autoconfirmed) account; overrides 'user' 'ip' => null, // for each anon and recent account - 'subnet' => null, // ... with final octet removed + 'subnet' => null, // ... within a /24 subnet in IPv4 or /64 in IPv6 ), 'move' => array( 'user' => null, @@ -4370,11 +4649,25 @@ $wgRateLimits = array( 'ip' => null, 'subnet' => null, ), - 'mailpassword' => array( + 'mailpassword' => array( // triggering password resets emails 'anon' => null, ), - 'emailuser' => array( + 'emailuser' => array( // emailing other users using MediaWiki + 'user' => null, + ), + 'linkpurge' => array( // purges of link tables + 'anon' => null, 'user' => null, + 'newbie' => null, + 'ip' => null, + 'subnet' => null, + ), + 'renderfile' => array( // files rendered via thumb.php or thumb_handler.php + 'anon' => null, + 'user' => null, + 'newbie' => null, + 'ip' => null, + 'subnet' => null, ), ); @@ -4417,22 +4710,8 @@ $wgPasswordAttemptThrottle = array( 'count' => 5, 'seconds' => 300 ); */ /** - * If you enable this, every editor's IP address will be scanned for open HTTP - * proxies. - * - * @warning Don't enable this. Many sysops will report "hostile TCP port scans" - * to your ISP and ask for your server to be shut down. - * You have been warned. - * + * This should always be customised in LocalSettings.php */ -$wgBlockOpenProxies = false; -/** Port we want to scan for a proxy */ -$wgProxyPorts = array( 80, 81, 1080, 3128, 6588, 8000, 8080, 8888, 65506 ); -/** Script used to scan */ -$wgProxyScriptPath = "$IP/maintenance/proxyCheck.php"; -/** */ -$wgProxyMemcExpiry = 86400; -/** This should always be customised in LocalSettings.php */ $wgSecretKey = false; /** @@ -4516,10 +4795,14 @@ $wgHttpOnlyBlacklist = array( '/^Mozilla\/4\.0 \(compatible; MSIE \d+\.\d+; Mac_PowerPC\)/', ); -/** A list of cookies that vary the cache (for use by extensions) */ +/** + * A list of cookies that vary the cache (for use by extensions) + */ $wgCacheVaryCookies = array(); -/** Override to customise the session name */ +/** + * Override to customise the session name + */ $wgSessionName = false; /** @} */ # end of cookie settings } @@ -4537,7 +4820,7 @@ $wgSessionName = false; */ $wgUseTeX = false; -/* @} */ # end LaTeX } +/** @} */ # end LaTeX } /************************************************************************//** * @name Profiling, testing and debugging @@ -4676,10 +4959,14 @@ $wgDevelopmentWarnings = false; */ $wgDeprecationReleaseLimit = false; -/** Only record profiling info for pages that took longer than this */ +/** + * Only record profiling info for pages that took longer than this + */ $wgProfileLimit = 0.0; -/** Don't put non-profiling info into log file */ +/** + * Don't put non-profiling info into log file + */ $wgProfileOnly = false; /** @@ -4694,10 +4981,14 @@ $wgProfileOnly = false; */ $wgProfileToDatabase = false; -/** If true, print a raw call tree instead of per-function report */ +/** + * If true, print a raw call tree instead of per-function report + */ $wgProfileCallTree = false; -/** Should application server host be put into profiling table */ +/** + * Should application server host be put into profiling table + */ $wgProfilePerHost = false; /** @@ -4714,10 +5005,25 @@ $wgUDPProfilerHost = '127.0.0.1'; */ $wgUDPProfilerPort = '3811'; -/** Detects non-matching wfProfileIn/wfProfileOut calls */ +/** + * Format string for the UDP profiler. The UDP profiler invokes sprintf() with + * (profile id, count, cpu, cpu_sq, real, real_sq, entry name) as arguments. + * You can use sprintf's argument numbering/swapping capability to repeat, + * re-order or omit fields. + * + * @see $wgStatsFormatString + * @since 1.22 + */ +$wgUDPProfilerFormatString = "%s - %d %f %f %f %f %s\n"; + +/** + * Detects non-matching wfProfileIn/wfProfileOut calls + */ $wgDebugProfiling = false; -/** Output debug message on every wfProfileIn/wfProfileOut */ +/** + * Output debug message on every wfProfileIn/wfProfileOut + */ $wgDebugFunctionEntry = false; /** @@ -4736,7 +5042,21 @@ $wgStatsMethod = 'cache'; */ $wgAggregateStatsID = false; -/** Whereas to count the number of time an article is viewed. +/** + * When $wgStatsMethod is 'udp', this variable specifies how stats should be + * formatted. Its value should be a format string suitable for a sprintf() + * invocation with (id, count, key) arguments, where 'id' is either + * $wgAggregateStatsID or the DB name, 'count' is the value by which the metric + * is being incremented, and 'key' is the metric name. + * + * @see $wgUDPProfilerFormatString + * @see $wgAggregateStatsID + * @since 1.22 + */ +$wgStatsFormatString = "stats/%s - %s 1 1 1 1 %s\n"; + +/** + * Whereas to count the number of time an article is viewed. * Does not work if pages are cached (for example with squid). */ $wgDisableCounters = false; @@ -4867,7 +5187,6 @@ $wgCountTotalSearchHits = false; * PHP wrapper to avoid firing up mediawiki for every keystroke * * Placeholders: {searchTerms} - * */ $wgOpenSearchTemplate = false; @@ -4919,7 +5238,6 @@ $wgNamespacesToBeSearchedHelp = array( * logged-in users. * Useful for big wikis to maintain different search profiles for anonymous and * logged-in users. - * */ $wgSearchEverythingOnlyLoggedIn = false; @@ -5011,12 +5329,8 @@ $wgPreviewOnOpenNamespaces = array( ); /** - * Activate external editor interface for files and pages - * See http://www.mediawiki.org/wiki/Manual:External_editors + * Go button goes straight to the edit screen if the article doesn't exist. */ -$wgUseExternalEditor = true; - -/** Go button goes straight to the edit screen if the article doesn't exist. */ $wgGoToEdit = false; /** @@ -5050,7 +5364,9 @@ if ( !isset( $wgCommandLineMode ) ) { } /** @endcond */ -/** For colorized maintenance script output, is your terminal background dark ? */ +/** + * For colorized maintenance script output, is your terminal background dark ? + */ $wgCommandLineDarkBg = false; /** @@ -5091,6 +5407,11 @@ $wgReadOnlyFile = false; $wgUpgradeKey = false; /** + * Fully specified path to git binary + */ +$wgGitBin = '/usr/bin/git'; + +/** * Map GIT repository URLs to viewer URLs to provide links in Special:Version * * Key is a pattern passed to preg_match() and preg_replace(), @@ -5098,12 +5419,14 @@ $wgUpgradeKey = false; * The value is the replacement for the key (it can contain $1, etc.) * %h will be replaced by the short SHA-1 (7 first chars) and %H by the * full SHA-1 of the HEAD revision. + * %r will be replaced with a URL-encoded version of $1. * * @since 1.20 */ $wgGitRepositoryViewers = array( - 'https://gerrit.wikimedia.org/r/p/(.*)' => 'https://gerrit.wikimedia.org/r/gitweb?p=$1;h=%H', - 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' => 'https://gerrit.wikimedia.org/r/gitweb?p=$1;h=%H', + 'https://gerrit.wikimedia.org/r/p/(.*)' => 'https://git.wikimedia.org/commit/%r/%H', + 'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' + => 'https://git.wikimedia.org/commit/%r/%H', ); /** @} */ # End of maintenance } @@ -5139,11 +5462,15 @@ $wgRCLinkDays = array( 1, 3, 7, 14, 30 ); /** * Send recent changes updates via UDP. The updates will be formatted for IRC. * Set this to the IP address of the receiver. + * + * @deprecated since 1.22, use $wgRCFeeds */ $wgRC2UDPAddress = false; /** * Port number for RC updates + * + * @deprecated since 1.22, use $wgRCFeeds */ $wgRC2UDPPort = false; @@ -5152,42 +5479,108 @@ $wgRC2UDPPort = false; * This can be used to identify the wiki. A script is available called * mxircecho.py which listens on a UDP port, and uses a prefix ending in a * tab to identify the IRC channel to send the log line to. + * + * @deprecated since 1.22, use $wgRCFeeds */ $wgRC2UDPPrefix = ''; /** * If this is set to true, $wgLocalInterwiki will be prepended to links in the * IRC feed. If this is set to a string, that string will be used as the prefix. + * + * @deprecated since 1.22, use $wgRCFeeds */ $wgRC2UDPInterwikiPrefix = false; /** * Set to true to omit "bot" edits (by users with the bot permission) from the * UDP feed. + * + * @deprecated since 1.22, use $wgRCFeeds */ $wgRC2UDPOmitBots = false; /** + * Destinations to which notifications about recent changes + * should be sent. + * + * As of MediaWiki 1.22, the only supported 'engine' parameter option in core + * is 'UDPRCFeedEngine', which is used to send recent changes over UDP to the + * specified server. + * The common options are: + * * 'uri' -- the address to which the notices are to be sent. + * * 'formatter' -- the class name (implementing RCFeedFormatter) which will + * produce the text to send. + * * 'omit_bots' -- whether the bot edits should be in the feed + * The IRC-specific options are: + * * 'add_interwiki_prefix' -- whether the titles should be prefixed with + * $wgLocalInterwiki. + * The JSON-specific options are: + * * 'channel' -- if set, the 'channel' parameter is also set in JSON values. + * + * To ensure backwards-compatability, whenever $wgRC2UDPAddress is set, a + * 'default' feed will be created reusing the deprecated $wgRC2UDP* variables. + * + * @example $wgRCFeeds['example'] = array( + * 'formatter' => 'JSONRCFeedFormatter', + * 'uri' => "udp://localhost:1336", + * 'add_interwiki_prefix' => false, + * 'omit_bots' => true, + * ); + * @example $wgRCFeeds['exampleirc'] = array( + * 'formatter' => 'IRCColourfulRCFeedFormatter', + * 'uri' => "udp://localhost:1338", + * 'add_interwiki_prefix' => false, + * 'omit_bots' => true, + * ); + * @since 1.22 + */ +$wgRCFeeds = array(); + +/** + * Used by RecentChange::getEngine to find the correct engine to use for a given URI scheme. + * Keys are scheme names, values are names of engine classes. + */ +$wgRCEngines = array( + 'redis' => 'RedisPubSubFeedEngine', + 'udp' => 'UDPRCFeedEngine', +); + +/** * Enable user search in Special:Newpages * This is really a temporary hack around an index install bug on some Wikipedias. * Kill it once fixed. */ $wgEnableNewpagesUserFilter = true; -/** Use RC Patrolling to check for vandalism */ +/** + * Use RC Patrolling to check for vandalism + */ $wgUseRCPatrol = true; -/** Use new page patrolling to check new pages on Special:Newpages */ +/** + * Use new page patrolling to check new pages on Special:Newpages + */ $wgUseNPPatrol = true; -/** Provide syndication feeds (RSS, Atom) for, e.g., Recentchanges, Newpages */ +/** + * Log autopatrol actions to the log table + */ +$wgLogAutopatrol = true; + +/** + * Provide syndication feeds (RSS, Atom) for, e.g., Recentchanges, Newpages + */ $wgFeed = true; -/** Set maximum number of results to return in syndication feeds (RSS, Atom) for - * eg Recentchanges, Newpages. */ +/** + * Set maximum number of results to return in syndication feeds (RSS, Atom) for + * eg Recentchanges, Newpages. + */ $wgFeedLimit = 50; -/** _Minimum_ timeout for cached Recentchanges feed, in seconds. +/** + * _Minimum_ timeout for cached Recentchanges feed, in seconds. * A cached version will continue to be served out even if changes * are made, until this many seconds runs out since the last render. * @@ -5196,11 +5589,14 @@ $wgFeedLimit = 50; */ $wgFeedCacheTimeout = 60; -/** When generating Recentchanges RSS/Atom feed, diffs will not be generated for - * pages larger than this size. */ +/** + * When generating Recentchanges RSS/Atom feed, diffs will not be generated for + * pages larger than this size. + */ $wgFeedDiffCutoff = 32768; -/** Override the site's default RSS/ATOM feed for recentchanges that appears on +/** + * Override the site's default RSS/ATOM feed for recentchanges that appears on * every page. Some sites might have a different feed they'd like to promote * instead of the RC feed (maybe like a "Recent New Articles" or "Breaking news" one). * Should be a format as key (either 'rss' or 'atom') and an URL to the feed @@ -5229,11 +5625,19 @@ $wgFeedClasses = array( */ $wgAdvertisedFeedTypes = array( 'atom' ); -/** Show watching users in recent changes, watchlist and page history views */ +/** + * Show watching users in recent changes, watchlist and page history views + */ $wgRCShowWatchingUsers = false; # UPO -/** Show watching users in Page views */ + +/** + * Show watching users in Page views + */ $wgPageShowWatchingUsers = false; -/** Show the amount of changed characters in recent changes */ + +/** + * Show the amount of changed characters in recent changes + */ $wgRCShowChangedSize = true; /** @@ -5245,7 +5649,8 @@ $wgRCChangedSizeThreshold = 500; /** * Show "Updated (since my last visit)" marker in RC view, watchlist and history - * view for watched pages with new changes */ + * view for watched pages with new changes + */ $wgShowUpdatedMarker = true; /** @@ -5274,6 +5679,33 @@ $wgUseTagFilter = true; */ $wgUnwatchedPageThreshold = false; +/** + * Flags (letter symbols) shown in recent changes and watchlist to indicate + * certain types of edits. + * + * To register a new one: + * @code + * $wgRecentChangesFlags['flag'] => array( + * 'letter' => 'letter-msg', + * 'title' => 'tooltip-msg' + * ); + * @endcode + * + * Optional 'class' allows to set a css class different than the flag name. + * + * @since 1.22 + */ +$wgRecentChangesFlags = array( + 'newpage' => array( 'letter' => 'newpageletter', + 'title' => 'recentchanges-label-newpage' ), + 'minor' => array( 'letter' => 'minoreditletter', + 'title' => 'recentchanges-label-minor', 'class' => 'minoredit' ), + 'bot' => array( 'letter' => 'boteditletter', + 'title' => 'recentchanges-label-bot', 'class' => 'botedit' ), + 'unpatrolled' => array( 'letter' => 'unpatrolledletter', + 'title' => 'recentchanges-label-unpatrolled' ), +); + /** @} */ # end RC/watchlist } /************************************************************************//** @@ -5291,15 +5723,17 @@ $wgUnwatchedPageThreshold = false; $wgRightsPage = null; /** - * Set this to specify an external URL containing details about the content license used on your wiki. + * Set this to specify an external URL containing details about the content license used on your + * wiki. * If $wgRightsPage is set then this setting is ignored. */ $wgRightsUrl = null; /** - * If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the link. - * If using $wgRightsUrl then this value must be specified. If using $wgRightsPage then the name of the - * page will also be used as the link if this variable is not set. + * If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the + * link. + * If using $wgRightsUrl then this value must be specified. If using $wgRightsPage then the name + * of the page will also be used as the link if this variable is not set. */ $wgRightsText = null; @@ -5319,7 +5753,9 @@ $wgLicenseTerms = false; */ $wgCopyrightIcon = null; -/** Set this to true if you want detailed copyright information forms on Upload. */ +/** + * Set this to true if you want detailed copyright information forms on Upload. + */ $wgUseCopyrightUpload = false; /** @@ -5331,8 +5767,10 @@ $wgUseCopyrightUpload = false; */ $wgMaxCredits = 0; -/** If there are more than $wgMaxCredits authors, show $wgMaxCredits of them. - * Otherwise, link to a separate credits page. */ +/** + * If there are more than $wgMaxCredits authors, show $wgMaxCredits of them. + * Otherwise, link to a separate credits page. + */ $wgShowCreditsIfMax = true; /** @} */ # end of copyright and credits settings } @@ -5435,6 +5873,13 @@ $wgExtensionFunctions = array(); $wgExtensionMessagesFiles = array(); /** + * Array of files with list(s) of extension entry points to be used in + * maintenance/mergeMessageFileList.php + * @since 1.22 + */ +$wgExtensionEntryPointListFiles = array(); + +/** * Parser output hooks. * This is an associative array where the key is an extension-defined tag * (typically the extension name), and the value is a PHP callback. @@ -5451,6 +5896,11 @@ $wgExtensionMessagesFiles = array(); $wgParserOutputHooks = array(); /** + * Whether to include the NewPP limit report as a HTML comment + */ +$wgEnableParserLimitReporting = true; + +/** * List of valid skin names. * The key should be the name in all lower case, the value should be a properly * cased name for the skin. This value will be prefixed with "Skin" to create the @@ -5521,17 +5971,24 @@ $wgAuth = null; * @endcode * - A function with some data: * @code - * $wgHooks['event_name'][] = array($function, $data); + * $wgHooks['event_name'][] = array( $function, $data ); * @endcode * - A an object method: * @code - * $wgHooks['event_name'][] = array($object, 'method'); + * $wgHooks['event_name'][] = array( $object, 'method' ); + * @endcode + * - A closure: + * @code + * $wgHooks['event_name'][] = function ( $hookParam ) { + * // Handler code goes here. + * }; * @endcode * * @warning You should always append to an event array or you will end up * deleting a previous registered hook. * - * @todo Does it support PHP closures? + * @warning Hook handlers should be registered at file scope. Registering + * handlers after file scope can lead to unexpected results due to caching. */ $wgHooks = array(); @@ -5543,7 +6000,6 @@ $wgJobClasses = array( 'refreshLinks' => 'RefreshLinksJob', 'refreshLinks2' => 'RefreshLinksJob2', 'htmlCacheUpdate' => 'HTMLCacheUpdateJob', - 'html_cache_update' => 'HTMLCacheUpdateJob', // backwards-compatible 'sendMail' => 'EmaillingJob', 'enotifNotify' => 'EnotifNotifyJob', 'fixDoubleRedirect' => 'DoubleRedirectJob', @@ -5843,12 +6299,13 @@ $wgDisableQueryPageUpdate = false; * List of special pages, followed by what subtitle they should go under * at Special:SpecialPages * - * @deprecated 1.21 Override SpecialPage::getGroupName instead + * @deprecated since 1.21 Override SpecialPage::getGroupName instead */ $wgSpecialPageGroups = array(); -/** Whether or not to sort special pages in Special:Specialpages */ - +/** + * Whether or not to sort special pages in Special:Specialpages + */ $wgSortSpecialPages = true; /** @@ -6066,6 +6523,16 @@ $wgAPIRequestLog = false; $wgAPICacheHelpTimeout = 60 * 60; /** + * The ApiQueryQueryPages module should skip pages that are redundant to true + * API queries. + */ +$wgAPIUselessQueryPages = array( + 'MIMEsearch', // aiprop=mime + 'LinkSearch', // list=exturlusage + 'FileDuplicateSearch', // prop=duplicatefiles +); + +/** * Enable AJAX framework */ $wgUseAjax = true; @@ -6119,7 +6586,6 @@ $wgCrossSiteAJAXdomains = array(); * even if they match one of the domains allowed by $wgCrossSiteAJAXdomains * Uses the same syntax as $wgCrossSiteAJAXdomains */ - $wgCrossSiteAJAXdomainExceptions = array(); /** @} */ # End AJAX and API } @@ -6132,7 +6598,7 @@ $wgCrossSiteAJAXdomainExceptions = array(); /** * Maximum amount of virtual memory available to shell processes under linux, in KB. */ -$wgMaxShellMemory = 102400; +$wgMaxShellMemory = 307200; /** * Maximum file size created by shell processes under linux, in KB @@ -6210,6 +6676,12 @@ $wgAsyncHTTPTimeout = 25; */ $wgHTTPProxy = false; +/** + * Timeout for connections done internally (in seconds) + * Only works for curl + */ +$wgHTTPConnectTimeout = 5e0; + /** @} */ # End HTTP client } /************************************************************************//** @@ -6248,61 +6720,13 @@ $wgMaxBacklinksInvalidate = false; /** @} */ # End job queue } /************************************************************************//** - * @name HipHop compilation + * @name Miscellaneous * @{ */ /** - * The build directory for HipHop compilation. - * Defaults to '$IP/maintenance/hiphop/build'. - */ -$wgHipHopBuildDirectory = false; - -/** - * The HipHop build type. Can be either "Debug" or "Release". + * Name of the external diff engine to use */ -$wgHipHopBuildType = 'Debug'; - -/** - * Number of parallel processes to use during HipHop compilation, or "detect" - * to guess from system properties. - */ -$wgHipHopCompilerProcs = 'detect'; - -/** - * Filesystem extensions directory. Defaults to $IP/../extensions. - * - * To compile extensions with HipHop, set $wgExtensionsDirectory correctly, - * and use code like: - * @code - * require( MWInit::extensionSetupPath( 'Extension/Extension.php' ) ); - * @endcode - * - * to include the extension setup file from LocalSettings.php. It is not - * necessary to set this variable unless you use MWInit::extensionSetupPath(). - */ -$wgExtensionsDirectory = false; - -/** - * A list of files that should be compiled into a HipHop build, in addition to - * those listed in $wgAutoloadClasses. Add to this array in an extension setup - * file in order to add files to the build. - * - * The files listed here must either be either absolute paths under $IP or - * under $wgExtensionsDirectory, or paths relative to the virtual source root - * "$IP/..", i.e. starting with "phase3" for core files, and "extensions" for - * extension files. - */ -$wgCompiledFiles = array(); - -/** @} */ # End of HipHop compilation } - -/************************************************************************//** - * @name Miscellaneous - * @{ - */ - -/** Name of the external diff engine to use */ $wgExternalDiffEngine = false; /** @@ -6372,25 +6796,18 @@ $wgPoolCounterConf = null; $wgUploadMaintenance = false; /** - * Allows running of selenium tests via maintenance/tests/RunSeleniumTests.php - */ -$wgEnableSelenium = false; -$wgSeleniumTestConfigs = array(); -$wgSeleniumConfigFile = null; -$wgDBtestuser = ''; //db user that has permission to create and drop the test databases only -$wgDBtestpassword = ''; - -/** - * Associative array mapping namespace IDs to the name of the content model pages in that namespace should have by - * default (use the CONTENT_MODEL_XXX constants). If no special content type is defined for a given namespace, - * pages in that namespace will use the CONTENT_MODEL_WIKITEXT (except for the special case of JS and CS pages). + * Associative array mapping namespace IDs to the name of the content model pages in that namespace + * should have by default (use the CONTENT_MODEL_XXX constants). If no special content type is + * defined for a given namespace, pages in that namespace will use the CONTENT_MODEL_WIKITEXT + * (except for the special case of JS and CS pages). * * @since 1.21 */ $wgNamespaceContentModels = array(); /** - * How to react if a plain text version of a non-text Content object is requested using ContentHandler::getContentText(): + * How to react if a plain text version of a non-text Content object is requested using + * ContentHandler::getContentText(): * * * 'ignore': return null * * 'fail': throw an MWException @@ -6402,14 +6819,14 @@ $wgContentHandlerTextFallback = 'ignore'; /** * Set to false to disable use of the database fields introduced by the ContentHandler facility. - * This way, the ContentHandler facility can be used without any additional information in the database. - * A page's content model is then derived solely from the page's title. This however means that changing - * a page's default model (e.g. using $wgNamespaceContentModels) will break the page and/or make the content - * inaccessible. This also means that pages can not be moved to a title that would default to a different - * content model. + * This way, the ContentHandler facility can be used without any additional information in the + * database. A page's content model is then derived solely from the page's title. This however + * means that changing a page's default model (e.g. using $wgNamespaceContentModels) will break + * the page and/or make the content inaccessible. This also means that pages can not be moved to + * a title that would default to a different content model. * - * Overall, with $wgContentHandlerUseDB = false, no database updates are needed, but content handling - * is less robust and less flexible. + * Overall, with $wgContentHandlerUseDB = false, no database updates are needed, but content + * handling is less robust and less flexible. * * @since 1.21 */ @@ -6420,7 +6837,7 @@ $wgContentHandlerUseDB = true; * of texts are also rendered as wikitext, it only means that links, magic words, etc will have * the effect on the database they would have on a wikitext page. * - * @todo: On the long run, it would be nice to put categories etc into a separate structure, + * @todo On the long run, it would be nice to put categories etc into a separate structure, * or at least parse only the contents of comments in the scripts. * * @since 1.21 @@ -6448,6 +6865,12 @@ $wgSiteTypes = array( ); /** + * Formerly a list of files for HipHop compilation + * @deprecated since 1.22 + */ +$wgCompiledFiles = array(); + +/** * For really cool vim folding this needs to be at the end: * vim: foldmarker=@{,@} foldmethod=marker * @} diff --git a/includes/DeferredUpdates.php b/includes/DeferredUpdates.php index 89c4df68..c385f138 100644 --- a/includes/DeferredUpdates.php +++ b/includes/DeferredUpdates.php @@ -64,6 +64,16 @@ class DeferredUpdates { } /** + * Add a callable update. In a lot of cases, we just need a callback/closure, + * defining a new DeferrableUpdate object is not necessary + * @see MWCallableUpdate::__construct() + * @param callable $callable + */ + public static function addCallableUpdate( $callable ) { + self::addUpdate( new MWCallableUpdate( $callable ) ); + } + + /** * Do any deferred updates and clear the list * * @param string $commit set to 'commit' to commit after every update to @@ -99,7 +109,7 @@ class DeferredUpdates { // be reported to the user since the output is already sent. // Instead we just log them. if ( !$e instanceof ErrorPageError ) { - wfDebugLog( 'exception', $e->getLogMessage() ); + MWExceptionHandler::logException( $e ); } } } diff --git a/includes/Defines.php b/includes/Defines.php index c4a86335..86c5520b 100644 --- a/includes/Defines.php +++ b/includes/Defines.php @@ -197,14 +197,14 @@ define( 'EDIT_AUTOSUMMARY', 64 ); define( 'LIST_COMMA', 0 ); define( 'LIST_AND', 1 ); define( 'LIST_SET', 2 ); -define( 'LIST_NAMES', 3); -define( 'LIST_OR', 4); +define( 'LIST_NAMES', 3 ); +define( 'LIST_OR', 4 ); /**@}*/ /** * Unicode and normalisation related */ -require_once __DIR__.'/normal/UtfNormalDefines.php'; +require_once __DIR__ . '/normal/UtfNormalDefines.php'; /**@{ * Hook support constants @@ -237,11 +237,6 @@ define( 'SFH_OBJECT_ARGS', 2 ); /**@}*/ /** - * Flags for Parser::setLinkHook - */ -define( 'SLH_PATTERN', 1 ); - -/** * Flags for Parser::replaceLinkHolders */ define( 'RLH_FOR_UPDATE', 1 ); diff --git a/includes/EditPage.php b/includes/EditPage.php index 8b2dbb5f..530e2674 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -100,7 +100,7 @@ class EditPage { /** * Status: user tried to create this page, but is not allowed to do that - * ( Title->usercan('create') == false ) + * ( Title->userCan('create') == false ) */ const AS_NO_CREATE_PERMISSION = 223; @@ -487,7 +487,7 @@ class EditPage { // The edit page was reached via a red link. // Redirect to the article page and let them click the edit tab if // they really want a permission error. - $wgOut->redirect( $this->mTitle->getFullUrl() ); + $wgOut->redirect( $this->mTitle->getFullURL() ); return; } @@ -521,6 +521,8 @@ class EditPage { $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ), Linker::formatTemplates( $this->getTemplates() ) ) ); + $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' ); + if ( $this->mTitle->exists() ) { $wgOut->returnToMain( null, $this->mTitle ); } @@ -540,7 +542,7 @@ class EditPage { // The edit page was reached via a red link. // Redirect to the article page and let them click the edit tab if // they really want a permission error. - $wgOut->redirect( $this->mTitle->getFullUrl() ); + $wgOut->redirect( $this->mTitle->getFullURL() ); } else { $wgOut->readOnlyPage( $source, $protected, $reasons, $action ); } @@ -621,6 +623,7 @@ class EditPage { $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) ); if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) { + wfProfileOut( __METHOD__ ); throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' ); } @@ -663,6 +666,11 @@ class EditPage { $this->edittime = $request->getVal( 'wpEdittime' ); $this->starttime = $request->getVal( 'wpStarttime' ); + $undidRev = $request->getInt( 'wpUndidRevision' ); + if ( $undidRev ) { + $this->undidRev = $undidRev; + } + $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' ); if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) { @@ -832,7 +840,6 @@ class EditPage { if ( $this->textbox1 === false ) { return false; } - wfProxyCheck(); return true; } @@ -897,7 +904,9 @@ class EditPage { $orig = $this->getOriginalContent(); $content = $orig ? $orig->getSection( $this->section ) : null; - if ( !$content ) $content = $def_content; + if ( !$content ) { + $content = $def_content; + } } else { $undoafter = $wgRequest->getInt( 'undoafter' ); $undo = $wgRequest->getInt( 'undo' ); @@ -932,7 +941,19 @@ class EditPage { # If we just undid one rev, use an autosummary $firstrev = $oldrev->getNext(); if ( $firstrev && $firstrev->getId() == $undo ) { - $undoSummary = wfMessage( 'undo-summary', $undo, $undorev->getUserText() )->inContentLanguage()->text(); + $userText = $undorev->getUserText(); + if ( $userText === '' ) { + $undoSummary = wfMessage( + 'undo-summary-username-hidden', + $undo + )->inContentLanguage()->text(); + } else { + $undoSummary = wfMessage( + 'undo-summary', + $undo, + $userText + )->inContentLanguage()->text(); + } if ( $this->summary === '' ) { $this->summary = $undoSummary; } else { @@ -950,6 +971,7 @@ class EditPage { $undoMsg = 'norev'; } + // Messages: undo-success, undo-failure, undo-norev $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}"; $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" . wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true ); @@ -985,7 +1007,9 @@ class EditPage { } $revision = $this->mArticle->getRevisionFetched(); if ( $revision === null ) { - if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel(); + if ( !$this->contentModel ) { + $this->contentModel = $this->getTitle()->getContentModel(); + } $handler = ContentHandler::getForModelID( $this->contentModel ); return $handler->makeEmptyContent(); @@ -1007,7 +1031,9 @@ class EditPage { $content = $rev ? $rev->getContent( Revision::RAW ) : null; if ( $content === false || $content === null ) { - if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel(); + if ( !$this->contentModel ) { + $this->contentModel = $this->getTitle()->getContentModel(); + } $handler = ContentHandler::getForModelID( $this->contentModel ); return $handler->makeEmptyContent(); @@ -1157,20 +1183,20 @@ class EditPage { * marked HttpOnly. The JavaScript code converts the cookie to a wgPostEdit config * variable. * - * Since WebResponse::setcookie does not allow forcing HttpOnly for a single - * cookie, we have to use PHP's setcookie() directly. - * * We use a path of '/' since wgCookiePath is not exposed to JS * * If the variable were set on the server, it would be cached, which is unwanted * since the post-edit state should only apply to the load right after the save. */ protected function setPostEditCookie() { - global $wgCookiePrefix, $wgCookieDomain; $revisionId = $this->mArticle->getLatest(); $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId; - setcookie( $wgCookiePrefix . $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, '/', $wgCookieDomain ); + $response = RequestContext::getMain()->getRequest()->response(); + $response->setcookie( $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, array( + 'path' => '/', + 'httpOnly' => false, + ) ); } /** @@ -1213,7 +1239,7 @@ class EditPage { case self::AS_SUCCESS_NEW_ARTICLE: $query = $resultDetails['redirect'] ? 'redirect=no' : ''; - $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : ''; + $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : ''; $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor ); return false; @@ -1355,6 +1381,24 @@ class EditPage { 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 ); @@ -1381,6 +1425,18 @@ class EditPage { # 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 ); } @@ -1454,7 +1510,7 @@ class EditPage { wfProfileOut( __METHOD__ ); return $status; } - if ( $wgUser->pingLimiter() ) { + if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) { $status->fatal( 'actionthrottledtext' ); $status->value = self::AS_RATE_LIMITED; wfProfileOut( __METHOD__ . '-checks' ); @@ -1488,8 +1544,17 @@ class EditPage { return $status; } - # Don't save a new article if it's blank. - if ( $this->textbox1 == '' ) { + // 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->textbox1 === $defaultText ) { $status->setResult( false, self::AS_BLANK_ARTICLE ); wfProfileOut( __METHOD__ ); return $status; @@ -1566,8 +1631,7 @@ class EditPage { } } - // If sectiontitle is set, use it, otherwise use the summary as the section title (for - // backwards compatibility with old forms/bots). + // If sectiontitle is set, use it, otherwise use the summary as the section title. if ( $this->sectiontitle !== '' ) { $sectionTitle = $this->sectiontitle; } else { @@ -1724,6 +1788,10 @@ class EditPage { } $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__ ); @@ -1736,7 +1804,9 @@ class EditPage { protected function updateWatchlist() { global $wgUser; - if ( $wgUser->isLoggedIn() && $this->watchthis != $wgUser->isWatched( $this->mTitle ) ) { + if ( $wgUser->isLoggedIn() + && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS ) + ) { $fname = __METHOD__; $title = $this->mTitle; $watch = $this->watchthis; @@ -1745,11 +1815,7 @@ class EditPage { $dbw = wfGetDB( DB_MASTER ); $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) { $dbw->begin( $fname ); - if ( $watch ) { - WatchAction::doWatch( $title, $wgUser ); - } else { - WatchAction::doUnwatch( $title, $wgUser ); - } + WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser ); $dbw->commit( $fname ); } ); } @@ -1854,11 +1920,11 @@ class EditPage { } /** - * Check given input text against $wgSpamRegex, and return the text of the first match. + * Check given input text against $wgSummarySpamRegex, and return the text of the first match. * * @param $text string * - * @return string|bool matching string or false + * @return string|bool matching string or false */ public static function matchSummarySpamRegex( $text ) { global $wgSummarySpamRegex; @@ -1885,10 +1951,16 @@ class EditPage { global $wgOut, $wgUser; $wgOut->addModules( 'mediawiki.action.edit' ); + $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' ); if ( $wgUser->getOption( 'uselivepreview', false ) ) { $wgOut->addModules( 'mediawiki.action.edit.preview' ); } + + if ( $wgUser->getOption( 'useeditwarning', false ) ) { + $wgOut->addModules( 'mediawiki.action.edit.editWarning' ); + } + // Bug #19334: textarea jumps when editing articles in IE8 $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' ); @@ -1928,15 +2000,15 @@ class EditPage { if ( $namespace == NS_MEDIAWIKI ) { # Show a warning if editing an interface message $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' ); - } else if( $namespace == NS_FILE ) { + } elseif ( $namespace == NS_FILE ) { # Show a hint to shared repo $file = wfFindFile( $this->mTitle ); - if( $file && !$file->isLocal() ) { + if ( $file && !$file->isLocal() ) { $descUrl = $file->getDescriptionUrl(); # there must be a description url to show a hint to shared repo - if( $descUrl ) { - if( !$this->mTitle->exists() ) { - $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array ( + if ( $descUrl ) { + if ( !$this->mTitle->exists() ) { + $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array( 'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl ) ); } else { @@ -2126,7 +2198,7 @@ class EditPage { } } - //@todo: add EditForm plugin interface and use it here! + // @todo add EditForm plugin interface and use it here! // search for textarea1 and textares2, and allow EditForm to override all uses. $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID, 'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ), @@ -2136,6 +2208,14 @@ class EditPage { call_user_func_array( $formCallback, array( &$wgOut ) ); } + // Add an empty field to trip up spambots + $wgOut->addHTML( + Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) ) + . Html::rawElement( 'label', array( 'for' => 'wpAntiSpam' ), wfMessage( 'simpleantispam-label' )->parse() ) + . Xml::element( 'input', array( 'type' => 'text', 'name' => 'wpAntispam', 'id' => 'wpAntispam', 'value' => '' ) ) + . Xml::closeElement( 'div' ) + ); + wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) ); // Put these up at the top to ensure they aren't lost on early form submission @@ -2161,7 +2241,7 @@ class EditPage { } # When the summary is hidden, also hide them on preview/show changes - if( $this->nosummary ) { + if ( $this->nosummary ) { $wgOut->addHTML( Html::hidden( 'nosummary', true ) ); } @@ -2239,6 +2319,11 @@ class EditPage { $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ), Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) ); + $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ), + self::getPreviewLimitReport( $this->mParserOutput ) ) ); + + $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' ); + if ( $this->isConflict ) { try { $this->showConflict(); @@ -2282,7 +2367,7 @@ class EditPage { } // Add edit notices - $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices() ) ); + $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) ); if ( $this->isConflict ) { $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' ); @@ -2509,7 +2594,7 @@ class EditPage { global $wgParser; if ( $isSubjectPreview ) { - $summary = wfMessage( 'newsectionsummary', $wgParser->stripSectionName( $summary ) ) + $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) ) ->inContentLanguage()->text(); } @@ -2626,7 +2711,7 @@ HTML $attribs = $customAttribs + array( 'accesskey' => ',', - 'id' => $name, + 'id' => $name, 'cols' => $wgUser->getIntOption( 'cols' ), 'rows' => $wgUser->getIntOption( 'rows' ), 'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work @@ -2702,9 +2787,9 @@ HTML $oldtitlemsg = 'currentrev'; # if message does not exist, show diff against the preloaded default - if( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) { + if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) { $oldtext = $this->mTitle->getDefaultMessageText(); - if( $oldtext !== false ) { + if ( $oldtext !== false ) { $oldtitlemsg = 'defaultmessagetext'; $oldContent = $this->toEditContent( $oldtext ); } else { @@ -2799,7 +2884,15 @@ HTML return self::getCopyrightWarning( $this->mTitle ); } - public static function getCopyrightWarning( $title ) { + /** + * Get the copyright warning, by default returns wikitext + * + * @param Title $title + * @param string $format output format, valid values are any function of + * a Message object + * @return string + */ + public static function getCopyrightWarning( $title, $format = 'plain' ) { global $wgRightsText; if ( $wgRightsText ) { $copywarnMsg = array( 'copyrightwarning', @@ -2813,7 +2906,60 @@ HTML wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) ); return "<div id=\"editpage-copywarn\">\n" . - call_user_func_array( 'wfMessage', $copywarnMsg )->plain() . "\n</div>"; + call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>"; + } + + /** + * Get the Limit report for page previews + * + * @since 1.22 + * @param ParserOutput $output ParserOutput object from the parse + * @return string HTML + */ + public static function getPreviewLimitReport( $output ) { + if ( !$output || !$output->getLimitReportData() ) { + return ''; + } + + wfProfileIn( __METHOD__ ); + + $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ), + wfMessage( 'limitreport-title' )->parseAsBlock() + ); + + // Show/hide animation doesn't work correctly on a table, so wrap it in a div. + $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) ); + + $limitReport .= Html::openElement( 'table', array( + 'class' => 'preview-limit-report wikitable' + ) ) . + Html::openElement( 'tbody' ); + + foreach ( $output->getLimitReportData() as $key => $value ) { + if ( wfRunHooks( 'ParserLimitReportFormat', + array( $key, $value, &$limitReport, true, true ) + ) ) { + $keyMsg = wfMessage( $key ); + $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) ); + if ( !$valueMsg->exists() ) { + $valueMsg = new RawMessage( '$1' ); + } + if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) { + $limitReport .= Html::openElement( 'tr' ) . + Html::rawElement( 'th', null, $keyMsg->parse() ) . + Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) . + Html::closeElement( 'tr' ); + } + } + } + + $limitReport .= Html::closeElement( 'tbody' ) . + Html::closeElement( 'table' ) . + Html::closeElement( 'div' ); + + wfProfileOut( __METHOD__ ); + + return $limitReport; } protected function showStandardInputs( &$tabindex = 2 ) { @@ -2838,7 +2984,9 @@ HTML $cancel = $this->getCancelLink(); if ( $cancel !== '' ) { - $cancel .= wfMessage( 'pipe-separator' )->text(); + $cancel .= Html::element( 'span', + array( 'class' => 'mw-editButtons-pipe-separator' ), + wfMessage( 'pipe-separator' )->text() ); } $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() ); $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' . @@ -3028,9 +3176,9 @@ HTML # don't parse non-wikitext pages, show message about preview if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) { - if( $this->mTitle->isCssJsSubpage() ) { + if ( $this->mTitle->isCssJsSubpage() ) { $level = 'user'; - } elseif( $this->mTitle->isCssOrJsPage() ) { + } elseif ( $this->mTitle->isCssOrJsPage() ) { $level = 'site'; } else { $level = false; @@ -3046,7 +3194,7 @@ HTML # Used messages to make sure grep find them: # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview - if( $level && $format ) { + if ( $level && $format ) { $note = "<div id='mw-{$level}{$format}preview'>" . wfMessage( "{$level}{$format}preview" )->text() . "</div>"; } } @@ -3098,9 +3246,9 @@ HTML '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" . $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n"; - $pageLang = $this->mTitle->getPageLanguage(); - $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(), - 'class' => 'mw-content-' . $pageLang->getDir() ); + $pageViewLang = $this->mTitle->getPageViewLanguage(); + $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(), + 'class' => 'mw-content-' . $pageViewLang->getDir() ); $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML ); wfProfileOut( __METHOD__ ); @@ -3314,9 +3462,9 @@ HTML $minorLabel = wfMessage( 'minoredit' )->parse(); if ( $wgUser->isAllowed( 'minoredit' ) ) { $attribs = array( - 'tabindex' => ++$tabindex, + 'tabindex' => ++$tabindex, 'accesskey' => wfMessage( 'accesskey-minoredit' )->text(), - 'id' => 'wpMinoredit', + 'id' => 'wpMinoredit', ); $checkboxes['minor'] = Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) . @@ -3330,9 +3478,9 @@ HTML $checkboxes['watch'] = ''; if ( $wgUser->isLoggedIn() ) { $attribs = array( - 'tabindex' => ++$tabindex, + 'tabindex' => ++$tabindex, 'accesskey' => wfMessage( 'accesskey-watch' )->text(), - 'id' => 'wpWatchthis', + 'id' => 'wpWatchthis', ); $checkboxes['watch'] = Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) . @@ -3356,37 +3504,37 @@ HTML $buttons = array(); $temp = array( - 'id' => 'wpSave', - 'name' => 'wpSave', - 'type' => 'submit', - 'tabindex' => ++$tabindex, - 'value' => wfMessage( 'savearticle' )->text(), + 'id' => 'wpSave', + 'name' => 'wpSave', + 'type' => 'submit', + 'tabindex' => ++$tabindex, + 'value' => wfMessage( 'savearticle' )->text(), 'accesskey' => wfMessage( 'accesskey-save' )->text(), - 'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']', + 'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']', ); $buttons['save'] = Xml::element( 'input', $temp, '' ); ++$tabindex; // use the same for preview and live preview $temp = array( - 'id' => 'wpPreview', - 'name' => 'wpPreview', - 'type' => 'submit', - 'tabindex' => $tabindex, - 'value' => wfMessage( 'showpreview' )->text(), + 'id' => 'wpPreview', + 'name' => 'wpPreview', + 'type' => 'submit', + 'tabindex' => $tabindex, + 'value' => wfMessage( 'showpreview' )->text(), 'accesskey' => wfMessage( 'accesskey-preview' )->text(), - 'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']', + 'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']', ); $buttons['preview'] = Xml::element( 'input', $temp, '' ); $buttons['live'] = ''; $temp = array( - 'id' => 'wpDiff', - 'name' => 'wpDiff', - 'type' => 'submit', - 'tabindex' => ++$tabindex, - 'value' => wfMessage( 'showdiff' )->text(), + 'id' => 'wpDiff', + 'name' => 'wpDiff', + 'type' => 'submit', + 'tabindex' => ++$tabindex, + 'value' => wfMessage( 'showdiff' )->text(), 'accesskey' => wfMessage( 'accesskey-diff' )->text(), - 'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']', + 'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']', ); $buttons['diff'] = Xml::element( 'input', $temp, '' ); @@ -3506,7 +3654,7 @@ HTML global $wgOut, $wgLang; $this->textbox2 = $this->textbox1; - if( is_array( $match ) ) { + if ( is_array( $match ) ) { $match = $wgLang->listToText( $match ); } $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) ); diff --git a/includes/Exception.php b/includes/Exception.php index 0bd7a2a7..5bad88c2 100644 --- a/includes/Exception.php +++ b/includes/Exception.php @@ -30,7 +30,6 @@ * @ingroup Exception */ class MWException extends Exception { - var $logId; /** * Should the exception use $wgOut to output the error? @@ -45,6 +44,16 @@ class MWException extends Exception { } /** + * Whether to log this exception in the exception debug log. + * + * @since 1.23 + * @return boolean + */ + function isLoggable() { + return true; + } + + /** * Can the extension use the Message class/wfMessage to get i18n-ed messages? * * @return bool @@ -75,11 +84,11 @@ class MWException extends Exception { return null; // Just silently ignore } - if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) ) { + if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[$name] ) ) { return null; } - $hooks = $wgExceptionHooks[ $name ]; + $hooks = $wgExceptionHooks[$name]; $callargs = array_merge( array( $this ), $args ); foreach ( $hooks as $hook ) { @@ -126,13 +135,12 @@ class MWException extends Exception { global $wgShowExceptionDetails; if ( $wgShowExceptionDetails ) { - return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) . - '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . + return '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) . + '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) . "</p>\n"; } else { - return - "<div class=\"errorbox\">" . - '[' . $this->getLogId() . '] ' . + return "<div class=\"errorbox\">" . + '[' . MWExceptionHandler::getLogId( $this ) . '] ' . gmdate( 'Y-m-d H:i:s' ) . ": Fatal exception of type " . get_class( $this ) . "</div>\n" . "<!-- Set \$wgShowExceptionDetails = true; " . @@ -152,8 +160,8 @@ class MWException extends Exception { global $wgShowExceptionDetails; if ( $wgShowExceptionDetails ) { - return $this->getMessage() . - "\nBacktrace:\n" . $this->getTraceAsString() . "\n"; + return MWExceptionHandler::getLogMessage( $this ) . + "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n"; } else { return "Set \$wgShowExceptionDetails = true; " . "in LocalSettings.php to show detailed debugging information.\n"; @@ -170,43 +178,28 @@ class MWException extends Exception { } /** - * Get a random ID for this error. - * This allows to link the exception to its corresponding log entry when - * $wgShowExceptionDetails is set to false. + * Get a the ID for this error. * + * @since 1.20 + * @deprecated since 1.22 Use MWExceptionHandler::getLogId instead. * @return string */ function getLogId() { - if ( $this->logId === null ) { - $this->logId = wfRandomString( 8 ); - } - return $this->logId; + wfDeprecated( __METHOD__, '1.22' ); + return MWExceptionHandler::getLogId( $this ); } /** * Return the requested URL and point to file and line number from which the * exception occurred * + * @since 1.8 + * @deprecated since 1.22 Use MWExceptionHandler::getLogMessage instead. * @return string */ function getLogMessage() { - global $wgRequest; - - $id = $this->getLogId(); - $file = $this->getFile(); - $line = $this->getLine(); - $message = $this->getMessage(); - - if ( isset( $wgRequest ) && !$wgRequest instanceof FauxRequest ) { - $url = $wgRequest->getRequestURL(); - if ( !$url ) { - $url = '[no URL]'; - } - } else { - $url = '[no req]'; - } - - return "[$id] $url Exception from line $line of $file: $message"; + wfDeprecated( __METHOD__, '1.22' ); + return MWExceptionHandler::getLogMessage( $this ); } /** @@ -248,16 +241,9 @@ class MWException extends Exception { * It will be either HTML or plain text based on isCommandLine(). */ function report() { - global $wgLogExceptionBacktrace; - $log = $this->getLogMessage(); + global $wgMimeType; - if ( $log ) { - if ( $wgLogExceptionBacktrace ) { - wfDebugLog( 'exception', $log . "\n" . $this->getTraceAsString() . "\n" ); - } else { - wfDebugLog( 'exception', $log ); - } - } + MWExceptionHandler::logException( $this ); if ( defined( 'MW_API' ) ) { // Unhandled API exception, we can't be sure that format printer is alive @@ -268,6 +254,7 @@ class MWException extends Exception { } else { header( "HTTP/1.1 500 MediaWiki exception" ); header( "Status: 500 MediaWiki exception", true ); + header( "Content-Type: $wgMimeType; charset=utf-8", true ); $this->reportHTML(); } @@ -329,11 +316,17 @@ class ErrorPageError extends MWException { $this->msg = $msg; $this->params = $params; - if( $msg instanceof Message ) { - parent::__construct( $msg ); + // Bug 44111: Messages in the log files should be in English and not + // customized by the local wiki. So get the default English version for + // passing to the parent constructor. Our overridden report() below + // makes sure that the page shown to the user is not forced to English. + if ( $msg instanceof Message ) { + $enMsg = clone( $msg ); } else { - parent::__construct( wfMessage( $msg )->text() ); + $enMsg = wfMessage( $msg, $params ); } + $enMsg->inLanguage( 'en' )->useDatabase( false ); + parent::__construct( $enMsg->text() ); } function report() { @@ -461,39 +454,9 @@ class ThrottledError extends ErrorPageError { */ class UserBlockedError extends ErrorPageError { public function __construct( Block $block ) { - global $wgLang, $wgRequest; - - $blocker = $block->getBlocker(); - if ( $blocker instanceof User ) { // local user - $blockerUserpage = $block->getBlocker()->getUserPage(); - $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]"; - } else { // foreign user - $link = $blocker; - } - - $reason = $block->mReason; - if( $reason == '' ) { - $reason = wfMessage( 'blockednoreason' )->text(); - } - - /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked. - * This could be a username, an IP range, or a single IP. */ - $intended = $block->getTarget(); - - parent::__construct( - 'blockedtitle', - $block->mAuto ? 'autoblockedtext' : 'blockedtext', - array( - $link, - $reason, - $wgRequest->getIP(), - $block->getByName(), - $block->getId(), - $wgLang->formatExpiry( $block->mExpiry ), - $intended, - $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true ) - ) - ); + // @todo FIXME: Implement a more proper way to get context here. + $params = $block->getPermissionsError( RequestContext::getMain() ); + parent::__construct( 'blockedtitle', array_shift( $params ), $params ); } } @@ -611,7 +574,7 @@ class HttpError extends MWException { $content = htmlspecialchars( $this->content ); } - return "<!DOCTYPE html>\n". + return "<!DOCTYPE html>\n" . "<html><head><title>$header</title></head>\n" . "<body><h1>$header</h1><p>$content</p></body></html>\n"; } @@ -648,8 +611,10 @@ class MWExceptionHandler { $message = "MediaWiki internal error.\n\n"; if ( $wgShowExceptionDetails ) { - $message .= 'Original exception: ' . $e->__toString() . "\n\n" . - 'Exception caught inside exception handler: ' . $e2->__toString(); + $message .= 'Original exception: ' . self::getLogMessage( $e ) . + "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) . + "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) . + "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 ); } else { $message .= "Exception caught inside exception handler.\n\n" . "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " . @@ -665,11 +630,11 @@ class MWExceptionHandler { } } } else { - $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" . - $e->__toString() . "\n"; + $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\""; if ( $wgShowExceptionDetails ) { - $message .= "\n" . $e->getTraceAsString() . "\n"; + $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" . + self::getRedactedTraceAsString( $e ) . "\n"; } if ( $cmdLine ) { @@ -692,7 +657,7 @@ class MWExceptionHandler { if ( defined( 'STDERR' ) ) { fwrite( STDERR, $message ); } else { - echo( $message ); + echo $message; } } @@ -715,11 +680,145 @@ class MWExceptionHandler { // Final cleanup if ( $wgFullyInitialised ) { try { - wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition - } catch ( Exception $e ) {} + // uses $wgRequest, hence the $wgFullyInitialised condition + wfLogProfilingData(); + } catch ( Exception $e ) { + } } // Exit value should be nonzero for the benefit of shell jobs exit( 1 ); } + + /** + * Generate a string representation of an exception's stack trace + * + * Like Exception::getTraceAsString, but replaces argument values with + * argument type or class name. + * + * @param Exception $e + * @return string + */ + public static function getRedactedTraceAsString( Exception $e ) { + $text = ''; + + foreach ( self::getRedactedTrace( $e ) as $level => $frame ) { + if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) { + $text .= "#{$level} {$frame['file']}({$frame['line']}): "; + } else { + // 'file' and 'line' are unset for calls via call_user_func (bug 55634) + // This matches behaviour of Exception::getTraceAsString to instead + // display "[internal function]". + $text .= "#{$level} [internal function]: "; + } + + if ( isset( $frame['class'] ) ) { + $text .= $frame['class'] . $frame['type'] . $frame['function']; + } else { + $text .= $frame['function']; + } + + if ( isset( $frame['args'] ) ) { + $text .= '(' . implode( ', ', $frame['args'] ) . ")\n"; + } else { + $text .= "()\n"; + } + } + + $level = $level + 1; + $text .= "#{$level} {main}"; + + return $text; + } + + /** + * Return a copy of an exception's backtrace as an array. + * + * Like Exception::getTrace, but replaces each element in each frame's + * argument array with the name of its class (if the element is an object) + * or its type (if the element is a PHP primitive). + * + * @since 1.22 + * @param Exception $e + * @return array + */ + public static function getRedactedTrace( Exception $e ) { + return array_map( function ( $frame ) { + if ( isset( $frame['args'] ) ) { + $frame['args'] = array_map( function ( $arg ) { + return is_object( $arg ) ? get_class( $arg ) : gettype( $arg ); + }, $frame['args'] ); + } + return $frame; + }, $e->getTrace() ); + } + + + /** + * Get the ID for this error. + * + * The ID is saved so that one can match the one output to the user (when + * $wgShowExceptionDetails is set to false), to the entry in the debug log. + * + * @since 1.22 + * @param Exception $e + * @return string + */ + public static function getLogId( Exception $e ) { + if ( !isset( $e->_mwLogId ) ) { + $e->_mwLogId = wfRandomString( 8 ); + } + return $e->_mwLogId; + } + + /** + * Return the requested URL and point to file and line number from which the + * exception occurred. + * + * @since 1.22 + * @param Exception $e + * @return string + */ + public static function getLogMessage( Exception $e ) { + global $wgRequest; + + $id = self::getLogId( $e ); + $file = $e->getFile(); + $line = $e->getLine(); + $message = $e->getMessage(); + + if ( isset( $wgRequest ) && !$wgRequest instanceof FauxRequest ) { + $url = $wgRequest->getRequestURL(); + if ( !$url ) { + $url = '[no URL]'; + } + } else { + $url = '[no req]'; + } + + return "[$id] $url Exception from line $line of $file: $message"; + } + + /** + * Log an exception to the exception log (if enabled). + * + * This method must not assume the exception is an MWException, + * it is also used to handle PHP errors or errors from other libraries. + * + * @since 1.22 + * @param Exception $e + */ + public static function logException( Exception $e ) { + global $wgLogExceptionBacktrace; + + if ( !( $e instanceof MWException ) || $e->isLoggable() ) { + $log = self::getLogMessage( $e ); + if ( $wgLogExceptionBacktrace ) { + wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() . "\n" ); + } else { + wfDebugLog( 'exception', $log ); + } + } + } + } diff --git a/includes/Export.php b/includes/Export.php index d8cc0242..98de4c00 100644 --- a/includes/Export.php +++ b/includes/Export.php @@ -249,9 +249,13 @@ class WikiExporter { $where = array( 'user_id = log_user' ); # Hide private logs $hideLogs = LogEventsList::getExcludeClause( $this->db ); - if ( $hideLogs ) $where[] = $hideLogs; + if ( $hideLogs ) { + $where[] = $hideLogs; + } # Add on any caller specified conditions - if ( $cond ) $where[] = $cond; + if ( $cond ) { + $where[] = $cond; + } # Get logging table name for logging.* clause $logging = $this->db->tableName( 'logging' ); @@ -296,6 +300,7 @@ class WikiExporter { } // Inform caller about problem + wfProfileOut( __METHOD__ ); throw $e; } # For page dumps... @@ -541,7 +546,7 @@ class XmlDumpWriter { * @return string */ function homelink() { - return Xml::element( 'base', array(), Title::newMainPage()->getCanonicalUrl() ); + return Xml::element( 'base', array(), Title::newMainPage()->getCanonicalURL() ); } /** @@ -563,8 +568,9 @@ class XmlDumpWriter { foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) { $spaces .= ' ' . Xml::element( 'namespace', - array( 'key' => $ns, - 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive', + array( + 'key' => $ns, + 'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive', ), $title ) . "\n"; } $spaces .= " </namespaces>"; @@ -593,7 +599,7 @@ class XmlDumpWriter { $out = " <page>\n"; $title = Title::makeTitle( $row->page_namespace, $row->page_title ); $out .= ' ' . Xml::elementClean( 'title', array(), self::canonicalTitle( $title ) ) . "\n"; - $out .= ' ' . Xml::element( 'ns', array(), strval( $row->page_namespace) ) . "\n"; + $out .= ' ' . Xml::element( 'ns', array(), strval( $row->page_namespace ) ) . "\n"; $out .= ' ' . Xml::element( 'id', array(), strval( $row->page_id ) ) . "\n"; if ( $row->page_is_redirect ) { $page = WikiPage::factory( $title ); @@ -636,7 +642,7 @@ class XmlDumpWriter { $out = " <revision>\n"; $out .= " " . Xml::element( 'id', null, strval( $row->rev_id ) ) . "\n"; - if( isset( $row->rev_parent_id ) && $row->rev_parent_id ) { + if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) { $out .= " " . Xml::element( 'parentid', null, strval( $row->rev_parent_id ) ) . "\n"; } @@ -683,7 +689,7 @@ class XmlDumpWriter { $content_model = strval( $row->rev_content_model ); } else { // probably using $wgContentHandlerUseDB = false; - // @todo: test! + // @todo test! $title = Title::makeTitle( $row->page_namespace, $row->page_title ); $content_model = ContentHandler::getDefaultModelFor( $title ); } @@ -694,7 +700,7 @@ class XmlDumpWriter { $content_format = strval( $row->rev_content_format ); } else { // probably using $wgContentHandlerUseDB = false; - // @todo: test! + // @todo test! $content_handler = ContentHandler::getForModelID( $content_model ); $content_format = $content_handler->getDefaultFormat(); } @@ -818,10 +824,13 @@ class XmlDumpWriter { $archiveName = ''; } if ( $dumpContents ) { + $be = $file->getRepo()->getBackend(); # Dump file as base64 # Uses only XML-safe characters, so does not need escaping + # @TODO: too bad this loads the contents into memory (script might swap) $contents = ' <contents encoding="base64">' . - chunk_split( base64_encode( file_get_contents( $file->getPath() ) ) ) . + chunk_split( base64_encode( + $be->getFileContents( array( 'src' => $file->getPath() ) ) ) ) . " </contents>\n"; } else { $contents = ''; @@ -837,7 +846,7 @@ class XmlDumpWriter { " " . $comment . "\n" . " " . Xml::element( 'filename', null, $file->getName() ) . "\n" . $archiveName . - " " . Xml::element( 'src', null, $file->getCanonicalUrl() ) . "\n" . + " " . Xml::element( 'src', null, $file->getCanonicalURL() ) . "\n" . " " . Xml::element( 'size', null, $file->getSize() ) . "\n" . " " . Xml::element( 'sha1base36', null, $file->getSha1() ) . "\n" . " " . Xml::element( 'rel', null, $file->getRel() ) . "\n" . @@ -1185,7 +1194,7 @@ class Dump7ZipOutput extends DumpPipeOutput { // Suppress annoying useless crap from p7zip // Unfortunately this could suppress real error messages too $command .= ' >' . wfGetNull() . ' 2>&1'; - return( $command ); + return $command; } /** diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php deleted file mode 100644 index 11e94230..00000000 --- a/includes/ExternalEdit.php +++ /dev/null @@ -1,132 +0,0 @@ -<?php -/** - * External editors support - * - * License: Public domain - * - * @file - * @author Erik Moeller <moeller@scireview.de> - */ - -/** - * Support for external editors to modify both text and files - * in external applications. It works as follows: MediaWiki - * sends a meta-file with the MIME type 'application/x-external-editor' - * to the client. The user has to associate that MIME type with - * a helper application (a reference implementation in Perl - * can be found in extensions/ee), which will launch the editor, - * and save the modified data back to the server. - * - */ -class ExternalEdit extends ContextSource { - - /** - * Array of URLs to link to - * @var Array - */ - private $urls; - - /** - * Constructor - * @param $context IContextSource context to use - * @param $urls array - */ - public function __construct( IContextSource $context, array $urls = array() ) { - $this->setContext( $context ); - $this->urls = $urls; - } - - /** - * Check whether external edit or diff should be used. - * - * @param $context IContextSource context to use - * @param string $type can be either 'edit' or 'diff' - * @return Bool - */ - public static function useExternalEngine( IContextSource $context, $type ) { - global $wgUseExternalEditor; - - if ( !$wgUseExternalEditor ) { - return false; - } - - $pref = $type == 'diff' ? 'externaldiff' : 'externaleditor'; - $request = $context->getRequest(); - - return !$request->getVal( 'internaledit' ) && - ( $context->getUser()->getOption( $pref ) || $request->getVal( 'externaledit' ) ); - } - - /** - * Output the information for the external editor - */ - public function execute() { - global $wgContLang, $wgScript, $wgScriptPath, $wgCanonicalServer; - - $this->getOutput()->disable(); - - $response = $this->getRequest()->response(); - $response->header( 'Content-type: application/x-external-editor; charset=utf-8' ); - $response->header( 'Cache-control: no-cache' ); - - $special = $wgContLang->getNsText( NS_SPECIAL ); - - # $type can be "Edit text", "Edit file" or "Diff text" at the moment - # See the protocol specifications at [[m:Help:External editors/Tech]] for - # details. - if ( count( $this->urls ) ) { - $urls = $this->urls; - $type = "Diff text"; - } elseif ( $this->getRequest()->getVal( 'mode' ) == 'file' ) { - $type = "Edit file"; - $image = wfLocalFile( $this->getTitle() ); - if ( $image ) { - $urls = array( - 'File' => array( - 'Extension' => $image->getExtension(), - 'URL' => $image->getCanonicalURL() - ) - ); - } else { - $urls = array(); - } - } else { - $type = "Edit text"; - # *.wiki file extension is used by some editors for syntax - # highlighting, so we follow that convention - $urls = array( 'File' => array( - 'Extension' => 'wiki', - 'URL' => $this->getTitle()->getCanonicalURL( - array( 'action' => 'edit', 'internaledit' => 'true' ) ) - ) ); - } - - $files = ''; - foreach( $urls as $key => $vars ) { - $files .= "\n[$key]\n"; - foreach( $vars as $varname => $varval ) { - $files .= "$varname=$varval\n"; - } - } - - $url = $this->getTitle()->getFullURL( - $this->getRequest()->appendQueryValue( 'internaledit', 1, true ) ); - - $control = <<<CONTROL -; You're seeing this file because you're using Mediawiki's External Editor feature. -; This is probably because you selected use external editor in your preferences. -; To edit normally, either disable that preference or go to the URL: -; $url -; See http://www.mediawiki.org/wiki/Manual:External_editors for details. -[Process] -Type=$type -Engine=MediaWiki -Script={$wgCanonicalServer}{$wgScript} -Server={$wgCanonicalServer} -Path={$wgScriptPath} -Special namespace=$special -$files -CONTROL; - echo $control; - } -} diff --git a/includes/ExternalUser.php b/includes/ExternalUser.php deleted file mode 100644 index 580b9896..00000000 --- a/includes/ExternalUser.php +++ /dev/null @@ -1,309 +0,0 @@ -<?php -/** - * Authentication with a foreign database - * - * Copyright © 2009 Aryeh Gregor - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - */ - -/** - * @defgroup ExternalUser ExternalUser - */ - -/** - * A class intended to supplement, and perhaps eventually replace, AuthPlugin. - * See: http://www.mediawiki.org/wiki/ExternalAuth - * - * The class represents a user whose data is in a foreign database. The - * database may have entirely different conventions from MediaWiki, but it's - * assumed to at least support the concept of a user id (possibly not an - * integer), a user name (possibly not meeting MediaWiki's username - * requirements), and a password. - * - * @ingroup ExternalUser - */ -abstract class ExternalUser { - protected function __construct() {} - - /** - * Wrappers around initFrom*(). - */ - - /** - * @param $name string - * @return mixed ExternalUser, or false on failure - */ - public static function newFromName( $name ) { - global $wgExternalAuthType; - if ( is_null( $wgExternalAuthType ) ) { - return false; - } - $obj = new $wgExternalAuthType; - if ( !$obj->initFromName( $name ) ) { - return false; - } - return $obj; - } - - /** - * @param $id string - * @return mixed ExternalUser, or false on failure - */ - public static function newFromId( $id ) { - global $wgExternalAuthType; - if ( is_null( $wgExternalAuthType ) ) { - return false; - } - $obj = new $wgExternalAuthType; - if ( !$obj->initFromId( $id ) ) { - return false; - } - return $obj; - } - - /** - * @return mixed ExternalUser, or false on failure - */ - public static function newFromCookie() { - global $wgExternalAuthType; - if ( is_null( $wgExternalAuthType ) ) { - return false; - } - $obj = new $wgExternalAuthType; - if ( !$obj->initFromCookie() ) { - return false; - } - return $obj; - } - - /** - * Creates the object corresponding to the given User object, assuming the - * user exists on the wiki and is linked to an external account. If either - * of these is false, this will return false. - * - * This is a wrapper around newFromId(). - * - * @param $user User - * @return ExternalUser|bool False on failure - */ - public static function newFromUser( $user ) { - global $wgExternalAuthType; - if ( is_null( $wgExternalAuthType ) ) { - # Short-circuit to avoid database query in common case so no one - # kills me - return false; - } - - $dbr = wfGetDB( DB_SLAVE ); - $id = $dbr->selectField( 'external_user', 'eu_external_id', - array( 'eu_local_id' => $user->getId() ), __METHOD__ ); - if ( $id === false ) { - return false; - } - return self::newFromId( $id ); - } - - /** - * Given a name, which is a string exactly as input by the user in the - * login form but with whitespace stripped, initialize this object to be - * the corresponding ExternalUser. Return true if successful, otherwise - * false. - * - * @param $name string - * @return bool Success? - */ - abstract protected function initFromName( $name ); - - /** - * Given an id, which was at some previous point in history returned by - * getId(), initialize this object to be the corresponding ExternalUser. - * Return true if successful, false otherwise. - * - * @param $id string - * @return bool Success? - */ - abstract protected function initFromId( $id ); - - /** - * Try to magically initialize the user from cookies or similar information - * so he or she can be logged in on just viewing the wiki. If this is - * impossible to do, just return false. - * - * TODO: Actually use this. - * - * @return bool Success? - */ - protected function initFromCookie() { - return false; - } - - /** - * This must return some identifier that stably, uniquely identifies the - * user. In a typical web application, this could be an integer - * representing the "user id". In other cases, it might be a string. In - * any event, the return value should be a string between 1 and 255 - * characters in length; must uniquely identify the user in the foreign - * database; and, if at all possible, should be permanent. - * - * This will only ever be used to reconstruct this ExternalUser object via - * newFromId(). The resulting object in that case should correspond to the - * same user, even if details have changed in the interim (e.g., renames or - * preference changes). - * - * @return string - */ - abstract public function getId(); - - /** - * This must return the name that the user would normally use for login to - * the external database. It is subject to no particular restrictions - * beyond rudimentary sanity, and in particular may be invalid as a - * MediaWiki username. It's used to auto-generate an account name that - * *is* valid for MediaWiki, either with or without user input, but - * basically is only a hint. - * - * @return string - */ - abstract public function getName(); - - /** - * Is the given password valid for the external user? The password is - * provided in plaintext. - * - * @param $password string - * @return bool - */ - abstract public function authenticate( $password ); - - /** - * Retrieve the value corresponding to the given preference key. The most - * important values are: - * - * - emailaddress - * - language - * - * The value must meet MediaWiki's requirements for values of this type, - * and will be checked for validity before use. If the preference makes no - * sense for the backend, or it makes sense but is unset for this user, or - * is unrecognized, return null. - * - * $pref will never equal 'password', since passwords are usually hashed - * and cannot be directly retrieved. authenticate() is used for this - * instead. - * - * TODO: Currently this is only called for 'emailaddress'; generalize! Add - * some config option to decide which values are grabbed on user - * initialization. - * - * @param $pref string - * @return mixed - */ - public function getPref( $pref ) { - return null; - } - - /** - * Return an array of identifiers for all the foreign groups that this user - * has. The identifiers are opaque objects that only need to be - * specifiable by the administrator in LocalSettings.php when configuring - * $wgAutopromote. They may be, for instance, strings or integers. - * - * TODO: Support this in $wgAutopromote. - * - * @return array - */ - public function getGroups() { - return array(); - } - - /** - * Given a preference key (e.g., 'emailaddress'), provide an HTML message - * telling the user how to change it in the external database. The - * administrator has specified that this preference cannot be changed on - * the wiki, and may only be changed in the foreign database. If no - * message is available, such as for an unrecognized preference, return - * false. - * - * TODO: Use this somewhere. - * - * @param $pref string - * @return mixed String or false - */ - public static function getPrefMessage( $pref ) { - return false; - } - - /** - * Set the given preference key to the given value. Two important - * preference keys that you might want to implement are 'password' and - * 'emailaddress'. If the set fails, such as because the preference is - * unrecognized or because the external database can't be changed right - * now, return false. If it succeeds, return true. - * - * If applicable, you should make sure to validate the new value against - * any constraints the external database may have, since MediaWiki may have - * more limited constraints (e.g., on password strength). - * - * TODO: Untested. - * - * @param $key string - * @param $value string - * @return bool Success? - */ - public static function setPref( $key, $value ) { - return false; - } - - /** - * Create a link for future reference between this object and the provided - * user_id. If the user was already linked, the old link will be - * overwritten. - * - * This is part of the core code and is not overridable by specific - * plugins. It's in this class only for convenience. - * - * @param int $id user_id - */ - final public function linkToLocal( $id ) { - $dbw = wfGetDB( DB_MASTER ); - $dbw->replace( 'external_user', - array( 'eu_local_id', 'eu_external_id' ), - array( 'eu_local_id' => $id, - 'eu_external_id' => $this->getId() ), - __METHOD__ ); - } - - /** - * Check whether this external user id is already linked with - * a local user. - * @return Mixed User if the account is linked, Null otherwise. - */ - final public function getLocalUser() { - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( - 'external_user', - '*', - array( 'eu_external_id' => $this->getId() ) - ); - return $row - ? User::newFromId( $row->eu_local_id ) - : null; - } - -} diff --git a/includes/Fallback.php b/includes/Fallback.php index 2e19a095..cdf6c88e 100644 --- a/includes/Fallback.php +++ b/includes/Fallback.php @@ -35,13 +35,13 @@ class Fallback { if ( substr( $to, -8 ) == '//IGNORE' ) { $to = substr( $to, 0, strlen( $to ) - 8 ); } - if( strcasecmp( $from, $to ) == 0 ) { + if ( strcasecmp( $from, $to ) == 0 ) { return $string; } - if( strcasecmp( $from, 'utf-8' ) == 0 ) { + if ( strcasecmp( $from, 'utf-8' ) == 0 ) { return utf8_decode( $string ); } - if( strcasecmp( $to, 'utf-8' ) == 0 ) { + if ( strcasecmp( $to, 'utf-8' ) == 0 ) { return utf8_encode( $string ); } return $string; @@ -64,12 +64,12 @@ class Fallback { * @return string */ public static function mb_substr( $str, $start, $count = 'end' ) { - if( $start != 0 ) { + if ( $start != 0 ) { $split = self::mb_substr_split_unicode( $str, intval( $start ) ); $str = substr( $str, $split ); } - if( $count !== 'end' ) { + if ( $count !== 'end' ) { $split = self::mb_substr_split_unicode( $str, intval( $count ) ); $str = substr( $str, 0, $split ); } @@ -83,14 +83,14 @@ class Fallback { * @return int */ public static function mb_substr_split_unicode( $str, $splitPos ) { - if( $splitPos == 0 ) { + if ( $splitPos == 0 ) { return 0; } $byteLen = strlen( $str ); - if( $splitPos > 0 ) { - if( $splitPos > 256 ) { + if ( $splitPos > 0 ) { + if ( $splitPos > 256 ) { // Optimize large string offsets by skipping ahead N bytes. // This will cut out most of our slow time on Latin-based text, // and 1/2 to 1/3 on East European and Asian scripts. @@ -104,7 +104,7 @@ class Fallback { $bytePos = 0; } - while( $charPos++ < $splitPos ) { + while ( $charPos++ < $splitPos ) { ++$bytePos; // Move past any tail bytes while ( $bytePos < $byteLen && $str[$bytePos] >= "\x80" && $str[$bytePos] < "\xc0" ) { @@ -115,7 +115,7 @@ class Fallback { $splitPosX = $splitPos + 1; $charPos = 0; // relative to end of string; we don't care about the actual char position here $bytePos = $byteLen; - while( $bytePos > 0 && $charPos-- >= $splitPosX ) { + while ( $bytePos > 0 && $charPos-- >= $splitPosX ) { --$bytePos; // Move past any tail bytes while ( $bytePos > 0 && $str[$bytePos] >= "\x80" && $str[$bytePos] < "\xc0" ) { @@ -138,12 +138,12 @@ class Fallback { $total = 0; // Count ASCII bytes - for( $i = 0; $i < 0x80; $i++ ) { + for ( $i = 0; $i < 0x80; $i++ ) { $total += $counts[$i]; } // Count multibyte sequence heads - for( $i = 0xc0; $i < 0xff; $i++ ) { + for ( $i = 0xc0; $i < 0xff; $i++ ) { $total += $counts[$i]; } return $total; @@ -163,7 +163,7 @@ class Fallback { $ar = array(); preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset ); - if( isset( $ar[0][1] ) ) { + if ( isset( $ar[0][1] ) ) { return $ar[0][1]; } else { return false; @@ -184,7 +184,7 @@ class Fallback { $ar = array(); preg_match_all( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset ); - if( isset( $ar[0] ) && count( $ar[0] ) > 0 && + if ( isset( $ar[0] ) && count( $ar[0] ) > 0 && isset( $ar[0][count( $ar[0] ) - 1][1] ) ) { return $ar[0][count( $ar[0] ) - 1][1]; } else { diff --git a/includes/Feed.php b/includes/Feed.php index caf2e571..635b04e4 100644 --- a/includes/Feed.php +++ b/includes/Feed.php @@ -245,7 +245,7 @@ abstract class ChannelFeed extends FeedItem { global $wgRequest; $ctype = $wgRequest->getVal( 'ctype', 'application/xml' ); $allowedctypes = array( 'application/xml', 'text/xml', 'application/rss+xml', 'application/atom+xml' ); - return (in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml'); + return ( in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml' ); } /** @@ -306,13 +306,13 @@ class RSSFeed extends ChannelFeed { function outItem( $item ) { ?> <item> - <title><?php print $item->getTitle() ?></title> - <link><?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ) ?></link> - <guid<?php if( !$item->rssIsPermalink ) print ' isPermaLink="false"' ?>><?php print $item->getUniqueId() ?></guid> + <title><?php print $item->getTitle(); ?></title> + <link><?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ); ?></link> + <guid<?php if ( !$item->rssIsPermalink ) { print ' isPermaLink="false"'; } ?>><?php print $item->getUniqueId(); ?></guid> <description><?php print $item->getDescription() ?></description> - <?php if( $item->getDate() ) { ?><pubDate><?php print $this->formatTime( $item->getDate() ) ?></pubDate><?php } ?> - <?php if( $item->getAuthor() ) { ?><dc:creator><?php print $item->getAuthor() ?></dc:creator><?php }?> - <?php if( $item->getComments() ) { ?><comments><?php print wfExpandUrl( $item->getComments(), PROTO_CURRENT ) ?></comments><?php }?> + <?php if ( $item->getDate() ) { ?><pubDate><?php print $this->formatTime( $item->getDate() ); ?></pubDate><?php } ?> + <?php if ( $item->getAuthor() ) { ?><dc:creator><?php print $item->getAuthor(); ?></dc:creator><?php }?> + <?php if ( $item->getComments() ) { ?><comments><?php print wfExpandUrl( $item->getComments(), PROTO_CURRENT ); ?></comments><?php }?> </item> <?php } @@ -392,15 +392,15 @@ class AtomFeed extends ChannelFeed { global $wgMimeType; ?> <entry> - <id><?php print $item->getUniqueId() ?></id> - <title><?php print $item->getTitle() ?></title> - <link rel="alternate" type="<?php print $wgMimeType ?>" href="<?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ) ?>"/> - <?php if( $item->getDate() ) { ?> - <updated><?php print $this->formatTime( $item->getDate() ) ?>Z</updated> + <id><?php print $item->getUniqueId(); ?></id> + <title><?php print $item->getTitle(); ?></title> + <link rel="alternate" type="<?php print $wgMimeType ?>" href="<?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ); ?>"/> + <?php if ( $item->getDate() ) { ?> + <updated><?php print $this->formatTime( $item->getDate() ); ?>Z</updated> <?php } ?> <summary type="html"><?php print $item->getDescription() ?></summary> - <?php if( $item->getAuthor() ) { ?><author><name><?php print $item->getAuthor() ?></name></author><?php }?> + <?php if ( $item->getAuthor() ) { ?><author><name><?php print $item->getAuthor(); ?></name></author><?php }?> </entry> <?php /* @todo FIXME: Need to add comments diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php index adc1f781..22cb52be 100644 --- a/includes/FeedUtils.php +++ b/includes/FeedUtils.php @@ -59,7 +59,7 @@ class FeedUtils { return false; } - if( !isset( $wgFeedClasses[$type] ) ) { + if ( !isset( $wgFeedClasses[$type] ) ) { $wgOut->addWikiMsg( 'feed-invalid' ); return false; } @@ -77,14 +77,14 @@ class FeedUtils { $titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title ); $timestamp = wfTimestamp( TS_MW, $row->rc_timestamp ); $actiontext = ''; - if( $row->rc_type == RC_LOG ) { + if ( $row->rc_type == RC_LOG ) { $rcRow = (array)$row; // newFromRow() only accepts arrays for RC rows $actiontext = LogFormatter::newFromRow( $rcRow )->getActionText(); } return self::formatDiffRow( $titleObj, $row->rc_last_oldid, $row->rc_this_oldid, $timestamp, - ($row->rc_deleted & Revision::DELETED_COMMENT) + $row->rc_deleted & Revision::DELETED_COMMENT ? wfMessage( 'rev-deleted-comment' )->escaped() : $row->rc_comment, $actiontext @@ -121,13 +121,13 @@ class FeedUtils { // Can't diff special pages, unreadable pages or pages with no new revision // to compare against: just return the text. - if( $title->getNamespace() < 0 || $accErrors || !$newid ) { + if ( $title->getNamespace() < 0 || $accErrors || !$newid ) { wfProfileOut( __METHOD__ ); return $completeText; } - if( $oldid ) { - wfProfileIn( __METHOD__."-dodiff" ); + if ( $oldid ) { + wfProfileIn( __METHOD__ . "-dodiff" ); #$diffText = $de->getDiff( wfMessage( 'revisionasof', # $wgLang->timeanddate( $timestamp ), @@ -168,10 +168,10 @@ class FeedUtils { $diffText = UtfNormal::cleanUp( $diffText ); $diffText = self::applyDiffStyle( $diffText ); } - wfProfileOut( __METHOD__."-dodiff" ); + wfProfileOut( __METHOD__ . "-dodiff" ); } else { $rev = Revision::newFromId( $newid ); - if( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) { + if ( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) { $newContent = ContentHandler::getForTitle( $title )->makeEmptyContent(); } else { $newContent = $rev->getContent(); @@ -220,10 +220,11 @@ class FeedUtils { * @return string */ protected static function getDiffLink( Title $title, $newid, $oldid = null ) { - $queryParameters = ($oldid == null) - ? "diff={$newid}" - : "diff={$newid}&oldid={$oldid}"; - $diffUrl = $title->getFullUrl( $queryParameters ); + $queryParameters = array( 'diff' => $newid ); + if ( $oldid != null ) { + $queryParameters['oldid'] = $oldid; + } + $diffUrl = $title->getFullURL( $queryParameters ); $diffLink = Html::element( 'a', array( 'href' => $diffUrl ), wfMessage( 'showdiff' )->inContentLanguage()->text() ); @@ -250,7 +251,7 @@ class FeedUtils { 'diffchange' => 'font-weight: bold; text-decoration: none;', ); - foreach( $styles as $class => $style ) { + foreach ( $styles as $class => $style ) { $text = preg_replace( "/(<[^>]+)class=(['\"])$class\\2([^>]*>)/", "\\1style=\"$style\"\\3", $text ); } diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php index 28403cca..65d82b87 100644 --- a/includes/FileDeleteForm.php +++ b/includes/FileDeleteForm.php @@ -82,18 +82,18 @@ class FileDeleteForm { # Flag to hide all contents of the archived revisions $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); - if( $this->oldimage ) { + if ( $this->oldimage ) { $this->oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->title, $this->oldimage ); } - if( !self::haveDeletableFile( $this->file, $this->oldfile, $this->oldimage ) ) { + if ( !self::haveDeletableFile( $this->file, $this->oldfile, $this->oldimage ) ) { $wgOut->addHTML( $this->prepareMessage( 'filedelete-nofile' ) ); $wgOut->addReturnTo( $this->title ); return; } // Perform the deletion if appropriate - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->oldimage ) ) { + if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->oldimage ) ) { $deleteReasonList = $wgRequest->getText( 'wpDeleteReasonList' ); $deleteReason = $wgRequest->getText( 'wpReason' ); @@ -109,24 +109,18 @@ class FileDeleteForm { $status = self::doDelete( $this->title, $this->file, $this->oldimage, $reason, $suppress, $wgUser ); - if( !$status->isGood() ) { + if ( !$status->isGood() ) { $wgOut->addHTML( '<h2>' . $this->prepareMessage( 'filedeleteerror-short' ) . "</h2>\n" ); $wgOut->addWikiText( '<div class="error">' . $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' ) . '</div>' ); } - if( $status->ok ) { + if ( $status->ok ) { $wgOut->setPageTitle( wfMessage( 'actioncomplete' ) ); $wgOut->addHTML( $this->prepareMessage( 'filedelete-success' ) ); // Return to the main page if we just deleted all versions of the // file, otherwise go back to the description page $wgOut->addReturnTo( $this->oldimage ? $this->title : Title::newMainPage() ); - if ( $wgUser->isLoggedIn() && $wgRequest->getCheck( 'wpWatch' ) != $wgUser->isWatched( $this->title ) ) { - if ( $wgRequest->getCheck( 'wpWatch' ) ) { - WatchAction::doWatch( $this->title, $wgUser ); - } else { - WatchAction::doUnwatch( $this->title, $wgUser ); - } - } + WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'wpWatch' ), $this->title, $wgUser ); } return; } @@ -153,13 +147,13 @@ class FileDeleteForm { $user = $wgUser; } - if( $oldimage ) { + if ( $oldimage ) { $page = null; $status = $file->deleteOld( $oldimage, $reason, $suppress ); - if( $status->ok ) { + if ( $status->ok ) { // Need to do a log item $logComment = wfMessage( 'deletedrevision', $oldimage )->inContentLanguage()->text(); - if( trim( $reason ) != '' ) { + if ( trim( $reason ) != '' ) { $logComment .= wfMessage( 'colon-separator' ) ->inContentLanguage()->text() . $reason; } @@ -187,7 +181,7 @@ class FileDeleteForm { // or revision is missing, so check for isOK() rather than isGood() if ( $deleteStatus->isOK() ) { $status = $file->delete( $reason, $suppress ); - if( $status->isOK() ) { + if ( $status->isOK() ) { $dbw->commit( __METHOD__ ); } else { $dbw->rollback( __METHOD__ ); @@ -213,7 +207,7 @@ class FileDeleteForm { private function showForm() { global $wgOut, $wgUser, $wgRequest; - if( $wgUser->isAllowed( 'suppressrevision' ) ) { + if ( $wgUser->isAllowed( 'suppressrevision' ) ) { $suppress = "<tr id=\"wpDeleteSuppressRow\"> <td></td> <td class='mw-input'><strong>" . @@ -258,7 +252,7 @@ class FileDeleteForm { "</td> </tr> {$suppress}"; - if( $wgUser->isLoggedIn() ) { + if ( $wgUser->isLoggedIn() ) { $form .= " <tr> <td></td> @@ -314,7 +308,7 @@ class FileDeleteForm { */ private function prepareMessage( $message ) { global $wgLang; - if( $this->oldimage ) { + if ( $this->oldimage ) { return wfMessage( "{$message}-old", # To ensure grep will find them: 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old' wfEscapeWikiText( $this->title->getText() ), @@ -375,11 +369,11 @@ class FileDeleteForm { $q = array(); $q['action'] = 'delete'; - if( $this->oldimage ) { + if ( $this->oldimage ) { $q['oldimage'] = $this->oldimage; } - return $this->title->getLocalUrl( $q ); + return $this->title->getLocalURL( $q ); } /** diff --git a/includes/ForkController.php b/includes/ForkController.php index 89ad9553..ced45af6 100644 --- a/includes/ForkController.php +++ b/includes/ForkController.php @@ -121,7 +121,9 @@ class ForkController { if ( function_exists( 'pcntl_signal_dispatch' ) ) { pcntl_signal_dispatch(); } else { - declare (ticks=1) { $status = $status; } + declare( ticks = 1 ) { + $status = $status; + } } // Respond to TERM signal if ( $this->termReceived ) { diff --git a/includes/FormOptions.php b/includes/FormOptions.php index 8477ed98..54822e32 100644 --- a/includes/FormOptions.php +++ b/includes/FormOptions.php @@ -28,7 +28,8 @@ /** * Helper class to keep track of options when mixing links and form elements. * - * @todo This badly need some examples and tests :-) + * @todo This badly needs some examples and tests :) The usage in SpecialRecentchanges class is a + * good ersatz in the meantime. */ class FormOptions implements ArrayAccess { /** @name Type constants @@ -50,12 +51,26 @@ class FormOptions implements ArrayAccess { /* @} */ /** - * @todo Document! + * Map of known option names to information about them. + * + * Each value is an array with the following keys: + * - 'default' - the default value as passed to add() + * - 'value' - current value, start with null, can be set by various functions + * - 'consumed' - true/false, whether the option was consumed using + * consumeValue() or consumeValues() + * - 'type' - one of the type constants (but never AUTO) */ protected $options = array(); # Setting up + /** + * Add an option to be handled by this FormOptions instance. + * + * @param string $name Request parameter name + * @param mixed $default Default value when the request parameter is not present + * @param int $type One of the type constants (optional, defaults to AUTO) + */ public function add( $name, $default, $type = self::AUTO ) { $option = array(); $option['default'] = $default; @@ -71,20 +86,25 @@ class FormOptions implements ArrayAccess { $this->options[$name] = $option; } + /** + * Remove an option being handled by this FormOptions instance. This is the inverse of add(). + * + * @param string $name Request parameter name + */ public function delete( $name ) { $this->validateName( $name, true ); unset( $this->options[$name] ); } /** - * Used to find out which type the data is. - * All types are defined in the 'Type constants' section of this class - * Please note we do not support detection of INTNULL MediaWiki type - * which will be assumed as INT if the data is an integer. + * Used to find out which type the data is. All types are defined in the 'Type constants' section + * of this class. * - * @param $data Mixed: value to guess type for - * @throws MWException - * @exception MWException Unsupported datatype + * Detection of the INTNULL type is not supported; INT will be assumed if the data is an integer, + * MWException will be thrown if it's null. + * + * @param mixed $data Value to guess the type for + * @throws MWException If unable to guess the type * @return int Type constant */ public static function guessType( $data ) { @@ -102,12 +122,12 @@ class FormOptions implements ArrayAccess { # Handling values /** - * Verify the given option name exist. + * Verify that the given option name exists. * - * @param string $name option name - * @param $strict Boolean: throw an exception when the option does not exist (default false) + * @param string $name Option name + * @param bool $strict Throw an exception when the option doesn't exist instead of returning false * @throws MWException - * @return Boolean: true if option exist, false otherwise + * @return bool True if the option exists, false otherwise */ public function validateName( $name, $strict = false ) { if ( !isset( $this->options[$name] ) ) { @@ -117,16 +137,17 @@ class FormOptions implements ArrayAccess { return false; } } + return true; } /** * Use to set the value of an option. * - * @param string $name option name - * @param $value Mixed: value for the option - * @param $force Boolean: whether to set the value when it is equivalent to the default value for this option (default false). - * @return null + * @param string $name Option name + * @param mixed $value Value for the option + * @param bool $force Whether to set the value when it is equivalent to the default value for this + * option (default false). */ public function setValue( $name, $value, $force = false ) { $this->validateName( $name, true ); @@ -140,11 +161,10 @@ class FormOptions implements ArrayAccess { } /** - * Get the value for the given option name. - * Internally use getValueReal() + * Get the value for the given option name. Uses getValueReal() internally. * - * @param string $name option name - * @return Mixed + * @param string $name Option name + * @return mixed */ public function getValue( $name ) { $this->validateName( $name, true ); @@ -153,9 +173,10 @@ class FormOptions implements ArrayAccess { } /** - * @todo Document - * @param array $option array structure describing the option - * @return Mixed. Value or the default value if it is null + * Return current option value, based on a structure taken from $options. + * + * @param array $option Array structure describing the option + * @return mixed Value, or the default value if it is null */ protected function getValueReal( $option ) { if ( $option['value'] !== null ) { @@ -167,9 +188,8 @@ class FormOptions implements ArrayAccess { /** * Delete the option value. - * This will make future calls to getValue() return the default value. - * @param string $name option name - * @return null + * This will make future calls to getValue() return the default value. + * @param string $name Option name */ public function reset( $name ) { $this->validateName( $name, true ); @@ -177,10 +197,13 @@ class FormOptions implements ArrayAccess { } /** - * @todo Document + * Get the value of given option and mark it as 'consumed'. Consumed options are not returned + * by getUnconsumedValues(). + * + * @see consumeValues() + * @throws MWException If the option does not exist * @param string $name Option name - * @throws MWException If option does not exist. - * @return mixed Value or the default value if it is null. + * @return mixed Value, or the default value if it is null */ public function consumeValue( $name ) { $this->validateName( $name, true ); @@ -190,11 +213,15 @@ class FormOptions implements ArrayAccess { } /** - * @todo Document - * @param array $names array of option names - * @return null + * Get the values of given options and mark them as 'consumed'. Consumed options are not returned + * by getUnconsumedValues(). + * + * @see consumeValue() + * @throws MWException If any option does not exist + * @param array $names Array of option names as strings + * @return array Array of option values, or the default values if they are null */ - public function consumeValues( /*Array*/ $names ) { + public function consumeValues( $names ) { $out = array(); foreach ( $names as $name ) { @@ -213,9 +240,7 @@ class FormOptions implements ArrayAccess { * @param string $name option name * @param int $min minimum value * @param int $max maximum value - * @throws MWException - * @exception MWException Option is not of type int - * @return null + * @throws MWException If option is not of type INT */ public function validateIntBounds( $name, $min, $max ) { $this->validateName( $name, true ); @@ -231,9 +256,10 @@ class FormOptions implements ArrayAccess { } /** - * Getting the data out for use - * @param $all Boolean: whether to include unchanged options (default: false) - * @return Array + * Get all remaining values which have not been consumed by consumeValue() or consumeValues(). + * + * @param bool $all Whether to include unchanged options (default: false) + * @return array */ public function getUnconsumedValues( $all = false ) { $values = array(); @@ -251,7 +277,7 @@ class FormOptions implements ArrayAccess { /** * Return options modified as an array ( name => value ) - * @return Array + * @return array */ public function getChangedValues() { $values = array(); @@ -266,8 +292,8 @@ class FormOptions implements ArrayAccess { } /** - * Format options to an array ( name => value) - * @return Array + * Format options to an array ( name => value ) + * @return array */ public function getAllValues() { $values = array(); @@ -281,24 +307,38 @@ class FormOptions implements ArrayAccess { # Reading values - public function fetchValuesFromRequest( WebRequest $r, $values = false ) { - if ( !$values ) { - $values = array_keys( $this->options ); + /** + * Fetch values for all options (or selected options) from the given WebRequest, making them + * available for accessing with getValue() or consumeValue() etc. + * + * @param WebRequest $r The request to fetch values from + * @param array $optionKeys Which options to fetch the values for (default: + * all of them). Note that passing an empty array will also result in + * values for all keys being fetched. + * @throws MWException If the type of any option is invalid + */ + public function fetchValuesFromRequest( WebRequest $r, $optionKeys = null ) { + if ( !$optionKeys ) { + $optionKeys = array_keys( $this->options ); } - foreach ( $values as $name ) { + foreach ( $optionKeys as $name ) { $default = $this->options[$name]['default']; $type = $this->options[$name]['type']; - switch( $type ) { + switch ( $type ) { case self::BOOL: - $value = $r->getBool( $name, $default ); break; + $value = $r->getBool( $name, $default ); + break; case self::INT: - $value = $r->getInt( $name, $default ); break; + $value = $r->getInt( $name, $default ); + break; case self::STRING: - $value = $r->getText( $name, $default ); break; + $value = $r->getText( $name, $default ); + break; case self::INTNULL: - $value = $r->getIntOrNull( $name ); break; + $value = $r->getIntOrNull( $name ); + break; default: throw new MWException( 'Unsupported datatype' ); } @@ -310,29 +350,26 @@ class FormOptions implements ArrayAccess { } /** @name ArrayAccess functions - * Those function implements PHP ArrayAccess interface + * These functions implement the ArrayAccess PHP interface. * @see http://php.net/manual/en/class.arrayaccess.php */ /* @{ */ - /** - * Whether option exist - * @return bool - */ + /** Whether the option exists. */ public function offsetExists( $name ) { return isset( $this->options[$name] ); } - /** - * Retrieve an option value - * @return Mixed - */ + + /** Retrieve an option value. */ public function offsetGet( $name ) { return $this->getValue( $name ); } - /** Set an option to given value */ + + /** Set an option to given value. */ public function offsetSet( $name, $value ) { $this->setValue( $name, $value ); } - /** Delete the option */ + + /** Delete the option. */ public function offsetUnset( $name ) { $this->delete( $name ); } diff --git a/includes/GitInfo.php b/includes/GitInfo.php index 6f7f8020..f49f9be1 100644 --- a/includes/GitInfo.php +++ b/includes/GitInfo.php @@ -121,6 +121,32 @@ class GitInfo { } /** + * Return the commit date of HEAD entry of the git code repository + * + * @since 1.22 + * @return int|bool Commit date (UNIX timestamp) or false + */ + public function getHeadCommitDate() { + global $wgGitBin; + + if ( !is_file( $wgGitBin ) || !is_executable( $wgGitBin ) ) { + return false; + } + + $environment = array( "GIT_DIR" => $this->basedir ); + $cmd = wfEscapeShellArg( $wgGitBin ) . " show -s --format=format:%ct HEAD"; + $retc = false; + $commitDate = wfShellExec( $cmd, $retc, $environment ); + + if ( $retc !== 0 ) { + return false; + } else { + return (int)$commitDate; + } + + } + + /** * Return the name of the current branch, or HEAD if not found * @return string The branch name, HEAD, or false */ @@ -136,7 +162,7 @@ class GitInfo { /** * Get an URL to a web viewer link to the HEAD revision. * - * @return string|bool string if an URL is available or false otherwise. + * @return string|bool string if a URL is available or false otherwise. */ public function getHeadViewUrl() { $config = "{$this->basedir}/config"; @@ -151,7 +177,7 @@ class GitInfo { if ( isset( $configArray['remote origin'] ) ) { $remote = $configArray['remote origin']; } else { - foreach( $configArray as $sectionName => $sectionConf ) { + foreach ( $configArray as $sectionName => $sectionConf ) { if ( substr( $sectionName, 0, 6 ) == 'remote' ) { $remote = $sectionConf; } @@ -166,14 +192,15 @@ class GitInfo { if ( substr( $url, -4 ) !== '.git' ) { $url .= '.git'; } - foreach( self::getViewers() as $repo => $viewer ) { + foreach ( self::getViewers() as $repo => $viewer ) { $pattern = '#^' . $repo . '$#'; - if ( preg_match( $pattern, $url ) ) { + if ( preg_match( $pattern, $url, $matches ) ) { $viewerUrl = preg_replace( $pattern, $viewer, $url ); $headSHA1 = $this->getHeadSHA1(); $replacements = array( '%h' => substr( $headSHA1, 0, 7 ), - '%H' => $headSHA1 + '%H' => $headSHA1, + '%r' => urlencode( $matches[1] ), ); return strtr( $viewerUrl, $replacements ); } @@ -212,7 +239,7 @@ class GitInfo { protected static function getViewers() { global $wgGitRepositoryViewers; - if( self::$viewers === false ) { + if ( self::$viewers === false ) { self::$viewers = $wgGitRepositoryViewers; wfRunHooks( 'GitViewers', array( &self::$viewers ) ); } diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 016736f4..77c09e53 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -35,7 +35,7 @@ if ( !defined( 'MEDIAWIKI' ) ) { * PHP extensions may be included here. */ -if( !function_exists( 'iconv' ) ) { +if ( !function_exists( 'iconv' ) ) { /** * @codeCoverageIgnore * @return string @@ -73,7 +73,7 @@ if ( !function_exists( 'mb_strlen' ) ) { } } -if( !function_exists( 'mb_strpos' ) ) { +if ( !function_exists( 'mb_strpos' ) ) { /** * @codeCoverageIgnore * @return int @@ -84,7 +84,7 @@ if( !function_exists( 'mb_strpos' ) ) { } -if( !function_exists( 'mb_strrpos' ) ) { +if ( !function_exists( 'mb_strrpos' ) ) { /** * @codeCoverageIgnore * @return int @@ -94,24 +94,16 @@ if( !function_exists( 'mb_strrpos' ) ) { } } -// Support for Wietse Venema's taint feature -if ( !function_exists( 'istainted' ) ) { +// gzdecode function only exists in PHP >= 5.4.0 +// http://php.net/gzdecode +if ( !function_exists( 'gzdecode' ) ) { /** * @codeCoverageIgnore - * @return int + * @return string */ - function istainted( $var ) { - return 0; + function gzdecode( $data ) { + return gzinflate( substr( $data, 10, -8 ) ); } - /** @codeCoverageIgnore */ - function taint( $var, $level = 0 ) {} - /** @codeCoverageIgnore */ - function untaint( $var, $level = 0 ) {} - define( 'TC_HTML', 1 ); - define( 'TC_SHELL', 1 ); - define( 'TC_MYSQL', 1 ); - define( 'TC_PCRE', 1 ); - define( 'TC_SELF', 1 ); } /// @endcond @@ -126,19 +118,19 @@ function wfArrayDiff2( $a, $b ) { } /** - * @param $a - * @param $b + * @param $a array|string + * @param $b array|string * @return int */ function wfArrayDiff2_cmp( $a, $b ) { - if ( !is_array( $a ) ) { + if ( is_string( $a ) && is_string( $b ) ) { return strcmp( $a, $b ); } elseif ( count( $a ) !== count( $b ) ) { return count( $a ) < count( $b ) ? -1 : 1; } else { reset( $a ); reset( $b ); - while( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) { + while ( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) { $cmp = strcmp( $valueA, $valueB ); if ( $cmp !== 0 ) { return $cmp; @@ -150,14 +142,16 @@ function wfArrayDiff2_cmp( $a, $b ) { /** * Array lookup - * Returns an array where the values in the first array are replaced by the - * values in the second array with the corresponding keys + * Returns an array where the values in array $b are replaced by the + * values in array $a with the corresponding keys * + * @deprecated since 1.22; use array_intersect_key() * @param $a Array * @param $b Array * @return array */ function wfArrayLookup( $a, $b ) { + wfDeprecated( __FUNCTION__, '1.22' ); return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) ); } @@ -183,11 +177,13 @@ function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) { * Backwards array plus for people who haven't bothered to read the PHP manual * XXX: will not darn your socks for you. * + * @deprecated since 1.22; use array_replace() * @param $array1 Array * @param [$array2, [...]] Arrays * @return Array */ function wfArrayMerge( $array1/* ... */ ) { + wfDeprecated( __FUNCTION__, '1.22' ); $args = func_get_args(); $args = array_reverse( $args, true ); $out = array(); @@ -262,7 +258,7 @@ function wfArrayInsertAfter( array $array, array $insert, $after ) { */ function wfObjectToArray( $objOrArray, $recursive = true ) { $array = array(); - if( is_object( $objOrArray ) ) { + if ( is_object( $objOrArray ) ) { $objOrArray = get_object_vars( $objOrArray ); } foreach ( $objOrArray as $key => $value ) { @@ -277,24 +273,6 @@ function wfObjectToArray( $objOrArray, $recursive = true ) { } /** - * Wrapper around array_map() which also taints variables - * - * @param $function Callback - * @param $input Array - * @return Array - */ -function wfArrayMap( $function, $input ) { - $ret = array_map( $function, $input ); - foreach ( $ret as $key => $value ) { - $taint = istainted( $input[$key] ); - if ( $taint ) { - taint( $ret[$key], $taint ); - } - } - return $ret; -} - -/** * Get a random decimal value between 0 and 1, in a way * not likely to give duplicate values for any realistic * number of articles. @@ -322,8 +300,8 @@ function wfRandom() { */ function wfRandomString( $length = 32 ) { $str = ''; - while ( strlen( $str ) < $length ) { - $str .= dechex( mt_rand() ); + for ( $n = 0; $n < $length; $n += 7 ) { + $str .= sprintf( '%07x', mt_rand() & 0xfffffff ); } return substr( $str, 0, $length ); } @@ -480,8 +458,8 @@ function wfAppendQuery( $url, $query ) { if ( is_array( $query ) ) { $query = wfArrayToCgi( $query ); } - if( $query != '' ) { - if( false === strpos( $url, '?' ) ) { + if ( $query != '' ) { + if ( false === strpos( $url, '?' ) ) { $url .= '?'; } else { $url .= '&'; @@ -913,10 +891,10 @@ function wfMakeUrlIndexes( $url ) { function wfMatchesDomainList( $url, $domains ) { $bits = wfParseUrl( $url ); if ( is_array( $bits ) && isset( $bits['host'] ) ) { + $host = '.' . $bits['host']; foreach ( (array)$domains as $domain ) { - // FIXME: This gives false positives. http://nds-nl.wikipedia.org will match nl.wikipedia.org - // We should use something that interprets dots instead - if ( substr( $bits['host'], -strlen( $domain ) ) === $domain ) { + $domain = '.' . $domain; + if ( substr( $host, -strlen( $domain ) ) === $domain ) { return true; } } @@ -953,14 +931,12 @@ function wfDebug( $text, $logonly = false ) { MWDebug::debugMsg( $text ); } - if ( wfRunHooks( 'Debug', array( $text, null /* no log group */ ) ) ) { - if ( $wgDebugLogFile != '' && !$wgProfileOnly ) { - # Strip unprintables; they can switch terminal modes when binary data - # gets dumped, which is pretty annoying. - $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text ); - $text = $wgDebugLogPrefix . $text; - wfErrorLog( $text, $wgDebugLogFile ); - } + if ( $wgDebugLogFile != '' && !$wgProfileOnly ) { + # Strip unprintables; they can switch terminal modes when binary data + # gets dumped, which is pretty annoying. + $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text ); + $text = $wgDebugLogPrefix . $text; + wfErrorLog( $text, $wgDebugLogFile ); } } @@ -1011,7 +987,7 @@ function wfDebugTimer() { */ function wfDebugMem( $exact = false ) { $mem = memory_get_usage(); - if( !$exact ) { + if ( !$exact ) { $mem = floor( $mem / 1024 ) . ' kilobytes'; } else { $mem .= ' bytes'; @@ -1031,15 +1007,13 @@ function wfDebugMem( $exact = false ) { function wfDebugLog( $logGroup, $text, $public = true ) { global $wgDebugLogGroups; $text = trim( $text ) . "\n"; - if( isset( $wgDebugLogGroups[$logGroup] ) ) { + if ( isset( $wgDebugLogGroups[$logGroup] ) ) { $time = wfTimestamp( TS_DB ); $wiki = wfWikiID(); $host = wfHostname(); - if ( wfRunHooks( 'Debug', array( $text, $logGroup ) ) ) { - wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] ); - } + wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] ); } elseif ( $public === true ) { - wfDebug( "[$logGroup] $text", true ); + wfDebug( "[$logGroup] $text", false ); } } @@ -1093,16 +1067,29 @@ function wfDeprecated( $function, $version = false, $component = false, $callerO /** * Send a warning either to the debug log or in a PHP error depending on - * $wgDevelopmentWarnings + * $wgDevelopmentWarnings. To log warnings in production, use wfLogWarning() instead. * * @param string $msg message to send * @param $callerOffset Integer: number of items to go back in the backtrace to * find the correct caller (1 = function calling wfWarn, ...) - * @param $level Integer: PHP error level; only used when $wgDevelopmentWarnings - * is true + * @param $level Integer: PHP error level; defaults to E_USER_NOTICE; + * only used when $wgDevelopmentWarnings is true */ function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) { - MWDebug::warning( $msg, $callerOffset + 1, $level ); + MWDebug::warning( $msg, $callerOffset + 1, $level, 'auto' ); +} + +/** + * Send a warning as a PHP error and the debug log. This is intended for logging + * warnings in production. For logging development warnings, use WfWarn instead. + * + * @param $msg String: message to send + * @param $callerOffset Integer: number of items to go back in the backtrace to + * find the correct caller (1 = function calling wfLogWarning, ...) + * @param $level Integer: PHP error level; defaults to E_USER_WARNING + */ +function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) { + MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' ); } /** @@ -1177,6 +1164,8 @@ function wfLogProfilingData() { global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest; global $wgProfileLimit, $wgUser; + StatCounter::singleton()->flush(); + $profiler = Profiler::instance(); # Profiling must actually be enabled... @@ -1240,84 +1229,35 @@ function wfLogProfilingData() { * @return void */ function wfIncrStats( $key, $count = 1 ) { - global $wgStatsMethod; - - $count = intval( $count ); - if ( $count == 0 ) { - return; - } - - if( $wgStatsMethod == 'udp' ) { - global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgAggregateStatsID; - static $socket; - - $id = $wgAggregateStatsID !== false ? $wgAggregateStatsID : wfWikiID(); - - if ( !$socket ) { - $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP ); - $statline = "stats/{$id} - 1 1 1 1 1 -total\n"; - socket_sendto( - $socket, - $statline, - strlen( $statline ), - 0, - $wgUDPProfilerHost, - $wgUDPProfilerPort - ); - } - $statline = "stats/{$id} - {$count} 1 1 1 1 {$key}\n"; - wfSuppressWarnings(); - socket_sendto( - $socket, - $statline, - strlen( $statline ), - 0, - $wgUDPProfilerHost, - $wgUDPProfilerPort - ); - wfRestoreWarnings(); - } elseif( $wgStatsMethod == 'cache' ) { - global $wgMemc; - $key = wfMemcKey( 'stats', $key ); - if ( is_null( $wgMemc->incr( $key, $count ) ) ) { - $wgMemc->add( $key, $count ); - } - } else { - // Disabled - } + StatCounter::singleton()->incr( $key, $count ); } /** - * Check if the wiki read-only lock file is present. This can be used to lock - * off editing functions, but doesn't guarantee that the database will not be - * modified. + * Check whether the wiki is in read-only mode. * * @return bool */ function wfReadOnly() { - global $wgReadOnlyFile, $wgReadOnly; - - if ( !is_null( $wgReadOnly ) ) { - return (bool)$wgReadOnly; - } - if ( $wgReadOnlyFile == '' ) { - return false; - } - // Set $wgReadOnly for faster access next time - if ( is_file( $wgReadOnlyFile ) ) { - $wgReadOnly = file_get_contents( $wgReadOnlyFile ); - } else { - $wgReadOnly = false; - } - return (bool)$wgReadOnly; + return wfReadOnlyReason() !== false; } /** - * @return bool + * Get the value of $wgReadOnly or the contents of $wgReadOnlyFile. + * + * @return string|bool: String when in read-only mode; false otherwise */ function wfReadOnlyReason() { - global $wgReadOnly; - wfReadOnly(); + global $wgReadOnly, $wgReadOnlyFile; + + if ( $wgReadOnly === null ) { + // Set $wgReadOnly for faster access next time + if ( is_file( $wgReadOnlyFile ) && filesize( $wgReadOnlyFile ) > 0 ) { + $wgReadOnly = file_get_contents( $wgReadOnlyFile ); + } else { + $wgReadOnly = false; + } + } + return $wgReadOnly; } @@ -1339,27 +1279,27 @@ function wfReadOnlyReason() { function wfGetLangObj( $langcode = false ) { # Identify which language to get or create a language object for. # Using is_object here due to Stub objects. - if( is_object( $langcode ) ) { + if ( is_object( $langcode ) ) { # Great, we already have the object (hopefully)! return $langcode; } global $wgContLang, $wgLanguageCode; - if( $langcode === true || $langcode === $wgLanguageCode ) { + if ( $langcode === true || $langcode === $wgLanguageCode ) { # $langcode is the language code of the wikis content language object. # or it is a boolean and value is true return $wgContLang; } global $wgLang; - if( $langcode === false || $langcode === $wgLang->getCode() ) { + if ( $langcode === false || $langcode === $wgLang->getCode() ) { # $langcode is the language code of user language object. # or it was a boolean and value is false return $wgLang; } $validCodes = array_keys( Language::fetchLanguageNames() ); - if( in_array( $langcode, $validCodes ) ) { + if ( in_array( $langcode, $validCodes ) ) { # $langcode corresponds to a valid language. return Language::factory( $langcode ); } @@ -1414,7 +1354,7 @@ function wfMessage( $key /*...*/) { */ function wfMessageFallback( /*...*/ ) { $args = func_get_args(); - return MWFunction::callArray( 'Message::newFallbackSequence', $args ); + return call_user_func_array( 'Message::newFallbackSequence', $args ); } /** @@ -1492,7 +1432,7 @@ function wfMsgForContent( $key ) { $args = func_get_args(); array_shift( $args ); $forcontent = true; - if( is_array( $wgForceUIMsgAsContentMsg ) && + if ( is_array( $wgForceUIMsgAsContentMsg ) && in_array( $key, $wgForceUIMsgAsContentMsg ) ) { $forcontent = false; @@ -1515,7 +1455,7 @@ function wfMsgForContentNoTrans( $key ) { $args = func_get_args(); array_shift( $args ); $forcontent = true; - if( is_array( $wgForceUIMsgAsContentMsg ) && + if ( is_array( $wgForceUIMsgAsContentMsg ) && in_array( $key, $wgForceUIMsgAsContentMsg ) ) { $forcontent = false; @@ -1564,7 +1504,7 @@ function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true $cache = MessageCache::singleton(); $message = $cache->get( $key, $useDB, $langCode ); - if( $message === false ) { + if ( $message === false ) { $message = '<' . htmlspecialchars( $key ) . '>'; } elseif ( $transform ) { $message = $cache->transform( $message ); @@ -1591,7 +1531,7 @@ function wfMsgReplaceArgs( $message, $args ) { $args = array_values( $args[0] ); } $replacementKeys = array(); - foreach( $args as $n => $param ) { + foreach ( $args as $n => $param ) { $replacementKeys['$' . ( $n + 1 )] = $param; } $message = strtr( $message, $replacementKeys ); @@ -1675,11 +1615,11 @@ function wfMsgExt( $key, $options ) { array_shift( $args ); $options = (array)$options; - foreach( $options as $arrayKey => $option ) { - if( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) { + foreach ( $options as $arrayKey => $option ) { + if ( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) { # An unknown index, neither numeric nor "language" wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING ); - } elseif( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option, + } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option, array( 'parse', 'parseinline', 'escape', 'escapenoentities', 'replaceafter', 'parsemag', 'content' ) ) ) { # A numeric index with unknown value @@ -1687,11 +1627,11 @@ function wfMsgExt( $key, $options ) { } } - if( in_array( 'content', $options, true ) ) { + if ( in_array( 'content', $options, true ) ) { $forContent = true; $langCode = true; $langCodeObj = null; - } elseif( array_key_exists( 'language', $options ) ) { + } elseif ( array_key_exists( 'language', $options ) ) { $forContent = false; $langCode = wfGetLangObj( $options['language'] ); $langCodeObj = $langCode; @@ -1703,13 +1643,13 @@ function wfMsgExt( $key, $options ) { $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false ); - if( !in_array( 'replaceafter', $options, true ) ) { + if ( !in_array( 'replaceafter', $options, true ) ) { $string = wfMsgReplaceArgs( $string, $args ); } $messageCache = MessageCache::singleton(); $parseInline = in_array( 'parseinline', $options, true ); - if( in_array( 'parse', $options, true ) || $parseInline ) { + if ( in_array( 'parse', $options, true ) || $parseInline ) { $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj ); if ( $string instanceof ParserOutput ) { $string = $string->getText(); @@ -1717,7 +1657,7 @@ function wfMsgExt( $key, $options ) { if ( $parseInline ) { $m = array(); - if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) { + if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) { $string = $m[1]; } } @@ -1732,7 +1672,7 @@ function wfMsgExt( $key, $options ) { $string = Sanitizer::escapeHtmlAllowEntities( $string ); } - if( in_array( 'replaceafter', $options, true ) ) { + if ( in_array( 'replaceafter', $options, true ) ) { $string = wfMsgReplaceArgs( $string, $args ); } @@ -1741,7 +1681,7 @@ function wfMsgExt( $key, $options ) { /** * Since wfMsg() and co suck, they don't return false if the message key they - * looked up didn't exist but a XHTML string, this function checks for the + * looked up didn't exist but instead the key wrapped in <>'s, this function checks for the * nonexistence of messages by checking the MessageCache::get() result directly. * * @deprecated since 1.18. Use Message::isDisabled(). @@ -1759,10 +1699,12 @@ function wfEmptyMsg( $key ) { * Throw a debugging exception. This function previously once exited the process, * but now throws an exception instead, with similar results. * + * @deprecated since 1.22; just throw an MWException yourself * @param string $msg message shown when dying. * @throws MWException */ function wfDebugDieBacktrace( $msg = '' ) { + wfDeprecated( __FUNCTION__, '1.22' ); throw new MWException( $msg ); } @@ -1779,7 +1721,7 @@ function wfHostname() { # Hostname overriding global $wgOverrideHostname; - if( $wgOverrideHostname !== false ) { + if ( $wgOverrideHostname !== false ) { # Set static and skip any detection $host = $wgOverrideHostname; return $host; @@ -1791,7 +1733,7 @@ function wfHostname() { } else { $uname = false; } - if( is_array( $uname ) && isset( $uname['nodename'] ) ) { + if ( is_array( $uname ) && isset( $uname['nodename'] ) ) { $host = $uname['nodename']; } elseif ( getenv( 'COMPUTERNAME' ) ) { # Windows computer name @@ -1838,7 +1780,7 @@ function wfReportTime() { function wfDebugBacktrace( $limit = 0 ) { static $disabled = null; - if( extension_loaded( 'Zend Optimizer' ) ) { + if ( extension_loaded( 'Zend Optimizer' ) ) { wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" ); return array(); } @@ -1878,14 +1820,14 @@ function wfBacktrace() { $msg = "<ul>\n"; } $backtrace = wfDebugBacktrace(); - foreach( $backtrace as $call ) { - if( isset( $call['file'] ) ) { + foreach ( $backtrace as $call ) { + if ( isset( $call['file'] ) ) { $f = explode( DIRECTORY_SEPARATOR, $call['file'] ); $file = $f[count( $f ) - 1]; } else { $file = '-'; } - if( isset( $call['line'] ) ) { + if ( isset( $call['line'] ) ) { $line = $call['line']; } else { $line = '-'; @@ -1895,7 +1837,7 @@ function wfBacktrace() { } else { $msg .= '<li>' . $file . ' line ' . $line . ' calls '; } - if( !empty( $call['class'] ) ) { + if ( !empty( $call['class'] ) ) { $msg .= $call['class'] . $call['type']; } $msg .= $call['function'] . '()'; @@ -1993,11 +1935,11 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) { $query = wfCgiToArray( $query ); - if( is_object( $link ) ) { + if ( is_object( $link ) ) { $title = $link; } else { $title = Title::newFromText( $link ); - if( is_null( $title ) ) { + if ( is_null( $title ) ) { return false; } } @@ -2006,23 +1948,6 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) { } /** - * Make a list item, used by various special pages - * - * @param string $page Page link - * @param string $details Text between brackets - * @param $oppositedm Boolean Add the direction mark opposite to your - * language, to display text properly - * @return String - * @deprecated since 1.19; use Language::specialList() instead - */ -function wfSpecialList( $page, $details, $oppositedm = true ) { - wfDeprecated( __METHOD__, '1.19' ); - - global $wgLang; - return $wgLang->specialList( $page, $details, $oppositedm ); -} - -/** * @todo document * @todo FIXME: We may want to blacklist some broken browsers * @@ -2033,16 +1958,16 @@ function wfClientAcceptsGzip( $force = false ) { static $result = null; if ( $result === null || $force ) { $result = false; - if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) { + if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) { # @todo FIXME: We may want to blacklist some broken browsers $m = array(); - if( preg_match( + if ( preg_match( '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/', $_SERVER['HTTP_ACCEPT_ENCODING'], $m ) ) { - if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) { + if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) { $result = false; return $result; } @@ -2078,22 +2003,54 @@ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) { * @return String */ function wfEscapeWikiText( $text ) { - $text = strtr( "\n$text", array( - '"' => '"', '&' => '&', "'" => ''', '<' => '<', - '=' => '=', '>' => '>', '[' => '[', ']' => ']', - '{' => '{', '|' => '|', '}' => '}', - "\n#" => "\n#", "\n*" => "\n*", - "\n:" => "\n:", "\n;" => "\n;", - '://' => '://', 'ISBN ' => 'ISBN ', 'RFC ' => 'RFC ', - ) ); - return substr( $text, 1 ); + static $repl = null, $repl2 = null; + if ( $repl === null ) { + $repl = array( + '"' => '"', '&' => '&', "'" => ''', '<' => '<', + '=' => '=', '>' => '>', '[' => '[', ']' => ']', + '{' => '{', '|' => '|', '}' => '}', ';' => ';', + "\n#" => "\n#", "\r#" => "\r#", + "\n*" => "\n*", "\r*" => "\r*", + "\n:" => "\n:", "\r:" => "\r:", + "\n " => "\n ", "\r " => "\r ", + "\n\n" => "\n ", "\r\n" => " \n", + "\n\r" => "\n ", "\r\r" => "\r ", + "\n\t" => "\n	", "\r\t" => "\r	", // "\n\t\n" is treated like "\n\n" + "\n----" => "\n----", "\r----" => "\r----", + '__' => '__', '://' => '://', + ); + + // We have to catch everything "\s" matches in PCRE + foreach ( array( 'ISBN', 'RFC', 'PMID' ) as $magic ) { + $repl["$magic "] = "$magic "; + $repl["$magic\t"] = "$magic	"; + $repl["$magic\r"] = "$magic "; + $repl["$magic\n"] = "$magic "; + $repl["$magic\f"] = "$magic"; + } + + // And handle protocols that don't use "://" + global $wgUrlProtocols; + $repl2 = array(); + foreach ( $wgUrlProtocols as $prot ) { + if ( substr( $prot, -1 ) === ':' ) { + $repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' ); + } + } + $repl2 = $repl2 ? '/\b(' . join( '|', $repl2 ) . '):/i' : '/^(?!)/'; + } + $text = substr( strtr( "\n$text", $repl ), 1 ); + $text = preg_replace( $repl2, '$1:', $text ); + return $text; } /** * Get the current unix timestamp with microseconds. Useful for profiling + * @deprecated since 1.22; call microtime() directly * @return Float */ function wfTime() { + wfDeprecated( __FUNCTION__, '1.22' ); return microtime( true ); } @@ -2195,14 +2152,14 @@ function wfHttpError( $code, $label, $desc ) { * @param $resetGzipEncoding Bool */ function wfResetOutputBuffers( $resetGzipEncoding = true ) { - if( $resetGzipEncoding ) { + if ( $resetGzipEncoding ) { // Suppress Content-Encoding and Content-Length // headers from 1.10+s wfOutputHandler global $wgDisableOutputCompression; $wgDisableOutputCompression = true; } - while( $status = ob_get_status() ) { - if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) { + while ( $status = ob_get_status() ) { + if ( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) { // Probably from zlib.output_compression or other // PHP-internal setting which can't be removed. // @@ -2210,13 +2167,13 @@ function wfResetOutputBuffers( $resetGzipEncoding = true ) { // output behavior. break; } - if( !ob_end_clean() ) { + if ( !ob_end_clean() ) { // Could not remove output buffer handler; abort now // to avoid getting in some kind of infinite loop. break; } - if( $resetGzipEncoding ) { - if( $status['name'] == 'ob_gzhandler' ) { + if ( $resetGzipEncoding ) { + if ( $status['name'] == 'ob_gzhandler' ) { // Reset the 'Content-Encoding' field set by this handler // so we can start fresh. header_remove( 'Content-Encoding' ); @@ -2252,7 +2209,7 @@ function wfClearOutputBuffers() { */ function wfAcceptToPrefs( $accept, $def = '*/*' ) { # No arg means accept anything (per HTTP spec) - if( !$accept ) { + if ( !$accept ) { return array( $def => 1.0 ); } @@ -2260,7 +2217,7 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) { $parts = explode( ',', $accept ); - foreach( $parts as $part ) { + foreach ( $parts as $part ) { # @todo FIXME: Doesn't deal with params like 'text/html; level=1' $values = explode( ';', trim( $part ) ); $match = array(); @@ -2287,13 +2244,13 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) { * @private */ function mimeTypeMatch( $type, $avail ) { - if( array_key_exists( $type, $avail ) ) { + if ( array_key_exists( $type, $avail ) ) { return $type; } else { $parts = explode( '/', $type ); - if( array_key_exists( $parts[0] . '/*', $avail ) ) { + if ( array_key_exists( $parts[0] . '/*', $avail ) ) { return $parts[0] . '/*'; - } elseif( array_key_exists( '*/*', $avail ) ) { + } elseif ( array_key_exists( '*/*', $avail ) ) { return '*/*'; } else { return null; @@ -2317,21 +2274,21 @@ function mimeTypeMatch( $type, $avail ) { function wfNegotiateType( $cprefs, $sprefs ) { $combine = array(); - foreach( array_keys( $sprefs ) as $type ) { + foreach ( array_keys( $sprefs ) as $type ) { $parts = explode( '/', $type ); - if( $parts[1] != '*' ) { + if ( $parts[1] != '*' ) { $ckey = mimeTypeMatch( $type, $cprefs ); - if( $ckey ) { + if ( $ckey ) { $combine[$type] = $sprefs[$type] * $cprefs[$ckey]; } } } - foreach( array_keys( $cprefs ) as $type ) { + foreach ( array_keys( $cprefs ) as $type ) { $parts = explode( '/', $type ); - if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) { + if ( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) { $skey = mimeTypeMatch( $type, $sprefs ); - if( $skey ) { + if ( $skey ) { $combine[$type] = $sprefs[$skey] * $cprefs[$type]; } } @@ -2340,8 +2297,8 @@ function wfNegotiateType( $cprefs, $sprefs ) { $bestq = 0; $besttype = null; - foreach( array_keys( $combine ) as $type ) { - if( $combine[$type] > $bestq ) { + foreach ( array_keys( $combine ) as $type ) { + if ( $combine[$type] > $bestq ) { $besttype = $type; $bestq = $combine[$type]; } @@ -2447,7 +2404,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) { try { $timestamp = new MWTimestamp( $ts ); return $timestamp->getTimestamp( $outputtype ); - } catch( TimestampException $e ) { + } catch ( TimestampException $e ) { wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" ); return false; } @@ -2462,7 +2419,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) { * @return String */ function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) { - if( is_null( $ts ) ) { + if ( is_null( $ts ) ) { return null; } else { return wfTimestamp( $outputtype, $ts ); @@ -2498,7 +2455,7 @@ function wfIsWindows() { * @return Bool */ function wfIsHipHop() { - return function_exists( 'hphp_thread_set_warmup_enabled' ); + return defined( 'HPHP_VERSION' ); } /** @@ -2533,8 +2490,8 @@ function wfTempDir() { $tmpDir = array_map( "getenv", array( 'TMPDIR', 'TMP', 'TEMP' ) ); - foreach( $tmpDir as $tmp ) { - if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) { + foreach ( $tmpDir as $tmp ) { + if ( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) { return $tmp; } } @@ -2561,7 +2518,7 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) { wfDebug( "$caller: called wfMkdirParents($dir)\n" ); } - if( strval( $dir ) === '' || file_exists( $dir ) ) { + if ( strval( $dir ) === '' || ( file_exists( $dir ) && is_dir( $dir ) ) ) { return true; } @@ -2576,10 +2533,14 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) { $ok = mkdir( $dir, $mode, true ); // PHP5 <3 wfRestoreWarnings(); - if( !$ok ) { + if ( !$ok ) { + //directory may have been created on another request since we last checked + if ( is_dir( $dir ) ) { + return true; + } + // PHP doesn't report the path in its warning message, so add our own to aid in diagnosis. - trigger_error( sprintf( "%s: failed to mkdir \"%s\" mode 0%o", __FUNCTION__, $dir, $mode ), - E_USER_WARNING ); + wfLogWarning( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ) ); } return $ok; } @@ -2630,7 +2591,9 @@ function wfPercent( $nr, $acc = 2, $round = true ) { function in_string( $needle, $str, $insensitive = false ) { wfDeprecated( __METHOD__, '1.21' ); $func = 'strpos'; - if( $insensitive ) $func = 'stripos'; + if ( $insensitive ) { + $func = 'stripos'; + } return $func( $str, $needle ) !== false; } @@ -2659,47 +2622,15 @@ function in_string( $needle, $str, $insensitive = false ) { * @return Bool */ function wfIniGetBool( $setting ) { - $val = ini_get( $setting ); + $val = strtolower( ini_get( $setting ) ); // 'on' and 'true' can't have whitespace around them, but '1' can. - return strtolower( $val ) == 'on' - || strtolower( $val ) == 'true' - || strtolower( $val ) == 'yes' + return $val == 'on' + || $val == 'true' + || $val == 'yes' || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function } /** - * Wrapper function for PHP's dl(). This doesn't work in most situations from - * PHP 5.3 onward, and is usually disabled in shared environments anyway. - * - * @param string $extension A PHP extension. The file suffix (.so or .dll) - * should be omitted - * @param string $fileName Name of the library, if not $extension.suffix - * @return Bool - Whether or not the extension is loaded - */ -function wfDl( $extension, $fileName = null ) { - if( extension_loaded( $extension ) ) { - return true; - } - - $canDl = false; - if( PHP_SAPI == 'cli' || PHP_SAPI == 'cgi' || PHP_SAPI == 'embed' ) { - $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' ) - && wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) ); - } - - if( $canDl ) { - $fileName = $fileName ? $fileName : $extension; - if( wfIsWindows() ) { - $fileName = 'php_' . $fileName; - } - wfSuppressWarnings(); - dl( $fileName . '.' . PHP_SHLIB_SUFFIX ); - wfRestoreWarnings(); - } - return extension_loaded( $extension ); -} - -/** * Windows-compatible version of escapeshellarg() * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg() * function puts single quotes in regardless of OS. @@ -2764,25 +2695,15 @@ function wfEscapeShellArg() { } /** - * Execute a shell command, with time and memory limits mirrored from the PHP - * configuration if supported. - * @param string $cmd Command line, properly escaped for shell. - * @param &$retval null|Mixed optional, will receive the program's exit code. - * (non-zero is usually failure) - * @param array $environ optional environment variables which should be - * added to the executed command environment. - * @param array $limits optional array with limits(filesize, memory, time, walltime) - * this overwrites the global wgShellMax* limits. - * @return string collected stdout as a string (trailing newlines stripped) + * Check if wfShellExec() is effectively disabled via php.ini config + * @return bool|string False or one of (safemode,disabled) + * @since 1.22 */ -function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array() ) { - global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime, - $wgMaxShellWallClockTime, $wgShellCgroup; - - static $disabled; +function wfShellExecDisabled() { + static $disabled = null; if ( is_null( $disabled ) ) { $disabled = false; - if( wfIniGetBool( 'safe_mode' ) ) { + if ( wfIniGetBool( 'safe_mode' ) ) { wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" ); $disabled = 'safemode'; } else { @@ -2795,6 +2716,28 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array } } } + return $disabled; +} + +/** + * Execute a shell command, with time and memory limits mirrored from the PHP + * configuration if supported. + * @param string $cmd Command line, properly escaped for shell. + * @param &$retval null|Mixed optional, will receive the program's exit code. + * (non-zero is usually failure) + * @param array $environ optional environment variables which should be + * added to the executed command environment. + * @param array $limits optional array with limits(filesize, memory, time, walltime) + * this overwrites the global wgShellMax* limits. + * @param array $options Array of options. Only one is "duplicateStderr" => true, which + * Which duplicates stderr to stdout, including errors from limit.sh + * @return string collected stdout as a string + */ +function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array(), $options = array() ) { + global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime, + $wgMaxShellWallClockTime, $wgShellCgroup; + + $disabled = wfShellExecDisabled(); if ( $disabled ) { $retval = 1; return $disabled == 'safemode' ? @@ -2802,10 +2745,12 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array 'Unable to run external programs, passthru() is disabled.'; } + $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr']; + wfInitShellLocale(); $envcmd = ''; - foreach( $environ as $k => $v ) { + foreach ( $environ as $k => $v ) { if ( wfIsWindows() ) { /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves * appear in the environment variable, so we must use carat escaping as documented in @@ -2839,17 +2784,25 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array $cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' . escapeshellarg( $cmd ) . ' ' . escapeshellarg( + "MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' . "MW_CPU_LIMIT=$time; " . 'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' . "MW_MEM_LIMIT=$mem; " . "MW_FILE_SIZE_LIMIT=$filesize; " . "MW_WALL_CLOCK_LIMIT=$wallTime" ); + } elseif ( $includeStderr ) { + $cmd .= ' 2>&1'; } + } elseif ( $includeStderr ) { + $cmd .= ' 2>&1'; } wfDebug( "wfShellExec: $cmd\n" ); - $retval = 1; // error by default? + // Default to an unusual value that shouldn't happen naturally, + // so in the unlikely event of a weird php bug, it would be + // more obvious what happened. + $retval = 200; ob_start(); passthru( $cmd, $retval ); $output = ob_get_contents(); @@ -2862,6 +2815,24 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array } /** + * Execute a shell command, returning both stdout and stderr. Convenience + * function, as all the arguments to wfShellExec can become unwieldy. + * + * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded. + * @param string $cmd Command line, properly escaped for shell. + * @param &$retval null|Mixed optional, will receive the program's exit code. + * (non-zero is usually failure) + * @param array $environ optional environment variables which should be + * added to the executed command environment. + * @param array $limits optional array with limits(filesize, memory, time, walltime) + * this overwrites the global wgShellMax* limits. + * @return string collected stdout and stderr as a string + */ +function wfShellExecWithStderr( $cmd, &$retval = null, $environ = array(), $limits = array() ) { + return wfShellExec( $cmd, $retval, $environ, $limits, array( 'duplicateStderr' => true ) ); +} + +/** * Workaround for http://bugs.php.net/bug.php?id=45132 * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale */ @@ -2930,7 +2901,7 @@ function wfMerge( $old, $mine, $yours, &$result ) { $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); wfRestoreWarnings(); - if( !$haveDiff3 ) { + if ( !$haveDiff3 ) { wfDebug( "diff3 not found\n" ); return false; } @@ -2959,7 +2930,7 @@ function wfMerge( $old, $mine, $yours, &$result ) { wfEscapeShellArg( $yourtextName ); $handle = popen( $cmd, 'r' ); - if( fgets( $handle, 1024 ) ) { + if ( fgets( $handle, 1024 ) ) { $conflict = true; } else { $conflict = false; @@ -3011,7 +2982,7 @@ function wfDiff( $before, $after, $params = '-u' ) { # This check may also protect against code injection in # case of broken installations. - if( !$haveDiff ) { + if ( !$haveDiff ) { wfDebug( "diff executable not found\n" ); $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) ); $format = new UnifiedDiffFormatter(); @@ -3125,7 +3096,7 @@ function wfBaseName( $path, $suffix = '' ) { ? '' : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' ); $matches = array(); - if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) { + if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) { return $matches[1]; } else { return ''; @@ -3153,21 +3124,21 @@ function wfRelativePath( $path, $from ) { $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) ); $against = explode( DIRECTORY_SEPARATOR, $from ); - if( $pieces[0] !== $against[0] ) { + if ( $pieces[0] !== $against[0] ) { // Non-matching Windows drive letters? // Return a full path. return $path; } // Trim off common prefix - while( count( $pieces ) && count( $against ) + while ( count( $pieces ) && count( $against ) && $pieces[0] == $against[0] ) { array_shift( $pieces ); array_shift( $against ); } // relative dots to bump us to the parent - while( count( $against ) ) { + while ( count( $against ) ) { array_unshift( $pieces, '..' ); array_shift( $against ); } @@ -3206,20 +3177,20 @@ function wfDoUpdates( $commit = '' ) { */ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true, $engine = 'auto' ) { $input = (string)$input; - if( + if ( $sourceBase < 2 || $sourceBase > 36 || $destBase < 2 || $destBase > 36 || - $sourceBase != (int) $sourceBase || - $destBase != (int) $destBase || - $pad != (int) $pad || + $sourceBase != (int)$sourceBase || + $destBase != (int)$destBase || + $pad != (int)$pad || !preg_match( "/^[" . substr( '0123456789abcdefghijklmnopqrstuvwxyz', 0, $sourceBase ) . "]+$/i", $input ) ) { return false; } - static $baseChars = array ( + static $baseChars = array( 10 => 'a', 11 => 'b', 12 => 'c', 13 => 'd', 14 => 'e', 15 => 'f', 16 => 'g', 17 => 'h', 18 => 'i', 19 => 'j', 20 => 'k', 21 => 'l', 22 => 'm', 23 => 'n', 24 => 'o', 25 => 'p', 26 => 'q', 27 => 'r', @@ -3234,40 +3205,40 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t 'u' => 30, 'v' => 31, 'w' => 32, 'x' => 33, 'y' => 34, 'z' => 35 ); - if( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) { + if ( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) { $result = gmp_strval( gmp_init( $input, $sourceBase ), $destBase ); - } elseif( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) { + } elseif ( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) { $decimal = '0'; - foreach( str_split( strtolower( $input ) ) as $char ) { + foreach ( str_split( strtolower( $input ) ) as $char ) { $decimal = bcmul( $decimal, $sourceBase ); $decimal = bcadd( $decimal, $baseChars[$char] ); } - for( $result = ''; bccomp( $decimal, 0 ); $decimal = bcdiv( $decimal, $destBase, 0 ) ) { + for ( $result = ''; bccomp( $decimal, 0 ); $decimal = bcdiv( $decimal, $destBase, 0 ) ) { $result .= $baseChars[bcmod( $decimal, $destBase )]; } $result = strrev( $result ); } else { $inDigits = array(); - foreach( str_split( strtolower( $input ) ) as $char ) { + foreach ( str_split( strtolower( $input ) ) as $char ) { $inDigits[] = $baseChars[$char]; } // Iterate over the input, modulo-ing out an output digit // at a time until input is gone. $result = ''; - while( $inDigits ) { + while ( $inDigits ) { $work = 0; $workDigits = array(); // Long division... - foreach( $inDigits as $digit ) { + foreach ( $inDigits as $digit ) { $work *= $sourceBase; $work += $digit; - if( $workDigits || $work >= $destBase ) { - $workDigits[] = (int) ( $work / $destBase ); + if ( $workDigits || $work >= $destBase ) { + $workDigits[] = (int)( $work / $destBase ); } $work %= $destBase; } @@ -3283,7 +3254,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t $result = strrev( $result ); } - if( !$lowercase ) { + if ( !$lowercase ) { $result = strtoupper( $result ); } @@ -3309,9 +3280,9 @@ function wfCreateObject( $name, $p ) { function wfHttpOnlySafe() { global $wgHttpOnlyBlacklist; - if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) { - foreach( $wgHttpOnlyBlacklist as $regex ) { - if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) { + if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) { + foreach ( $wgHttpOnlyBlacklist as $regex ) { + if ( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) { return false; } } @@ -3338,7 +3309,7 @@ function wfCheckEntropy() { */ function wfFixSessionID() { // If the cookie or session id is already set we already have a session and should abort - if ( isset( $_COOKIE[ session_name() ] ) || session_id() ) { + if ( isset( $_COOKIE[session_name()] ) || session_id() ) { return; } @@ -3356,6 +3327,27 @@ function wfFixSessionID() { } /** + * Reset the session_id + * @since 1.22 + */ +function wfResetSessionID() { + global $wgCookieSecure; + $oldSessionId = session_id(); + $cookieParams = session_get_cookie_params(); + if ( wfCheckEntropy() && $wgCookieSecure == $cookieParams['secure'] ) { + session_regenerate_id( false ); + } else { + $tmp = $_SESSION; + session_destroy(); + wfSetupSession( MWCryptRand::generateHex( 32 ) ); + $_SESSION = $tmp; + } + $newSessionId = session_id(); + wfRunHooks( 'ResetSessionID', array( $oldSessionId, $newSessionId ) ); +} + + +/** * Initialise php session * * @param $sessionId Bool @@ -3363,9 +3355,9 @@ function wfFixSessionID() { function wfSetupSession( $sessionId = false ) { global $wgSessionsInMemcached, $wgSessionsInObjectCache, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly, $wgSessionHandler; - if( $wgSessionsInObjectCache || $wgSessionsInMemcached ) { + if ( $wgSessionsInObjectCache || $wgSessionsInMemcached ) { ObjectCacheSessionHandler::install(); - } elseif( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) { + } elseif ( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) { # Only set this if $wgSessionHandler isn't null and session.save_handler # hasn't already been set to the desired value (that causes errors) ini_set( 'session.save_handler', $wgSessionHandler ); @@ -3440,7 +3432,7 @@ function wfForeignMemcKey( $db, $prefix /*, ... */ ) { } else { $key = $db . ':' . implode( ':', $args ); } - return $key; + return str_replace( ' ', '_', $key ); } /** @@ -3588,7 +3580,7 @@ function wfScript( $script = 'index' ) { global $wgScriptPath, $wgScriptExtension, $wgScript, $wgLoadScript; if ( $script === 'index' ) { return $wgScript; - } else if ( $script === 'load' ) { + } elseif ( $script === 'load' ) { return $wgLoadScript; } else { return "{$wgScriptPath}/{$script}{$wgScriptExtension}"; @@ -3601,7 +3593,7 @@ function wfScript( $script = 'index' ) { * @return string script URL */ function wfGetScriptUrl() { - if( isset( $_SERVER['SCRIPT_NAME'] ) ) { + if ( isset( $_SERVER['SCRIPT_NAME'] ) ) { # # as it was called, minus the query string. # @@ -3649,15 +3641,22 @@ function wfGetNull() { * * @param $maxLag Integer (deprecated) * @param $wiki mixed Wiki identifier accepted by wfGetLB + * @param $cluster string cluster name accepted by LBFactory */ -function wfWaitForSlaves( $maxLag = false, $wiki = false ) { - $lb = wfGetLB( $wiki ); +function wfWaitForSlaves( $maxLag = false, $wiki = false, $cluster = false ) { + $lb = ( $cluster !== false ) + ? wfGetLBFactory()->getExternalLB( $cluster ) + : wfGetLB( $wiki ); // bug 27975 - Don't try to wait for slaves if there are none // Prevents permission error when getting master position if ( $lb->getServerCount() > 1 ) { - $dbw = $lb->getConnection( DB_MASTER ); + $dbw = $lb->getConnection( DB_MASTER, array(), $wiki ); $pos = $dbw->getMasterPos(); - $lb->waitForAll( $pos ); + // The DBMS may not support getMasterPos() or the whole + // load balancer might be fake (e.g. $wgAllDBsAreLocalhost). + if ( $pos !== false ) { + $lb->waitForAll( $pos ); + } } } @@ -3739,9 +3738,9 @@ function wfStripIllegalFilenameChars( $name ) { function wfMemoryLimit() { global $wgMemoryLimit; $memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) ); - if( $memlimit != -1 ) { + if ( $memlimit != -1 ) { $conflimit = wfShorthandToInteger( $wgMemoryLimit ); - if( $conflimit == -1 ) { + if ( $conflimit == -1 ) { wfDebug( "Removing PHP's memory limit\n" ); wfSuppressWarnings(); ini_set( 'memory_limit', $conflimit ); @@ -3766,12 +3765,12 @@ function wfMemoryLimit() { */ function wfShorthandToInteger( $string = '' ) { $string = trim( $string ); - if( $string === '' ) { + if ( $string === '' ) { return -1; } $last = $string[strlen( $string ) - 1]; $val = intval( $string ); - switch( $last ) { + switch ( $last ) { case 'g': case 'G': $val *= 1024; @@ -3799,22 +3798,17 @@ function wfBCP47( $code ) { $codeSegment = explode( '-', $code ); $codeBCP = array(); foreach ( $codeSegment as $segNo => $seg ) { - if ( count( $codeSegment ) > 0 ) { - // when previous segment is x, it is a private segment and should be lc - if( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) { - $codeBCP[$segNo] = strtolower( $seg ); - // ISO 3166 country code - } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) { - $codeBCP[$segNo] = strtoupper( $seg ); - // ISO 15924 script code - } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) { - $codeBCP[$segNo] = ucfirst( strtolower( $seg ) ); - // Use lowercase for other cases - } else { - $codeBCP[$segNo] = strtolower( $seg ); - } + // when previous segment is x, it is a private segment and should be lc + if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) { + $codeBCP[$segNo] = strtolower( $seg ); + // ISO 3166 country code + } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) { + $codeBCP[$segNo] = strtoupper( $seg ); + // ISO 15924 script code + } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) { + $codeBCP[$segNo] = ucfirst( strtolower( $seg ) ); + // Use lowercase for other cases } else { - // Use lowercase for single segment $codeBCP[$segNo] = strtolower( $seg ); } } @@ -3879,7 +3873,7 @@ function wfGetLangConverterCacheStorage() { * @param array $args parameters passed to hook functions * @return Boolean True if no handler aborted the hook */ -function wfRunHooks( $event, $args = array() ) { +function wfRunHooks( $event, array $args = array() ) { return Hooks::run( $event, $args ); } @@ -3897,7 +3891,7 @@ function wfRunHooks( $event, $args = array() ) { * @throws MWException if $data not long enough, or if unpack fails * @return array Associative array of the extracted data */ -function wfUnpack( $format, $data, $length=false ) { +function wfUnpack( $format, $data, $length = false ) { if ( $length !== false ) { $realLen = strlen( $data ); if ( $realLen < $length ) { @@ -3939,19 +3933,19 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { # Handle redirects $redirectTitle = RepoGroup::singleton()->checkRedirect( Title::makeTitle( NS_FILE, $name ) ); - if( $redirectTitle ) { - $name = $redirectTitle->getDbKey(); + if ( $redirectTitle ) { + $name = $redirectTitle->getDBkey(); } # Run the extension hook $bad = false; - if( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) { + if ( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) { wfProfileOut( __METHOD__ ); return $bad; } $cacheable = ( $blacklist === null ); - if( $cacheable && $badImageCache !== null ) { + if ( $cacheable && $badImageCache !== null ) { $badImages = $badImageCache; } else { // cache miss if ( $blacklist === null ) { @@ -3960,7 +3954,7 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { # Build the list now $badImages = array(); $lines = explode( "\n", $blacklist ); - foreach( $lines as $line ) { + foreach ( $lines as $line ) { # List items only if ( substr( $line, 0, 1 ) !== '*' ) { continue; @@ -3999,3 +3993,16 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { wfProfileOut( __METHOD__ ); return $bad; } + +/** + * Determine whether the client at a given source IP is likely to be able to + * access the wiki via HTTPS. + * + * @param string $ip The IPv4/6 address in the normal human-readable form + * @return boolean + */ +function wfCanIPUseHTTPS( $ip ) { + $canDo = true; + wfRunHooks( 'CanIPUseHTTPS', array( $ip, &$canDo ) ); + return !!$canDo; +} diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php index 68639739..d260862c 100644 --- a/includes/HTMLForm.php +++ b/includes/HTMLForm.php @@ -83,9 +83,8 @@ * $form = new HTMLForm( $someFields ); * $form->setMethod( 'get' ) * ->setWrapperLegendMsg( 'message-key' ) - * ->suppressReset() * ->prepareForm() - * ->displayForm(); + * ->displayForm( '' ); * @endcode * Note that you will have prepareForm and displayForm at the end. Other * methods call done after that would simply not be part of the form :( @@ -95,7 +94,7 @@ class HTMLForm extends ContextSource { // A mapping of 'type' inputs onto standard HTMLFormField subclasses - static $typeMappings = array( + public static $typeMappings = array( 'api' => 'HTMLApiField', 'text' => 'HTMLTextField', 'textarea' => 'HTMLTextAreaField', @@ -128,6 +127,7 @@ class HTMLForm extends ContextSource { protected $mFieldTree; protected $mShowReset = false; + protected $mShowSubmit = true; public $mFieldData; protected $mSubmitCallback; @@ -140,6 +140,7 @@ class HTMLForm extends ContextSource { protected $mSectionFooters = array(); protected $mPost = ''; protected $mId; + protected $mTableId = ''; protected $mSubmitID; protected $mSubmitName; @@ -186,6 +187,7 @@ class HTMLForm extends ContextSource { 'table', 'div', 'raw', + 'vform', ); /** @@ -200,12 +202,12 @@ class HTMLForm extends ContextSource { $this->setContext( $context ); $this->mTitle = false; // We don't need them to set a title $this->mMessagePrefix = $messagePrefix; - } else { + } elseif ( is_null( $context ) && $messagePrefix !== '' ) { + $this->mMessagePrefix = $messagePrefix; + } elseif ( is_string( $context ) && $messagePrefix === '' ) { // B/C since 1.18 - if ( is_string( $context ) && $messagePrefix === '' ) { - // it's actually $messagePrefix - $this->mMessagePrefix = $context; - } + // it's actually $messagePrefix + $this->mMessagePrefix = $context; } // Expand out into a tree. @@ -222,8 +224,15 @@ class HTMLForm extends ContextSource { } $field = self::loadInputFromParameters( $fieldname, $info ); + // FIXME During field's construct, the parent form isn't available! + // could add a 'parent' name-value to $info, could add a third parameter. $field->mParent = $this; + // vform gets too much space if empty labels generate HTML. + if ( $this->isVForm() ) { + $field->setShowEmptyLabel( false ); + } + $setSection =& $loadedDescriptor; if ( $section ) { $sectionParts = explode( '/', $section ); @@ -256,7 +265,7 @@ class HTMLForm extends ContextSource { */ public function setDisplayFormat( $format ) { if ( !in_array( $format, $this->availableDisplayFormats ) ) { - throw new MWException ( 'Display format must be one of ' . print_r( $this->availableDisplayFormats, true ) ); + throw new MWException( 'Display format must be one of ' . print_r( $this->availableDisplayFormats, true ) ); } $this->displayFormat = $format; return $this; @@ -272,11 +281,22 @@ class HTMLForm extends ContextSource { } /** + * Test if displayFormat is 'vform' + * @since 1.22 + * @return Bool + */ + public function isVForm() { + return $this->displayFormat === 'vform'; + } + + /** * Add the HTMLForm-specific JavaScript, if it hasn't been * done already. * @deprecated since 1.18 load modules with ResourceLoader instead */ - static function addJS() { wfDeprecated( __METHOD__, '1.18' ); } + static function addJS() { + wfDeprecated( __METHOD__, '1.18' ); + } /** * Initialise a new Object for the field @@ -572,6 +592,21 @@ class HTMLForm extends ContextSource { } /** + * Add an array of hidden fields to the output + * + * @since 1.22 + * @param array $fields Associative array of fields to add; + * mapping names to their values + * @return HTMLForm $this for chaining calls + */ + public function addHiddenFields( array $fields ) { + foreach ( $fields as $name => $value ) { + $this->mHiddenFields[] = array( $value, array( 'name' => $name ) ); + } + return $this; + } + + /** * Add a button to the form * @param string $name field name. * @param string $value field value @@ -585,8 +620,8 @@ class HTMLForm extends ContextSource { } /** - * Display the form (sending to $wgOut), with an appropriate error - * message or stack of messages, and any validation errors, etc. + * Display the form (sending to the context's OutputPage object), with an + * appropriate error message or stack of messages, and any validation errors, etc. * * @attention You should call prepareForm() before calling this function. * Moreover, when doing method chaining this should be the very last method @@ -608,6 +643,11 @@ class HTMLForm extends ContextSource { # For good measure (it is the default) $this->getOutput()->preventClickjacking(); $this->getOutput()->addModules( 'mediawiki.htmlform' ); + if ( $this->isVForm() ) { + $this->getOutput()->addModuleStyles( 'mediawiki.ui' ); + // TODO should vertical form set setWrapperLegend( false ) + // to hide ugly fieldsets? + } $html = '' . $this->getErrors( $submitResult ) @@ -640,15 +680,18 @@ class HTMLForm extends ContextSource { : 'application/x-www-form-urlencoded'; # Attributes $attribs = array( - 'action' => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction, - 'method' => $this->mMethod, - 'class' => 'visualClear', + 'action' => $this->getAction(), + 'method' => $this->getMethod(), + 'class' => array( 'visualClear' ), 'enctype' => $encType, ); if ( !empty( $this->mId ) ) { $attribs['id'] = $this->mId; } + if ( $this->isVForm() ) { + array_push( $attribs['class'], 'mw-ui-vform', 'mw-ui-container' ); + } return Html::rawElement( 'form', $attribs, $html ); } @@ -682,24 +725,40 @@ class HTMLForm extends ContextSource { * @return String HTML. */ function getButtons() { - $html = ''; - $attribs = array(); + $html = '<span class="mw-htmlform-submit-buttons">'; - if ( isset( $this->mSubmitID ) ) { - $attribs['id'] = $this->mSubmitID; - } + if ( $this->mShowSubmit ) { + $attribs = array(); - if ( isset( $this->mSubmitName ) ) { - $attribs['name'] = $this->mSubmitName; - } + if ( isset( $this->mSubmitID ) ) { + $attribs['id'] = $this->mSubmitID; + } - if ( isset( $this->mSubmitTooltip ) ) { - $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip ); - } + if ( isset( $this->mSubmitName ) ) { + $attribs['name'] = $this->mSubmitName; + } - $attribs['class'] = 'mw-htmlform-submit'; + if ( isset( $this->mSubmitTooltip ) ) { + $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip ); + } - $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n"; + $attribs['class'] = array( 'mw-htmlform-submit' ); + + if ( $this->isVForm() ) { + // mw-ui-block is necessary because the buttons aren't necessarily in an + // immediate child div of the vform. + array_push( $attribs['class'], 'mw-ui-button', 'mw-ui-big', 'mw-ui-primary', 'mw-ui-block' ); + } + + $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n"; + + // Buttons are top-level form elements in table and div layouts, + // but vform wants all elements inside divs to get spaced-out block + // styling. + if ( $this->isVForm() ) { + $html = Html::rawElement( 'div', null, "\n$html\n" ); + } + } if ( $this->mShowReset ) { $html .= Html::element( @@ -729,6 +788,8 @@ class HTMLForm extends ContextSource { $html .= Html::element( 'input', $attrs ); } + $html .= '</span>'; + return $html; } @@ -737,7 +798,7 @@ class HTMLForm extends ContextSource { * @return String */ function getBody() { - return $this->displaySection( $this->mFieldTree ); + return $this->displaySection( $this->mFieldTree, $this->mTableId ); } /** @@ -852,6 +913,33 @@ class HTMLForm extends ContextSource { } /** + * Stop a default submit button being shown for this form. This implies that an + * alternate submit method must be provided manually. + * + * @since 1.22 + * + * @param bool $suppressSubmit Set to false to re-enable the button again + * + * @return HTMLForm $this for chaining calls + */ + function suppressDefaultSubmit( $suppressSubmit = true ) { + $this->mShowSubmit = !$suppressSubmit; + return $this; + } + + /** + * Set the id of the \<table\> or outermost \<div\> element. + * + * @since 1.22 + * @param string $id new value of the id attribute, or "" to remove + * @return HTMLForm $this for chaining calls + */ + public function setTableId( $id ) { + $this->mTableId = $id; + return $this; + } + + /** * @param string $id DOM id for the form * @return HTMLForm $this for chaining calls (since 1.20) */ @@ -859,10 +947,12 @@ class HTMLForm extends ContextSource { $this->mId = $id; return $this; } + /** * Prompt the whole form to be wrapped in a "<fieldset>", with * this text as its "<legend>" element. - * @param string $legend HTML to go inside the "<legend>" element. + * @param string|false $legend HTML to go inside the "<legend>" element, or + * false for no <legend> * Will be escaped * @return HTMLForm $this for chaining calls (since 1.20) */ @@ -931,19 +1021,30 @@ class HTMLForm extends ContextSource { /** * @todo Document - * @param $fields array[]|HTMLFormField[] array of fields (either arrays or objects) + * @param array[]|HTMLFormField[] $fields array of fields (either arrays or objects) * @param string $sectionName ID attribute of the "<table>" tag for this section, ignored if empty * @param string $fieldsetIDPrefix ID prefix for the "<fieldset>" tag of each subsection, ignored if empty + * @param boolean &$hasUserVisibleFields Whether the section had user-visible fields * @return String */ - public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) { + public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '', &$hasUserVisibleFields = false ) { $displayFormat = $this->getDisplayFormat(); $html = ''; $subsectionHtml = ''; $hasLabel = false; - $getFieldHtmlMethod = ( $displayFormat == 'table' ) ? 'getTableRow' : 'get' . ucfirst( $displayFormat ); + switch( $displayFormat ) { + case 'table': + $getFieldHtmlMethod = 'getTableRow'; + break; + case 'vform': + // Close enough to a div. + $getFieldHtmlMethod = 'getDiv'; + break; + default: + $getFieldHtmlMethod = 'get' . ucfirst( $displayFormat ); + } foreach ( $fields as $key => $value ) { if ( $value instanceof HTMLFormField ) { @@ -956,20 +1057,38 @@ class HTMLForm extends ContextSource { if ( $labelValue != ' ' && $labelValue !== '' ) { $hasLabel = true; } - } elseif ( is_array( $value ) ) { - $section = $this->displaySection( $value, $key, "$fieldsetIDPrefix$key-" ); - $legend = $this->getLegend( $key ); - if ( isset( $this->mSectionHeaders[$key] ) ) { - $section = $this->mSectionHeaders[$key] . $section; - } - if ( isset( $this->mSectionFooters[$key] ) ) { - $section .= $this->mSectionFooters[$key]; + + if ( get_class( $value ) !== 'HTMLHiddenField' && + get_class( $value ) !== 'HTMLApiField' ) { + $hasUserVisibleFields = true; } - $attributes = array(); - if ( $fieldsetIDPrefix ) { - $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" ); + } elseif ( is_array( $value ) ) { + $subsectionHasVisibleFields = false; + $section = $this->displaySection( $value, "mw-htmlform-$key", "$fieldsetIDPrefix$key-", $subsectionHasVisibleFields ); + $legend = null; + + if ( $subsectionHasVisibleFields === true ) { + // Display the section with various niceties. + $hasUserVisibleFields = true; + + $legend = $this->getLegend( $key ); + + if ( isset( $this->mSectionHeaders[$key] ) ) { + $section = $this->mSectionHeaders[$key] . $section; + } + if ( isset( $this->mSectionFooters[$key] ) ) { + $section .= $this->mSectionFooters[$key]; + } + + $attributes = array(); + if ( $fieldsetIDPrefix ) { + $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" ); + } + $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n"; + } else { + // Just return the inputs, nothing fancy. + $subsectionHtml .= $section; } - $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n"; } } @@ -985,13 +1104,13 @@ class HTMLForm extends ContextSource { ); if ( $sectionName ) { - $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" ); + $attribs['id'] = Sanitizer::escapeId( $sectionName ); } if ( $displayFormat === 'table' ) { $html = Html::rawElement( 'table', $attribs, Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n"; - } elseif ( $displayFormat === 'div' ) { + } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) { $html = Html::rawElement( 'div', $attribs, "\n$html\n" ); } } @@ -1074,6 +1193,32 @@ class HTMLForm extends ContextSource { return $this; } + /** + * Get the value for the action attribute of the form. + * + * @since 1.22 + * + * @return string + */ + public function getAction() { + global $wgScript, $wgArticlePath; + + // If an action is alredy provided, return it + if ( $this->mAction !== false ) { + return $this->mAction; + } + + // Check whether we are in GET mode and $wgArticlePath contains a "?" + // meaning that getLocalURL() would return something like "index.php?title=...". + // As browser remove the query string before submitting GET forms, + // it means that the title would be lost. In such case use $wgScript instead + // and put title in an hidden field (see getHiddenFields()). + if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() === 'get' ) { + return $wgScript; + } + + return $this->getTitle()->getLocalURL(); + } } /** @@ -1092,6 +1237,12 @@ abstract class HTMLFormField { protected $mDefault; /** + * @var bool If true will generate an empty div element with no label + * @since 1.22 + */ + protected $mShowEmptyLabels = true; + + /** * @var HTMLForm */ public $mParent; @@ -1167,6 +1318,18 @@ abstract class HTMLFormField { } /** + * Tell the field whether to generate a separate label element if its label + * is blank. + * + * @since 1.22 + * @param bool $show Set to false to not generate a label. + * @return void + */ + public function setShowEmptyLabel( $show ) { + $this->mShowEmptyLabels = $show; + } + + /** * Get the value that this input has been set to from a posted form, * or the input's default value if it has not been set. * @param $request WebRequest @@ -1183,6 +1346,8 @@ abstract class HTMLFormField { /** * Initialise the object * @param array $params Associative Array. See HTMLForm doc for syntax. + * + * @since 1.22 The 'label' attribute no longer accepts raw HTML, use 'label-raw' instead * @throws MWException */ function __construct( $params ) { @@ -1201,7 +1366,14 @@ abstract class HTMLFormField { $this->mLabel = wfMessage( $msg, $msgInfo )->parse(); } elseif ( isset( $params['label'] ) ) { - $this->mLabel = $params['label']; + if ( $params['label'] === ' ' ) { + // Apparently some things set   directly and in an odd format + $this->mLabel = ' '; + } else { + $this->mLabel = htmlspecialchars( $params['label'] ); + } + } elseif ( isset( $params['label-raw'] ) ) { + $this->mLabel = $params['label-raw']; } $this->mName = "wp{$params['fieldname']}"; @@ -1246,6 +1418,10 @@ abstract class HTMLFormField { if ( isset( $params['flatlist'] ) ) { $this->mClass .= ' mw-htmlform-flatlist'; } + + if ( isset( $params['hidelabel'] ) ) { + $this->mShowEmptyLabels = false; + } } /** @@ -1306,13 +1482,22 @@ abstract class HTMLFormField { $cellAttributes = array(); $label = $this->getLabelHtml( $cellAttributes ); + $outerDivClass = array( + 'mw-input', + 'mw-htmlform-nolabel' => ( $label === '' ) + ); + $field = Html::rawElement( 'div', - array( 'class' => 'mw-input' ) + $cellAttributes, + array( 'class' => $outerDivClass ) + $cellAttributes, $inputHtml . "\n$errors" ); + $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass, $errorClass ); + if ( $this->mParent->isVForm() ) { + $divCssClasses[] = 'mw-ui-vform-div'; + } $html = Html::rawElement( 'div', - array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), + array( 'class' => $divCssClasses ), $label . $field ); $html .= $helptext; return $html; @@ -1437,7 +1622,7 @@ abstract class HTMLFormField { } function getLabel() { - return $this->mLabel; + return is_null( $this->mLabel ) ? '' : $this->mLabel; } function getLabelHtml( $cellAttributes = array() ) { @@ -1449,20 +1634,32 @@ abstract class HTMLFormField { $for['for'] = $this->mID; } + $labelValue = trim( $this->getLabel() ); + $hasLabel = false; + if ( $labelValue !== ' ' && $labelValue !== '' ) { + $hasLabel = true; + } + $displayFormat = $this->mParent->getDisplayFormat(); - $labelElement = Html::rawElement( 'label', $for, $this->getLabel() ); + $html = ''; - if ( $displayFormat == 'table' ) { - return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, - Html::rawElement( 'label', $for, $this->getLabel() ) + if ( $displayFormat === 'table' ) { + $html = Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, + Html::rawElement( 'label', $for, $labelValue ) ); - } elseif ( $displayFormat == 'div' ) { - return Html::rawElement( 'div', array( 'class' => 'mw-label' ) + $cellAttributes, - Html::rawElement( 'label', $for, $this->getLabel() ) - ); - } else { - return $labelElement; + } elseif ( $hasLabel || $this->mShowEmptyLabels ) { + if ( $displayFormat === 'div' ) { + $html = Html::rawElement( + 'div', + array( 'class' => 'mw-label' ) + $cellAttributes, + Html::rawElement( 'label', $for, $labelValue ) + ); + } else { + $html = Html::rawElement( 'label', $for, $labelValue ); + } } + + return $html; } function getDefault() { @@ -1602,16 +1799,19 @@ class HTMLTextField extends HTMLFormField { } } class HTMLTextAreaField extends HTMLFormField { + const DEFAULT_COLS = 80; + const DEFAULT_ROWS = 25; + function getCols() { return isset( $this->mParams['cols'] ) ? $this->mParams['cols'] - : 80; + : static::DEFAULT_COLS; } function getRows() { return isset( $this->mParams['rows'] ) ? $this->mParams['rows'] - : 25; + : static::DEFAULT_ROWS; } function getInputHTML( $value ) { @@ -1741,8 +1941,25 @@ class HTMLCheckField extends HTMLFormField { $attr['class'] = $this->mClass; } - return Xml::check( $this->mName, $value, $attr ) . ' ' . - Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel ); + if ( $this->mParent->isVForm() ) { + // Nest checkbox inside label. + return Html::rawElement( + 'label', + array( + 'class' => 'mw-ui-checkbox-label' + ), + Xml::check( + $this->mName, + $value, + $attr + ) . + // Html:rawElement doesn't escape contents. + htmlspecialchars( $this->mLabel ) + ); + } else { + return Xml::check( $this->mName, $value, $attr ) . ' ' . + Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel ); + } } /** @@ -1755,6 +1972,13 @@ class HTMLCheckField extends HTMLFormField { } /** + * checkboxes don't need a label. + */ + protected function needsLabel() { + return false; + } + + /** * @param $request WebRequest * @return String */ @@ -1786,9 +2010,39 @@ class HTMLCheckField extends HTMLFormField { * A checkbox matrix * Operates similarly to HTMLMultiSelectField, but instead of using an array of * options, uses an array of rows and an array of columns to dynamically - * construct a matrix of options. + * construct a matrix of options. The tags used to identify a particular cell + * are of the form "columnName-rowName" + * + * Options: + * - columns + * - Required list of columns in the matrix. + * - rows + * - Required list of rows in the matrix. + * - force-options-on + * - Accepts array of column-row tags to be displayed as enabled but unavailable to change + * - force-options-off + * - Accepts array of column-row tags to be displayed as disabled but unavailable to change. + * - tooltips + * - Optional array mapping row label to tooltip content + * - tooltip-class + * - Optional CSS class used on tooltip container span. Defaults to mw-icon-question. */ -class HTMLCheckMatrix extends HTMLFormField { +class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { + + static private $requiredParams = array( + // Required by underlying HTMLFormField + 'fieldname', + // Required by HTMLCheckMatrix + 'rows', 'columns' + ); + + public function __construct( $params ) { + $missing = array_diff( self::$requiredParams, array_keys( $params ) ); + if ( $missing ) { + throw new HTMLFormFieldRequiredOptionsException( $this, $missing ); + } + parent::__construct( $params ); + } function validate( $value, $alldata ) { $rows = $this->mParams['rows']; @@ -1848,27 +2102,42 @@ class HTMLCheckMatrix extends HTMLFormField { } $tableContents .= Html::rawElement( 'tr', array(), "\n$headerContents\n" ); + $tooltipClass = 'mw-icon-question'; + if ( isset( $this->mParams['tooltip-class'] ) ) { + $tooltipClass = $this->mParams['tooltip-class']; + } + // Build the options matrix foreach ( $rows as $rowLabel => $rowTag ) { + // Append tooltip if configured + if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) { + $tooltipAttribs = array( + 'class' => "mw-htmlform-tooltip $tooltipClass", + 'title' => $this->mParams['tooltips'][$rowLabel], + ); + $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' ); + } $rowContents = Html::rawElement( 'td', array(), $rowLabel ); foreach ( $columns as $columnTag ) { - // Knock out any options that are not wanted - if ( isset( $this->mParams['remove-options'] ) - && in_array( "$columnTag-$rowTag", $this->mParams['remove-options'] ) ) - { - $rowContents .= Html::rawElement( 'td', array(), ' ' ); - } else { - // Construct the checkbox - $thisAttribs = array( - 'id' => "{$this->mID}-$columnTag-$rowTag", - 'value' => $columnTag . '-' . $rowTag - ); - $checkbox = Xml::check( - $this->mName . '[]', - in_array( $columnTag . '-' . $rowTag, (array)$value, true ), - $attribs + $thisAttribs ); - $rowContents .= Html::rawElement( 'td', array(), $checkbox ); + $thisTag = "$columnTag-$rowTag"; + // Construct the checkbox + $thisAttribs = array( + 'id' => "{$this->mID}-$thisTag", + 'value' => $thisTag, + ); + $checked = in_array( $thisTag, (array)$value, true ); + if ( $this->isTagForcedOff( $thisTag ) ) { + $checked = false; + $thisAttribs['disabled'] = 1; + } elseif ( $this->isTagForcedOn( $thisTag ) ) { + $checked = true; + $thisAttribs['disabled'] = 1; } + $rowContents .= Html::rawElement( + 'td', + array(), + Xml::check( "{$this->mName}[]", $checked, $attribs + $thisAttribs ) + ); } $tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" ); } @@ -1880,6 +2149,16 @@ class HTMLCheckMatrix extends HTMLFormField { return $html; } + protected function isTagForcedOff( $tag ) { + return isset( $this->mParams['force-options-off'] ) + && in_array( $tag, $this->mParams['force-options-off'] ); + } + + protected function isTagForcedOn( $tag ) { + return isset( $this->mParams['force-options-on'] ) + && in_array( $tag, $this->mParams['force-options-on'] ); + } + /** * Get the complete table row for the input, including help text, * labels, and whatever. @@ -1944,6 +2223,27 @@ class HTMLCheckMatrix extends HTMLFormField { return array(); } } + + function filterDataForSubmit( $data ) { + $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] ); + $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] ); + $res = array(); + foreach ( $columns as $column ) { + foreach ( $rows as $row ) { + // Make sure option hasn't been forced + $thisTag = "$column-$row"; + if ( $this->isTagForcedOff( $thisTag ) ) { + $res[$thisTag] = false; + } elseif ( $this->isTagForcedOn( $thisTag ) ) { + $res[$thisTag] = true; + } else { + $res[$thisTag] = in_array( $thisTag, $data ); + } + } + } + + return $res; + } } /** @@ -1959,10 +2259,11 @@ class HTMLSelectField extends HTMLFormField { $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] ); - if ( in_array( $value, $validOptions ) ) + if ( in_array( $value, $validOptions ) ) { return true; - else + } else { return $this->msg( 'htmlform-select-badoption' )->parse(); + } } function getInputHTML( $value ) { @@ -1996,7 +2297,6 @@ class HTMLSelectField extends HTMLFormField { * Select dropdown field, with an additional "other" textbox. */ class HTMLSelectOrOtherField extends HTMLTextField { - static $jsAdded = false; function __construct( $params ) { if ( !in_array( 'other', $params['options'], true ) ) { @@ -2085,7 +2385,7 @@ class HTMLSelectOrOtherField extends HTMLTextField { /** * Multi-select field */ -class HTMLMultiSelectField extends HTMLFormField { +class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable { function validate( $value, $alldata ) { $p = parent::validate( $value, $alldata ); @@ -2178,6 +2478,17 @@ class HTMLMultiSelectField extends HTMLFormField { } } + function filterDataForSubmit( $data ) { + $options = HTMLFormField::flattenOptions( $this->mParams['options'] ); + + $res = array(); + foreach ( $options as $opt ) { + $res["$opt"] = in_array( $opt, $data ); + } + + return $res; + } + protected function needsLabel() { return false; } @@ -2512,14 +2823,28 @@ class HTMLHiddenField extends HTMLFormField { return $this->getTableRow( $value ); } - public function getInputHTML( $value ) { return ''; } + public function getInputHTML( $value ) { + return ''; + } } /** * Add a submit button inline in the form (as opposed to * HTMLForm::addButton(), which will add it at the end). */ -class HTMLSubmitField extends HTMLFormField { +class HTMLSubmitField extends HTMLButtonField { + protected $buttonType = 'submit'; +} + +/** + * Adds a generic button inline to the form. Does not do anything, you must add + * click handling code in JavaScript. Use a HTMLSubmitField if you merely + * wish to add a submit button to a form. + * + * @since 1.22 + */ +class HTMLButtonField extends HTMLFormField { + protected $buttonType = 'button'; public function __construct( $info ) { $info['nodata'] = true; @@ -2527,13 +2852,20 @@ class HTMLSubmitField extends HTMLFormField { } public function getInputHTML( $value ) { - return Xml::submitButton( + $attr = array( + 'class' => 'mw-htmlform-submit ' . $this->mClass, + 'id' => $this->mID, + ); + + if ( !empty( $this->mParams['disabled'] ) ) { + $attr['disabled'] = 'disabled'; + } + + return Html::input( + $this->mName, $value, - array( - 'class' => 'mw-htmlform-submit ' . $this->mClass, - 'name' => $this->mName, - 'id' => $this->mID, - ) + $this->buttonType, + $attr ); } @@ -2612,3 +2944,22 @@ class HTMLApiField extends HTMLFormField { return ''; } } + +interface HTMLNestedFilterable { + /** + * Support for seperating multi-option preferences into multiple preferences + * Due to lack of array support. + * @param $data array + */ + function filterDataForSubmit( $data ); +} + +class HTMLFormFieldRequiredOptionsException extends MWException { + public function __construct( HTMLFormField $field, array $missing ) { + parent::__construct( sprintf( + "Form type `%s` expected the following parameters to be set: %s", + get_class( $field ), + implode( ', ', $missing ) + ) ); + } +} diff --git a/includes/HashRing.php b/includes/HashRing.php new file mode 100644 index 00000000..930f8c0a --- /dev/null +++ b/includes/HashRing.php @@ -0,0 +1,142 @@ +<?php +/** + * Convenience class for weighted consistent hash rings. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @author Aaron Schulz + */ + +/** + * Convenience class for weighted consistent hash rings + * + * @since 1.22 + */ +class HashRing { + /** @var Array (location => weight) */ + protected $sourceMap = array(); + /** @var Array (location => (start, end)) */ + protected $ring = array(); + + const RING_SIZE = 268435456; // 2^28 + + /** + * @param array $map (location => weight) + */ + public function __construct( array $map ) { + $map = array_filter( $map, function( $w ) { return $w > 0; } ); + if ( !count( $map ) ) { + throw new MWException( "Ring is empty or all weights are zero." ); + } + $this->sourceMap = $map; + // Sort the locations based on the hash of their names + $hashes = array(); + foreach ( $map as $location => $weight ) { + $hashes[$location] = sha1( $location ); + } + uksort( $map, function ( $a, $b ) use ( $hashes ) { + return strcmp( $hashes[$a], $hashes[$b] ); + } ); + // Fit the map to weight-proportionate one with a space of size RING_SIZE + $sum = array_sum( $map ); + $standardMap = array(); + foreach ( $map as $location => $weight ) { + $standardMap[$location] = (int)floor( $weight / $sum * self::RING_SIZE ); + } + // Build a ring of RING_SIZE spots, with each location at a spot in location hash order + $index = 0; + foreach ( $standardMap as $location => $weight ) { + // Location covers half-closed interval [$index,$index + $weight) + $this->ring[$location] = array( $index, $index + $weight ); + $index += $weight; + } + // Make sure the last location covers what is left + end( $this->ring ); + $this->ring[key( $this->ring )][1] = self::RING_SIZE; + } + + /** + * Get the location of an item on the ring + * + * @param string $item + * @return string Location + */ + public function getLocation( $item ) { + $locations = $this->getLocations( $item, 1 ); + return $locations[0]; + } + + /** + * Get the location of an item on the ring, as well as the next clockwise locations + * + * @param string $item + * @param integer $limit Maximum number of locations to return + * @return array List of locations + */ + public function getLocations( $item, $limit ) { + $locations = array(); + $primaryLocation = null; + $spot = hexdec( substr( sha1( $item ), 0, 7 ) ); // first 28 bits + foreach ( $this->ring as $location => $range ) { + if ( count( $locations ) >= $limit ) { + break; + } + // The $primaryLocation is the location the item spot is in. + // After that is reached, keep appending the next locations. + if ( ( $range[0] <= $spot && $spot < $range[1] ) || $primaryLocation !== null ) { + if ( $primaryLocation === null ) { + $primaryLocation = $location; + } + $locations[] = $location; + } + } + // If more locations are requested, wrap-around and keep adding them + reset( $this->ring ); + while ( count( $locations ) < $limit ) { + list( $location, ) = each( $this->ring ); + if ( $location === $primaryLocation ) { + break; // don't go in circles + } + $locations[] = $location; + } + return $locations; + } + + /** + * Get the map of locations to weight (ignores 0-weight items) + * + * @return array + */ + public function getLocationWeights() { + return $this->sourceMap; + } + + /** + * Get a new hash ring with a location removed from the ring + * + * @param string $location + * @return HashRing|bool Returns false if no non-zero weighted spots are left + */ + public function newWithoutLocation( $location ) { + $map = $this->sourceMap; + unset( $map[$location] ); + if ( count( $map ) ) { + return new self( $map ); + } + return false; + } +} diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php index 1af733a2..31aa0f87 100644 --- a/includes/HistoryBlob.php +++ b/includes/HistoryBlob.php @@ -143,7 +143,7 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob * Compress the bulk data in the object */ public function compress() { - if ( !$this->mCompressed ) { + if ( !$this->mCompressed ) { $this->mItems = gzdeflate( serialize( $this->mItems ) ); $this->mCompressed = true; } @@ -231,16 +231,16 @@ class HistoryBlobStub { * @return string */ function getText() { - if( isset( self::$blobCache[$this->mOldId] ) ) { + if ( isset( self::$blobCache[$this->mOldId] ) ) { $obj = self::$blobCache[$this->mOldId]; } else { $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'text', array( 'old_flags', 'old_text' ), array( 'old_id' => $this->mOldId ) ); - if( !$row ) { + if ( !$row ) { return false; } $flags = explode( ',', $row->old_flags ); - if( in_array( 'external', $flags ) ) { + if ( in_array( 'external', $flags ) ) { $url = $row->old_text; $parts = explode( '://', $url, 2 ); if ( !isset( $parts[1] ) || $parts[1] == '' ) { @@ -249,11 +249,11 @@ class HistoryBlobStub { $row->old_text = ExternalStore::fetchFromUrl( $url ); } - if( !in_array( 'object', $flags ) ) { + if ( !in_array( 'object', $flags ) ) { return false; } - if( in_array( 'gzip', $flags ) ) { + if ( in_array( 'gzip', $flags ) ) { // This shouldn't happen, but a bug in the compress script // may at times gzip-compress a HistoryBlob object row. $obj = unserialize( gzinflate( $row->old_text ) ); @@ -261,7 +261,7 @@ class HistoryBlobStub { $obj = unserialize( $row->old_text ); } - if( !is_object( $obj ) ) { + if ( !is_object( $obj ) ) { // Correct for old double-serialization bug. $obj = unserialize( $obj ); } @@ -318,7 +318,7 @@ class HistoryBlobCurStub { function getText() { $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'cur', array( 'cur_text' ), array( 'cur_id' => $this->mCurId ) ); - if( !$row ) { + if ( !$row ) { return false; } return $row->cur_text; @@ -530,7 +530,7 @@ class DiffHistoryBlob implements HistoryBlob { $header = unpack( 'Vofp/Vcsize', substr( $diff, 0, 8 ) ); - # Check the checksum if hash/mhash is available + # Check the checksum if hash extension is available $ofp = $this->xdiffAdler32( $base ); if ( $ofp !== false && $ofp !== substr( $diff, 0, 4 ) ) { wfDebug( __METHOD__ . ": incorrect base checksum\n" ); @@ -577,24 +577,23 @@ class DiffHistoryBlob implements HistoryBlob { * Compute a binary "Adler-32" checksum as defined by LibXDiff, i.e. with * the bytes backwards and initialised with 0 instead of 1. See bug 34428. * - * Returns false if no hashing library is available + * @param string $s + * @return string|bool: false if the hash extension is not available */ function xdiffAdler32( $s ) { + if ( !function_exists( 'hash' ) ) { + return false; + } + static $init; if ( $init === null ) { $init = str_repeat( "\xf0", 205 ) . "\xee" . str_repeat( "\xf0", 67 ) . "\x02"; } + // The real Adler-32 checksum of $init is zero, so it initialises the // state to zero, as it is at the start of LibXDiff's checksum // algorithm. Appending the subject string then simulates LibXDiff. - if ( function_exists( 'hash' ) ) { - $hash = hash( 'adler32', $init . $s, true ); - } elseif ( function_exists( 'mhash' ) ) { - $hash = mhash( MHASH_ADLER32, $init . $s ); - } else { - return false; - } - return strrev( $hash ); + return strrev( hash( 'adler32', $init . $s, true ) ); } function uncompress() { diff --git a/includes/Hooks.php b/includes/Hooks.php index 8cc7acea..396e360d 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -1,4 +1,5 @@ <?php + /** * A tool for running hook functions. * @@ -37,40 +38,43 @@ class MWHookException extends MWException {} */ class Hooks { + /** + * Array of events mapped to an array of callbacks to be run + * when that event is triggered. + */ protected static $handlers = array(); /** - * Clears hooks registered via Hooks::register(). Does not touch $wgHooks. - * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined. + * Attach an event handler to a given hook. * - * @since 1.21 - * - * @param string $name the name of the hook to clear. + * @param string $name Name of hook + * @param mixed $callback Callback function to attach * - * @throws MWException if not in testing mode. + * @since 1.18 */ - public static function clear( $name ) { - if ( !defined( 'MW_PHPUNIT_TEST' ) ) { - throw new MWException( 'can not reset hooks in operation.' ); + public static function register( $name, $callback ) { + if ( !isset( self::$handlers[$name] ) ) { + self::$handlers[$name] = array(); } - unset( self::$handlers[$name] ); + self::$handlers[$name][] = $callback; } /** - * Attach an event handler to a given hook + * Clears hooks registered via Hooks::register(). Does not touch $wgHooks. + * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined. * - * @since 1.18 + * @param string $name the name of the hook to clear. * - * @param string $name name of hook - * @param $callback Mixed: callback function to attach + * @since 1.21 + * @throws MWException if not in testing mode. */ - public static function register( $name, $callback ) { - if( !isset( self::$handlers[$name] ) ) { - self::$handlers[$name] = array(); + public static function clear( $name ) { + if ( !defined( 'MW_PHPUNIT_TEST' ) ) { + throw new MWException( 'Cannot reset hooks in operation.' ); } - self::$handlers[$name][] = $callback; + unset( self::$handlers[$name] ); } /** @@ -79,216 +83,139 @@ class Hooks { * * @since 1.18 * - * @param string $name name of hook - * @return Boolean: true if the hook has a function registered to it + * @param string $name Name of hook + * @return bool True if the hook has a function registered to it */ public static function isRegistered( $name ) { global $wgHooks; - return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] ); } /** * Returns an array of all the event functions attached to a hook * This combines functions registered via Hooks::register and with $wgHooks. - * @since 1.18 * - * @throws MWException - * @throws FatalError - * @param string $name name of the hook + * @since 1.18 * + * @param string $name Name of the hook * @return array */ public static function getHandlers( $name ) { global $wgHooks; - // Return quickly in the most common case - if ( empty( self::$handlers[$name] ) && empty( $wgHooks[$name] ) ) { + if ( !self::isRegistered( $name ) ) { return array(); - } - - if ( !is_array( self::$handlers ) ) { - throw new MWException( "Local hooks array is not an array!\n" ); - } - - if ( !is_array( $wgHooks ) ) { - throw new MWException( "Global hooks array is not an array!\n" ); - } - - if ( empty( Hooks::$handlers[$name] ) ) { - $hooks = $wgHooks[$name]; - } elseif ( empty( $wgHooks[$name] ) ) { - $hooks = Hooks::$handlers[$name]; + } elseif ( !isset( self::$handlers[$name] ) ) { + return $wgHooks[$name]; + } elseif ( !isset( $wgHooks[$name] ) ) { + return self::$handlers[$name]; } else { - // so they are both not empty... - $hooks = array_merge( Hooks::$handlers[$name], $wgHooks[$name] ); + return array_merge( self::$handlers[$name], $wgHooks[$name] ); } - - if ( !is_array( $hooks ) ) { - throw new MWException( "Hooks array for event '$name' is not an array!\n" ); - } - - return $hooks; } /** - * Call hook functions defined in Hooks::register + * Call hook functions defined in Hooks::register and $wgHooks. * - * @param string $event event name - * @param $args Array: parameters passed to hook functions + * For a certain hook event, fetch the array of hook events and + * process them. Determine the proper callback for each hook and + * then call the actual hook using the appropriate arguments. + * Finally, process the return value and return/throw accordingly. * + * @param string $event Event name + * @param array $args Array of parameters passed to hook functions + * @return bool True if no handler aborted the hook + * + * @since 1.22 A hook function is not required to return a value for + * processing to continue. Not returning a value (or explicitly + * returning null) is equivalent to returning true. * @throws MWException * @throws FatalError - * @return Boolean True if no handler aborted the hook */ - public static function run( $event, $args = array() ) { - global $wgHooks; - - // Return quickly in the most common case - if ( empty( self::$handlers[$event] ) && empty( $wgHooks[$event] ) ) { - return true; - } - + public static function run( $event, array $args = array() ) { wfProfileIn( 'hook: ' . $event ); - $hooks = self::getHandlers( $event ); + foreach ( self::getHandlers( $event ) as $hook ) { + // Turn non-array values into an array. (Can't use casting because of objects.) + if ( !is_array( $hook ) ) { + $hook = array( $hook ); + } - foreach ( $hooks as $hook ) { - $object = null; - $method = null; - $func = null; - $data = null; - $have_data = false; - $closure = false; - $badhookmsg = false; + if ( !array_filter( $hook ) ) { + // Either array is empty or it's an array filled with null/false/empty. + continue; + } elseif ( is_array( $hook[0] ) ) { + // First element is an array, meaning the developer intended + // the first element to be a callback. Merge it in so that + // processing can be uniform. + $hook = array_merge( $hook[0], array_slice( $hook, 1 ) ); + } /** * $hook can be: a function, an object, an array of $function and * $data, an array of just a function, an array of object and * method, or an array of object, method, and data. */ - if ( is_array( $hook ) ) { - if ( count( $hook ) < 1 ) { - throw new MWException( 'Empty array in hooks for ' . $event . "\n" ); - } elseif ( is_object( $hook[0] ) ) { - $object = $hook[0]; - if ( $object instanceof Closure ) { - $closure = true; - if ( count( $hook ) > 1 ) { - $data = $hook[1]; - $have_data = true; - } - } else { - if ( count( $hook ) < 2 ) { - $method = 'on' . $event; - } else { - $method = $hook[1]; - if ( count( $hook ) > 2 ) { - $data = $hook[2]; - $have_data = true; - } - } - } - } elseif ( is_string( $hook[0] ) ) { - $func = $hook[0]; - if ( count( $hook ) > 1) { - $data = $hook[1]; - $have_data = true; - } - } else { - throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" ); - } - } elseif ( is_string( $hook ) ) { # functions look like strings, too - $func = $hook; - } elseif ( is_object( $hook ) ) { - $object = $hook; - if ( $object instanceof Closure ) { - $closure = true; - } else { - $method = "on" . $event; + if ( $hook[0] instanceof Closure ) { + $func = "hook-$event-closure"; + $callback = array_shift( $hook ); + } elseif ( is_object( $hook[0] ) ) { + $object = array_shift( $hook ); + $method = array_shift( $hook ); + + // If no method was specified, default to on$event. + if ( $method === null ) { + $method = "on$event"; } - } else { - throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" ); - } - - /* We put the first data element on, if needed. */ - if ( $have_data ) { - $hook_args = array_merge( array( $data ), $args ); - } else { - $hook_args = $args; - } - if ( $closure ) { - $callback = $object; - $func = "hook-$event-closure"; - } elseif ( isset( $object ) ) { $func = get_class( $object ) . '::' . $method; $callback = array( $object, $method ); + } elseif ( is_string( $hook[0] ) ) { + $func = $callback = array_shift( $hook ); } else { - $callback = $func; + throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" ); } // Run autoloader (workaround for call_user_func_array bug) - is_callable( $callback ); + // and throw error if not callable. + if ( !is_callable( $callback ) ) { + throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" ); + } - /** - * Call the hook. The documentation of call_user_func_array clearly - * states that FALSE is returned on failure. However this is not - * case always. In some version of PHP if the function signature - * does not match the call signature, PHP will issue an warning: - * Param y in x expected to be a reference, value given. - * - * In that case the call will also return null. The following code - * catches that warning and provides better error message. The - * function documentation also says that: - * In other words, it does not depend on the function signature - * whether the parameter is passed by a value or by a reference. - * There is also PHP bug http://bugs.php.net/bug.php?id=47554 which - * is unsurprisingly marked as bogus. In short handling of failures - * with call_user_func_array is a failure, the documentation for that - * function is wrong and misleading and PHP developers don't see any - * problem here. + /* + * Call the hook. The documentation of call_user_func_array says + * false is returned on failure. However, if the function signature + * does not match the call signature, PHP will issue an warning and + * return null instead. The following code catches that warning and + * provides better error message. */ $retval = null; - set_error_handler( 'Hooks::hookErrorHandler' ); + $badhookmsg = null; + $hook_args = array_merge( $hook, $args ); + + // Profile first in case the Profiler causes errors. wfProfileIn( $func ); + set_error_handler( 'Hooks::hookErrorHandler' ); try { $retval = call_user_func_array( $callback, $hook_args ); } catch ( MWHookException $e ) { $badhookmsg = $e->getMessage(); } - wfProfileOut( $func ); restore_error_handler(); + wfProfileOut( $func ); - /* String return is an error; false return means stop processing. */ + // Process the return value. if ( is_string( $retval ) ) { + // String returned means error. throw new FatalError( $retval ); - } elseif( $retval === null ) { - if ( $closure ) { - $prettyFunc = "$event closure"; - } elseif( is_array( $callback ) ) { - if( is_object( $callback[0] ) ) { - $prettyClass = get_class( $callback[0] ); - } else { - $prettyClass = strval( $callback[0] ); - } - $prettyFunc = $prettyClass . '::' . strval( $callback[1] ); - } else { - $prettyFunc = strval( $callback ); - } - if ( $badhookmsg ) { - throw new MWException( - 'Detected bug in an extension! ' . - "Hook $prettyFunc has invalid call signature; " . $badhookmsg - ); - } else { - throw new MWException( - 'Detected bug in an extension! ' . - "Hook $prettyFunc failed to return a value; " . - 'should return true to continue hook processing or false to abort.' - ); - } - } elseif ( !$retval ) { + } elseif ( $badhookmsg !== null ) { + // Exception was thrown from Hooks::hookErrorHandler. + throw new MWException( + 'Detected bug in an extension! ' . + "Hook $func has invalid call signature; " . $badhookmsg + ); + } elseif ( $retval === false ) { wfProfileOut( 'hook: ' . $event ); + // False was returned. Stop processing, but no error. return false; } } @@ -298,18 +225,21 @@ class Hooks { } /** + * Handle PHP errors issued inside a hook. Catch errors that have to do with + * a function expecting a reference, and let all others pass through. + * * This REALLY should be protected... but it's public for compatibility * * @since 1.18 * - * @param int $errno Unused - * @param string $errstr error message - * @throws MWHookException - * @return Boolean: false + * @param int $errno Error number (unused) + * @param string $errstr Error message + * @throws MWHookException If the error has to do with the function signature + * @return bool Always returns false */ public static function hookErrorHandler( $errno, $errstr ) { if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) { - throw new MWHookException( $errstr ); + throw new MWHookException( $errstr, $errno ); } return false; } diff --git a/includes/Html.php b/includes/Html.php index af4b4bbf..932f753e 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -36,8 +36,8 @@ * * There are two important configuration options this class uses: * - * $wgHtml5: If this is set to false, then all output should be valid XHTML 1.0 - * Transitional. + * $wgMimeType: If this is set to an xml mimetype then output should be + * valid XHTML5. * $wgWellFormedXml: If this is set to true, then all output should be * well-formed XML (quotes on attributes, self-closing tags, etc.). * @@ -101,19 +101,6 @@ class Html { 'itemscope', ); - private static $HTMLFiveOnlyAttribs = array( - 'autocomplete', - 'autofocus', - 'max', - 'min', - 'multiple', - 'pattern', - 'placeholder', - 'required', - 'step', - 'spellcheck', - ); - /** * Returns an HTML element in a string. The major advantage here over * manually typing out the HTML is that it will escape all attribute @@ -177,7 +164,7 @@ class Html { * @return string */ public static function openElement( $element, $attribs = array() ) { - global $wgHtml5, $wgWellFormedXml; + global $wgWellFormedXml; $attribs = (array)$attribs; // This is not required in HTML5, but let's do it anyway, for // consistency and better compression. @@ -185,7 +172,7 @@ class Html { // In text/html, initial <html> and <head> tags can be omitted under // pretty much any sane circumstances, if they have no attributes. See: - // <http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags> + // <http://www.whatwg.org/html/syntax.html#optional-tags> if ( !$wgWellFormedXml && !$attribs && in_array( $element, array( 'html', 'head' ) ) ) { return ''; @@ -204,36 +191,28 @@ class Html { 'image', 'reset', 'button', - ); - // Allow more input types in HTML5 mode - if( $wgHtml5 ) { - $validTypes = array_merge( $validTypes, array( - 'datetime', - 'datetime-local', - 'date', - 'month', - 'time', - 'week', - 'number', - 'range', - 'email', - 'url', - 'search', - 'tel', - 'color', - ) ); - } + // HTML input types + 'datetime', + 'datetime-local', + 'date', + 'month', + 'time', + 'week', + 'number', + 'range', + 'email', + 'url', + 'search', + 'tel', + 'color', + ); if ( isset( $attribs['type'] ) && !in_array( $attribs['type'], $validTypes ) ) { unset( $attribs['type'] ); } } - if ( !$wgHtml5 && $element == 'textarea' && isset( $attribs['maxlength'] ) ) { - unset( $attribs['maxlength'] ); - } - // According to standard the default type for <button> elements is "submit". // Depending on compatibility mode IE might use "button", instead. // We enforce the standard "submit". @@ -259,7 +238,7 @@ class Html { $element = strtolower( $element ); // Reference: - // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags + // http://www.whatwg.org/html/syntax.html#optional-tags if ( !$wgWellFormedXml && in_array( $element, array( 'html', 'head', @@ -294,12 +273,6 @@ class Html { * @return array An array of attributes functionally identical to $attribs */ private static function dropDefaults( $element, $attribs ) { - // Don't bother doing anything if we aren't outputting HTML5; it's too - // much of a pain to maintain two sets of defaults. - global $wgHtml5; - if ( !$wgHtml5 ) { - return $attribs; - } // Whenever altering this array, please provide a covering test case // in HtmlTest::provideElementsWithAttributesHavingDefaultValues @@ -340,7 +313,7 @@ class Html { foreach ( $attribs as $attrib => $value ) { $lcattrib = strtolower( $attrib ); - if( is_array( $value ) ) { + if ( is_array( $value ) ) { $value = implode( ' ', $value ); } else { $value = strval( $value ); @@ -444,11 +417,12 @@ class Html { * (starting with a space if at least one attribute is output) */ public static function expandAttributes( $attribs ) { - global $wgHtml5, $wgWellFormedXml; + global $wgWellFormedXml; $ret = ''; $attribs = (array)$attribs; foreach ( $attribs as $key => $value ) { + // Support intuitive array( 'checked' => true/false ) form if ( $value === false || is_null( $value ) ) { continue; } @@ -460,15 +434,10 @@ class Html { $key = $value; } - // Not technically required in HTML5, but required in XHTML 1.0, - // and we'd like consistency and better compression anyway. + // Not technically required in HTML5 but we'd like consistency + // and better compression anyway. $key = strtolower( $key ); - // Here we're blacklisting some HTML5-only attributes... - if ( !$wgHtml5 && in_array( $key, self::$HTMLFiveOnlyAttribs ) ) { - continue; - } - // Bug 23769: Blacklist all form validation attributes for now. Current // (June 2010) WebKit has no UI, so the form just refuses to submit // without telling the user why, which is much worse than failing @@ -552,15 +521,12 @@ class Html { } if ( in_array( $key, self::$boolAttribs ) ) { - // In XHTML 1.0 Transitional, the value needs to be equal to the - // key. In HTML5, we can leave the value empty instead. If we - // don't need well-formed XML, we can omit the = entirely. + // In HTML5, we can leave the value empty. If we don't need + // well-formed XML, we can omit the = entirely. if ( !$wgWellFormedXml ) { $ret .= " $key"; - } elseif ( $wgHtml5 ) { - $ret .= " $key=\"\""; } else { - $ret .= " $key=\"$key\""; + $ret .= " $key=\"\""; } } else { // Apparently we need to entity-encode \n, \r, \t, although the @@ -602,14 +568,10 @@ class Html { * @return string Raw HTML */ public static function inlineScript( $contents ) { - global $wgHtml5, $wgJsMimeType, $wgWellFormedXml; + global $wgWellFormedXml; $attrs = array(); - if ( !$wgHtml5 ) { - $attrs['type'] = $wgJsMimeType; - } - if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) { $contents = "/*<![CDATA[*/$contents/*]]>*/"; } @@ -625,14 +587,8 @@ class Html { * @return string Raw HTML */ public static function linkedScript( $url ) { - global $wgHtml5, $wgJsMimeType; - $attrs = array( 'src' => $url ); - if ( !$wgHtml5 ) { - $attrs['type'] = $wgJsMimeType; - } - return self::element( 'script', $attrs ); } @@ -677,8 +633,7 @@ class Html { /** * Convenience function to produce an "<input>" element. This supports the - * new HTML5 input types and attributes, and will silently strip them if - * $wgHtml5 is false. + * new HTML5 input types and attributes. * * @param $name string name attribute * @param $value mixed value attribute @@ -712,9 +667,7 @@ class Html { * Convenience function to produce an "<input>" element. * * This supports leaving out the cols= and rows= which Xml requires and are - * required by HTML4/XHTML but not required by HTML5 and will silently set - * cols="" and rows="" if $wgHtml5 is false and cols and rows are omitted - * (HTML4 validates present but empty cols="" and rows="" as valid). + * required by HTML4/XHTML but not required by HTML5. * * @param $name string name attribute * @param $value string value attribute @@ -723,20 +676,8 @@ class Html { * @return string Raw HTML */ public static function textarea( $name, $value = '', $attribs = array() ) { - global $wgHtml5; - $attribs['name'] = $name; - if ( !$wgHtml5 ) { - if ( !isset( $attribs['cols'] ) ) { - $attribs['cols'] = ""; - } - - if ( !isset( $attribs['rows'] ) ) { - $attribs['rows'] = ""; - } - } - if ( substr( $value, 0, 1 ) == "\n" ) { // Workaround for bug 12130: browsers eat the initial newline // assuming that it's just for show, but they do keep the later @@ -859,28 +800,29 @@ class Html { public static function htmlHeader( $attribs = array() ) { $ret = ''; - global $wgMimeType; - - if ( self::isXmlMimeType( $wgMimeType ) ) { - $ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\n"; - } + global $wgHtml5Version, $wgMimeType, $wgXhtmlNamespaces; - global $wgHtml5, $wgHtml5Version, $wgDocType, $wgDTD; - global $wgXhtmlNamespaces, $wgXhtmlDefaultNamespace; + $isXHTML = self::isXmlMimeType( $wgMimeType ); - if ( $wgHtml5 ) { - $ret .= "<!DOCTYPE html>\n"; + if ( $isXHTML ) { // XHTML5 + // XML mimetyped markup should have an xml header. + // However a DOCTYPE is not needed. + $ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\n"; - if ( $wgHtml5Version ) { - $attribs['version'] = $wgHtml5Version; - } - } else { - $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n"; - $attribs['xmlns'] = $wgXhtmlDefaultNamespace; + // Add the standard xmlns + $attribs['xmlns'] = 'http://www.w3.org/1999/xhtml'; + // And support custom namespaces foreach ( $wgXhtmlNamespaces as $tag => $ns ) { $attribs["xmlns:$tag"] = $ns; } + } else { // HTML5 + // DOCTYPE + $ret .= "<!DOCTYPE html>\n"; + } + + if ( $wgHtml5Version ) { + $attribs['version'] = $wgHtml5Version; } $html = Html::openElement( 'html', $attribs ); @@ -901,14 +843,11 @@ class Html { * @return Boolean */ public static function isXmlMimeType( $mimetype ) { - switch ( $mimetype ) { - case 'text/xml': - case 'application/xhtml+xml': - case 'application/xml': - return true; - default: - return false; - } + # http://www.whatwg.org/html/infrastructure.html#xml-mime-type + # * text/xml + # * application/xml + # * Any mimetype with a subtype ending in +xml (this implicitly includes application/xhtml+xml) + return (bool)preg_match( '!^(text|application)/xml$|^.+/.+\+xml$!', $mimetype ); } /** @@ -926,22 +865,22 @@ class Html { global $wgStylePath; if ( $useStylePath ) { - $icon = $wgStylePath.'/common/images/'.$icon; + $icon = $wgStylePath . '/common/images/' . $icon; } $s = Html::openElement( 'div', array( 'class' => "mw-infobox $class" ) ); - $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ). + $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ) . Html::element( 'img', array( 'src' => $icon, 'alt' => $alt, ) - ). + ) . Html::closeElement( 'div' ); - $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ). - $text. + $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ) . + $text . Html::closeElement( 'div' ); $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' ); @@ -962,9 +901,9 @@ class Html { */ static function srcSet( $urls ) { $candidates = array(); - foreach( $urls as $density => $url ) { + foreach ( $urls as $density => $url ) { // Image candidate syntax per current whatwg live spec, 2012-09-23: - // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-srcset + // http://www.whatwg.org/html/embedded-content-1.html#attr-img-srcset $candidates[] = "{$url} {$density}x"; } return implode( ", ", $candidates ); diff --git a/includes/HtmlFormatter.php b/includes/HtmlFormatter.php new file mode 100644 index 00000000..248a76fe --- /dev/null +++ b/includes/HtmlFormatter.php @@ -0,0 +1,356 @@ +<?php +/** + * Performs transformations of HTML by wrapping around libxml2 and working + * around its countless bugs. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ +class HtmlFormatter { + /** + * @var DOMDocument + */ + private $doc; + + private $html; + private $itemsToRemove = array(); + private $elementsToFlatten = array(); + protected $removeMedia = false; + + /** + * Constructor + * + * @param string $html: Text to process + */ + public function __construct( $html ) { + $this->html = $html; + } + + /** + * Turns a chunk of HTML into a proper document + * @param string $html + * @return string + */ + public static function wrapHTML( $html ) { + return '<!doctype html><html><head></head><body>' . $html . '</body></html>'; + } + + /** + * Override this in descendant class to modify HTML after it has been converted from DOM tree + * @param string $html: HTML to process + * @return string: Processed HTML + */ + protected function onHtmlReady( $html ) { + return $html; + } + + /** + * @return DOMDocument: DOM to manipulate + */ + public function getDoc() { + if ( !$this->doc ) { + $html = mb_convert_encoding( $this->html, 'HTML-ENTITIES', 'UTF-8' ); + + // Workaround for bug that caused spaces before references + // to disappear during processing: + // https://bugzilla.wikimedia.org/show_bug.cgi?id=53086 + // + // Please replace with a better fix if one can be found. + $html = str_replace( ' <', ' <', $html ); + + libxml_use_internal_errors( true ); + $loader = libxml_disable_entity_loader(); + $this->doc = new DOMDocument(); + $this->doc->strictErrorChecking = false; + $this->doc->loadHTML( $html ); + libxml_disable_entity_loader( $loader ); + libxml_use_internal_errors( false ); + $this->doc->encoding = 'UTF-8'; + } + return $this->doc; + } + + /** + * Sets whether images/videos/sounds should be removed from output + * @param bool $flag + */ + public function setRemoveMedia( $flag = true ) { + $this->removeMedia = $flag; + } + + /** + * Adds one or more selector of content to remove. A subset of CSS selector + * syntax is supported: + * + * <tag> + * <tag>.class + * .<class> + * #<id> + * + * @param Array|string $selectors: Selector(s) of stuff to remove + */ + public function remove( $selectors ) { + $this->itemsToRemove = array_merge( $this->itemsToRemove, (array)$selectors ); + } + + /** + * Adds one or more element name to the list to flatten (remove tag, but not its content) + * Can accept undelimited regexes + * + * Note this interface may fail in surprising unexpected ways due to usage of regexes, + * so should not be relied on for HTML markup security measures. + * + * @param Array|string $elements: Name(s) of tag(s) to flatten + */ + public function flatten( $elements ) { + $this->elementsToFlatten = array_merge( $this->elementsToFlatten, (array)$elements ); + } + + /** + * Instructs the formatter to flatten all tags + */ + public function flattenAllTags() { + $this->flatten( '[?!]?[a-z0-9]+' ); + } + + /** + * Removes content we've chosen to remove + */ + public function filterContent() { + wfProfileIn( __METHOD__ ); + $removals = $this->parseItemsToRemove(); + + if ( !$removals ) { + wfProfileOut( __METHOD__ ); + return; + } + + $doc = $this->getDoc(); + + // Remove tags + + // You can't remove DOMNodes from a DOMNodeList as you're iterating + // over them in a foreach loop. It will seemingly leave the internal + // iterator on the foreach out of wack and results will be quite + // strange. Though, making a queue of items to remove seems to work. + $domElemsToRemove = array(); + foreach ( $removals['TAG'] as $tagToRemove ) { + $tagToRemoveNodes = $doc->getElementsByTagName( $tagToRemove ); + foreach ( $tagToRemoveNodes as $tagToRemoveNode ) { + if ( $tagToRemoveNode ) { + $domElemsToRemove[] = $tagToRemoveNode; + } + } + } + + $this->removeElements( $domElemsToRemove ); + + // Elements with named IDs + $domElemsToRemove = array(); + foreach ( $removals['ID'] as $itemToRemove ) { + $itemToRemoveNode = $doc->getElementById( $itemToRemove ); + if ( $itemToRemoveNode ) { + $domElemsToRemove[] = $itemToRemoveNode; + } + } + $this->removeElements( $domElemsToRemove ); + + // CSS Classes + $domElemsToRemove = array(); + $xpath = new DOMXpath( $doc ); + foreach ( $removals['CLASS'] as $classToRemove ) { + $elements = $xpath->query( '//*[contains(@class, "' . $classToRemove . '")]' ); + + /** @var $element DOMElement */ + foreach ( $elements as $element ) { + $classes = $element->getAttribute( 'class' ); + if ( preg_match( "/\b$classToRemove\b/", $classes ) && $element->parentNode ) { + $domElemsToRemove[] = $element; + } + } + } + $this->removeElements( $domElemsToRemove ); + + // Tags with CSS Classes + foreach ( $removals['TAG_CLASS'] as $classToRemove ) { + $parts = explode( '.', $classToRemove ); + + $elements = $xpath->query( + '//' . $parts[0] . '[@class="' . $parts[1] . '"]' + ); + + $this->removeElements( $elements ); + } + + wfProfileOut( __METHOD__ ); + } + + /** + * Removes a list of elelments from DOMDocument + * @param array|DOMNodeList $elements + */ + private function removeElements( $elements ) { + $list = $elements; + if ( $elements instanceof DOMNodeList ) { + $list = array(); + foreach ( $elements as $element ) { + $list[] = $element; + } + } + /** @var $element DOMElement */ + foreach ( $list as $element ) { + if ( $element->parentNode ) { + $element->parentNode->removeChild( $element ); + } + } + } + + /** + * libxml in its usual pointlessness converts many chars to entities - this function + * perfoms a reverse conversion + * @param string $html + * @return string + */ + private function fixLibXML( $html ) { + wfProfileIn( __METHOD__ ); + static $replacements; + if ( ! $replacements ) { + // We don't include rules like '"' => '&quot;' because entities had already been + // normalized by libxml. Using this function with input not sanitized by libxml is UNSAFE! + $replacements = new ReplacementArray( array( + '"' => '&quot;', + '&' => '&amp;', + '<' => '&lt;', + '>' => '&gt;', + ) ); + } + $html = $replacements->replace( $html ); + $html = mb_convert_encoding( $html, 'UTF-8', 'HTML-ENTITIES' ); + wfProfileOut( __METHOD__ ); + return $html; + } + + /** + * Performs final transformations and returns resulting HTML + * + * @param DOMElement|string|null $element: ID of element to get HTML from or false to get it from the whole tree + * @return string: Processed HTML + */ + public function getText( $element = null ) { + wfProfileIn( __METHOD__ ); + + if ( $this->doc ) { + if ( $element !== null && !( $element instanceof DOMElement ) ) { + $element = $this->doc->getElementById( $element ); + } + if ( $element ) { + $body = $this->doc->getElementsByTagName( 'body' )->item( 0 ); + $nodesArray = array(); + foreach ( $body->childNodes as $node ) { + $nodesArray[] = $node; + } + foreach ( $nodesArray as $nodeArray ) { + $body->removeChild( $nodeArray ); + } + $body->appendChild( $element ); + } + $html = $this->doc->saveHTML(); + $html = $this->fixLibXml( $html ); + } else { + $html = $this->html; + } + if ( wfIsWindows() ) { + // Appears to be cleanup for CRLF misprocessing of unknown origin + // when running server on Windows platform. + // + // If this error continues in the future, please track it down in the + // XML code paths if possible and fix there. + $html = str_replace( ' ', '', $html ); + } + $html = preg_replace( '/<!--.*?-->|^.*?<body>|<\/body>.*$/s', '', $html ); + $html = $this->onHtmlReady( $html ); + + if ( $this->elementsToFlatten ) { + $elements = implode( '|', $this->elementsToFlatten ); + $html = preg_replace( "#</?($elements)\\b[^>]*>#is", '', $html ); + } + + wfProfileOut( __METHOD__ ); + return $html; + } + + /** + * @param $selector: CSS selector to parse + * @param $type + * @param $rawName + * @return bool: Whether the selector was successfully recognised + */ + protected function parseSelector( $selector, &$type, &$rawName ) { + if ( strpos( $selector, '.' ) === 0 ) { + $type = 'CLASS'; + $rawName = substr( $selector, 1 ); + } elseif ( strpos( $selector, '#' ) === 0 ) { + $type = 'ID'; + $rawName = substr( $selector, 1 ); + } elseif ( strpos( $selector, '.' ) !== 0 && + strpos( $selector, '.' ) !== false ) + { + $type = 'TAG_CLASS'; + $rawName = $selector; + } elseif ( strpos( $selector, '[' ) === false + && strpos( $selector, ']' ) === false ) + { + $type = 'TAG'; + $rawName = $selector; + } else { + throw new MWException( __METHOD__ . "(): unrecognized selector '$selector'" ); + } + + return true; + } + + /** + * Transforms CSS selectors into an internal representation suitable for processing + * @return array + */ + protected function parseItemsToRemove() { + wfProfileIn( __METHOD__ ); + $removals = array( + 'ID' => array(), + 'TAG' => array(), + 'CLASS' => array(), + 'TAG_CLASS' => array(), + ); + + foreach ( $this->itemsToRemove as $itemToRemove ) { + $type = ''; + $rawName = ''; + if ( $this->parseSelector( $itemToRemove, $type, $rawName ) ) { + $removals[$type][] = $rawName; + } + } + + if ( $this->removeMedia ) { + $removals['TAG'][] = 'img'; + $removals['TAG'][] = 'audio'; + $removals['TAG'][] = 'video'; + } + + wfProfileOut( __METHOD__ ); + return $removals; + } +} diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php index dc65c67e..78c2ac7a 100644 --- a/includes/HttpFunctions.php +++ b/includes/HttpFunctions.php @@ -40,14 +40,15 @@ class Http { * @param array $options options to pass to MWHttpRequest object. * Possible keys for the array: * - timeout Timeout length in seconds + * - connectTimeout Timeout for connection, in seconds (curl only) * - postData An array of key-value pairs or a url-encoded form data * - proxy The proxy to use. * Otherwise it will use $wgHTTPProxy (if set) * Otherwise it will use the environment variable "http_proxy" (if set) * - noProxy Don't use any proxy at all. Takes precedence over proxy value(s). - * - sslVerifyHost (curl only) Verify hostname against certificate - * - sslVerifyCert (curl only) Verify SSL certificate - * - caInfo (curl only) Provide CA information + * - sslVerifyHost Verify hostname against certificate + * - sslVerifyCert Verify SSL certificate + * - caInfo Provide CA information * - maxRedirects Maximum number of redirects to follow (defaults to 5) * - followRedirects Whether to follow redirects (defaults to false). * Note: this should only be used when the target URL is trusted, @@ -58,20 +59,26 @@ class Http { */ public static function request( $method, $url, $options = array() ) { wfDebug( "HTTP: $method: $url\n" ); + wfProfileIn( __METHOD__ . "-$method" ); + $options['method'] = strtoupper( $method ); if ( !isset( $options['timeout'] ) ) { $options['timeout'] = 'default'; } + if ( !isset( $options['connectTimeout'] ) ) { + $options['connectTimeout'] = 'default'; + } $req = MWHttpRequest::factory( $url, $options ); $status = $req->execute(); + $content = false; if ( $status->isOK() ) { - return $req->getContent(); - } else { - return false; + $content = $req->getContent(); } + wfProfileOut( __METHOD__ . "-$method" ); + return $content; } /** @@ -213,7 +220,7 @@ class MWHttpRequest { * @param array $options (optional) extra params to pass (see Http::request()) */ protected function __construct( $url, $options = array() ) { - global $wgHTTPTimeout; + global $wgHTTPTimeout, $wgHTTPConnectTimeout; $this->url = wfExpandUrl( $url, PROTO_HTTP ); $this->parsedUrl = wfParseUrl( $this->url ); @@ -229,7 +236,12 @@ class MWHttpRequest { } else { $this->timeout = $wgHTTPTimeout; } - if( isset( $options['userAgent'] ) ) { + if ( isset( $options['connectTimeout'] ) && $options['connectTimeout'] != 'default' ) { + $this->connectTimeout = $options['connectTimeout']; + } else { + $this->connectTimeout = $wgHTTPConnectTimeout; + } + if ( isset( $options['userAgent'] ) ) { $this->setUserAgent( $options['userAgent'] ); } @@ -277,7 +289,7 @@ class MWHttpRequest { ' Http::$httpEngine is set to "curl"' ); } - switch( Http::$httpEngine ) { + switch ( Http::$httpEngine ) { case 'curl': return new CurlHttpRequest( $url, $options ); case 'php': @@ -302,7 +314,7 @@ class MWHttpRequest { /** * Set the parameters of the request - + * * @param $args Array * @todo overload the args param */ @@ -427,6 +439,8 @@ class MWHttpRequest { public function execute() { global $wgTitle; + wfProfileIn( __METHOD__ ); + $this->content = ""; if ( strtoupper( $this->method ) == "HEAD" ) { @@ -446,6 +460,8 @@ class MWHttpRequest { if ( !isset( $this->reqHeaders['User-Agent'] ) ) { $this->setUserAgent( Http::userAgent() ); } + + wfProfileOut( __METHOD__ ); } /** @@ -454,6 +470,8 @@ class MWHttpRequest { * found in an array in the member variable headerList. */ protected function parseHeader() { + wfProfileIn( __METHOD__ ); + $lastname = ""; foreach ( $this->headerList as $header ) { @@ -470,6 +488,8 @@ class MWHttpRequest { } $this->parseCookies(); + + wfProfileOut( __METHOD__ ); } /** @@ -552,8 +572,8 @@ class MWHttpRequest { $this->parseHeader(); } - if ( isset( $this->respHeaders[strtolower ( $header ) ] ) ) { - $v = $this->respHeaders[strtolower ( $header ) ]; + if ( isset( $this->respHeaders[strtolower( $header )] ) ) { + $v = $this->respHeaders[strtolower( $header )]; return $v[count( $v ) - 1]; } @@ -603,6 +623,8 @@ class MWHttpRequest { * Parse the cookies in the response headers and store them in the cookie jar. */ protected function parseCookies() { + wfProfileIn( __METHOD__ ); + if ( !$this->cookieJar ) { $this->cookieJar = new CookieJar; } @@ -613,6 +635,8 @@ class MWHttpRequest { $this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] ); } } + + wfProfileOut( __METHOD__ ); } /** @@ -631,17 +655,17 @@ class MWHttpRequest { $headers = $this->getResponseHeaders(); //return full url (fix for incorrect but handled relative location) - if ( isset( $headers[ 'location' ] ) ) { - $locations = $headers[ 'location' ]; + if ( isset( $headers['location'] ) ) { + $locations = $headers['location']; $domain = ''; $foundRelativeURI = false; $countLocations = count( $locations ); for ( $i = $countLocations - 1; $i >= 0; $i-- ) { - $url = parse_url( $locations[ $i ] ); + $url = parse_url( $locations[$i] ); if ( isset( $url['host'] ) ) { - $domain = $url[ 'scheme' ] . '://' . $url[ 'host' ]; + $domain = $url['scheme'] . '://' . $url['host']; break; //found correct URI (with host) } else { $foundRelativeURI = true; @@ -650,15 +674,15 @@ class MWHttpRequest { if ( $foundRelativeURI ) { if ( $domain ) { - return $domain . $locations[ $countLocations - 1 ]; + return $domain . $locations[$countLocations - 1]; } else { $url = parse_url( $this->url ); - if ( isset($url[ 'host' ]) ) { - return $url[ 'scheme' ] . '://' . $url[ 'host' ] . $locations[ $countLocations - 1 ]; + if ( isset( $url['host'] ) ) { + return $url['scheme'] . '://' . $url['host'] . $locations[$countLocations - 1]; } } } else { - return $locations[ $countLocations - 1 ]; + return $locations[$countLocations - 1]; } } @@ -681,11 +705,6 @@ class MWHttpRequest { class CurlHttpRequest extends MWHttpRequest { const SUPPORTS_FILE_POSTS = true; - static $curlMessageMap = array( - 6 => 'http-host-unreachable', - 28 => 'http-timed-out' - ); - protected $curlOptions = array(); protected $headerText = ""; @@ -700,14 +719,18 @@ class CurlHttpRequest extends MWHttpRequest { } public function execute() { + wfProfileIn( __METHOD__ ); + parent::execute(); if ( !$this->status->isOK() ) { + wfProfileOut( __METHOD__ ); return $this->status; } $this->curlOptions[CURLOPT_PROXY] = $this->proxy; $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout; + $this->curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = $this->connectTimeout * 1000; $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; $this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback; $this->curlOptions[CURLOPT_HEADERFUNCTION] = array( $this, "readHeader" ); @@ -746,6 +769,7 @@ class CurlHttpRequest extends MWHttpRequest { $curlHandle = curl_init( $this->url ); if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) { + wfProfileOut( __METHOD__ ); throw new MWException( "Error setting curl options." ); } @@ -760,14 +784,11 @@ class CurlHttpRequest extends MWHttpRequest { wfRestoreWarnings(); } - if ( false === curl_exec( $curlHandle ) ) { - $code = curl_error( $curlHandle ); - - if ( isset( self::$curlMessageMap[$code] ) ) { - $this->status->fatal( self::$curlMessageMap[$code] ); - } else { - $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) ); - } + $curlRes = curl_exec( $curlHandle ); + if ( curl_errno( $curlHandle ) == CURLE_OPERATION_TIMEOUTED ) { + $this->status->fatal( 'http-timed-out', $this->url ); + } elseif ( $curlRes === false ) { + $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) ); } else { $this->headerList = explode( "\r\n", $this->headerText ); } @@ -777,6 +798,8 @@ class CurlHttpRequest extends MWHttpRequest { $this->parseHeader(); $this->setStatus(); + wfProfileOut( __METHOD__ ); + return $this->status; } @@ -811,6 +834,8 @@ class PhpHttpRequest extends MWHttpRequest { } public function execute() { + wfProfileIn( __METHOD__ ); + parent::execute(); if ( is_array( $this->postData ) ) { @@ -826,7 +851,7 @@ class PhpHttpRequest extends MWHttpRequest { if ( $this->method == 'POST' ) { // Required for HTTP 1.0 POSTs $this->reqHeaders['Content-Length'] = strlen( $this->postData ); - if( !isset( $this->reqHeaders['Content-Type'] ) ) { + if ( !isset( $this->reqHeaders['Content-Type'] ) ) { $this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded"; } } @@ -860,7 +885,23 @@ class PhpHttpRequest extends MWHttpRequest { $options['timeout'] = $this->timeout; - $context = stream_context_create( array( 'http' => $options ) ); + if ( $this->sslVerifyHost ) { + $options['CN_match'] = $this->parsedUrl['host']; + } + if ( $this->sslVerifyCert ) { + $options['verify_peer'] = true; + } + + if ( is_dir( $this->caInfo ) ) { + $options['capath'] = $this->caInfo; + } elseif ( is_file( $this->caInfo ) ) { + $options['cafile'] = $this->caInfo; + } elseif ( $this->caInfo ) { + throw new MWException( "Invalid CA info passed: {$this->caInfo}" ); + } + + $scheme = $this->parsedUrl['scheme']; + $context = stream_context_create( array( "$scheme" => $options ) ); $this->headerList = array(); $reqCount = 0; @@ -903,18 +944,19 @@ class PhpHttpRequest extends MWHttpRequest { if ( $fh === false ) { $this->status->fatal( 'http-request-error' ); + wfProfileOut( __METHOD__ ); return $this->status; } if ( $result['timed_out'] ) { $this->status->fatal( 'http-timed-out', $this->url ); + wfProfileOut( __METHOD__ ); return $this->status; } // If everything went OK, or we received some error code // get the response body content. - if ( $this->status->isOK() - || (int)$this->respStatus >= 300) { + if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) { while ( !feof( $fh ) ) { $buf = fread( $fh, 8192 ); @@ -930,6 +972,8 @@ class PhpHttpRequest extends MWHttpRequest { } fclose( $fh ); + wfProfileOut( __METHOD__ ); + return $this->status; } } diff --git a/includes/IP.php b/includes/IP.php index 72b9a52c..73834a59 100644 --- a/includes/IP.php +++ b/includes/IP.php @@ -33,24 +33,17 @@ define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX ); // An IPv6 address is made up of 8 words (each x0000 to xFFFF). // However, the "::" abbreviation can be used on consecutive x0000 words. define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' ); -define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)'); +define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' ); define( 'RE_IPV6_ADD', '(?:' . // starts with "::" (including "::") ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' . '|' . // ends with "::" (except "::") RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' . - '|' . // contains one "::" in the middle, ending in "::WORD" - RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,5}' . '::' . RE_IPV6_WORD . - '|' . // contains one "::" in the middle, not ending in "::WORD" (regex for PCRE 4.0+) - RE_IPV6_WORD . '(?::(?P<abn>:(?P<iabn>))?' . RE_IPV6_WORD . '(?!:(?P=abn))){1,5}' . - ':' . RE_IPV6_WORD . '(?P=iabn)' . - // NOTE: (?!(?P=abn)) fails iff "::" used twice; (?P=iabn) passes iff a "::" was found. + '|' . // contains one "::" in the middle (the ^ makes the test fail if none found) + RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' . '|' . // contains no "::" RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' . ')' - // NOTE: With PCRE 7.2+, we can combine the two '"::" in the middle' cases into: - // RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' - // This also improves regex concatenation by using relative references. ); // An IPv6 block is an IP address and a prefix (d1 to d128) define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX ); @@ -212,7 +205,7 @@ class IP { $longest = $match; $longestPos = $pos; } - $offset += ( $pos + strlen( $match ) ); // advance + $offset = ( $pos + strlen( $match ) ); // advance } if ( $longest !== false ) { // Replace this portion of the string with the '::' abbreviation @@ -392,11 +385,11 @@ class IP { static $privateRanges = false; if ( !$privateRanges ) { $privateRanges = array( - array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private) - array( '172.16.0.0', '172.31.255.255' ), # " - array( '192.168.0.0', '192.168.255.255' ), # " - array( '0.0.0.0', '0.255.255.255' ), # this network - array( '127.0.0.0', '127.255.255.255' ), # loopback + array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private) + array( '172.16.0.0', '172.31.255.255' ), # RFC 1918 (private) + array( '192.168.0.0', '192.168.255.255' ), # RFC 1918 (private) + array( '0.0.0.0', '0.255.255.255' ), # this network + array( '127.0.0.0', '127.255.255.255' ), # loopback ); } @@ -492,6 +485,11 @@ class IP { $n = ip2long( $ip ); if ( $n < 0 ) { $n += pow( 2, 32 ); + # On 32-bit platforms (and on Windows), 2^32 does not fit into an int, + # so $n becomes a float. We convert it to string instead. + if ( is_float( $n ) ) { + $n = (string)$n; + } } } return $n; @@ -526,7 +524,7 @@ class IP { if ( $bits == 0 ) { $network = 0; } else { - $network &= ~( ( 1 << ( 32 - $bits ) ) - 1); + $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 ); } # Convert to unsigned if ( $network < 0 ) { diff --git a/includes/ImagePage.php b/includes/ImagePage.php index b3a485aa..7ea06b0e 100644 --- a/includes/ImagePage.php +++ b/includes/ImagePage.php @@ -206,7 +206,7 @@ class ImagePage extends Article { } // Add remote Filepage.css - if( !$this->repo->isLocal() ) { + if ( !$this->repo->isLocal() ) { $css = $this->repo->getDescriptionStylesheetUrl(); if ( $css ) { $out->addStyle( $css ); @@ -250,7 +250,7 @@ class ImagePage extends Article { * * @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata(). * - * @param array $metadata the array containing the EXIF data + * @param array $metadata the array containing the Exif data * @return String The metadata table. This is treated as Wikitext (!) */ protected function makeMetadataTable( $metadata ) { @@ -311,6 +311,12 @@ class ImagePage extends Article { } else { $params = array( 'page' => $page ); } + + $renderLang = $request->getVal( 'lang' ); + if ( !is_null( $renderLang ) ) { + $params['lang'] = $renderLang; + } + $width_orig = $this->displayImg->getWidth( $page ); $width = $width_orig; $height_orig = $this->displayImg->getHeight( $page ); @@ -395,6 +401,7 @@ class ImagePage extends Article { $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1; if ( $isMulti ) { + $out->addModules( 'mediawiki.page.image.pagination' ); $out->addHTML( '<table class="multipageimage"><tr><td>' ); } @@ -444,7 +451,6 @@ class ImagePage extends Article { $formParams = array( 'name' => 'pageselector', 'action' => $wgScript, - 'onchange' => 'document.pageselector.submit();', ); $options = array(); for ( $i = 1; $i <= $count; $i++ ) { @@ -597,7 +603,7 @@ EOT $descText = $this->mPage->getFile()->getDescriptionText(); /* Add canonical to head if there is no local page for this shared file */ - if( $descUrl && $this->mPage->getID() == 0 ) { + if ( $descUrl && $this->mPage->getID() == 0 ) { $out->setCanonicalUrl( $descUrl ); } @@ -631,7 +637,7 @@ EOT * external editing (and instructions link) etc. */ protected function uploadLinksBox() { - global $wgEnableUploads, $wgUseExternalEditor; + global $wgEnableUploads; if ( !$wgEnableUploads ) { return; @@ -654,25 +660,6 @@ EOT $out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">" . $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" ); } - # External editing link - if ( $wgUseExternalEditor ) { - $elink = Linker::linkKnown( - $this->getTitle(), - wfMessage( 'edit-externally' )->escaped(), - array(), - array( - 'action' => 'edit', - 'externaledit' => 'true', - 'mode' => 'file' - ) - ); - $out->addHTML( - '<li id="mw-imagepage-edit-external">' . $elink . ' <small>' . - wfMessage( 'edit-externally-help' )->parse() . - "</small></li>\n" - ); - } - $out->addHTML( "</ul>\n" ); } @@ -719,7 +706,7 @@ EOT $limit = 100; $out = $this->getContext()->getOutput(); - $res = $this->queryImageLinks( $this->getTitle()->getDbKey(), $limit + 1); + $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 ); $rows = array(); $redirects = array(); foreach ( $res as $row ) { @@ -772,13 +759,21 @@ EOT // Create links for every element $currentCount = 0; - foreach( $rows as $element ) { + foreach ( $rows as $element ) { $currentCount++; if ( $currentCount > $limit ) { break; } - $link = Linker::linkKnown( Title::makeTitle( $element->page_namespace, $element->page_title ) ); + $query = array(); + # Add a redirect=no to make redirect pages reachable + if ( isset( $redirects[$element->page_title] ) ) { + $query['redirect'] = 'no'; + } + $link = Linker::linkKnown( + Title::makeTitle( $element->page_namespace, $element->page_title ), + null, array(), $query + ); if ( !isset( $redirects[$element->page_title] ) ) { # No redirects $liContents = $link; diff --git a/includes/ImageQueryPage.php b/includes/ImageQueryPage.php index f9f6ceed..75f7ba64 100644 --- a/includes/ImageQueryPage.php +++ b/includes/ImageQueryPage.php @@ -29,25 +29,25 @@ * @author Rob Church <robchur@gmail.com> */ abstract class ImageQueryPage extends QueryPage { - /** * Format and output report results using the given information plus * OutputPage * - * @param $out OutputPage to print to - * @param $skin Skin: user skin to use [unused] - * @param $dbr DatabaseBase (read) connection to use - * @param $res Integer: result pointer - * @param $num Integer: number of available result rows - * @param $offset Integer: paging offset + * @param OutputPage $out OutputPage to print to + * @param Skin $skin User skin to use [unused] + * @param DatabaseBase $dbr (read) connection to use + * @param int $res Result pointer + * @param int $num Number of available result rows + * @param int $offset Paging offset */ protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { - if( $num > 0 ) { - $gallery = new ImageGallery(); + if ( $num > 0 ) { + $gallery = ImageGalleryBase::factory(); + $gallery->setContext( $this->getContext() ); # $res might contain the whole 1,000 rows, so we read up to # $num [should update this to use a Pager] - for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) { + for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) { $namespace = isset( $row->namespace ) ? $row->namespace : NS_FILE; $title = Title::makeTitleSafe( $namespace, $row->title ); if ( $title instanceof Title && $title->getNamespace() == NS_FILE ) { @@ -65,11 +65,10 @@ abstract class ImageQueryPage extends QueryPage { /** * Get additional HTML to be shown in a results' cell * - * @param $row Object: result row - * @return String + * @param object $row Result row + * @return string */ protected function getCellHtml( $row ) { return ''; } - } diff --git a/includes/Import.php b/includes/Import.php index bb5d6349..8b7af02a 100644 --- a/includes/Import.php +++ b/includes/Import.php @@ -47,7 +47,7 @@ class WikiImporter { stream_wrapper_register( 'uploadsource', 'UploadSourceAdapter' ); $id = UploadSourceAdapter::registerSource( $source ); - if (defined( 'LIBXML_PARSEHUGE' ) ) { + if ( defined( 'LIBXML_PARSEHUGE' ) ) { $this->reader->open( "uploadsource://$id", null, LIBXML_PARSEHUGE ); } else { $this->reader->open( "uploadsource://$id" ); @@ -66,7 +66,7 @@ class WikiImporter { } private function debug( $data ) { - if( $this->mDebug ) { + if ( $this->mDebug ) { wfDebug( "IMPORT: $data\n" ); } } @@ -188,10 +188,10 @@ class WikiImporter { * @return bool */ public function setTargetNamespace( $namespace ) { - if( is_null( $namespace ) ) { + if ( is_null( $namespace ) ) { // Don't override namespaces $this->mTargetNamespace = null; - } elseif( $namespace >= 0 ) { + } elseif ( $namespace >= 0 ) { // @todo FIXME: Check for validity $this->mTargetNamespace = intval( $namespace ); } else { @@ -206,16 +206,16 @@ class WikiImporter { */ public function setTargetRootPage( $rootpage ) { $status = Status::newGood(); - if( is_null( $rootpage ) ) { + if ( is_null( $rootpage ) ) { // No rootpage $this->mTargetRootPage = null; - } elseif( $rootpage !== '' ) { + } elseif ( $rootpage !== '' ) { $rootpage = rtrim( $rootpage, '/' ); //avoid double slashes $title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace ) ? $this->mTargetNamespace : NS_MAIN ); - if( !$title || $title->isExternal() ) { + if ( !$title || $title->isExternal() ) { $status->fatal( 'import-rootpage-invalid' ); } else { - if( !MWNamespace::hasSubpages( $title->getNamespace() ) ) { + if ( !MWNamespace::hasSubpages( $title->getNamespace() ) ) { global $wgContLang; $displayNSText = $title->getNamespace() == NS_MAIN @@ -304,7 +304,7 @@ class WikiImporter { */ public function debugRevisionHandler( &$revision ) { $this->debug( "Got revision:" ); - if( is_object( $revision->title ) ) { + if ( is_object( $revision->title ) ) { $this->debug( "-- Title: " . $revision->title->getPrefixedText() ); } else { $this->debug( "-- Title: <invalid>" ); @@ -320,7 +320,7 @@ class WikiImporter { * @param $title Title */ function pageCallback( $title ) { - if( isset( $this->mPageCallback ) ) { + if ( isset( $this->mPageCallback ) ) { call_user_func( $this->mPageCallback, $title ); } } @@ -334,7 +334,7 @@ class WikiImporter { * @param array $pageInfo associative array of page information */ private function pageOutCallback( $title, $origTitle, $revCount, $sucCount, $pageInfo ) { - if( isset( $this->mPageOutCallback ) ) { + if ( isset( $this->mPageOutCallback ) ) { $args = func_get_args(); call_user_func_array( $this->mPageOutCallback, $args ); } @@ -376,12 +376,12 @@ class WikiImporter { * @access private */ private function nodeContents() { - if( $this->reader->isEmptyElement ) { + if ( $this->reader->isEmptyElement ) { return ""; } $buffer = ""; - while( $this->reader->read() ) { - switch( $this->reader->nodeType ) { + while ( $this->reader->read() ) { + switch ( $this->reader->nodeType ) { case XmlReader::TEXT: case XmlReader::SIGNIFICANT_WHITESPACE: $buffer .= $this->reader->value; @@ -420,19 +420,19 @@ class WikiImporter { "END_ELEMENT", "END_ENTITY", "XML_DECLARATION", - ); + ); $lookup = array(); - foreach( $xmlReaderConstants as $name ) { - $lookup[constant("XmlReader::$name")] = $name; + foreach ( $xmlReaderConstants as $name ) { + $lookup[constant( "XmlReader::$name" )] = $name; } } - print( var_dump( + print var_dump( $lookup[$this->reader->nodeType], $this->reader->name, $this->reader->value - )."\n\n" ); + ) . "\n\n"; } /** @@ -450,7 +450,7 @@ class WikiImporter { if ( $this->reader->name != 'mediawiki' ) { libxml_disable_entity_loader( $oldDisable ); - throw new MWException( "Expected <mediawiki> tag, got ". + throw new MWException( "Expected <mediawiki> tag, got " . $this->reader->name ); } $this->debug( "<mediawiki> tag is correct." ); @@ -463,7 +463,7 @@ class WikiImporter { $tag = $this->reader->name; $type = $this->reader->nodeType; - if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', $this ) ) { + if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', array( $this ) ) ) { // Do nothing } elseif ( $tag == 'mediawiki' && $type == XmlReader::END_ELEMENT ) { break; @@ -522,8 +522,9 @@ class WikiImporter { $tag = $this->reader->name; - if ( !wfRunHooks( 'ImportHandleLogItemXMLTag', - $this, $logInfo ) ) { + if ( !wfRunHooks( 'ImportHandleLogItemXMLTag', array( + $this, $logInfo + ) ) ) { // Do nothing } elseif ( in_array( $tag, $normalFields ) ) { $logInfo[$tag] = $this->nodeContents(); @@ -639,8 +640,9 @@ class WikiImporter { $tag = $this->reader->name; - if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', $this, - $pageInfo, $revisionInfo ) ) { + if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', array( + $this, $pageInfo, $revisionInfo + ) ) ) { // Do nothing } elseif ( in_array( $tag, $normalFields ) ) { $revisionInfo[$tag] = $this->nodeContents(); @@ -666,7 +668,7 @@ class WikiImporter { private function processRevision( $pageInfo, $revisionInfo ) { $revision = new WikiRevision; - if( isset( $revisionInfo['id'] ) ) { + if ( isset( $revisionInfo['id'] ) ) { $revision->setID( $revisionInfo['id'] ); } if ( isset( $revisionInfo['text'] ) ) { @@ -725,8 +727,9 @@ class WikiImporter { $tag = $this->reader->name; - if ( !wfRunHooks( 'ImportHandleUploadXMLTag', $this, - $pageInfo ) ) { + if ( !wfRunHooks( 'ImportHandleUploadXMLTag', array( + $this, $pageInfo + ) ) ) { // Do nothing } elseif ( in_array( $tag, $normalFields ) ) { $uploadInfo[$tag] = $this->nodeContents(); @@ -840,33 +843,33 @@ class WikiImporter { $workTitle = $text; $origTitle = Title::newFromText( $workTitle ); - if( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) { + if ( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) { # makeTitleSafe, because $origTitle can have a interwiki (different setting of interwiki map) # and than dbKey can begin with a lowercase char $title = Title::makeTitleSafe( $this->mTargetNamespace, $origTitle->getDBkey() ); } else { - if( !is_null( $this->mTargetRootPage ) ) { + if ( !is_null( $this->mTargetRootPage ) ) { $workTitle = $this->mTargetRootPage . '/' . $workTitle; } $title = Title::newFromText( $workTitle ); } - if( is_null( $title ) ) { + if ( is_null( $title ) ) { # Invalid page title? Ignore the page $this->notice( 'import-error-invalid', $workTitle ); return false; - } elseif( $title->isExternal() ) { + } elseif ( $title->isExternal() ) { $this->notice( 'import-error-interwiki', $title->getPrefixedText() ); return false; - } elseif( !$title->canExist() ) { + } elseif ( !$title->canExist() ) { $this->notice( 'import-error-special', $title->getPrefixedText() ); return false; - } elseif( !$title->userCan( 'edit' ) && !$wgCommandLineMode ) { + } elseif ( !$title->userCan( 'edit' ) && !$wgCommandLineMode ) { # Do not import if the importing wiki user cannot edit this page $this->notice( 'import-error-edit', $title->getPrefixedText() ); return false; - } elseif( !$title->exists() && !$title->userCan( 'create' ) && !$wgCommandLineMode ) { + } elseif ( !$title->exists() && !$title->userCan( 'create' ) && !$wgCommandLineMode ) { # Do not import if the importing wiki user cannot create this page $this->notice( 'import-error-create', $title->getPrefixedText() ); return false; @@ -997,12 +1000,12 @@ class XMLReader2 extends XMLReader { * @return bool|string */ function nodeContents() { - if( $this->isEmptyElement ) { + if ( $this->isEmptyElement ) { return ""; } $buffer = ""; - while( $this->read() ) { - switch( $this->nodeType ) { + while ( $this->read() ) { + switch ( $this->nodeType ) { case XmlReader::TEXT: case XmlReader::SIGNIFICANT_WHITESPACE: $buffer .= $this->value; @@ -1051,9 +1054,9 @@ class WikiRevision { * @throws MWException */ function setTitle( $title ) { - if( is_object( $title ) ) { + if ( is_object( $title ) ) { $this->title = $title; - } elseif( is_null( $title ) ) { + } elseif ( is_null( $title ) ) { throw new MWException( "WikiRevision given a null title in import. You may need to adjust \$wgLegalTitleChars." ); } else { throw new MWException( "WikiRevision given non-object title in import." ); @@ -1369,7 +1372,7 @@ class WikiRevision { # Sneak a single revision into place $user = User::newFromName( $this->getUser() ); - if( $user ) { + if ( $user ) { $userId = intval( $user->getId() ); $userText = $user->getName(); $userObj = $user; @@ -1384,7 +1387,7 @@ class WikiRevision { $linkCache->clear(); $page = WikiPage::factory( $this->title ); - if( !$page->exists() ) { + if ( !$page->exists() ) { # must create the page... $pageId = $page->insertOn( $dbw ); $created = true; @@ -1400,7 +1403,7 @@ class WikiRevision { 'rev_comment' => $this->getComment() ), __METHOD__ ); - if( $prior ) { + if ( $prior ) { // @todo FIXME: This could fail slightly for multiple matches :P wfDebug( __METHOD__ . ": skipping existing revision for [[" . $this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" ); @@ -1440,7 +1443,7 @@ class WikiRevision { function importLogItem() { $dbw = wfGetDB( DB_MASTER ); # @todo FIXME: This will not record autoblocks - if( !$this->getTitle() ) { + if ( !$this->getTitle() ) { wfDebug( __METHOD__ . ": skipping invalid {$this->type}/{$this->action} log time, timestamp " . $this->timestamp . "\n" ); return; @@ -1459,7 +1462,7 @@ class WikiRevision { __METHOD__ ); // @todo FIXME: This could fail slightly for multiple matches :P - if( $prior ) { + if ( $prior ) { wfDebug( __METHOD__ . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp " . $this->timestamp . "\n" ); return; @@ -1500,7 +1503,7 @@ class WikiRevision { wfDebug( __METHOD__ . "File already exists; importing as $archiveName\n" ); } } - if( !$file ) { + if ( !$file ) { wfDebug( __METHOD__ . ': Bad file for ' . $this->getTitle() . "\n" ); return false; } @@ -1512,7 +1515,7 @@ class WikiRevision { $source = $this->downloadSource(); $flags |= File::DELETE_SOURCE; } - if( !$source ) { + if ( !$source ) { wfDebug( __METHOD__ . ": Could not fetch remote file.\n" ); return false; } @@ -1551,13 +1554,13 @@ class WikiRevision { */ function downloadSource() { global $wgEnableUploads; - if( !$wgEnableUploads ) { + if ( !$wgEnableUploads ) { return false; } $tempo = tempnam( wfTempDir(), 'download' ); $f = fopen( $tempo, 'wb' ); - if( !$f ) { + if ( !$f ) { wfDebug( "IMPORT: couldn't write to temp file $tempo\n" ); return false; } @@ -1565,7 +1568,7 @@ class WikiRevision { // @todo FIXME! $src = $this->getSrc(); $data = Http::get( $src ); - if( !$data ) { + if ( !$data ) { wfDebug( "IMPORT: couldn't fetch source $src\n" ); fclose( $f ); unlink( $tempo ); @@ -1601,7 +1604,7 @@ class ImportStringSource { * @return bool|string */ function readChunk() { - if( $this->atEnd() ) { + if ( $this->atEnd() ) { return false; } $this->mRead = true; @@ -1640,7 +1643,7 @@ class ImportStreamSource { wfSuppressWarnings(); $file = fopen( $filename, 'rt' ); wfRestoreWarnings(); - if( !$file ) { + if ( !$file ) { return Status::newFatal( "importcantopen" ); } return Status::newGood( new ImportStreamSource( $file ) ); @@ -1653,11 +1656,11 @@ class ImportStreamSource { static function newFromUpload( $fieldname = "xmlimport" ) { $upload =& $_FILES[$fieldname]; - if( $upload === null || !$upload['name'] ) { + if ( $upload === null || !$upload['name'] ) { return Status::newFatal( 'importnofile' ); } - if( !empty( $upload['error'] ) ) { - switch( $upload['error'] ) { + if ( !empty( $upload['error'] ) ) { + switch ( $upload['error'] ) { case 1: # The uploaded file exceeds the upload_max_filesize directive in php.ini. return Status::newFatal( 'importuploaderrorsize' ); case 2: # The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form. @@ -1671,7 +1674,7 @@ class ImportStreamSource { } $fname = $upload['tmp_name']; - if( is_uploaded_file( $fname ) ) { + if ( is_uploaded_file( $fname ) ) { return ImportStreamSource::newFromFile( $fname ); } else { return Status::newFatal( 'importnofile' ); @@ -1690,7 +1693,7 @@ class ImportStreamSource { # otherwise prevent importing from large sites, such # as the Wikimedia cluster, etc. $data = Http::request( $method, $url, array( 'followRedirects' => true ) ); - if( $data !== false ) { + if ( $data !== false ) { $file = tmpfile(); fwrite( $file, $data ); fflush( $file ); @@ -1710,18 +1713,24 @@ class ImportStreamSource { * @return Status */ public static function newFromInterwiki( $interwiki, $page, $history = false, $templates = false, $pageLinkDepth = 0 ) { - if( $page == '' ) { + if ( $page == '' ) { return Status::newFatal( 'import-noarticle' ); } $link = Title::newFromText( "$interwiki:Special:Export/$page" ); - if( is_null( $link ) || $link->getInterwiki() == '' ) { + if ( is_null( $link ) || $link->getInterwiki() == '' ) { return Status::newFatal( 'importbadinterwiki' ); } else { $params = array(); - if ( $history ) $params['history'] = 1; - if ( $templates ) $params['templates'] = 1; - if ( $pageLinkDepth ) $params['pagelink-depth'] = $pageLinkDepth; - $url = $link->getFullUrl( $params ); + if ( $history ) { + $params['history'] = 1; + } + if ( $templates ) { + $params['templates'] = 1; + } + if ( $pageLinkDepth ) { + $params['pagelink-depth'] = $pageLinkDepth; + } + $url = $link->getFullURL( $params ); # For interwikis, use POST to avoid redirects. return ImportStreamSource::newFromURL( $url, "POST" ); } diff --git a/includes/Init.php b/includes/Init.php index 66f9544d..64431f09 100644 --- a/includes/Init.php +++ b/includes/Init.php @@ -2,6 +2,9 @@ /** * Some functions that are useful during startup. * + * This class previously contained some functionality related to a PHP compiler + * called hphpc. That compiler has now been discontinued. + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -22,40 +25,35 @@ /** * Some functions that are useful during startup. + * + * This class previously contained some functionality related to a PHP compiler + * called hphpc. That compiler has now been discontinued. All methods are now + * deprecated. */ class MWInit { static $compilerVersion; /** - * Get the version of HipHop used to compile, or false if MediaWiki was not - * compiled. This works by having our build script insert a special function - * into the compiled code. + * @deprecated since 1.22 */ static function getCompilerVersion() { - if ( self::$compilerVersion === null ) { - if ( self::functionExists( 'wfHipHopCompilerVersion' ) ) { - self::$compilerVersion = wfHipHopCompilerVersion(); - } else { - self::$compilerVersion = false; - } - } - return self::$compilerVersion; + return false; } /** * Returns true if we are running under HipHop, whether in compiled or * interpreted mode. * + * @deprecated since 1.22 * @return bool */ static function isHipHop() { - return function_exists( 'hphp_thread_set_warmup_enabled' ); + return defined( 'HPHP_VERSION' ); } /** - * Get a fully-qualified path for a source file relative to $IP. Including - * such a path under HipHop will force the file to be interpreted. This is - * useful for configuration files. + * Get a fully-qualified path for a source file relative to $IP. + * @deprecated since 1.22 * * @param $file string * @@ -67,117 +65,39 @@ class MWInit { } /** - * If we are running code compiled by HipHop, this will pass through the - * input path, assumed to be relative to $IP. If the code is interpreted, - * it will converted to a fully qualified path. It is necessary to use a - * path which is relative to $IP in order to make HipHop use its compiled - * code. - * + * @deprecated since 1.22 * @param $file string - * * @return string */ static function compiledPath( $file ) { global $IP; - - if ( defined( 'MW_COMPILED' ) ) { - return "phase3/$file"; - } else { - return "$IP/$file"; - } - } - - /** - * The equivalent of MWInit::interpretedPath() but for files relative to the - * extensions directory. - * - * @param $file string - * @return string - */ - static function extInterpretedPath( $file ) { - return self::getExtensionsDirectory() . '/' . $file; + return "$IP/$file"; } /** - * The equivalent of MWInit::compiledPath() but for file |