diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2007-01-11 19:06:07 +0000 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2007-01-11 19:06:07 +0000 |
commit | a58285fd06c8113c45377c655dd43cef6337e815 (patch) | |
tree | dfe31d3d12652352fe44890b4811eda0728faefb | |
parent | 20194986f6638233732ba1fc3e838f117d3cc9ea (diff) |
Aktualisierung auf MediaWiki 1.9.0
410 files changed, 41055 insertions, 11656 deletions
@@ -1,10 +1,260 @@ -Change notes from older releases. For current info see RELEASE-NOTES. +Change notes from older releases. For current info see RELEASE-NOTES. = MediaWiki release notes = Security reminder: MediaWiki does not require PHP's register_globals setting since version 1.2.0. If you have it on, turn it *off* if you can. +== Changes since 1.7 == + +* Introduced AjaxResponse object, superceding AjaxCachePolicy +* Changes to sajax_do_call: optionally accept an element to fill instead of a + callback function; take the target function or element as a third parameter; + pass the full XMLHttpRequest object to the handler function, instead of just + the resultText value; use HTTP response codes to report errors. +* (bug 6562) Removed unmaintained ParserXml.php for now +* History paging overlap bug fixed +* (bug 6586) Regression in "unblocked" subtitle +* Don't put empty-page message into view-source when page text is blank +* (bug 6587) Remove redundant "allnonarticles" message +* Block improvements: Allow blocks on anonymous users only. Optionally allow + or disallow account creation from blocked IP addresses. Prevent duplicate + blocks. Fixed the problem of expiry and unblocking erroneously affecting + multiple blocks. Fixed confusing lack of error message when a blocked user + attempts to create an account. Fixed inefficiency of Special:Ipblocklist in + the presence of large numbers of blocks; added indexes and implemented an + indexed pager. +* (bug 6448) Allow filtering of Special:Newpages according to username +* (bug 6618) Improve permissions/error detection in Special:Lockdb +* Quick hack for extension testing: parser test doesn't create new message + cache object. +* (bug 6299) Maintain parser's revision ID across recursive calls to fix + {{REVISIONID}} when Cite extension is used +* (bug 6622) Removed deprecated function Image::newFromTitle +* (bug 6627) Fix regression in Special:Ipblocklist with table prefix +* Removed forced dereferencements (new() returns a reference in PHP5) +* Note about $wgUploadSizeWarning using byte +* (bug 6592) Add most viewed pages summary to Special:Statistics +* Pre-strip characters ignored in IDNs from URLs so they can't be used + to break the blacklists for regular URLs +* Fix regression in blocking of user accounts +* (bug 6635) Fix regression searching for range blocks on Ipblocklist +* Fix regression searching Ipblocklist with ugly URLs +* (bug 6639) Use a consistent default for upload directories +* Preserve entered reason when reporting unconfirmed lock on Special:Lockdb +* (bug 6642) Don't offer to unlock the database when it isn't locked +* cleanupTitles.php changed from --dry-run option to --fix, so default + behavior is now a non-invasive check as with namespaceDupes.php +* (bug 6660) Fix behaviour of EditPage::blockedPage() when the article does + not exist; now doesn't show the source box if the user hasn't provided it + (blocked mid-edit) and the page doesn't exist +* Improve default value of "blockedtext" +* (bug 6680) Added localisation for Dutch bookstore list (nl) +* Renamed maintainace script redundanttrans.php to unusedMessages.php - clearer usage +* Fix regression which allowed some blocked users to create additional accounts +* (bug 6657) Fix Hungarian linktrail +* (bug 6751) Fix preview of blanked section with edit on first preview option +* (bug 5456) Separate MediaWiki:Search into messages for both noun and verb, + introduced 'MediaWiki:Searchbutton' +* Made lines from initialiseMessages() appear as list items during installation +* Moved the bulk of the localisation data from the Language*.php files to the + Messages*.php files. Deleted most of the Languages*.php files. +* Introduced "stub global" framework to provide deferred initialisation of core + modules. +* Removed placeholder values for $wgTitle and $wgArticle, these variables will + now be null during the initialisation process, until they are set by index.php + or another entry point. +* Added DBA cache type, for BDB-style caches. +* Removed custom date format functions, replacing them with a format string in + the style of PHP's date(). Used string identifiers instead of integer + identifiers, in both the language files and user preferences. Migration should + be transparent in most cases. +* Simplified the initialisation API for LoadBalancer objects. +* Removed the broken altencoding feature. +* Moved default user options and toggles from Language to User. Language objects + are still able to define default preference overrides and extra user toggles, + via a slightly different interface. +* Don't include the date option in the parser cache rendering hash unless + $wgUseDynamicDates is enabled. +* Merged LanguageUtf8 with Language. Removed LanguageUtf8.php. +* Removed inclusion of language files from the bottom of Language.php. This is + now consistently done from Language::factory(). +* Add the name of the executing maintenance script to the debug log. Start the + profiler during maintenance scripts. +* Added "serialized" directory, for storing precompiled data in serialized form. +* Fix regression in auto-set NS_PROJECT_TALK namespace +* Fix regression in ordering of namespaces +* (bug 6806, 6030) Added several global JS variables for article path, user name, + page title, etc. +* hooks registered with addOnloadHook are now called at the one of the html body + by all skins. +* Split ajax aided search from core ajax framework. Use wgUseAjax to enable the + framework and wgAjaxSearch to enable the suggest feature for the search box. +* Added experimental installer for extensions. + See maintenance/installExtension.php +* Added Tajic (tg) language file. +* (bug 6903) Added Cantonese localisation (zh-yue) +* Fix regression in Korean and Japanese date formatting (day of week) +* (bug 6919) Add English alias magic words for Tatar (tt) language file. +* (bug 6753) Fixed broken Kazakh linktrail (kk) +* (bug 6700) Added Kazakh language variants to Names.php +* (bug 6827) some i18n specific maintenance scripts fails after merge of localisation-work branch +* Throwed an exception for the deprecated functions OutputPage::sysopRequired and + OutputPage::developerRequired - use OutputPage::permissionRequired instead. +* Removed the deprecated functions User::isSysop, User::isBureaucrat and User::isDeveloper - + use User::isAllowed instead. +* (bug 769) OutputPage::permissionRequired() should suggest groups with the needed permission +* (bug 6971) Fix regression in Special:Export history view +* Revamped Special:Imagelist +* (bug 7000) updated MessagesPl.php +* (bug 6946) Fix unexpected behavior change with GET hits to Special:Export +* (bug 1866) Improve navigation on Special:Listusers; user now a starting + point as with Special:Allpages, rather than a pure limit. +* Clean up tab order on Special:Blockip +* (bug 5969) Clean up tab order on Special:Userlogin forms +* (bug 3512) namespaceDupes now handles spaces and initial caps properly +* (bug 7037) Fix regression in login tab order +* (bug 7031) Report missing email on 'email password' instead of false success +* (bug 7010) Don't send email notifications for watched talk pages when user + has selected to receive only updates for their own talk page +* Added {{CURRENTHOUR}} +* Added [[:Image:Foo.png]] style links to the pagelinks table +* Avoid duplicate revision imports with Special:Import +* (bug 7054) Validate email address before sending email confirmation message +* (bug 7061) Format title on "from (page)" links on Special:Allpages +* (bug 7044) Introduce "padleft" and "padright" colon functions +* Pass page title as parameters to "linkshere" and "nolinkshere" and update + default message text +* Allows to upload from publicy accessible URL. Set $wgAllowCopyUploads = true ; in LocalSettings.php + Limited to $wgMaxUploadSize (default:100MB); URL upload is limited to sysops by default, and displayed as a second line if appropriate +* (bug 832) Return to user page after emailing a user +* (bug 366) Add local-system-timezone equivalents for date/time variables +* (bug 7109) Fix Atom feed version number in header links +* (bug 7075) List registered parser function hooks on Special:Version +* (bug 7059) Introduce "anchorencode" colon function +* Include SVN revision number in {{CURRENTVERSION}} output, where applicable +* Fix bug in wfRunHooks which caused corruption of objects in the hook list +* (bug 4979) Use simplified email addresses when running on Windows +* (bug 4434) Show block log fragment on Special:Blockip +* [[MediaWiki:Disambiguationspage]] may optionally contain wiki links to any number + of disambiguation templates. +* [[Special:Disambiguations]] now shows pages in NS:0 that link to any pages that embed + any of the templates listed at [[MediaWiki:Disambiguationspage]]. +* Fix formatting of titles on Special:Undelete +* (bug 7026) Fix action=raw&templates=expand +* (bug 6976) Add namespace and direction classes to classic skins +* (bug 7144) Don't "return to main" from OutputPage::loginToUse() if the the user can't + read the main page in the first place +* (bug 7188) Fix minor borkage in HTMLForm +* (bug 6675) Replaced message 'watchthis' with new message 'watchthisupload in Special:Upload +* Add a quickie script dumpSisterSites.php for generating a page list in the + format for WSR-1 SisterSites support +* (bug 7223) Monobook.js is used for site content, should not be localized +* Set default disabled values for DjVu render options +* Added Xml::option() for generating <option>s easily +* Localized page numbers in drop-down for DjVu page selection +* Fixed linktrail for vi +* (bug 6893) "Call to a member function exists() on a non-object" on trackback.php with bad input +* (bug 6886) PHP undefined offset on bad input to Special:Revisiondelete +* (bug 6887) PHP error for call to getId() on bad input to Special:Revisiondelete +* (bug 6888) PHP error for call to getTimestamp() on bad input to Special:Revisiondelete +* (bug 7252) Use dvipng support in texvc math rastrization. dvipng is required if texvc is rebuilt. +* (bug 7279) Use wfBaseName in place of basename() in more places +* Clear newtalk marker on diff links with explicit current revision number +* (bug 7064) Replace hard-coded empty message checks with wfEmptyMsg calls +* (bug 6777) Remove some PHP 4 compat cruft +* Add --user, --comment, and --license options to importImages.php +* (bug 6216) The immobile namespace message does not mention the source page +* (bug 7299) Normalize username filter on Special:Newpages +* (bug 7306) RTL text in an LTR wiki breaks appearance of Special:Recentchanges +* (bug 7312) Don't emit SET NAMES utf8 if connection failed +* (bug 7305) Proper compare for bot check on RC notify, should fix overrides + that force edits by non-bot users to bot mode +* Set Vary: Cookie on action=raw generated CSS and JS, to ensure that user + preferences don't get stuck in proxy caches for other people +* (bug 7324) Fix error message for failure of Database::sourceFile() +* (bug 7309) Plurals: use singular form for zero in French and Brazilian Portuguese +* Add page_no_title_convert field to support language variant conversion + for page titles which shouldn't be converted on display/linking +* Lazy extraction of text chunks in Revision objects, may reduce hits to + external storage when actual text content is not used +* Added experimental $wgRevisionCacheExpiry to cache extracted revision text + in $wgMemc, to further reduce hits to external storage. + Set to 0 (disabled) by default. +* Minor changes to the installer. +* Remove ":" for 'youremail' and 'yourrealname' in includes/templates/Userlogin.php + so that ":" could be used in i18n for Special:Preferences (like 'username' and 'uid'). +* Fix layout for Special:Preferences->Date and Time (position for 'timezonetext'). +* Updates to language variant code for Serbian et al +* (bug 6756) Enabling RTL direction for kk-cn +* (bug 6701) Kazakh language variants in MessagesEn.php +* (bug 7335) SVN revision check in Special:Version fails on SVN 1.4 working copy +* (bug 6518) Replaced 'lastmodified' with 'lastmodifiedat' and 'lastmodifiedby' with 'lastmodifiedatby' + with seperated parameters for date and time to allow better localisation. Updated all message files + to display the old format for compatibility. +* (bug 7357) Make supposedly static methods of Skin actually static +* Added info text to Special:Deadendpages and Special:Lonelypages +* Fix regression in cachability of generated CSS and JS for MonoBook skin, + while avoiding clobbering of different users' cached data +* (bug 6849) Block @ from usernames; interferes with multi-database tools and + was meant to be banned years ago... For now existing accounts will not be + prevented fromm login. +* (bug 6092) Introduce magic words {{REVISIONDAY}}, {{REVISIONDAY2}, {{REVISIONMONTH}}, + {{REVISIONYEAR}} and {{REVISIONTIMESTAMP}} +* (bug 7425) Preceeding whitespace in [[...]] breaks subpages +* Try to reconnect after transitory database errors in dumpTextPass.php +* (bug 6023) Fixed mismatch of 0/NULL for wl_notificationtimestamp; now notification + mails are working after 'Mark all pages visited' button on Special:Watchlist is clicked +* Made {{INT:}} a core parser function instead of a special case. The syntax + and behaviour is largely unchanged. +* (bug 7448) Fixing the native name for Ewe (ee) +* (bug 6864) Replace message 'editing' with new message 'editinguser' in Special:Userrights + to allow better localisation +* Add '*-summary' for special pages to MessagesEn.php to allow customizing/translation + directly through Special:Allmessages +* (bug 6130, bug 5818) Replaced message 'go' with the new message 'searcharticle' in skins + to allow better localisation +* Add + to $wgLegalTitleChars by default. Some sites may have occasional + problems with hard-to-reach pages, but it should be less trouble than + "I can't import dumps from Wikipedia" complaints +* (bug 7460) Revert broken patch for bug 7226 which slows down + Special:Allmessages by a factor of 16 +* Committed a bunch of live hacks from Wikimedia servers +* (bug 6889) PHP notices in thumb.php with missing params +* Cleaner error behavior on thumb.php with invalid page selection +* (bug 6617) Validate timestamps on Special:Undelete +* Do fewer unnecessary full writes of user rows; only update user_touched + for watch/unwatch, group membership change, and login operations +* Restructured the languages directory, to avoid problems when people + untar MW 1.8 over the top of a 1.7 installation. +* (bug 6890) SQL query error on bad input to Pager lists + due to negative LIMIT clause, caused by integer wraparound. +* Fixed various bugs related to table prefixes, especially the interaction + between table prefixes and memcached, which was formerly completely broken. +* (bug 7004) PHP iconv() notice on bad password input to Special:Userlogin. +* (bug 6826) Extend pre-save transform context link ("pipe trick") + syntax to pages with commas in title +* Use ImageMagick -thumbnail option instead of -resize to avoid including + excessive metadata in thumbs (requires ImageMagick 6.0.0 or newer). +* (bug 7499) Corrections to Swedish talk namespace names +* (bug 7508) Added option to compress HTML pages by dumpHTML.php +* (bug 7519) Add plural in SpecialWatchlist +* (bug 7459) Magic word variables are always case sensitive +* Replaced {{SERVER}}{{localurl:xxx}} with {{fullurl:xxx}} in localisation files +* Fix regression in Special:Watchlist text header +* (bug 7510) Update article counts etc on undelete +* (bug 7520) Update article counts on XML import +* (bug 7526) Make $wgDefaultUserOptions work again +* (bug 7472) Localize Help namespace for Basque +* (bug 7529) Including a non-existent category in an article places that article in the category +* (bug 4528) Lack of important LaTeX functions stackrel, rightleftharpoon +* (bug 6721) missing symbols ulcorner, urcorner, llcorner, lrcorner, twoheadrightarrow, twoheadleftarrow +* (bug 7367) Hyphens sometimes erroneously appended to equations when not converted to PNG +* Add "title" to the opensearch link to allow automatic adding of the search engine in Firefox 2 +* (bug 7537) Add php5 to $wgFileBlacklist +* (bug 6929) Restore AutoAuthenticate hook + + == Changes since 1.6 == * (bug 5458) Fix double-URL encoding in block log link in contribs and contribs @@ -4,7 +4,7 @@ Installing MediaWiki Starting with MediaWiki 1.2.0, it's possible to install and configure the wiki "in-place", as long as you have -the necessary prerequesites available. +the necessary prerequisites available. Required software: * Web server with PHP 5.x or higher. @@ -98,9 +98,9 @@ provide enough information to work with, and preferably be aware of what you're doing!) and keep track of major changes to the software, including performance improvements and security patches. -http://mail.wikimedia.org/mailman/listinfo/mediawiki-announce (low traffic) +http://lists.wikimedia.org/mailman/listinfo/mediawiki-announce (low traffic) -http://mail.wikimedia.org/mailman/listinfo/mediawiki-l (site admin support) +http://lists.wikimedia.org/mailman/listinfo/mediawiki-l (site admin support) -http://mail.wikimedia.org/mailman/listinfo/wikitech-l (development) +http://lists.wikimedia.org/mailman/listinfo/wikitech-l (development) diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..0cfba45a --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: Test.php + prove -r t diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 82eb053b..24d90c1f 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -3,18 +3,29 @@ Security reminder: MediaWiki does not require PHP's register_globals setting since version 1.2.0. If you have it on, turn it *off* if you can. -== MediaWiki 1.8.3 == +== MediaWiki 1.9 == -January 9, 2007 +January 10, 2007 + +This is the quarterly release snapshot for Winter 2007. While the code +has been running on Wikipedia for some time, installation and upgrade +bits may be less well tested. Bug fix releases may follow in the coming +days or weeks. + + +MediaWiki is now using a "continuous integration" development model with +quarterly snapshot releases. The latest development code is always kept +"ready to run", and in fact runs our own sites on Wikipedia. + +Release branches will continue to receive security updates for about a year +from first release, but nonessential bugfixes and feature development happen +will be made on the development trunk and appear in the next quarterly release. + +Those wishing to use the latest code instead of a branch release can obtain +it from source control: http://www.mediawiki.org/wiki/Download_from_SVN -MediaWiki 1.8.3 fixes several issues in the Fall 2006 snapshot release: -* (bug 7831) Regression in AutoAuthenticate hook -* Run PHP install version checks on update.php so command-line updaters see - new version requirements -* Do a check for the PHP 5.0.x 64-bit bug, since this is much more disruptive - as of MW 1.8 than it used to be. Install or upgrade now aborts with a - warning and a request to upgrade. -* XSS fix in AJAX module + +== Security fixes == An XSS injection vulnerability was located in the AJAX support module, affecting MediaWiki 1.6.x and up when the optional setting $wgUseAjax @@ -22,7 +33,7 @@ is enabled. There is no danger in the default configuration, with $wgUseAjax off. -If you are using an extension based on the optional AJAX module, +If you are using an extension based on the optional Ajax module, either disable it or upgrade to a version containing the fix: * 1.9: fixed in 1.9.0rc2 @@ -31,368 +42,736 @@ either disable it or upgrade to a version containing the fix: * 1.6: fixed in 1.6.9 -== MediaWiki 1.8.2 == +== Compatibility changes == -October 13, 2006 +=== Zend Optimizer === -MediaWiki 1.8.2 fixes several issues in the Fall 2006 snapshot release: +A bug in some versions of PHP 5 and Zend Optimizer which was triggered under +MediaWiki 1.8.x has been worked around by disabling some internal debugging +features when Zend Optimizer is loaded. This should solve some common +"blank page" problems. -* (bug 7565) Fixed typos in German localisation -* (bug 7562) Fix non-ASCII namespaces on Windows/XAMPP servers +=== PHP 5.0 64-bit === +MediaWiki now checks for a condition where PHP 5.0.x corrupts array data +on 64-bit systems and warns you to upgrade PHP to solve the problem. This +bug causes Special: pages to fail on affected systems under MediaWiki 1.8 +and higher, and subtler data corruption on earlier versions. -== MediaWiki 1.8.1 == +The only known workaround is to upgrade PHP to 5.1 or later, which you +probably should do anyway for security reasons! -October 11, 2006 +=== MySQL 5 === -MediaWiki 1.8.1 fixes several issues in the Fall 2006 snapshot release: +MediaWiki should now install and run correctly on MySQL 5.0 and higher when +MySQL's "strict mode" is enabled. (This is now the default for many Windows +installations, though it seems to remain off by default on Unix.) -* Fix PHP notice and estimates for dumpBackup.php and friends -* Improved register_globals paranoia checks -* (bug 7545) Fix PHP version check on install -* Experimental web API disabled by default -* Disable PHP exception backtrace printing unless $wgShowExceptionDetails - is set. Backtraces may contain sensitive information in function call - parameters. +This fixes errors about "cannot default default value for BLOB/TEXT fields". +=== ImageMagick === -== MediaWiki 1.8.0 == +Note that ImageMagick older than 6.x may no longer work for image resizing +due to use of the -thumbnail option. -October 10, 2006 -This is the quarterly release snapshot for Fall 2006. While the code -has been running on Wikipedia for some time, installation and upgrade -bits may be less well tested. Bug fix releases may follow in the coming -days or weeks. +== Behavior changes == +=== Localized special pages === -MediaWiki is now using a "continuous integration" development model with -quarterly snapshot releases. The latest development code is always kept -"ready to run", and in fact runs our own sites on Wikipedia. +The names of Special: pages can now be localized, so links and URLs to them +are more legible in languages that aren't English. -Release branches will continue to receive security updates for about a year -from first release, but nonessential bugfixes and feature development happen -will be made on the development trunk and appear in the next quarterly release. +Not all languages have included localized names yet. -Those wishing to use the latest code instead of a branch release can obtain -it from source control: http://www.mediawiki.org/wiki/Download_from_SVN +=== E-mail password === + +Users are now required to set a new password for themselves when they first +log in with a newly generated e-mailed password. + +Requesting passwords frequently is prevented to reduce abusive mailbombing. + +=== Undo revision === + +An "undo" link now appears in diff view for easier reverting of older edits. +When GNU diff3 is available for edit conflict merging, this can make it much +easier to "undo" the changes of an older edit when there are surrounding +changes elsewhere in the page. + +The changes must be manually reviewed and approved, as with conventional +full-revision reverts. + +=== Blocking === + +User blocks can be set to disable the automatic blocking of IP addresses the +account logs in with. + + +== Database changes == + +* new 'redirect' table stores data on page redirects +* new 'querycachetwo' table used for some cached special pages +* 'ipblocks' table adds 'ipb_enable_autoblock' +* 'recentchanges' table adds 'rc_old_len', 'rc_new_len' for size tracking +* 'user' table has added 'user_newpass_time' and 'user_editcount' fields +* some indexes have been updated on 'recentchanges' == Configuration changes == -* $wgUseETag, to enable/disable sending of HTTP ETag headers - (default: disabled) -* $wgLegalTitleChars now includes '+' by default for better compatibility - with importing data dumps from Wikipedia -* $wgDefaultUserOptions now includes all default option settings instead - of only overrides. - - -== Major new features == - -* (bug 7098) Add an option to disable/enable sending of HTTP ETag headers, - as it seems to result in broken behaviour in combination with Squid 2.6 - (disabled by default). -* (bug 550) Allow blocks on anonymous users only. -* (bug 6420) Render thumbnails for DJVU images, support multipage DJVU display - on image pages. Added new 'page=' thumbnail option to select a page from a - multipage djvu for thumbnail generation. -* Full Postgres support is now enabled. It requires version 8.1 or better, and - needs to have both plpgsql and tsearch2 already installed. -* (bug 6386) fix grammatical errors in danish naming of talk namespaces. - -== Changes since 1.7 == - -* Introduced AjaxResponse object, superceding AjaxCachePolicy -* Changes to sajax_do_call: optionally accept an element to fill instead of a - callback function; take the target function or element as a third parameter; - pass the full XMLHttpRequest object to the handler function, instead of just - the resultText value; use HTTP response codes to report errors. -* (bug 6562) Removed unmaintained ParserXml.php for now -* History paging overlap bug fixed -* (bug 6586) Regression in "unblocked" subtitle -* Don't put empty-page message into view-source when page text is blank -* (bug 6587) Remove redundant "allnonarticles" message -* Block improvements: Allow blocks on anonymous users only. Optionally allow - or disallow account creation from blocked IP addresses. Prevent duplicate - blocks. Fixed the problem of expiry and unblocking erroneously affecting - multiple blocks. Fixed confusing lack of error message when a blocked user - attempts to create an account. Fixed inefficiency of Special:Ipblocklist in - the presence of large numbers of blocks; added indexes and implemented an - indexed pager. -* (bug 6448) Allow filtering of Special:Newpages according to username -* (bug 6618) Improve permissions/error detection in Special:Lockdb -* Quick hack for extension testing: parser test doesn't create new message - cache object. -* (bug 6299) Maintain parser's revision ID across recursive calls to fix - {{REVISIONID}} when Cite extension is used -* (bug 6622) Removed deprecated function Image::newFromTitle -* (bug 6627) Fix regression in Special:Ipblocklist with table prefix -* Removed forced dereferencements (new() returns a reference in PHP5) -* Note about $wgUploadSizeWarning using byte -* (bug 6592) Add most viewed pages summary to Special:Statistics -* Pre-strip characters ignored in IDNs from URLs so they can't be used - to break the blacklists for regular URLs -* Fix regression in blocking of user accounts -* (bug 6635) Fix regression searching for range blocks on Ipblocklist -* Fix regression searching Ipblocklist with ugly URLs -* (bug 6639) Use a consistent default for upload directories -* Preserve entered reason when reporting unconfirmed lock on Special:Lockdb -* (bug 6642) Don't offer to unlock the database when it isn't locked -* cleanupTitles.php changed from --dry-run option to --fix, so default - behavior is now a non-invasive check as with namespaceDupes.php -* (bug 6660) Fix behaviour of EditPage::blockedPage() when the article does - not exist; now doesn't show the source box if the user hasn't provided it - (blocked mid-edit) and the page doesn't exist -* Improve default value of "blockedtext" -* (bug 6680) Added localisation for Dutch bookstore list (nl) -* Renamed maintainace script redundanttrans.php to unusedMessages.php - clearer usage -* Fix regression which allowed some blocked users to create additional accounts -* (bug 6657) Fix Hungarian linktrail -* (bug 6751) Fix preview of blanked section with edit on first preview option -* (bug 5456) Separate MediaWiki:Search into messages for both noun and verb, - introduced 'MediaWiki:Searchbutton' -* Made lines from initialiseMessages() appear as list items during installation -* Moved the bulk of the localisation data from the Language*.php files to the - Messages*.php files. Deleted most of the Languages*.php files. -* Introduced "stub global" framework to provide deferred initialisation of core - modules. -* Removed placeholder values for $wgTitle and $wgArticle, these variables will - now be null during the initialisation process, until they are set by index.php - or another entry point. -* Added DBA cache type, for BDB-style caches. -* Removed custom date format functions, replacing them with a format string in - the style of PHP's date(). Used string identifiers instead of integer - identifiers, in both the language files and user preferences. Migration should - be transparent in most cases. -* Simplified the initialisation API for LoadBalancer objects. -* Removed the broken altencoding feature. -* Moved default user options and toggles from Language to User. Language objects - are still able to define default preference overrides and extra user toggles, - via a slightly different interface. -* Don't include the date option in the parser cache rendering hash unless - $wgUseDynamicDates is enabled. -* Merged LanguageUtf8 with Language. Removed LanguageUtf8.php. -* Removed inclusion of language files from the bottom of Language.php. This is - now consistently done from Language::factory(). -* Add the name of the executing maintenance script to the debug log. Start the - profiler during maintenance scripts. -* Added "serialized" directory, for storing precompiled data in serialized form. -* Fix regression in auto-set NS_PROJECT_TALK namespace -* Fix regression in ordering of namespaces -* (bug 6806, 6030) Added several global JS variables for article path, user name, - page title, etc. -* hooks registered with addOnloadHook are now called at the one of the html body - by all skins. -* Split ajax aided search from core ajax framework. Use wgUseAjax to enable the - framework and wgAjaxSearch to enable the suggest feature for the search box. -* Added experimental installer for extensions. - See maintenance/installExtension.php -* Added Tajic (tg) language file. -* (bug 6903) Added Cantonese localisation (zh-yue) -* Fix regression in Korean and Japanese date formatting (day of week) -* (bug 6919) Add English alias magic words for Tatar (tt) language file. -* (bug 6753) Fixed broken Kazakh linktrail (kk) -* (bug 6700) Added Kazakh language variants to Names.php -* (bug 6827) some i18n specific maintenance scripts fails after merge of localisation-work branch -* Throwed an exception for the deprecated functions OutputPage::sysopRequired and - OutputPage::developerRequired - use OutputPage::permissionRequired instead. -* Removed the deprecated functions User::isSysop, User::isBureaucrat and User::isDeveloper - - use User::isAllowed instead. -* (bug 769) OutputPage::permissionRequired() should suggest groups with the needed permission -* (bug 6971) Fix regression in Special:Export history view -* Revamped Special:Imagelist -* (bug 7000) updated MessagesPl.php -* (bug 6946) Fix unexpected behavior change with GET hits to Special:Export -* (bug 1866) Improve navigation on Special:Listusers; user now a starting - point as with Special:Allpages, rather than a pure limit. -* Clean up tab order on Special:Blockip -* (bug 5969) Clean up tab order on Special:Userlogin forms -* (bug 3512) namespaceDupes now handles spaces and initial caps properly -* (bug 7037) Fix regression in login tab order -* (bug 7031) Report missing email on 'email password' instead of false success -* (bug 7010) Don't send email notifications for watched talk pages when user - has selected to receive only updates for their own talk page -* Added {{CURRENTHOUR}} -* Added [[:Image:Foo.png]] style links to the pagelinks table -* Avoid duplicate revision imports with Special:Import -* (bug 7054) Validate email address before sending email confirmation message -* (bug 7061) Format title on "from (page)" links on Special:Allpages -* (bug 7044) Introduce "padleft" and "padright" colon functions -* Pass page title as parameters to "linkshere" and "nolinkshere" and update - default message text -* Allows to upload from publicy accessible URL. Set $wgAllowCopyUploads = true ; in LocalSettings.php - Limited to $wgMaxUploadSize (default:100MB); URL upload is limited to sysops by default, and displayed as a second line if appropriate -* (bug 832) Return to user page after emailing a user -* (bug 366) Add local-system-timezone equivalents for date/time variables -* (bug 7109) Fix Atom feed version number in header links -* (bug 7075) List registered parser function hooks on Special:Version -* (bug 7059) Introduce "anchorencode" colon function -* Include SVN revision number in {{CURRENTVERSION}} output, where applicable -* Fix bug in wfRunHooks which caused corruption of objects in the hook list -* (bug 4979) Use simplified email addresses when running on Windows -* (bug 4434) Show block log fragment on Special:Blockip -* [[MediaWiki:Disambiguationspage]] may optionally contain wiki links to any number - of disambiguation templates. -* [[Special:Disambiguations]] now shows pages in NS:0 that link to any pages that embed - any of the templates listed at [[MediaWiki:Disambiguationspage]]. -* Fix formatting of titles on Special:Undelete -* (bug 7026) Fix action=raw&templates=expand -* (bug 6976) Add namespace and direction classes to classic skins -* (bug 7144) Don't "return to main" from OutputPage::loginToUse() if the the user can't - read the main page in the first place -* (bug 7188) Fix minor borkage in HTMLForm -* (bug 6675) Replaced message 'watchthis' with new message 'watchthisupload in Special:Upload -* Add a quickie script dumpSisterSites.php for generating a page list in the - format for WSR-1 SisterSites support -* (bug 7223) Monobook.js is used for site content, should not be localized -* Set default disabled values for DjVu render options -* Added Xml::option() for generating <option>s easily -* Localized page numbers in drop-down for DjVu page selection -* Fixed linktrail for vi -* (bug 6893) "Call to a member function exists() on a non-object" on trackback.php with bad input -* (bug 6886) PHP undefined offset on bad input to Special:Revisiondelete -* (bug 6887) PHP error for call to getId() on bad input to Special:Revisiondelete -* (bug 6888) PHP error for call to getTimestamp() on bad input to Special:Revisiondelete -* (bug 7252) Use dvipng support in texvc math rastrization. dvipng is required if texvc is rebuilt. -* (bug 7279) Use wfBaseName in place of basename() in more places -* Clear newtalk marker on diff links with explicit current revision number -* (bug 7064) Replace hard-coded empty message checks with wfEmptyMsg calls -* (bug 6777) Remove some PHP 4 compat cruft -* Add --user, --comment, and --license options to importImages.php -* (bug 6216) The immobile namespace message does not mention the source page -* (bug 7299) Normalize username filter on Special:Newpages -* (bug 7306) RTL text in an LTR wiki breaks appearance of Special:Recentchanges -* (bug 7312) Don't emit SET NAMES utf8 if connection failed -* (bug 7305) Proper compare for bot check on RC notify, should fix overrides - that force edits by non-bot users to bot mode -* Set Vary: Cookie on action=raw generated CSS and JS, to ensure that user - preferences don't get stuck in proxy caches for other people -* (bug 7324) Fix error message for failure of Database::sourceFile() -* (bug 7309) Plurals: use singular form for zero in French and Brazilian Portuguese -* Add page_no_title_convert field to support language variant conversion - for page titles which shouldn't be converted on display/linking -* Lazy extraction of text chunks in Revision objects, may reduce hits to - external storage when actual text content is not used -* Added experimental $wgRevisionCacheExpiry to cache extracted revision text - in $wgMemc, to further reduce hits to external storage. - Set to 0 (disabled) by default. -* Minor changes to the installer. -* Remove ":" for 'youremail' and 'yourrealname' in includes/templates/Userlogin.php - so that ":" could be used in i18n for Special:Preferences (like 'username' and 'uid'). -* Fix layout for Special:Preferences->Date and Time (position for 'timezonetext'). -* Updates to language variant code for Serbian et al -* (bug 6756) Enabling RTL direction for kk-cn -* (bug 6701) Kazakh language variants in MessagesEn.php -* (bug 7335) SVN revision check in Special:Version fails on SVN 1.4 working copy -* (bug 6518) Replaced 'lastmodified' with 'lastmodifiedat' and 'lastmodifiedby' with 'lastmodifiedatby' - with seperated parameters for date and time to allow better localisation. Updated all message files - to display the old format for compatibility. -* (bug 7357) Make supposedly static methods of Skin actually static -* Added info text to Special:Deadendpages and Special:Lonelypages -* Fix regression in cachability of generated CSS and JS for MonoBook skin, - while avoiding clobbering of different users' cached data -* (bug 6849) Block @ from usernames; interferes with multi-database tools and - was meant to be banned years ago... For now existing accounts will not be - prevented fromm login. -* (bug 6092) Introduce magic words {{REVISIONDAY}}, {{REVISIONDAY2}, {{REVISIONMONTH}}, - {{REVISIONYEAR}} and {{REVISIONTIMESTAMP}} -* (bug 7425) Preceeding whitespace in [[...]] breaks subpages -* Try to reconnect after transitory database errors in dumpTextPass.php -* (bug 6023) Fixed mismatch of 0/NULL for wl_notificationtimestamp; now notification - mails are working after 'Mark all pages visited' button on Special:Watchlist is clicked -* Made {{INT:}} a core parser function instead of a special case. The syntax - and behaviour is largely unchanged. -* (bug 7448) Fixing the native name for Ewe (ee) -* (bug 6864) Replace message 'editing' with new message 'editinguser' in Special:Userrights - to allow better localisation -* Add '*-summary' for special pages to MessagesEn.php to allow customizing/translation - directly through Special:Allmessages -* (bug 6130, bug 5818) Replaced message 'go' with the new message 'searcharticle' in skins - to allow better localisation -* Add + to $wgLegalTitleChars by default. Some sites may have occasional - problems with hard-to-reach pages, but it should be less trouble than - "I can't import dumps from Wikipedia" complaints -* (bug 7460) Revert broken patch for bug 7226 which slows down - Special:Allmessages by a factor of 16 -* Committed a bunch of live hacks from Wikimedia servers -* (bug 6889) PHP notices in thumb.php with missing params -* Cleaner error behavior on thumb.php with invalid page selection -* (bug 6617) Validate timestamps on Special:Undelete -* Do fewer unnecessary full writes of user rows; only update user_touched - for watch/unwatch, group membership change, and login operations -* Restructured the languages directory, to avoid problems when people - untar MW 1.8 over the top of a 1.7 installation. -* (bug 6890) SQL query error on bad input to Pager lists - due to negative LIMIT clause, caused by integer wraparound. -* Fixed various bugs related to table prefixes, especially the interaction - between table prefixes and memcached, which was formerly completely broken. -* (bug 7004) PHP iconv() notice on bad password input to Special:Userlogin. -* (bug 6826) Extend pre-save transform context link ("pipe trick") - syntax to pages with commas in title -* Use ImageMagick -thumbnail option instead of -resize to avoid including - excessive metadata in thumbs (requires ImageMagick 6.0.0 or newer). -* (bug 7499) Corrections to Swedish talk namespace names -* (bug 7508) Added option to compress HTML pages by dumpHTML.php -* (bug 7519) Add plural in SpecialWatchlist -* (bug 7459) Magic word variables are always case sensitive -* Replaced {{SERVER}}{{localurl:xxx}} with {{fullurl:xxx}} in localisation files -* Fix regression in Special:Watchlist text header -* (bug 7510) Update article counts etc on undelete -* (bug 7520) Update article counts on XML import -* (bug 7526) Make $wgDefaultUserOptions work again -* (bug 7472) Localize Help namespace for Basque -* (bug 7529) Including a non-existent category in an article places that article in the category -* (bug 4528) Lack of important LaTeX functions stackrel, rightleftharpoon -* (bug 6721) missing symbols ulcorner, urcorner, llcorner, lrcorner, twoheadrightarrow, twoheadleftarrow -* (bug 7367) Hyphens sometimes erroneously appended to equations when not converted to PNG -* Add "title" to the opensearch link to allow automatic adding of the search engine in Firefox 2 -* (bug 7537) Add php5 to $wgFileBlacklist -* (bug 6929) Restore AutoAuthenticate hook +Several configuration options have changed since 1.8: + +=== $wgEnableAPI === + +The experimental machine API interface is now enabled by default, read-only. +You can disable it by setting $wgEnableAPI = false; in LocalSettings.php. + +=== $wgPathInfo === + +The use of PATH_INFO (the text after the script name in 'index.php/Blah') +is controlled by the $wgUsePathInfo setting. This is now explicitly disabled +for CGI, apache2filter, and ISAPI configurations of PHP, for more consistency +with the autodetection from the installer. + +In some rarer configurations you may have to switch $wgUsePathInfo from false +to true or, perhaps, from true to false to make things work properly if bad +PATH_INFO data comes through the server. + +The wiki now tries to detect this condition and should show you an error +message describing what to change instead of sending the browser into an +infinite redirect loop. + +=== $wgScript and other path settings === + +The following configuration variables are now automatically set in Setup.php +if they are not overridden in LocalSettings.php: + +from $wgScriptPath: + + $wgScript + | \- $wgArticlePath + + $wgRedirectScript + + $wgStylePath + + $wgUploadPath + \- $wgLogo + + $wgMathPath + +from $IP: + - $wgStyleDirectory + + $wgUploadDirectory + \- $wgMathDirectory + + $wgTmpDirectory + + $wgReadOnlyFile + + $wgFileCacheDirectory + +Newly generated configuration files will by default include only $wgScriptPath +(hardcoded from the installer) and $IP (detected at runtime). + +Old configuration files which specify all these values explicitly should +continue to work just fine, but if you use the defaults you can remove them +to reduce clutter. + +=== $wgGroupPermissions === + +The sysop group now holds the "autopatrol" and "ipblock-exempt" rights by +default. + +"autopatrol" replaces the preference for marking ones own edits patrolled +by default; users holding this permission will automatically have their +edits patrolled, while others cannot mark their own edits as patrolled +even if they have patrolling rights. + +"ipblock-exempt" excludes the user from IP blocks; accounts which are blocked +explicitly by name will still be blocked, however. This is given to sysops +to minimize annoyance from accidental "collateral damage"; remember that a +sysop will be able to lift the block if they desire. + +The bot group now holds the "nominornewtalk" right. A user with this right +will not trigger new message notifications when making minor edits to user +talk pages. This is meant to minimize annoyance from maintenance bot +processes. + +=== $wgUseWatchlistCache === + +Watchlist caching has been removed. The feature was not maintained, and has +been unnecessary since switching to the 'recentchanges' database table +reduced server pressure for Wikipedia's watchlists. + +=== $wgBreakFrames === + +MediaWiki in the past attempted to detect when it was embedded in a frameset +and "break out" of it, assuming it to be hostile. + +This behavior is now disabled by default, but can be reenabled by setting +$wgBreakFrames to true in LocalSettings.php. + + +== New settings == + +=== $wgVariantArticlePath === + +For languages with script variant support (Chinese, Serbian, and others), +it's possible to use alternate URL paths to select the variant for article +display, setting $wgVariantArticlePath. + +Documentation for this setting would be useful. + +=== $wgMaxMsgCacheEntrySize === + +The message cache can now skip items larger than a given size; this allows +it to better handle the primary caching case when large CSS and JS blobs are +present. + +=== $wgStyleVersion === + +When making significant changes to skin stylesheets and JavaScript files, +you can append a string to this variable to tweak the generated URLs, +forcing newly rendered pages to bring in a fresh version despite server- +or browser-side caching. + +Normally this will be set in the course of MediaWiki development, but +if doing development on a custom skin you may wish to poke it as well. + +=== $wgRCShowChangedSize === + +Special:Recentchanges and Special:Watchlist now show the number of bytes +added or removed to an article to give an idea of the size of the edit. +This information was previously available only in the IRC update feeds. + +To disable this site-wide, set $wgRCShowChangedSize to false. +(Individual users can suppress the data in custom CSS.) + +Adjust $wgRCChangedSizeThreshold to trigger highlighting of particularly +large changes. + +The formatting of the size figure can be adjusted through the +[[MediaWiki:Rc-change-size]] message. + +=== $wgQueryCacheLimit === + +The number of rows stored for "expensive" special pages in miser mode +can now be adjusted up or down from the default 1000. + +=== $wgDisableQueryPageUpdate === + +Individual "expensive" special pages can be skipped in processing by +updateSpecialPages if added to this list. + +=== $wgSorbsUrl === + +The base hostname for the DNS-based proxy blacklist can now be overridden +when $wgEnableSorbs is set, to use a different blacklist instead of SORBS. +The blacklist would need to respond the same was as SORBS; any positive +response will be taken as a proxy. + +=== $wgAjaxWatch === + +Experimental AJAX mode for the watch/unwatch tabs to execute inline. +Does not include the UI messages describing how to reach the watchlist, +so you may not want it on a general-audience site just yet. + +=== $wgParserTestFiles === + +MediaWiki's parser test suite can now be expanded with additional test +files. Custom extensions can add their test files to this array, and +they will be run along with the main tests by maintenance/parserTests.php + + +== Changes since 1.8 == + +* (bug 8200) Make category lists sorted by name when using Postgres. +* (bug 7841) Support 'IGNORE' inserts for Postgres, fixes watchlist + adding problem. +* (bug 6835) Removing the includes/Parser.php::getTemplateArgs() function, + because it seems to be unused. +* (bug 7139) Increasing the visual width of the edit summary field on larger + screen sizes, for the default monobook skin. +* Fix PHP notice and estimates for dumpBackup.php and friends +* Improved register_globals paranoia checks +* (bug 7545) Fix PHP version check on install +* Disable PHP exception backtrace printing unless $wgShowExceptionDetails + is set. Backtraces may contain sensitive information in function call + parameters. +* (bug 6164) Avoid smashing Cite state if message transformation triggers + during bad image list check, by skipping message transformation. + This isn't a good permanent fix. +* (bug 6918) Stopped borders and backgrounds from showing through floated + tables in Monobook +* (bug 6868) Un-hardcode section edit link style +* (bug 3205) Stop right floats from stacking horizontally in non-Monobook skins +* Added global $wgStyleVersion to centralize bumping CSS and JS file versions + for cache-friendly style and script updating +* (bug 7562) Fix non-ASCII namespaces on Windows/XAMPP servers +* Friendlier check for PHP 5 in command-line scripts; it's common for parallel + PHP 4 and 5 installations to interfere on the command-line. +* Fix regression in autoconfirm permission check +* (bug 3015) Add CSS ids to subcategory and page sections on category pages +* (bug 7587) Fix erroneous id for specialpage tab, enabling informative popup +* (bug 7599) Fix thumbnail purging, PHP notices on HTCP image page purge +* (bug 7581) Update language name for cbk-zam +* (bug 7444) Update namespace translations for Telugu (te), kept old values as + alias for compatibility +* (bug 4525) Move section links down visually to same level as headings + (editsection links are now inside the heading elements) +* Workaround for http://bugs.php.net/bug.php?id=31892 , PATH_INFO and hence + URLs of the style /index.php/Main_Page were broken on some CGI installations. +* (bug 7623) Validate custom HTML id's correctly in Monobook interface +* (bug 2241) Fix collision of 'w' and 'd' accesskeys +* (bug 5795) CSS class added to body based on page name for page-specific + styling +* (bug 6276) Stopped search field from getting too large in Cologne Blue +* (bug 7644) User creations that are aborted by hooks shouldn't be counted + against account creations per day limit +* (bug 7636) Show Firefox 2 users correct accesskey prefix +* (bug 6427) Block blocked IPs from using the mail password function + to allow blocking of flooders +* Include common.css from classic-style skins in main HTML with the bump URL +* (bug 7607) Add Karakalpak (kaa) to Names.php and stub message file for linktrail +* (bug 7582) Add 'tog-nolangconversion' to MessagesEn.php. + This key is need for languages with variants (zh, sr, kk) +* (bug 7606) MediaWiki messages for "rss" and "atom" missing +* (bug 7609) Add some more '*-summary' messages to MessagesEn.php with empty + strings to allow better localisation via Special:Allmessages. Mark this new + messages as optional for localisation. +* Fix user_newpass upgrade for prefixed tables (reported by Fyren) +* (bug 7663) Include language variant switcher links on Nostalgia skin +* (bug 6531) Fix PHP fatal error on installation page with bad username input. +* (bug 6977) Remove 404 link for autogenerated database documentation. +* (bug 7369) Allow "Show Changes" without requiring edit token. +* (bug 7687) Fix movetalk box checks itself when confirming a delete and move. +* (bug 7684) Obey watchcreated preference for Special:Upload watch checkbox +* (bug 7686) Include id attribute on delete form confirmation button +* Allow compound interwiki prefixes in $wgImportSources +* (bug 7304) Added redirect table to store redirect targets. +* Added querycachetwo table (similar to querycache but has two titles) +* PageArchive can now return a Revision object for more convenient processing + of deleted revision data +* Added 'UndeleteShowRevision' hook in Special:Undelete +* Error message on attempt to view invalid or missing deleted revisions +* Remove unsightly "_" from namespace in Special:Allpages, Special:Prefixindex +* (bug 3224) Allow minor edits by bots to skip new message notification on + user talk pages. This can be disabled by adjusting the 'nominornewtalk' + permission. Patch by Werdna. +* (bug 7741) MATH: fixed broken syntax of underbrace etc. Fixed arrays +* Fix purging for updated SVG files +* (bug 7745) Add id attribute to search button in Monobook +* (bug 7749) MATH: added some more LaTeX symbols, e.g. parallel, diamond, ast, ... +* (bug 7304) Added code in Article.php to keep redirect table up to date. +* Made special page names case-insensitive and localisable. Care has been taken + to maintain backwards compatibility. +* Used special page subpages in a few more places, instead of query parameters. +* (bug 7758) Added wrapper span to "templates used" explanation to allow CSS + styling (class="mw-templatesUsedExplanation"). +* Added {{#special:}} parser function, to give the local default title for + special pages +* (bug 7766) Remove redundant / from AJAX requests, can break some servers +* Add tab links from extensions to classic-based skins (SkinTemplateTab hook) + Provides better cross-skin compatibility for extensions using the modern + skin hooks, such as Oversight +* Moved variant language links on Cologne Blue and Nostalgia to before the + login/logout link +* Fix for parser tests with MySQL 5 in strict mode +* Added block option "enable autoblocks" +* Amend Special:Ipblocklist to note when a block has autoblock DISABLED. +* (bug 7780) Fix regression in editing redirects +* Add whitespace above "templates included on this page" using CSS, not + hardcoded line break. +* Remove entries from redirect table on article deletion +* (bug 7788) Force section headers in new section links for users who have + 'prompt for blank edit summaries' on. +* (bug 1133) Special:Emailuser: add an option to send yourself a copy of your mail. +* (bug 461) Allow "Categories:" link at bottom of pages to be customized via + pagecategorieslink message. +* Sort the list of skins in "My Preferences" -> Skins by alphabetical order. +* (bug 7785) Postgres compatibility for timestamps in RC feeds +* (bug 7550) Normalize user parameter normally on Special:Log +* (bug 7294) Fix PATH search for diff3 on install +* Various fixes related to the blocking change re: autoblocks. On inserting + an IP block, the ipb_enable_autoblock field is now automagically blanked, + because it doesn't make any sense for an IP. Additionally, IP blocks + without the ipb_enable_autoblock option no longer show up as "autoblock + disabled" on Special:Ipblocklist. +* (bug 7774) MATH: aded more amstex functions +* (bug 1182) MATH: fixed inconsistent rendering of upper case Greek letters in TeX +* Fix regression in streaming page dump generation +* (bug 7801) Add support for parser function hooks in parser tests +* checkUsernames.php now uses wfDebugLog instead of hardcoded path to log +* (bug 7810) Update talk namespaces for Occitan +* Allow case-sensitive URLs to be used for uploading from URLs. +* (bug 1109) Correct fix for compressed 304 responses when additional output + buffers have been installed within the compression handler +* (bug 7819) Move automatic redirect edit summary after pre-save transform + to work properly with subst: fun +* (bug 7826) Fix typos in two English messages. +* (bug 5365) Stop users being prompted to enter an edit summary for null edits, + if they have selected that option in preferences. +* (bug 5936) Show an 'm' to the left of the edit summary on diff pages for minor edits. +* (bug 7820) Improve error reporting for uploads via URL. +* (bug 5149) When autoblocks are enabled, retroactively apply an autoblock to the most + recently used IP of a user when they are blocked. +* Add an index on (rc_user_text,rc_timestamp) on the recentchanges table. This will + make CheckUser.php and the new retroactive autoblock functionality faster. +* Fix regression in Special:Undelete for revisions deleted under MediaWiki 1.4 + with compression or legacy encoding +* (bug 6737) Fixes for MySQL 5 schema in strict mode +* Approximate height for client-side scaling fallback instead of passing -1 + into the HTML output. +* Make the DNSBL to check for proxy blocking configurable via $wgSorbsUrl +* Add experimental recording/reporting mode to parser tests runner, to + compare changes against the previous run. + Additional tables 'testrun' and 'testitem' are in maintenance/testRunner.sql, + source this and pass --record option to parserTests.php +* Make the set of default parser test input files extensible via + $wgParserTestFiles. This can now be appended to by extensions or local + configuration files so that extension or custom tests can be automatically + run along with the main batch. +* Run PHP install version checks on update.php so command-line updaters see + new version requirements +* Do a check for the PHP 5.0.x 64-bit bug, since this is much more disruptive + as of MW 1.8 than it used to be. Install or upgrade now aborts with a + warning and a request to upgrade. +* (bug 6440) Updated indexes to improve backlinking queries (links, templates, images) +* Switched 'anon-only' block mode to default for IP blocks +* (bug 3687, 7892) Add distinct heading for media files in category display, + with count. +* (bug 1578) Add different icons for external links to audio, video, or PDF in + Monobook. +* Made autoblocks block account creation if the user block has that option enabled. +* Add auto-summaries to blankings and large removals without summaries. +* (bug 7811) Allow preview of edit summaries. +* (bug 6839) Wikibits.js minor changes to make JS-lint happier. +* (bug 7932) Make sure that edit toolbar clears floats so it appears correctly. +* (bug 6873) When viewing old revisions, add link to diff to current version. +* (bug 3315) Provide rollback link directly on history page. +* Replace 'old-revision-navigation' message with 'revision-info' and + 'revision-nav' messages, wrapped in divs with appropriate id's. +* (bug 4178) MediaWiki:Common.js will now be included for all users if + $wgUseSiteJs is enabled, in addition to (if applicable) MediaWiki:Monobook.js + and user JS subpages. +* (bug 7918) "Templates used on this page" changes during preview to reflect + any added or removed templates, and works as expected for section edits. +* (bug 7919) "Templates used on this page" is now shown for read-only pages. +* (bug 7688) When viewing diff, section anchors in autosummary jump to section + on current page instead of loading the latest version. +* (bug 7970) Use current connection explicitly on Database::getServerVersion +* (bug 2001) Tables with class="sortable" can now be dynamically sorted via + JavaScript. +* Added autosummary for new pages with 500 or less characters, and refactor + the autosummary code so it's all done in one function. doEdit is getting too + big! +* (bug 7554) The correct MIME type for SVG images is now displayed on the + image page (image/svg+xml, not image/svg). +* (bug 7883) Added autoblock whitelisting feature, using which specific ranges + can be protected from autoblocking. These ranges are specified, in list format, + in the autoblock_whitelist system message. +* Added placeholders for text injection by hooks to EditPage.php +* (bug 8009) Automatic edit summary for redirects is not filled for edits in existing pages +* Installer support for experimental MySQL 4.1/5.0 binary-safe schema +* Use INSERT IGNORE for db-based BagOStuff add/insert, for more memcache-like + behavior when keys already exist on add (instead of dying with an error...) +* Add a hook 'UploadForm:initial' before the upload form is generated, and two + member variable for text injection into the form, which can be filled by the hooks. +* (bug 6295) Add a "revision patching" functionality, where an edit can be undone + (with a functionality similar to diff rev1 rev2 | patch -R rev3 -o rev3). + This is triggered by including &undo=revid in an edit URL. A link to a URL + that will undo a given edit is shown on NEW revision headers on diff pages. + The link leads to a "Show Changes" page showing what will be done to undo the + edit. +* Fix display of link in "already rolled back" message for image/category pages +* (bug 6016) Left-aligned images should stack vertically, like right-aligned + images, not horizontally. +* Patch from LeonWP: added UploadForm:BeforeProcessing hook in SpecialUpload.php +* Add AuthPluginSetup hook to override $wgAuth after configuration +* Fix regression in authentication hook auto-creation on login +* (bug 8110) Allow spaces in ISBNs +* (bug 8024) Introduce "send me copies of emails I send to others" preference +* Added 'EditPage::attemptSave' hook before an article is saved. +* (bug 8083) Applied patch for sk localisation +* Add a backslash character to the edit token, to prevent edits via certain + broken proxies that mangle such characters in form submissions +* (bug 7461) Allow overwriting pages using importTextFile.php +* (bug 7946) importTextFile.php doesn't perform pre-save transform +* (bug 8117) {{REVISIONTIMESTAMP}} showed weird default if $wgLocalTZoffset set; + now uses current time for previews and if timestamp can't be loaded from DB +* {{REVISIONTIMESTAMP}} now uses site local timezone instead of user timezone + to ensure consistent behavior +* {{REVISIONTIMESTAMP}} and friends should now work on non-MySQL backends +* (bug 7671) Observe canonical media namespace prefix in Linker::formatComment +* Added js variable wgCurRevisionId to the output +* (bug 8141) Cleanup of Parser::doTableStuff, patch by AzaTht +* (bug 8042) Make miser mode caching limits settable via $wgQueryCacheLimit + instead of hardcoding to 1000 +* Enable QueryPage classes to override list formatting +* (bug 5485) Show number of intervening revisions in diff view +* (bug 8100) Fix XHTML validity in Taiwanese localization +* Added redirect to section feature. Use it wisely. +* Added a configuration variable allowing the "break out of framesets" feature + to be switched on and off ($wgBreakFrames). Off by default. +* Allow Xml::check() $attribs parameter to override 'value' attribute +* DB schema change: added two columns (rc_old_len and rc_new_len) to the recentchanges table to store + the text lengths before and after the edit +* (bug 1085) Made Special:Recentchanges show the character difference between the changed revisions +* Removed a redundant <strong> tag from diff pages that was causing display issues for some users +* (bug 8203) The keyboard shortcut for "log out" was removed, because users were pressing it + when they intended to press the shortcut for "preview". +* (bug 8148) Handle non-removable output buffers gracefully when cleaning + buffers for HTTP 304 responses, StreamFile, and Special:Export. + Duplicated code merged into wfResetOutputBuffers() and wfClearOutputBuffers() +* Special:AllPages : 'next page' link now point to the first title of the next + chunk instead of pointing to the last title of current chunk. +* (bug 4673) Special:AllPages : add a 'previous' link (new message 'prevpage') +* (bug 8121) wfRandom() was not between 0 and 1 +* Add static method Parser::createAssocArgs($args), so parser functions can + use the same code to parse arguments as the templates do. +* Change behavior of logins using the temporary e-mailed password (as stored + in user_newpassword hash field). Instead of just logging in silently and + leaving the previous user_password field in place indefinitely, the user + is now prompted to set a new password. + + The password-changing form is at Special:Resetpass; currently it's only + usable for changing from the temporary password during login, but it + could perhaps be generalized, replacing the subform in preferences. + + Once the new password is set successfully, the temporary password is wiped + so it cannot be used to login a second time, and the login process + is completed. +* Suppress 'mail new password' button on login form if $wgAuth forbids + changing user passwords; it wouldn't work very well... +* Consolidate password length checks and $wgAuth manipulation into + User::setPassword() to avoid duplicate code in different places + that set passwords. +* User::setPassword() now throws PasswordError exceptions if the password + is illegal or cannot be set via $wgAuth. These can be caught and a human- + readable error message displayed by UI code. +* Added Title::isSubpage() +* (bug 8241) Don't consider user pages of User:Foo.css to be CSS subpages +* Set an explicit class on framed thumbnail inner divs and images, changed some + CSS to use these instead of using descendent selectors. +* Accept null parameter to User::setPassword() as indicating the password + field should be cleared to an unusable state. Login will only be possible + after the password is reset, for instance by e-mail. +* (bug 6394) Invalidate the password set for "by e-mail" account creations + to avoid accidental empty password creations. +* Made the show change size function work on page moves, page creations, and + log entries. Also fixed it in the javascript recentchanges. +* (bug 8239) correctly get 50 new contributions when clicking '(50 next)' +* (bug 2259) Fix old regression where e-mail addresses were no longer + confirmed on login with mailed password. +* Add a notification about the confirmation mail sent during account + creation, so people don't immediately go off to request a second one. +* Add a warning on Special:Confirmemail if a code was already sent and has + not yet expired. +* Add user_editcount field to provide data for heuristics on account use. + Incremented on edit, with lazy initialization from past revision data. + Can batch-initialize with maintenance/initEditCount.php (not yet friendly + to replication environments, this will do all accounts in one query). +* Allow raw SQL subsections in Database::update() SET portion as well as + for WHERE portion. Handy for increments and such. +* User::getOption now accept a default value to override default user values + this makes it consistent with WebRequest::get* methods. Corrected code in + various places accordingly. +* (bug 8264) Fix JavaScript global vars for XHTML mode +* Make $wgSiteNotice value wikitext again, for consistency with editable + MediaWiki:Sitenotice and MediaWiki:Anonnotice. +* (bug 8044) When redirecting from the canonical name of the special page + to the localised one, parameters/subpages are omitted +* (bug 8164) Special:Booksources should use GET for form submission +* Rewrite Special:Booksources to clean up interface and remove redundant code +* (bug 7925) Change Special:Allmessages message name filter javascript to be + a bit more responsive and easier on the CPU +* (bug 4488) Support watching pages on deletion; introduces new user preference +* Minor restructuring of Special:Preferences; "watch pages I edit" and "watch + pages I create" options now accessible under "Watchlist" options +* (bug 8153) <nowiki> doesn't work in site notice +* (bug 6690) wfMsgNoTrans() transforms messages +* (bug 8274) Wrap edit tools in a <div> with a specified class +* Detect PHP 5.0.x 64-bit bug and abort in WebStart.php; too many things break + mysteriously otherwise (detection code copied from install-utils.inc) +* (bug 8295) Change handling of <center> tags in doBlockLevels() to match that + of <div> +* (bug 8110) Make magic ISBN linking stricter: only match ten-digit sequences + (plus optional ISBN-13 prefix) with no immediately following alphanumeric + character, disallow multiple consecutive internal redirects +* (bug 2785) Accept optional colon prefix in links when formatting comments +* Don't show "you can view and copy the source of this page" message for + pages which don't exist +* (bug 8310) Blank line added to top of 'post' when page is blank +* (bug 8109) Template parameters ignored in "recentchangestext" +* Gracefully skip redirect-to-fragment on WebKit versions less than 420; + it messes up on current versions of Safari but is ok in the latest + nightlies. Checking the version number will allow it to automatically + work when new releases of Safari appear. +* Fix regression in thumb styles; size and padding didn't match with + new arrangement. +* (bug 8333) Fix quick user data update on login password change on + replication database setups. User data is now pulled from master + instead of slave in User::loadFromDatabase, ensuring that it is + fresh and accurate when read and then saved back into cache. + This was breaking with the Special:Rename operation which + automatically logs the user in with the new password after changing + it; pulling from slave meant the record was often not the updated + one. +* (bug 8335) Set image width to the first valid parameter found. +* (bug 8350) Fix watchlist viewing bug when using Postgres. +* (bug 6603) When warning about invalid file extensions, output the bit + of the extension we actually checked +* (bug 7669) Drop defaults on BLOB/TEXT columns for better compatibility + with MySQL's strict mode, often enabled by the Windows installer. + The defaults are ignored anyway when strict mode is off... +* (bug 7685) Use explicit values for ar_text and ar_flags when deleting, + for better compatibility with MySQL's strict mode +* Update default interwiki values to reflect changed location of ursine: +* (bug 5411) Remove autopatrol preference +* Users who have the "autopatrol" permission will have their edits marked as + patrolled automatically +* Users who do not have the "autopatrol" permission will no longer be able + to mark their own edits as patrolled +* Introduce 'PingLimiter' hook; see docs/hooks.txt for more information +* (bug 532) Tweaked alt text for some interface messages +* (bug 8231) Gave useful alt text to the main <img> on image pages +* (bug 371) Remove alt text for "Enlarge" icon on thumbnails +* Initialize user_editcount to 0 instead of NULL for newly created accounts +* (bug 3696) Strip LRM and RLM characters from titles to work around the + problem some people have where titles cut-and-pasted from lists include + the bidi override characters appended to the lists. + A more thorough blacklist for forbidden and translatable characters would + be wise, though, as might a cleaner method for the lists in the first place. +* Fix regression in email password resets on read-restricted sites +* Set tabindex on fields in deletion form so you don't have to tab through + the links in the sitenotice +* (bug 8271) Show full time and date on viewer for individual deleted + revisions +* (bug 8214) Output file size limit and actual file size in appropriate units + on Special:Upload +* (bug 8016) Purge objectcache table during upgrade processes - use the --nopurge + option to prevent this when running maintenance/update.php +* (bug 7612) Remove superfluous link to Special:Categories from result items + on Special:Mostcategories +* {{PLURAL:}} now handles formatted numbers correctly +* (bug 8331) Added the change size value to watchlists; therefore made + watchlists use RecentChange::newFromRow() instead of newFromCurRow() +* (bug 8351) Fix undo for simple reverts +* (bug 6856) User::clearNotification() does not respect read-only mode +* (bug 6853) Use a checkbox on the installer form to indicate that a superuser + account should be used; this is clearer than the old check which relied on + the password never being an obscure value +* Remove old unused watchlist cache, which was a leftover from the old schema + where watchlists were more expensive to generate +* Minor cosmetic changes to Special:Userrights +* Added wgCanonicalSpecialPageName to JavaScript variables +* Fix image deleting when using Postgres. +* Output both source and destination titles in maintenance/moveBatch.php +* Added basic parser tests for language variants +* Enable selflinks and categories to be written in some of the language variants +* Prevent conversion of JavaScript code in language variants +* Output software version number in maintenance/parserTests.php +* (bug 7169) Use Ajax to watch/unwatch articles if enabled +* Make variant table caching a little more robust, using main language code + in cache key. Probably this is still a bit wonky, though. Was breaking + parser tests when Chinese tables were getting loaded into Serbian code. +* (bug 8380) Be nicer about blank lines in deleteBatch.php +* (bug 8401) Fix regression in SORBS lookup for some DNS setups +* Use raw file descriptor in posix_isatty() check to avoid warning on + Linux systems with at least some versions of PHP +* (bug 5908) Allow overriding the default category sort key for all items on + a page using {{DEFAULTSORT}} +* (bug 6449) Throw a more definitive error message when installation fails + due to an invalid database name +* (bug 5827) Use full text for option link labels on Special:Watchlist +* (bug 8018) Allow hiding minor edits from the watchlist +* (bug 8427) MonoBook RTL IE 7.0 tweaks failed when sidebar's navigation + section is renamed; no longer relies on first section name +* Stabilize client-side table sorting even if the underlying Javascript sort() + implementation is unstable +* Add hook for extensions to add user information to the panel in preferences, + next to the user name and ID. +* (bug 8392) Display protection status of transcluded pages in the edit page + template list. Patch by Fyren, with i18n naming tweak. +* Fix for interwiki transclusion where target wiki uses query string for title +* Resolve namespaces on interwiki Title objects using canonical namespace names + if possible (should not happen, though, outside interwiki transclusion... and + maybe not even then, but it does) +* (bug 8447) Fix SQL typo breaking non-default $wgHitcounterUpdateFreq +* Do not allow previews of deleted images to be cached +* Add global variable $wgDefaultLanguageVariant used to set the default language + variant of a wiki to something different than the main language code +* Add 'variant' option to parserTests - runs test with the given variant as + preferred, utilize it for more parser tests of language variants code +* (bug 6503) Fix bug that stopped certain irrelevant links from being hidden + for printing +* Avoid PHP warning in Creative Commons metadata when a creative commons + license is not actually set up +* (bug 8463) Don't print external link icons for Monobook +* (bug 8461) Support watching pages on move +* (bug 8041) Work around bug with debug_backtrace when Zend Optimizer is + loaded by skipping the function. Use wfDebugBacktrace() wrapper function. +* Reduce config file clutter by setting various script and upload paths + based on $IP or $wgScriptPath in Setup.php. They can still be explicitly + overridden in LocalSettings.php if desired... +* Attempt to detect redirect loops for the canonical title redirect, and + give some hints to the poor confused administrator. +* Introduce new flag 'R' - raw output for language variant escape tags +* Advise users when updates for a query page have been disabled using + $wgDisableQueryPageUpdate +* (bug 8413) Improve comments for $wgNamespaceRobotPolicies +* (bug 8330) Show "bytes" suffix on recent changes diff counter + optionally... if set in rc-changes-size message (default empty for now) +* (bug 8489) Support basic links in <gallery> caption attribute +* (bug 8485) Correct Lingala number formatting +* The MediaWiki namespace is no longer pre-filled with default messages on + install. All default messages will be removed from the MediaWiki namespace + on upgrade. +* Recentchanges RSS/Atom feeds now use a separate message for the description + to avoid cluttering it with useless wiki formatting +* (bug 8417) Handle EXIF unknown dates +* (bug 8372) Return nothing on empty <math> tags. +* New maintenance script to show the cached statistics : showStats.php. +* Count deleted edits when regenerating total edits in maintenance/initStats.php +* (bug 3706) Allow users to be exempted from IP blocks. The ipblock-exempt permission + key has been added to enable this behaviour, by default assigned to sysops. +* (bug 7948) importDump.php now warn that Recentchanges need to be rebuild. +* (bug 7667) allow XHTML namespaces customization +* (bug 8531) Correct local name of Lingála (patch by Raymond) +* Fix regression with default lock file and cache directories; threw visible + warning with open_basedir == Languages updated == -* Albanian (sq) -* Bashkir (ba) -* Bavarian (bar) stub file -* Belarusian (be) -* Bishnupriya (bpy) stub file -* Brazilian Portuguese (pt-br) +* Basque (eu) +* Bishnupriya Manipuri (bpy) * Cantonese (zh-yue) -* Catalan (ca) -* Czech (cs) -* Dutch (nl) -* English (en) * Finnish (fi) -* French (fr) -* Georgian (ka) +* Frisian (fy) * German (de) * Hebrew (he) -* Hungarian (hu) * Indonesian (id) +* Italian (it) * Japanese (ja) -* Korean (ko) +* Kazakh (kk) +* Kongo (kg) * Latin (la) -* Lojban (jbo) -* Macedonian (mk) -* Mazandarani (mzn) +* Limburgish (li) +* Lingala (ln) +* Lithuanian (lt) +* Maltese (mt) +* Maori (mi) +* Norwegian (no) +* Occitan (oc) +* Old Church Slavonic (cu) * Polish (pl) * Portuguese (pt) -* Ripuarian (ksh) -* Romani (rmy) +* Ripurian (ksh) * Russian (ru) * Slovak (sk) -* Spanish (es) -* Tajic (tg) -* Tatar (tt) -* Telugu (te) -* Uzbek (uz) -* Yiddish (yi) +* Swedish (sv) +* Taiwanese/Holo: (bug 8217) changed language code to nan (from zh-min-nan) + due to http://www.sil.org/iso639-3/codes.asp?order=639_3&letter=n +* Upper Sorbian (hsb) +* Vietnamese (vi) == Compatibility == -MediaWiki 1.8 requires PHP 5 (5.1 recommended). PHP 4 is no longer supported. +MediaWiki 1.9 requires PHP 5 (5.1 recommended). PHP 4 is no longer supported. PHP 5.0.x fails on 64-bit systems due to serious bugs with array processing: http://bugs.php.net/bug.php?id=34879 @@ -429,7 +808,7 @@ cases, but this is not recommended on live sites. (This must be set for MathML to display properly in Mozilla.) -For notes on 1.5.x and older releases, see HISTORY. +For notes on 1.8.x and older releases, see HISTORY. === Online documentation === @@ -446,11 +825,11 @@ License: A MediaWiki-l mailing list has been set up distinct from the Wikipedia wikitech-l list: - http://mail.wikimedia.org/mailman/listinfo/mediawiki-l + http://lists.wikimedia.org/mailman/listinfo/mediawiki-l A low-traffic announcements-only list is also available: - http://mail.wikimedia.org/mailman/listinfo/mediawiki-announce + http://lists.wikimedia.org/mailman/listinfo/mediawiki-announce It's highly recommended that you sign up for one of these lists if you're going to run a public MediaWiki, so you can be notified of security fixes. @@ -1,18 +1,17 @@ This file provides an overview of the MediaWiki upgrade process. For help with specific problems, check -* the documentation at http://meta.wikimedia.org * the documentation at http://www.mediawiki.org * the mediawiki-l mailing list archive at - http://mail.wikipedia.org/pipermail/mediawiki-l + http://lists.wikimedia.org/pipermail/mediawiki-l/ * the bug tracker at http://bugzilla.wikimedia.org for information and workarounds to common issues. == Overview == -Documentation on upgrading to 1.7 can also be found at -http://www.mediawiki.org/wiki/Manual:Upgrading_to_1.7. +Comprehensive documentation on upgrading to the latest version of the software +is available at http://www.mediawiki.org/wiki/Manual:Upgrading_MediaWiki. === Consult the release notes === @@ -73,13 +72,30 @@ procedure, and especially after upgrading; check that page views and edits work normally and that special pages continue to function, etc. and correct errors and quirks which reveal themselves. +== Upgrading from 1.8 wikis == + +MediaWiki 1.9 and later no longer keep default localized message text +in the database; 'MediaWiki:'-namespace pages that do not exist in the +database are simply transparently filled-in on demand. + +The upgrade process will delete any 'MediaWiki:' pages which are left +in the default state (last edited by 'MediaWiki default'). This may +take a few moments, similar to the old initial setup. + +Note that the large number of deletions may cause older edits to expire +from the list on Special:Recentchanges, although the deletions themselves +will be hidden by default. (Click "show bot edits" to list them.) + + +See RELEASE-NOTES for more details about new and changed options. + + == Upgrading from 1.7 wikis == $wgDefaultUserOptions now contains all the defaults, not only overrides. If you're setting this as a complete array(), you may need to change it to set only specific items as recommended in DefaultSettings.php. - == Upgrading from 1.6 wikis == $wgLocalTZoffset was in hours, it is now using minutes. @@ -110,7 +126,7 @@ but note that the old directory hashes will no longer be valid, so you will also have to move them to new destinations. Message changes: -* A number of additional UI messages have been chagned from HTML to +* A number of additional UI messages have been changed from HTML to wikitext, and will need to be manually fixed if customized. === Configuration changes from 1.4.x: === @@ -189,8 +205,7 @@ http://dev.mysql.com/doc/mysql/en/mysqldump.html WARNING: If using MySQL 4.1.x, mysqldump's charset conversion may in some cases damage data in your wiki. If necessary, set the charset -option to 'latin1' to avoid the conversion. Fore more info see: -http://mail.wikipedia.org/pipermail/wikitech-l/2004-November/026359.html +option to 'latin1' to avoid the conversion. For general help on pg_dump: http://www.postgresql.org/docs/current/static/app-pgdump.html @@ -25,70 +25,6 @@ // Initialise common code require (dirname(__FILE__) . '/includes/WebStart.php'); -/** - * When no format parameter is given, this format will be used - */ -define('API_DEFAULT_FORMAT', 'xmlfm'); - -/** - * Location of all api-related files (must end with a slash '/') - */ -define('API_DIR', 'includes/api/'); - -/** - * List of classes and containing files. - */ -$wgApiAutoloadClasses = array ( - - 'ApiMain' => API_DIR . 'ApiMain.php', - - // Utility classes - 'ApiBase' => API_DIR . 'ApiBase.php', - 'ApiQueryBase' => API_DIR . 'ApiQueryBase.php', - 'ApiResult' => API_DIR . 'ApiResult.php', - 'ApiPageSet' => API_DIR . 'ApiPageSet.php', - - // Formats - 'ApiFormatBase' => API_DIR . 'ApiFormatBase.php', - 'ApiFormatYaml' => API_DIR . 'ApiFormatYaml.php', - 'ApiFormatXml' => API_DIR . 'ApiFormatXml.php', - 'ApiFormatJson' => API_DIR . 'ApiFormatJson.php', - - // Modules (action=...) - should match the $apiModules list - 'ApiHelp' => API_DIR . 'ApiHelp.php', - 'ApiLogin' => API_DIR . 'ApiLogin.php', - 'ApiQuery' => API_DIR . 'ApiQuery.php', - - // Query items (meta/prop/list=...) - 'ApiQuerySiteinfo' => API_DIR . 'ApiQuerySiteinfo.php', - 'ApiQueryInfo' => API_DIR . 'ApiQueryInfo.php', - 'ApiQueryRevisions' => API_DIR . 'ApiQueryRevisions.php', - 'ApiQueryAllpages' => API_DIR . 'ApiQueryAllpages.php' -); - -/** - * List of available modules: action name => module class - * The class must also be listed in the $wgApiAutoloadClasses array. - */ -$wgApiModules = array ( - 'help' => 'ApiHelp', - 'login' => 'ApiLogin', - 'query' => 'ApiQuery' -); - -/** - * List of available formats: format name => format class - * The class must also be listed in the $wgApiAutoloadClasses array. - */ -$wgApiFormats = array ( - 'json' => 'ApiFormatJson', - 'jsonfm' => 'ApiFormatJson', - 'xml' => 'ApiFormatXml', - 'xmlfm' => 'ApiFormatXml', - 'yaml' => 'ApiFormatYaml', - 'yamlfm' => 'ApiFormatYaml' -); - wfProfileIn('api.php'); // Verify that the API has not been disabled @@ -98,9 +34,7 @@ if (!$wgEnableAPI) { die(-1); } -$wgAutoloadClasses = array_merge($wgAutoloadClasses, $wgApiAutoloadClasses); - -$processor = new ApiMain($wgRequestTime, $wgApiModules, $wgApiFormats, $wgEnableWriteAPI); +$processor = new ApiMain($wgRequest, $wgEnableWriteAPI); $processor->execute(); wfProfileOut('api.php'); diff --git a/config/index.php b/config/index.php index 5443614e..53e77d44 100644 --- a/config/index.php +++ b/config/index.php @@ -343,22 +343,11 @@ if( ini_get( "safe_mode" ) ) { } $sapi = php_sapi_name(); -$conf->prettyURLs = true; print "<li>PHP server API is $sapi; "; -switch( $sapi ) { -case "apache": -case "apache2handler": +if( $wgUsePathInfo ) { print "ok, using pretty URLs (<tt>index.php/Page_Title</tt>)"; - break; -default: - print "unknown; "; -case "cgi": -case "cgi-fcgi": -case "apache2filter": -case "isapi": +} else { print "using ugly URLs (<tt>index.php?title=Page_Title</tt>)"; - $conf->prettyURLs = false; - break; } print "</li>\n"; @@ -431,7 +420,14 @@ if( !$conf->turck && !$conf->eaccel && !$conf->apc ) { } $conf->diff3 = false; -$diff3locations = array( "/usr/bin", "/usr/local/bin", "/opt/csw/bin", "/usr/gnu/bin", "/usr/sfw/bin" ) + explode( $sep, getenv( "PATH" ) ); +$diff3locations = array_merge( + array( + "/usr/bin", + "/usr/local/bin", + "/opt/csw/bin", + "/usr/gnu/bin", + "/usr/sfw/bin" ), + explode( $sep, getenv( "PATH" ) ) ); $diff3names = array( "gdiff3", "diff3", "diff3.exe" ); $diff3versioninfo = array( '$1 --version 2>&1', 'diff3 (GNU diffutils)' ); @@ -477,7 +473,14 @@ $conf->UseImageResize = $conf->HaveGD || $conf->ImageMagick; $conf->IP = dirname( dirname( __FILE__ ) ); print "<li>Installation directory: <tt>" . htmlspecialchars( $conf->IP ) . "</tt></li>\n"; -$conf->ScriptPath = preg_replace( '{^(.*)/config.*$}', '$1', $_SERVER["PHP_SELF"] ); # was SCRIPT_NAME + +// PHP_SELF isn't available sometimes, such as when PHP is CGI but +// cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME +// to get the path to the current script... hopefully it's reliable. SIGH +$path = ($_SERVER["PHP_SELF"] === '') + ? $_SERVER["SCRIPT_NAME"] + : $_SERVER["PHP_SELF"]; +$conf->ScriptPath = preg_replace( '{^(.*)/config.*$}', '$1', $path ); print "<li>Script URI path: <tt>" . htmlspecialchars( $conf->ScriptPath ) . "</tt></li>\n"; print "<li style='font-weight:bold;color:green;font-size:110%'>Environment checked. You can install MediaWiki.</li>\n"; @@ -501,12 +504,16 @@ print "<li style='font-weight:bold;color:green;font-size:110%'>Environment check $conf->SysopPass = importPost( "SysopPass" ); $conf->SysopPass2 = importPost( "SysopPass2" ); $conf->RootUser = importPost( "RootUser", "root" ); - $conf->RootPW = importPost( "RootPW", "-" ); + $conf->RootPW = importPost( "RootPW", "" ); + $useRoot = importCheck( 'useroot', false ); ## MySQL specific: - $conf->DBprefix = importPost( "DBprefix" ); - $conf->DBmysql5 = (importPost( "DBmysql5" ) == "true") ? "true" : "false"; - $conf->LanguageCode = importPost( "LanguageCode", "en" ); + $conf->DBprefix = importPost( "DBprefix" ); + $conf->DBschema = importPost( "DBschema", "mysql4" ); + $conf->DBmysql5 = ($conf->DBschema == "mysql5" || + $conf->DBschema == "mysql5-binary") + ? "true" : "false"; + $conf->LanguageCode = importPost( "LanguageCode", "en" ); ## Postgres specific: $conf->DBport = importPost( "DBport", "5432" ); @@ -584,12 +591,22 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { /* Load up the settings and get installin' */ $local = writeLocalSettings( $conf ); + echo "<li style=\"list-style: none\">\n"; echo "<p><b>Generating configuration file...</b></p>\n"; - // for debugging: // echo "<pre>" . htmlspecialchars( $local ) . "</pre>\n"; - + echo "</li>\n"; + $wgCommandLineMode = false; chdir( ".." ); - eval($local); + $ok = eval( $local ); + if( $ok === false ) { + dieout( "Errors in generated configuration; " . + "most likely due to a bug in the installer... " . + "Config file was: " . + "<pre>" . + htmlspecialchars( $local ) . + "</pre>" . + "</ul>" ); + } $conf->DBtypename = ''; foreach (array_keys($ourdb) as $db) { if ($conf->DBtype === $db) @@ -623,8 +640,6 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { require_once( "includes/Setup.php" ); chdir( "config" ); - require_once( "maintenance/InitialiseMessages.inc" ); - $wgTitle = Title::newFromText( "Installation script" ); error_reporting( E_ALL ); print "<li>Loading class: $dbclass"; @@ -641,16 +656,13 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { $ok = true; # Let's be optimistic # Decide if we're going to use the superuser or the regular database user - if( $conf->RootPW == '-' ) { - # Regular user - $conf->Root = false; - $db_user = $wgDBuser; - $db_pass = $wgDBpassword; - } else { - # Superuser - $conf->Root = true; + $conf->Root = $useRoot; + if( $conf->Root ) { $db_user = $conf->RootUser; $db_pass = $conf->RootPW; + } else { + $db_user = $wgDBuser; + $db_pass = $wgDBpassword; } # Attempt to connect @@ -701,7 +713,7 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { error_reporting( E_ALL ); $wgSuperUser = ''; ## Possible connect as a superuser - if( $conf->RootPW != '-' and strlen($conf->RootPW)) { + if( $conf->Root ) { $wgDBsuperuser = $conf->RootUser; echo( "<li>Attempting to connect to database \"postgres\" as superuser \"$wgDBsuperuser\"..." ); $wgDatabase = $dbc->newFromParams($wgDBserver, $wgDBsuperuser, $conf->RootPW, "postgres", 1); @@ -754,11 +766,16 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { print "<li>Database <tt>" . htmlspecialchars( $wgDBname ) . "</tt> exists</li>\n"; } else { $err = mysql_errno(); - if ( $err != 1049 ) { - print "<ul><li>Error selecting database $wgDBname: $err " . - htmlspecialchars( mysql_error() ) . "</li></ul>"; + $databaseSafe = htmlspecialchars( $wgDBname ); + if( $err == 1102 /* Invalid database name */ ) { + print "<ul><li><strong>{$databaseSafe}</strong> is not a valid database name.</li></ul>"; + continue; + } elseif( $err != 1049 /* Database doesn't exist */ ) { + print "<ul><li>Error selecting database <strong>{$databaseSafe}</strong>: {$err} "; + print htmlspecialchars( mysql_error() ) . "</li></ul>"; continue; } + print "<li>Attempting to create database...</li>"; $res = $wgDatabase->query( "CREATE DATABASE `$wgDBname`" ); if( !$res ) { print "<li>Couldn't create database <tt>" . @@ -806,12 +823,21 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { # FIXME: Check for errors print "<li>Creating tables..."; if ($conf->DBtype == 'mysql') { - if( $wgDBmysql5 ) { - print " using MySQL 5 table defs..."; - dbsource( "../maintenance/mysql5/tables.sql", $wgDatabase ); - } else { + switch( $conf->DBschema ) { + case "mysql4": print " using MySQL 4 table defs..."; dbsource( "../maintenance/tables.sql", $wgDatabase ); + break; + case "mysql5": + print " using MySQL 5 UTF-8 table defs..."; + dbsource( "../maintenance/mysql5/tables.sql", $wgDatabase ); + break; + case "mysql5-binary": + print " using MySQL 5 binary table defs..."; + dbsource( "../maintenance/mysql5/tables-binary.sql", $wgDatabase ); + break; + default: + dieout( " <b>invalid schema selection!</b></li>" ); } dbsource( "../maintenance/interwiki.sql", $wgDatabase ); } else if ($conf->DBtype == 'postgres') { @@ -824,7 +850,7 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { print " done.</li>\n"; - print "<li>Initializing data..."; + print "<li>Initializing data...</li>\n"; $wgDatabase->insert( 'site_stats', array ( 'ss_row_id' => 1, 'ss_total_views' => 0, @@ -838,18 +864,21 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { if( $wgDatabase2->isOpen() ) { # Nope, just close the test connection and continue $wgDatabase2->close(); - echo( "<li>User $wgDBuser exists. Skipping grants.</li>" ); + echo( "<li>User $wgDBuser exists. Skipping grants.</li>\n" ); } else { # Yes, so run the grants echo( "<li>Granting user permissions to $wgDBuser on $wgDBname..." ); dbsource( "../maintenance/users.sql", $wgDatabase ); - echo( "success.</li>" ); + echo( "success.</li>\n" ); } } if( $conf->SysopName ) { $u = User::newFromName( $conf->getSysopName() ); - if ( 0 == $u->idForName() ) { + if ( !$u ) { + print "<li><strong class=\"error\">Warning:</strong> Skipped sysop account creation - invalid username!</li>\n"; + } + else if ( 0 == $u->idForName() ) { $u->addToDatabase(); $u->setPassword( $conf->getSysopPass() ); $u->saveSettings(); @@ -878,11 +907,10 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { ) ); $revid = $revision->insertOn( $wgDatabase ); $article->updateRevisionOn( $wgDatabase, $revision ); - - initialiseMessages( false, false, 'printListItem' ); } /* Write out the config file now that all is well */ + print "<li style=\"list-style: none\">\n"; print "<p>Creating LocalSettings.php...</p>\n\n"; $localSettings = "<" . "?php$endl$local$endl?" . ">\r\n"; // Fix up a common line-ending problem (due to CVS on Windows) @@ -902,6 +930,7 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) { die("<p class='error'>An error occured while writing the config/LocalSettings.php file. Check user rights and disk space then try again.</p>\n"); } + print "</li>\n"; } while( false ); } @@ -958,7 +987,7 @@ if( count( $errs ) ) { </select> </div> <p class="config-desc"> - Select the language for your wiki's interface. Some localizations aren't fully complete. Unicode (UTF-8) used for all localizations. + Select the language for your wiki's interface. Some localizations aren't fully complete. Unicode (UTF-8) is used for all localizations. </p> <div class="config-input"> @@ -977,7 +1006,7 @@ if( count( $errs ) ) { ?> <?php if( $conf->License == "cc" ) { ?> <ul> - <li><?php aField( $conf, "RightsIcon", "<img src=\"" . htmlspecialchars( $conf->RightsIcon ) . "\" alt='icon' />", "hidden" ); ?></li> + <li><?php aField( $conf, "RightsIcon", "<img src=\"" . htmlspecialchars( $conf->RightsIcon ) . "\" alt='(Creative Commons icon)' />", "hidden" ); ?></li> <li><?php aField( $conf, "RightsText", htmlspecialchars( $conf->RightsText ), "hidden" ); ?></li> <li><?php aField( $conf, "RightsCode", "code: " . htmlspecialchars( $conf->RightsCode ), "hidden" ); ?></li> <li><?php aField( $conf, "RightsUrl", "<a href=\"" . htmlspecialchars( $conf->RightsUrl ) . "\">" . htmlspecialchars( $conf->RightsUrl ) . "</a>", "hidden" ); ?></li> @@ -1132,8 +1161,13 @@ if( count( $errs ) ) { </p> <div class="config-input"> + <label class="column">Superuser account:</label> + <input type="checkbox" name="useroot" id="useroot" <?php if( $useRoot ) { ?>checked="checked" <?php } ?>/> + <label for="useroot">Use superuser account</label> + </div> + <div class="config-input"> <?php - aField( $conf, "RootUser", "Superuser account:", "superuser" ); + aField( $conf, "RootUser", "Superuser name:", "superuser" ); ?> </div> <div class="config-input"> @@ -1144,8 +1178,8 @@ if( count( $errs ) ) { <p class="config-desc"> If the database user specified above does not exist, or does not have access to create - the database (if needed) or tables within it, please provide details of a superuser account, - such as <strong>root</strong>, which does. Leave the password set to <strong>-</strong> if this is not needed. + the database (if needed) or tables within it, please check the box and provide details + of a superuser account, such as <strong>root</strong>, which does. </p> <?php database_switcher('mysql'); ?> @@ -1163,8 +1197,9 @@ if( count( $errs ) ) { <div class="config-input"><label class="column">Database charset</label> <div>Select one:</div> <ul class="plain"> - <li><?php aField( $conf, "DBmysql5", "Backwards-compatible UTF-8", "radio", "false" ); ?></li> - <li><?php aField( $conf, "DBmysql5", "Experimental MySQL 4.1/5.0 UTF-8", "radio", "true" ); ?></li> + <li><?php aField( $conf, "DBschema", "Backwards-compatible UTF-8", "radio", "mysql4" ); ?></li> + <li><?php aField( $conf, "DBschema", "Experimental MySQL 4.1/5.0 UTF-8", "radio", "mysql5" ); ?></li> + <li><?php aField( $conf, "DBschema", "Experimental MySQL 4.1/5.0 binary", "radio", "mysql5-binary" ); ?></li> </ul> </div> <p class="config-desc"> @@ -1253,8 +1288,6 @@ function writeLocalSettings( $conf ) { $zlib = ($conf->zlib ? "" : "# "); $magic = ($conf->ImageMagick ? "" : "# "); $convert = ($conf->ImageMagick ? $conf->ImageMagick : "/usr/bin/convert" ); - $pretty = ($conf->prettyURLs ? "" : "# "); - $ugly = ($conf->prettyURLs ? "# " : ""); $rights = ($conf->RightsUrl) ? "" : "# "; $hashedUploads = $conf->safeMode ? '' : '# '; @@ -1354,22 +1387,12 @@ if ( \$wgCommandLineMode ) { \$wgSitename = \"{$slconf['Sitename']}\"; +## The URL base path to the directory containing the wiki; +## defaults for all runtime URL paths are based off of this. \$wgScriptPath = \"{$slconf['ScriptPath']}\"; -\$wgScript = \"\$wgScriptPath/index.php\"; -\$wgRedirectScript = \"\$wgScriptPath/redirect.php\"; ## For more information on customizing the URLs please see: -## http://meta.wikimedia.org/wiki/Eliminating_index.php_from_the_url -## If using PHP as a CGI module, the ?title= style usually must be used. -{$pretty}\$wgArticlePath = \"\$wgScript/\$1\"; -{$ugly}\$wgArticlePath = \"\$wgScript?title=\$1\"; - -\$wgStylePath = \"\$wgScriptPath/skins\"; -\$wgStyleDirectory = \"\$IP/skins\"; -\$wgLogo = \"\$wgStylePath/common/images/wiki.png\"; - -\$wgUploadPath = \"\$wgScriptPath/images\"; -\$wgUploadDirectory = \"\$IP/images\"; +## http://www.mediawiki.org/wiki/Manual:Short_URL \$wgEnableEmail = $enableemail; \$wgEnableUserEmail = $enableuseremail; @@ -1421,9 +1444,6 @@ if ( \$wgCommandLineMode ) { ## If you have the appropriate support software installed ## you can enable inline LaTeX equations: \$wgUseTeX = false; -\$wgMathPath = \"{\$wgUploadPath}/math\"; -\$wgMathDirectory = \"{\$wgUploadDirectory}/math\"; -\$wgTmpDirectory = \"{\$wgUploadDirectory}/tmp\"; \$wgLocalInterwiki = \$wgSitename; @@ -1478,6 +1498,10 @@ function importPost( $name, $default = "" ) { return importVar( $_POST, $name, $default ); } +function importCheck( $name ) { + return isset( $_POST[$name] ); +} + function importRequest( $name, $default = "" ) { return importVar( $_REQUEST, $name, $default ); } @@ -1690,7 +1714,7 @@ function printListItem( $item ) { <li><a href="http://meta.wikipedia.org/wiki/MediaWiki_User's_Guide">User's Guide</a></li> <li><a href="http://meta.wikimedia.org/wiki/MediaWiki_FAQ">FAQ</a></li> </ul> - <p style="font-size:90%;margin-top:1em">MediaWiki is Copyright © 2001-2006 by Magnus Manske, Brion Vibber, Lee Daniel Crocker, Tim Starling, Erik Möller, Gabriel Wicke and others.</p> + <p style="font-size:90%;margin-top:1em">MediaWiki is Copyright © 2001-2007 by Magnus Manske, Brion Vibber, Lee Daniel Crocker, Tim Starling, Erik Möller, Gabriel Wicke and others.</p> </div></div> </div> diff --git a/docs/database.txt b/docs/database.txt index 679492a1..25dce8b7 100644 --- a/docs/database.txt +++ b/docs/database.txt @@ -2,13 +2,18 @@ Some information about database access in MediaWiki. By Tim Starling, January 2006. ------------------------------------------------------------------------ - API + Database layout ------------------------------------------------------------------------ -For a database API reference, please see the auto-generated -documentation: +For information about the MediaWiki database layout, such as a +description of the tables and their contents, please see: + http://meta.wikimedia.org/wiki/Help:Database_layout + http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/maintenance/tables.sql?view=markup + -http://wikipedia.sourceforge.net/doc/MediaWiki/Database.html +------------------------------------------------------------------------ + API +------------------------------------------------------------------------ To make a read query, something like this usually suffices: diff --git a/docs/hooks.txt b/docs/hooks.txt index 4dd68f5f..d5a17660 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -286,6 +286,10 @@ $isminor: minor flag $iswatch: watch flag $section: section # +'AuthPluginSetup': update or replace authentication plugin object ($wgAuth) +Gives a chance for an extension to set it programattically to a variable class. +&$auth: the $wgAuth object, probably a stub + 'AutoAuthenticate': called to authenticate users on external/environmental means $user: writes user object to this parameter @@ -309,6 +313,10 @@ $diff: DifferenceEngine object that's calling $oldRev: Revision object of the "old" revision (may be null/invalid) $newRev: Revision object of the "new" revision +'EditPage::attemptSave': called before an article is +saved, that is before insertNewArticle() is called +&$editpage_Obj: the current EditPage object + 'EditFormPreloadText': Allows population of the edit form when creating new pages &$text: Text to preload with &$title: Title object representing the page being created @@ -408,6 +416,17 @@ my talk page, my contributions" etc). &$personal_urls: Array of link specifiers (see SkinTemplate.php) &$title: Title object representing the current page +'PingLimiter': Allows extensions to override the results of User::pingLimiter() +&$user : User performing the action +$action : Action being performed +&$result : Whether or not the action should be prevented +Change $result and return false to give a definitive answer, otherwise +the built-in rate limiting checks are used, if enabled. + +'PreferencesUserInformationPanel': Add HTML bits to user information list in preferences form +$form : PreferencesForm object +&$html : HTML to append to + 'SiteNoticeBefore': Before the sitenotice/anonnotice is composed &$siteNotice: HTML returned as the sitenotice Return true to allow the normal method of notice selection/rendering to work, @@ -437,6 +456,17 @@ $article: article object to be removed $user: user that was watching $article: article object removed +'UploadForm:initial': before the upload form is generated +$form: UploadForm object +You might set the member-variables $uploadFormTextTop and +$uploadFormTextAfterSummary to inject text (HTML) either before +or after the editform. + +'UploadForm:BeforeProcessing': at the beginning of processUpload() +$form: UploadForm object +Lets you poke at member variables like $mUploadDescription before the +file is saved. + 'UploadVerification': additional chances to reject an uploaded file string $saveName: destination file name string $tempName: filesystem path to the temporary file for checks @@ -488,9 +518,10 @@ $article: article object that was watched $catpage: CategoryPage instance 'SkinTemplateContentActions': after building the $content_action array right - before returning it, see content_action.php in - the extension module for a demonstration of how - to use this hook. + before returning it, see Content_action.php in + the extensions/examples/ directory + ( http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/examples/ ) + for a demonstration of how to use this hook. $content_actions: The array of content actions 'BeforePageDisplay': Called just before outputting a page (all kinds of, diff --git a/img_auth.php b/img_auth.php index cfe005e9..8794bc78 100644 --- a/img_auth.php +++ b/img_auth.php @@ -12,31 +12,38 @@ wfProfileIn( 'img_auth.php' ); require_once( './includes/StreamFile.php' ); if( !isset( $_SERVER['PATH_INFO'] ) ) { + wfDebugLog( 'img_auth', "missing PATH_INFO" ); wfForbidden(); } # Get filenames/directories +wfDebugLog( 'img_auth', "PATH_INFO is: " . $_SERVER['PATH_INFO'] ); $filename = realpath( $wgUploadDirectory . $_SERVER['PATH_INFO'] ); $realUploadDirectory = realpath( $wgUploadDirectory ); $imageName = $wgContLang->getNsText( NS_IMAGE ) . ":" . wfBaseName( $_SERVER['PATH_INFO'] ); # Check if the filename is in the correct directory if ( substr( $filename, 0, strlen( $realUploadDirectory ) ) != $realUploadDirectory ) { + wfDebugLog( 'img_auth', "requested path not in upload dir: $filename" ); wfForbidden(); } if ( is_array( $wgWhitelistRead ) && !in_array( $imageName, $wgWhitelistRead ) && !$wgUser->getID() ) { + wfDebugLog( 'img_auth', "not logged in and requested file not in whitelist: $imageName" ); wfForbidden(); } if( !file_exists( $filename ) ) { + wfDebugLog( 'img_auth', "requested file does not exist: $filename" ); wfForbidden(); } if( is_dir( $filename ) ) { + wfDebugLog( 'img_auth', "requested file is a directory: $filename" ); wfForbidden(); } # Write file +wfDebugLog( 'img_auth', "streaming file: $filename" ); wfStreamFile( $filename ); wfLogProfilingData(); diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index c2744980..89062f87 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -15,7 +15,7 @@ class AjaxDispatcher { var $args; function AjaxDispatcher() { - wfProfileIn( 'AjaxDispatcher::AjaxDispatcher' ); + wfProfileIn( __METHOD__ ); $this->mode = ""; @@ -42,7 +42,7 @@ class AjaxDispatcher { $this->args = array(); } } - wfProfileOut( 'AjaxDispatcher::AjaxDispatcher' ); + wfProfileOut( __METHOD__ ); } function performAction() { @@ -51,7 +51,7 @@ class AjaxDispatcher { if ( empty( $this->mode ) ) { return; } - wfProfileIn( 'AjaxDispatcher::performAction' ); + wfProfileIn( __METHOD__ ); if (! in_array( $this->func_name, $wgAjaxExportList ) ) { header( 'Status: 400 Bad Request', true, 400 ); @@ -72,7 +72,7 @@ class AjaxDispatcher { $result->sendHeaders(); $result->printText(); } - + } catch (Exception $e) { if (!headers_sent()) { header( 'Status: 500 Internal Error', true, 500 ); @@ -83,7 +83,7 @@ class AjaxDispatcher { } } - wfProfileOut( 'AjaxDispatcher::performAction' ); + wfProfileOut( __METHOD__ ); $wgOut = null; } } diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php index 9f7a332f..eee2a1a4 100644 --- a/includes/AjaxFunctions.php +++ b/includes/AjaxFunctions.php @@ -129,4 +129,43 @@ function wfSajaxSearch( $term ) { return $response; } +/** + * Called for AJAX watch/unwatch requests. + * @param $pageID Integer ID of the page to be watched/unwatched + * @param $watch String 'w' to watch, 'u' to unwatch + * @return String '<w#>' or '<u#>' on successful watch or unwatch, respectively, or '<err#>' on error (invalid XML in case we want to add HTML sometime) + */ +function wfAjaxWatch($pageID = "", $watch = "") { + if(wfReadOnly()) + return '<err#>'; // redirect to action=(un)watch, which will display the database lock message + + if(('w' !== $watch && 'u' !== $watch) || !is_numeric($pageID)) + return '<err#>'; + $watch = 'w' === $watch; + $pageID = intval($pageID); + + $title = Title::newFromID($pageID); + if(!$title) + return '<err#>'; + $article = new Article($title); + $watching = $title->userIsWatching(); + + if($watch) { + if(!$watching) { + $dbw =& wfGetDB(DB_MASTER); + $dbw->begin(); + $article->doWatch(); + $dbw->commit(); + } + } else { + if($watching) { + $dbw =& wfGetDB(DB_MASTER); + $dbw->begin(); + $article->doUnwatch(); + $dbw->commit(); + } + } + + return $watch ? '<w#>' : '<u#>'; +} ?> diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php index 40f50876..a59c73bb 100644 --- a/includes/AjaxResponse.php +++ b/includes/AjaxResponse.php @@ -61,7 +61,7 @@ class AjaxResponse { } function sendHeaders() { - global $wgUseSquid, $wgUseESI, $wgSquidMaxage; + global $wgUseSquid, $wgUseESI; if ( $this->mResponseCode ) { $n = preg_replace( '/^ *(\d+)/', '\1', $this->mResponseCode ); @@ -122,7 +122,7 @@ class AjaxResponse { * returns true iff the response code was set to 304 Not Modified. */ function checkLastModified ( $timestamp ) { - global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest; + global $wgCachePages, $wgCacheEpoch, $wgUser; $fname = 'AjaxResponse::checkLastModified'; if ( !$timestamp || $timestamp == '19700101000000' ) { diff --git a/includes/Article.php b/includes/Article.php index 8c07b06c..6b4f5270 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -9,7 +9,7 @@ * * See design.txt for an overview. * Note: edit user interface and cache support functions have been - * moved to separate EditPage and CacheManager classes. + * moved to separate EditPage and HTMLFileCache classes. * * @package MediaWiki */ @@ -48,7 +48,7 @@ class Article { $this->mOldId = $oldId; $this->clear(); } - + /** * Tell the page view functions that this view was redirected * from another page on the wiki. @@ -79,13 +79,13 @@ class Article { } } else { if( $rt->getNamespace() == NS_SPECIAL ) { - // Gotta hand redirects to special pages differently: + // Gotta handle redirects to special pages differently: // Fill the HTTP response "Location" header and ignore // the rest of the page we're on. // // This can be hard to reverse, so they may be disabled. - if( $rt->getNamespace() == NS_SPECIAL && $rt->getText() == 'Userlogout' ) { + if( $rt->isSpecial( 'Userlogout' ) ) { // rolleyes } else { return $rt->getFullURL(); @@ -139,7 +139,7 @@ class Article { * @return Return the text of this revision */ function getContent() { - global $wgRequest, $wgUser, $wgOut; + global $wgUser, $wgOut; wfProfileIn( __METHOD__ ); @@ -236,9 +236,6 @@ class Article { # Pre-fill content with error message so that if something # fails we'll have something telling us what we intended. - - $t = $this->mTitle->getPrefixedText(); - $this->mOldId = $oldid; $this->fetchContent( $oldid ); } @@ -575,13 +572,10 @@ class Article { function getContributors($limit = 0, $offset = 0) { # XXX: this is expensive; cache this info somewhere. - $title = $this->mTitle; $contribs = array(); $dbr =& wfGetDB( DB_SLAVE ); $revTable = $dbr->tableName( 'revision' ); $userTable = $dbr->tableName( 'user' ); - $encDBkey = $dbr->addQuotes( $title->getDBkey() ); - $ns = $title->getNamespace(); $user = $this->getUser(); $pageId = $this->getId(); @@ -638,6 +632,8 @@ class Article { if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) { $policy = $wgNamespaceRobotPolicies[$ns]; } else { + # The default policy. Dev note: make sure you change the documentation + # in DefaultSettings.php before changing it. $policy = 'index,follow'; } $wgOut->setRobotpolicy( $policy ); @@ -697,6 +693,12 @@ class Article { $redir = $sk->makeKnownLinkObj( $this->mRedirectedFrom, '', 'redirect=no' ); $s = wfMsg( 'redirectedfrom', $redir ); $wgOut->setSubtitle( $s ); + + // Set the fragment if one was specified in the redirect + if ( strval( $this->mTitle->getFragment() ) != '' ) { + $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() ); + $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" ); + } $wasRedirected = true; } } elseif ( !empty( $rdfrom ) ) { @@ -784,12 +786,9 @@ class Article { if( !$wasRedirected && $this->isCurrent() ) { $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) ); } - $targetUrl = $rt->escapeLocalURL(); - # fixme unused $titleText : - $titleText = htmlspecialchars( $rt->getPrefixedText() ); - $link = $sk->makeLinkObj( $rt ); + $link = $sk->makeLinkObj( $rt, $rt->getFullText() ); - $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT" />' . + $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT " />' . '<span class="redirectText">'.$link.'</span>' ); $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser)); @@ -997,32 +996,87 @@ class Article { * when different from the currently set value. * Giving 0 indicates the new page flag should * be set on. + * @param bool $lastRevIsRedirect If given, will optimize adding and + * removing rows in redirect table. * @return bool true on success, false on failure * @private */ - function updateRevisionOn( &$dbw, $revision, $lastRevision = null ) { + function updateRevisionOn( &$dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) { wfProfileIn( __METHOD__ ); + $text = $revision->getText(); + $rt = Title::newFromRedirect( $text ); + $conditions = array( 'page_id' => $this->getId() ); if( !is_null( $lastRevision ) ) { # An extra check against threads stepping on each other $conditions['page_latest'] = $lastRevision; } - $text = $revision->getText(); $dbw->update( 'page', array( /* SET */ 'page_latest' => $revision->getId(), 'page_touched' => $dbw->timestamp(), 'page_is_new' => ($lastRevision === 0) ? 1 : 0, - 'page_is_redirect' => Article::isRedirect( $text ) ? 1 : 0, + 'page_is_redirect' => $rt !== NULL ? 1 : 0, 'page_len' => strlen( $text ), ), $conditions, __METHOD__ ); + $result = $dbw->affectedRows() != 0; + + if ($result) { + // FIXME: Should the result from updateRedirectOn() be returned instead? + $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect ); + } + wfProfileOut( __METHOD__ ); - return ( $dbw->affectedRows() != 0 ); + return $result; + } + + /** + * Add row to the redirect table if this is a redirect, remove otherwise. + * + * @param Database $dbw + * @param $redirectTitle a title object pointing to the redirect target, + * or NULL if this is not a redirect + * @param bool $lastRevIsRedirect If given, will optimize adding and + * removing rows in redirect table. + * @return bool true on success, false on failure + * @private + */ + function updateRedirectOn( &$dbw, $redirectTitle, $lastRevIsRedirect = null ) { + + // Always update redirects (target link might have changed) + // Update/Insert if we don't know if the last revision was a redirect or not + // Delete if changing from redirect to non-redirect + $isRedirect = !is_null($redirectTitle); + if ($isRedirect || is_null($lastRevIsRedirect) || $lastRevIsRedirect !== $isRedirect) { + + wfProfileIn( __METHOD__ ); + + if ($isRedirect) { + + // This title is a redirect, Add/Update row in the redirect table + $set = array( /* SET */ + 'rd_namespace' => $redirectTitle->getNamespace(), + 'rd_title' => $redirectTitle->getDBkey(), + 'rd_from' => $this->getId(), + ); + + $dbw->replace( 'redirect', array( 'rd_from' ), $set, __METHOD__ ); + } else { + // This is not a redirect, remove row from redirect table + $where = array( 'rd_from' => $this->getId() ); + $dbw->delete( 'redirect', $where, __METHOD__); + } + + wfProfileOut( __METHOD__ ); + return ( $dbw->affectedRows() != 0 ); + } + + return true; } /** @@ -1037,7 +1091,7 @@ class Article { $row = $dbw->selectRow( array( 'revision', 'page' ), - array( 'rev_id', 'rev_timestamp' ), + array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ), array( 'page_id' => $this->getId(), 'page_latest=rev_id' ), @@ -1048,12 +1102,14 @@ class Article { return false; } $prev = $row->rev_id; + $lastRevIsRedirect = (bool)$row->page_is_redirect; } else { # No or missing previous revision; mark the page as new $prev = 0; + $lastRevIsRedirect = null; } - $ret = $this->updateRevisionOn( $dbw, $revision, $prev ); + $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect ); wfProfileOut( __METHOD__ ); return $ret; } @@ -1080,13 +1136,18 @@ class Article { } $oldtext = $rev->getText(); - if($section=='new') { - if($summary) $subject="== {$summary} ==\n\n"; - $text=$oldtext."\n\n".$subject.$text; + if( $section == 'new' ) { + # Inserting a new section + $subject = $summary ? "== {$summary} ==\n\n" : ''; + $text = strlen( trim( $oldtext ) ) > 0 + ? "{$oldtext}\n\n{$subject}{$text}" + : "{$subject}{$text}"; } else { + # Replacing an existing section; roll out the big guns global $wgParser; $text = $wgParser->replaceSection( $oldtext, $section, $text ); } + } wfProfileOut( __METHOD__ ); @@ -1097,7 +1158,7 @@ class Article { * @deprecated use Article::doEdit() */ function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) { - $flags = EDIT_NEW | EDIT_DEFER_UPDATES | + $flags = EDIT_NEW | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | ( $isminor ? EDIT_MINOR : 0 ) | ( $suppressRC ? EDIT_SUPPRESS_RC : 0 ); @@ -1129,7 +1190,7 @@ class Article { * @deprecated use Article::doEdit() */ function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) { - $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | + $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | ( $minor ? EDIT_MINOR : 0 ) | ( $forceBot ? EDIT_FORCE_BOT : 0 ); @@ -1178,6 +1239,8 @@ class Article { * Mark the edit a "bot" edit regardless of user rights * EDIT_DEFER_UPDATES * Defer some of the updates until the end of index.php + * EDIT_AUTOSUMMARY + * Fill in blank summaries with generated text where possible * * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If @@ -1215,7 +1278,15 @@ class Article { $isminor = ( $flags & EDIT_MINOR ) && $wgUser->isAllowed('minoredit'); $bot = $wgUser->isAllowed( 'bot' ) || ( $flags & EDIT_FORCE_BOT ); + $oldtext = $this->getContent(); + $oldsize = strlen( $oldtext ); + + # Provide autosummaries if one is not provided. + if ($flags & EDIT_AUTOSUMMARY && $summary == '') + $summary = $this->getAutosummary( $oldtext, $text, $flags ); + $text = $this->preSaveTransform( $text ); + $newsize = strlen( $text ); $dbw =& wfGetDB( DB_MASTER ); $now = wfTimestampNow(); @@ -1228,9 +1299,6 @@ class Article { $userAbort = ignore_user_abort( true ); } - $oldtext = $this->getContent(); - $oldsize = strlen( $oldtext ); - $newsize = strlen( $text ); $lastRevision = 0; $revisionId = 0; @@ -1273,11 +1341,12 @@ class Article { $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize, $revisionId ); - # Mark as patrolled if the user can do so and has it set in their options - if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) { + # Mark as patrolled if the user can do so + if( $wgUser->isAllowed( 'autopatrol' ) ) { RecentChange::markPatrolled( $rcid ); } } + $wgUser->incEditCount(); $dbw->commit(); } } else { @@ -1333,11 +1402,12 @@ class Article { if( !( $flags & EDIT_SUPPRESS_RC ) ) { $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot, '', strlen( $text ), $revisionId ); - # Mark as patrolled if the user can and has the option set - if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) { + # Mark as patrolled if the user can + if( $wgUser->isAllowed( 'autopatrol' ) ) { RecentChange::markPatrolled( $rcid ); } } + $wgUser->incEditCount(); $dbw->commit(); # Update links, etc. @@ -1393,7 +1463,7 @@ class Article { */ function markpatrolled() { global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUser; - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); # Check RC patrol config. option if( !$wgUseRCPatrol ) { @@ -1407,20 +1477,45 @@ class Article { return; } + # If we haven't been given an rc_id value, we can't do anything $rcid = $wgRequest->getVal( 'rcid' ); - if ( !is_null ( $rcid ) ) { - if( wfRunHooks( 'MarkPatrolled', array( &$rcid, &$wgUser, false ) ) ) { - RecentChange::markPatrolled( $rcid ); - wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); - $wgOut->setPagetitle( wfMsg( 'markedaspatrolled' ) ); - $wgOut->addWikiText( wfMsg( 'markedaspatrolledtext' ) ); - } - $rcTitle = Title::makeTitle( NS_SPECIAL, 'Recentchanges' ); - $wgOut->returnToMain( false, $rcTitle->getPrefixedText() ); + if( !$rcid ) { + $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); + return; } - else { - $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); + + # Handle the 'MarkPatrolled' hook + if( !wfRunHooks( 'MarkPatrolled', array( $rcid, &$wgUser, false ) ) ) { + return; + } + + $return = SpecialPage::getTitleFor( 'Recentchanges' ); + # If it's left up to us, check that the user is allowed to patrol this edit + # If the user has the "autopatrol" right, then we'll assume there are no + # other conditions stopping them doing so + if( !$wgUser->isAllowed( 'autopatrol' ) ) { + $rc = RecentChange::newFromId( $rcid ); + # Graceful error handling, as we've done before here... + # (If the recent change doesn't exist, then it doesn't matter whether + # the user is allowed to patrol it or not; nothing is going to happen + if( is_object( $rc ) && $wgUser->getName() == $rc->getAttribute( 'rc_user_text' ) ) { + # The user made this edit, and can't patrol it + # Tell them so, and then back off + $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) ); + $wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrollederror-noautopatrol' ) ); + $wgOut->returnToMain( false, $return ); + return; + } } + + # Mark the edit as patrolled + RecentChange::markPatrolled( $rcid ); + wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); + + # Inform the user + $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) ); + $wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrolledtext' ) ); + $wgOut->returnToMain( false, $return ); } /** @@ -1662,6 +1757,11 @@ class Article { if( $confirm ) { $this->doDelete( $reason ); + if( $wgRequest->getCheck( 'wpWatch' ) ) { + $this->doWatch(); + } elseif( $this->mTitle->userIsWatching() ) { + $this->doUnwatch(); + } return; } @@ -1801,6 +1901,7 @@ class Article { $confirm = htmlspecialchars( wfMsg( 'deletepage' ) ); $delcom = htmlspecialchars( wfMsg( 'deletecomment' ) ); $token = htmlspecialchars( $wgUser->editToken() ); + $watch = Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '2' ) ); $wgOut->addHTML( " <form id='deleteconfirm' method='post' action=\"{$formaction}\"> @@ -1810,13 +1911,17 @@ class Article { <label for='wpReason'>{$delcom}:</label> </td> <td align='left'> - <input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" /> + <input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" tabindex=\"1\" /> </td> </tr> <tr> <td> </td> + <td>$watch</td> + </tr> + <tr> + <td> </td> <td> - <input type='submit' name='wpConfirmB' value=\"{$confirm}\" /> + <input type='submit' name='wpConfirmB' id='wpConfirmB' value=\"{$confirm}\" tabindex=\"3\" /> </td> </tr> </table> @@ -1860,7 +1965,7 @@ class Article { */ function doDeleteArticle( $reason ) { global $wgUseSquid, $wgDeferredUpdateList; - global $wgPostCommitUpdateList, $wgUseTrackbacks; + global $wgUseTrackbacks; wfDebug( __METHOD__."\n" ); @@ -1897,6 +2002,8 @@ class Article { 'ar_minor_edit' => 'rev_minor_edit', 'ar_rev_id' => 'rev_id', 'ar_text_id' => 'rev_text_id', + 'ar_text' => '\'\'', // Be explicit to appease + 'ar_flags' => '\'\'', // MySQL's "strict mode"... ), array( 'page_id' => $id, 'page_id = rev_page' @@ -1921,6 +2028,7 @@ class Article { $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) ); $dbw->delete( 'externallinks', array( 'el_from' => $id ) ); $dbw->delete( 'langlinks', array( 'll_from' => $id ) ); + $dbw->delete( 'redirect', array( 'rd_from' => $id ) ); } # If using cleanup triggers, we can skip some manual deletes @@ -1976,8 +2084,6 @@ class Article { $bot = $wgRequest->getBool( 'bot' ); # Replace all this user's current edits with the next one down - $tt = $this->mTitle->getDBKey(); - $n = $this->mTitle->getNamespace(); # Get the last editor $current = Revision::newFromTitle( $this->mTitle ); @@ -2140,8 +2246,10 @@ class Article { # If this is another user's talk page, update newtalk # Don't do this if $changed = false otherwise some idiot can null-edit a - # load of user talk pages and piss people off - if( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed ) { + # load of user talk pages and piss people off, nor if it's a minor edit + # by a properly-flagged bot. + if( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed + && !($minoredit && $wgUser->isAllowed('nominornewtalk') ) ) { if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this)) ) { $other = User::newFromName( $shortTitle ); if( is_null( $other ) && User::isIP( $shortTitle ) ) { @@ -2202,6 +2310,9 @@ class Article { $lnk = $current ? wfMsg( 'currentrevisionlink' ) : $lnk = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) ); + $curdiff = $current + ? wfMsg( 'diff' ) + : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=cur&oldid='.$oldid ); $prev = $this->mTitle->getPreviousRevisionID( $oldid ) ; $prevlink = $prev ? $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'previousrevision' ), 'direction=prev&oldid='.$oldid ) @@ -2219,7 +2330,8 @@ class Article { $userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() ) . $sk->userToolLinks( $revision->getUser(), $revision->getUserText() ); - $r = wfMsg( 'old-revision-navigation', $td, $lnk, $prevlink, $nextlink, $userlinks, $prevdiff, $nextdiff ); + $r = "\n\t\t\t\t<div id=\"mw-revision-info\">" . wfMsg( 'revision-info', $td, $userlinks ) . "</div>\n" . + "\n\t\t\t\t<div id=\"mw-revision-nav\">" . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t"; $wgOut->setSubtitle( $r ); } @@ -2250,7 +2362,7 @@ class Article { $called = true; if($this->isFileCacheable()) { $touched = $this->mTouched; - $cache = new CacheManager( $this->mTitle ); + $cache = new HTMLFileCache( $this->mTitle ); if($cache->isFileCacheGood( $touched )) { wfDebug( "Article::tryFileCache(): about to load file\n" ); $cache->loadFromFileCache(); @@ -2270,7 +2382,11 @@ class Article { */ function isFileCacheable() { global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest; - extract( $wgRequest->getValues( 'action', 'oldid', 'diff', 'redirect', 'printable' ) ); + $action = $wgRequest->getVal( 'action' ); + $oldid = $wgRequest->getVal( 'oldid' ); + $diff = $wgRequest->getVal( 'diff' ); + $redirect = $wgRequest->getVal( 'redirect' ); + $printable = $wgRequest->getVal( 'printable' ); return $wgUseFileCache and (!$wgShowIPinHeader) @@ -2338,8 +2454,7 @@ class Article { 'comment' => $comment, 'minor_edit' => $minor ? 1 : 0, ) ); - # fixme : $revisionId never used - $revisionId = $revision->insertOn( $dbw ); + $revision->insertOn( $dbw ); $this->updateRevisionOn( $dbw, $revision ); $dbw->commit(); @@ -2361,7 +2476,7 @@ class Article { $hitcounterTable = $dbw->tableName( 'hitcounter' ); $acchitsTable = $dbw->tableName( 'acchits' ); - if( $wgHitcounterUpdateFreq <= 1 ){ // + if( $wgHitcounterUpdateFreq <= 1 ) { $dbw->query( "UPDATE $pageTable SET page_counter = page_counter + 1 WHERE page_id = $id" ); return; } @@ -2388,14 +2503,19 @@ class Article { if ($wgDBtype == 'mysql') $dbw->query("LOCK TABLES $hitcounterTable WRITE"); $tabletype = $wgDBtype == 'mysql' ? "ENGINE=HEAP " : ''; - $dbw->query("CREATE TEMPORARY TABLE $acchitsTable $tabletype". + $dbw->query("CREATE TEMPORARY TABLE $acchitsTable $tabletype AS ". "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable ". 'GROUP BY hc_id'); $dbw->query("DELETE FROM $hitcounterTable"); - if ($wgDBtype == 'mysql') + if ($wgDBtype == 'mysql') { $dbw->query('UNLOCK TABLES'); - $dbw->query("UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n ". - 'WHERE page_id = hc_id'); + $dbw->query("UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n ". + 'WHERE page_id = hc_id'); + } + else { + $dbw->query("UPDATE $pageTable SET page_counter=page_counter + hc_n ". + "FROM $acchitsTable WHERE page_id = hc_id"); + } $dbw->query("DROP TABLE $acchitsTable"); ignore_user_abort( $old_user_abort ); @@ -2438,7 +2558,7 @@ class Article { # File cache if ( $wgUseFileCache ) { - $cm = new CacheManager( $title ); + $cm = new HTMLFileCache( $title ); @unlink( $cm->fileCacheName() ); } @@ -2453,8 +2573,6 @@ class Article { static function onArticleEdit( $title ) { global $wgDeferredUpdateList, $wgUseFileCache; - $urls = array(); - // Invalidate caches of articles which include this page $update = new HTMLCacheUpdate( $title, 'templatelinks' ); $wgDeferredUpdateList[] = $update; @@ -2464,7 +2582,7 @@ class Article { # Clear file cache if ( $wgUseFileCache ) { - $cm = new CacheManager( $title ); + $cm = new HTMLFileCache( $title ); @unlink( $cm->fileCacheName() ); } } @@ -2590,6 +2708,83 @@ class Article { $dbr->freeResult( $res ); return $result; } + + /** + * Return an auto-generated summary if the text provided is a redirect. + * + * @param string $text The wikitext to check + * @return string '' or an appropriate summary + */ + public static function getRedirectAutosummary( $text ) { + $rt = Title::newFromRedirect( $text ); + if( is_object( $rt ) ) + return wfMsgForContent( 'autoredircomment', $rt->getFullText() ); + else + return ''; + } + + /** + * Return an auto-generated summary if the new text is much shorter than + * the old text. + * + * @param string $oldtext The previous text of the page + * @param string $text The submitted text of the page + * @return string An appropriate autosummary, or an empty string. + */ + public static function getBlankingAutosummary( $oldtext, $text ) { + if ($oldtext!='' && $text=='') { + return wfMsgForContent('autosumm-blank'); + } elseif (strlen($oldtext) > 10 * strlen($text) && strlen($text) < 500) { + #Removing more than 90% of the article + global $wgContLang; + $truncatedtext = $wgContLang->truncate($text, max(0, 200 - strlen(wfMsgForContent('autosumm-replace'))), '...'); + return wfMsgForContent('autosumm-replace', $truncatedtext); + } else { + return ''; + } + } + + /** + * Return an applicable autosummary if one exists for the given edit. + * @param string $oldtext The previous text of the page. + * @param string $newtext The submitted text of the page. + * @param bitmask $flags A bitmask of flags submitted for the edit. + * @return string An appropriate autosummary, or an empty string. + */ + public static function getAutosummary( $oldtext, $newtext, $flags ) { + + # This code is UGLY UGLY UGLY. + # Somebody PLEASE come up with a more elegant way to do it. + + #Redirect autosummaries + $summary = self::getRedirectAutosummary( $newtext ); + + if ($summary) + return $summary; + + #Blanking autosummaries + if (!($flags & EDIT_NEW)) + $summary = self::getBlankingAutosummary( $oldtext, $newtext ); + + if ($summary) + return $summary; + + #New page autosummaries + if ($flags & EDIT_NEW && strlen($newtext)) { + #If they're making a new article, give its text, truncated, in the summary. + global $wgContLang; + $truncatedtext = $wgContLang->truncate( + str_replace("\n", ' ', $newtext), + max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new') ) ), + '...' ); + $summary = wfMsgForContent( 'autosumm-new', $truncatedtext ); + } + + if ($summary) + return $summary; + + return $summary; + } } ?> diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php index 1d955418..e33ef1bf 100644 --- a/includes/AuthPlugin.php +++ b/includes/AuthPlugin.php @@ -146,13 +146,18 @@ class AuthPlugin { /** * Set the given password in the authentication database. + * As a special case, the password may be set to null to request + * locking the password to an unusable value, with the expectation + * that it will be set later through a mail reset or other method. + * * Return true if successful. * + * @param $user User object. * @param $password String: password. * @return bool * @public */ - function setPassword( $password ) { + function setPassword( $user, $password ) { return true; } diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 810a448e..8de5608f 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -22,7 +22,11 @@ function __autoload($className) { 'eAccelBagOStuff' => 'includes/BagOStuff.php', 'DBABagOStuff' => 'includes/BagOStuff.php', 'Block' => 'includes/Block.php', - 'CacheManager' => 'includes/CacheManager.php', + 'HTMLFileCache' => 'includes/HTMLFileCache.php', + 'DependencyWrapper' => 'includes/CacheDependency.php', + 'FileDependency' => 'includes/CacheDependency.php', + 'TitleDependency' => 'includes/CacheDependency.php', + 'TitleListDependency' => 'includes/CacheDependency.php', 'CategoryPage' => 'includes/CategoryPage.php', 'CategoryViewer' => 'includes/CategoryPage.php', 'Categoryfinder' => 'includes/Categoryfinder.php', @@ -82,7 +86,6 @@ function __autoload($className) { 'FileStore' => 'includes/FileStore.php', 'FSException' => 'includes/FileStore.php', 'FSTransaction' => 'includes/FileStore.php', - 'ReplacerCallback' => 'includes/GlobalFunctions.php', 'HTMLForm' => 'includes/HTMLForm.php', 'HistoryBlob' => 'includes/HistoryBlob.php', 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', @@ -146,7 +149,8 @@ function __autoload($className) { 'SearchUpdate' => 'includes/SearchUpdate.php', 'SearchUpdateMyISAM' => 'includes/SearchUpdate.php', 'SiteConfiguration' => 'includes/SiteConfiguration.php', - 'SiteStatsUpdate' => 'includes/SiteStatsUpdate.php', + 'SiteStats' => 'includes/SiteStats.php', + 'SiteStatsUpdate' => 'includes/SiteStats.php', 'Skin' => 'includes/Skin.php', 'MediaWiki_I18N' => 'includes/SkinTemplate.php', 'SkinTemplate' => 'includes/SkinTemplate.php', @@ -154,11 +158,11 @@ function __autoload($className) { 'SpecialAllpages' => 'includes/SpecialAllpages.php', 'AncientPagesPage' => 'includes/SpecialAncientpages.php', 'IPBlockForm' => 'includes/SpecialBlockip.php', - 'BookSourceList' => 'includes/SpecialBooksources.php', + 'SpecialBookSources' => 'includes/SpecialBooksources.php', 'BrokenRedirectsPage' => 'includes/SpecialBrokenRedirects.php', 'CategoriesPage' => 'includes/SpecialCategories.php', 'EmailConfirmation' => 'includes/SpecialConfirmemail.php', - 'ContribsFinder' => 'includes/SpecialContributions.php', + 'ContributionsPage' => 'includes/SpecialContributions.php', 'DeadendPagesPage' => 'includes/SpecialDeadendpages.php', 'DisambiguationsPage' => 'includes/SpecialDisambiguations.php', 'DoubleRedirectsPage' => 'includes/SpecialDoubleRedirects.php', @@ -182,6 +186,7 @@ function __autoload($className) { 'MostlinkedCategoriesPage' => 'includes/SpecialMostlinkedcategories.php', 'MostrevisionsPage' => 'includes/SpecialMostrevisions.php', 'MovePageForm' => 'includes/SpecialMovepage.php', + 'NewbieContributionsPage' => 'includes/SpecialNewbieContributions.php', 'NewPagesPage' => 'includes/SpecialNewpages.php', 'SpecialPage' => 'includes/SpecialPage.php', 'UnlistedSpecialPage' => 'includes/SpecialPage.php', @@ -211,6 +216,12 @@ function __autoload($className) { 'WantedPagesPage' => 'includes/SpecialWantedpages.php', 'WhatLinksHerePage' => 'includes/SpecialWhatlinkshere.php', 'SquidUpdate' => 'includes/SquidUpdate.php', + 'ReplacementArray' => 'includes/StringUtils.php', + 'Replacer' => 'includes/StringUtils.php', + 'RegexlikeReplacer' => 'includes/StringUtils.php', + 'DoubleReplacer' => 'includes/StringUtils.php', + 'HashtableReplacer' => 'includes/StringUtils.php', + 'StringUtils' => 'includes/StringUtils.php', 'Title' => 'includes/Title.php', 'User' => 'includes/User.php', 'MailAddress' => 'includes/UserMailer.php', @@ -230,7 +241,39 @@ function __autoload($className) { 'UsercreateTemplate' => 'includes/templates/Userlogin.php', 'UserloginTemplate' => 'includes/templates/Userlogin.php', 'Language' => 'languages/Language.php', + 'PasswordResetForm' => 'includes/SpecialResetpass.php', + + // API classes + 'ApiBase' => 'includes/api/ApiBase.php', + 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php', + 'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php', + 'ApiFormatBase' => 'includes/api/ApiFormatBase.php', + 'Services_JSON' => 'includes/api/ApiFormatJson_json.php', + 'ApiFormatJson' => 'includes/api/ApiFormatJson.php', + 'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php', + 'ApiFormatWddx' => 'includes/api/ApiFormatWddx.php', + 'ApiFormatXml' => 'includes/api/ApiFormatXml.php', + 'Spyc' => 'includes/api/ApiFormatYaml_spyc.php', + 'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php', + 'ApiHelp' => 'includes/api/ApiHelp.php', + 'ApiLogin' => 'includes/api/ApiLogin.php', + 'ApiMain' => 'includes/api/ApiMain.php', + 'ApiOpenSearch' => 'includes/api/ApiOpenSearch.php', + 'ApiPageSet' => 'includes/api/ApiPageSet.php', + 'ApiQuery' => 'includes/api/ApiQuery.php', + 'ApiQueryAllpages' => 'includes/api/ApiQueryAllpages.php', + 'ApiQueryBase' => 'includes/api/ApiQueryBase.php', + 'ApiQueryBacklinks' => 'includes/api/ApiQueryBacklinks.php', + 'ApiQueryContributions' => 'includes/api/ApiQueryUserContributions.php', + 'ApiQueryInfo' => 'includes/api/ApiQueryInfo.php', + 'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php', + 'ApiQueryRecentChanges'=> 'includes/api/ApiQueryRecentChanges.php', + 'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php', + 'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php', + 'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php', + 'ApiResult' => 'includes/api/ApiResult.php', ); + if ( isset( $localClasses[$className] ) ) { $filename = $localClasses[$className]; } elseif ( isset( $wgAutoloadClasses[$className] ) ) { diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php index 1dc93a2f..c720807d 100644 --- a/includes/BagOStuff.php +++ b/includes/BagOStuff.php @@ -240,6 +240,13 @@ abstract class SqlBagOStuff extends BagOStuff { } if($row=$this->_fetchobject($res)) { $this->_debug("get: retrieved data; exp time is " . $row->exptime); + if ( $row->exptime != $this->_maxdatetime() && + wfTimestamp( TS_UNIX, $row->exptime ) < time() ) + { + $this->_debug("get: key has expired, deleting"); + $this->delete($key); + return false; + } return $this->_unserialize($this->_blobdecode($row->value)); } else { $this->_debug('get: no matching rows'); @@ -253,7 +260,7 @@ abstract class SqlBagOStuff extends BagOStuff { if($exptime == 0) { $exp = $this->_maxdatetime(); } else { - if($exptime < 3600*24*30) + if($exptime < 3.16e8) # ~10 years $exptime += time(); $exp = $this->_fromunixtime($exptime); } @@ -390,7 +397,8 @@ class MediaWikiBagOStuff extends SqlBagOStuff { } function _doinsert($t, $v) { $dbw =& wfGetDB( DB_MASTER ); - return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert'); + return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert', + array( 'IGNORE' ) ); } function _fetchobject($result) { $dbw =& wfGetDB( DB_MASTER ); @@ -406,7 +414,11 @@ class MediaWikiBagOStuff extends SqlBagOStuff { } function _maxdatetime() { $dbw =& wfGetDB(DB_MASTER); - return $dbw->timestamp('9999-12-31 12:59:59'); + if ( time() > 0x7fffffff ) { + return $this->_fromunixtime( 1<<62 ); + } else { + return $this->_fromunixtime( 0x7fffffff ); + } } function _fromunixtime($ts) { $dbw =& wfGetDB(DB_MASTER); @@ -492,11 +504,14 @@ class TurckBagOStuff extends BagOStuff { class APCBagOStuff extends BagOStuff { function get($key) { $val = apc_fetch($key); + if ( is_string( $val ) ) { + $val = unserialize( $val ); + } return $val; } function set($key, $value, $exptime=0) { - apc_store($key, $value, $exptime); + apc_store($key, serialize($value), $exptime); return true; } diff --git a/includes/Block.php b/includes/Block.php index b11df22c..ff813ba3 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -17,7 +17,7 @@ class Block { /* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry, - $mRangeStart, $mRangeEnd, $mAnonOnly; + $mRangeStart, $mRangeEnd, $mAnonOnly, $mEnableAutoblock; /* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName; const EB_KEEP_EXPIRED = 1; @@ -25,7 +25,7 @@ class Block const EB_RANGE_ONLY = 4; function Block( $address = '', $user = 0, $by = 0, $reason = '', - $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0 ) + $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0 ) { $this->mId = 0; $this->mAddress = $address; @@ -37,6 +37,7 @@ class Block $this->mAnonOnly = $anonOnly; $this->mCreateAccount = $createAccount; $this->mExpiry = self::decodeExpiry( $expiry ); + $this->mEnableAutoblock = $enableAutoblock; $this->mForUpdate = false; $this->mFromMaster = false; @@ -72,7 +73,8 @@ class Block { $this->mAddress = $this->mReason = $this->mTimestamp = ''; $this->mId = $this->mAnonOnly = $this->mCreateAccount = - $this->mAuto = $this->mUser = $this->mBy = 0; + $this->mEnableAutoblock = $this->mAuto = $this->mUser = + $this->mBy = 0; $this->mByName = false; } @@ -111,9 +113,6 @@ class Block $options = array(); $db =& $this->getDBOptions( $options ); - $ret = false; - $killed = false; - if ( 0 == $user && $address == '' ) { # Invalid user specification, not blocked $this->clear(); @@ -239,14 +238,10 @@ class Block /** * Determine if a given integer IPv4 address is in a given CIDR network + * @deprecated Use IP::isInRange */ function isAddressInRange( $addr, $range ) { - list( $network, $bits ) = wfParseCIDR( $range ); - if ( $network !== false && $addr >> ( 32 - $bits ) == $network >> ( 32 - $bits ) ) { - return true; - } else { - return false; - } + return IP::isInRange( $addr, $range ); } function initFromRow( $row ) @@ -259,6 +254,7 @@ class Block $this->mAuto = $row->ipb_auto; $this->mAnonOnly = $row->ipb_anon_only; $this->mCreateAccount = $row->ipb_create_account; + $this->mEnableAutoblock = $row->ipb_enable_autoblock; $this->mId = $row->ipb_id; $this->mExpiry = self::decodeExpiry( $row->ipb_expiry ); if ( isset( $row->user_name ) ) { @@ -274,12 +270,9 @@ class Block { $this->mRangeStart = ''; $this->mRangeEnd = ''; + if ( $this->mUser == 0 ) { - list( $network, $bits ) = wfParseCIDR( $this->mAddress ); - if ( $network !== false ) { - $this->mRangeStart = sprintf( '%08X', $network ); - $this->mRangeEnd = sprintf( '%08X', $network + (1 << (32 - $bits)) - 1 ); - } + list( $this->mRangeStart, $this->mRangeEnd ) = IP::parseRange( $this->mAddress ); } } @@ -312,7 +305,7 @@ class Block $now = wfTimestampNow(); - extract( $db->tableNames( 'ipblocks', 'user' ) ); + list( $ipblocks, $user ) = $db->tableNamesN( 'ipblocks', 'user' ); $sql = "SELECT $ipblocks.*,user_name FROM $ipblocks,$user " . "WHERE user_id=ipb_by $cond ORDER BY ipb_timestamp DESC $options"; @@ -335,7 +328,7 @@ class Block call_user_func( $callback, $block, $tag ); } } - wfFreeResult( $res ); + $db->freeResult( $res ); return $num_rows; } @@ -353,6 +346,10 @@ class Block return $dbw->affectedRows() > 0; } + /** + * Insert a block into the block table. + *@return Whether or not the insertion was successful. + */ function insert() { wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" ); @@ -364,9 +361,14 @@ class Block $this->mAnonOnly = 0; } + # Unset ipb_enable_autoblock for IP blocks, makes no sense + if ( !$this->mUser ) { + $this->mEnableAutoblock = 0; + } + # Don't collide with expired blocks Block::purgeExpired(); - + $ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val'); $dbw->insert( 'ipblocks', array( @@ -379,6 +381,7 @@ class Block 'ipb_auto' => $this->mAuto, 'ipb_anon_only' => $this->mAnonOnly, 'ipb_create_account' => $this->mCreateAccount, + 'ipb_enable_autoblock' => $this->mEnableAutoblock, 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ), 'ipb_range_start' => $this->mRangeStart, 'ipb_range_end' => $this->mRangeEnd, @@ -386,9 +389,124 @@ class Block ); $affected = $dbw->affectedRows(); $dbw->commit(); + + if ($affected) + $this->doRetroactiveAutoblock(); + return $affected; } + /** + * Retroactively autoblocks the last IP used by the user (if it is a user) + * blocked by this Block. + *@return Whether or not a retroactive autoblock was made. + */ + function doRetroactiveAutoblock() { + $dbr = wfGetDB( DB_SLAVE ); + #If autoblock is enabled, autoblock the LAST IP used + # - stolen shamelessly from CheckUser_body.php + + if ($this->mEnableAutoblock && $this->mUser) { + wfDebug("Doing retroactive autoblocks for " . $this->mAddress . "\n"); + + $row = $dbr->selectRow( 'recentchanges', array( 'rc_ip' ), array( 'rc_user_text' => $this->mAddress ), + __METHOD__ , array( 'ORDER BY' => 'rc_timestamp DESC' ) ); + + if ( !$row || !$row->rc_ip ) { + #No results, don't autoblock anything + wfDebug("No IP found to retroactively autoblock\n"); + } else { + #Limit is 1, so no loop needed. + $retroblockip = $row->rc_ip; + return $this->doAutoblock($retroblockip); + } + } + } + + /** + * Autoblocks the given IP, referring to this Block. + * @param $autoblockip The IP to autoblock. + * @return bool Whether or not an autoblock was inserted. + */ + function doAutoblock( $autoblockip ) { + # Check if this IP address is already blocked + $dbw =& wfGetDB( DB_MASTER ); + $dbw->begin(); + + # If autoblocks are disabled, go away. + if ( !$this->mEnableAutoblock ) { + return; + } + + # Check for presence on the autoblock whitelist + # TODO cache this? + $lines = explode( "\n", wfMsgForContentNoTrans( 'autoblock_whitelist' ) ); + + $ip = $autoblockip; + + wfDebug("Checking the autoblock whitelist..\n"); + + foreach( $lines as $line ) { + # List items only + if ( substr( $line, 0, 1 ) !== '*' ) { + continue; + } + + $wlEntry = substr($line, 1); + $wlEntry = trim($wlEntry); + + wfDebug("Checking $ip against $wlEntry..."); + + # Is the IP in this range? + if (IP::isInRange( $ip, $wlEntry )) { + wfDebug(" IP $ip matches $wlEntry, not autoblocking\n"); + #$autoblockip = null; # Don't autoblock a whitelisted IP. + return; #This /SHOULD/ introduce a dummy block - but + # I don't know a safe way to do so. -werdna + } else { + wfDebug( " No match\n" ); + } + } + + # It's okay to autoblock. Go ahead and create/insert the block. + + $ipblock = Block::newFromDB( $autoblockip ); + if ( $ipblock ) { + # If the user is already blocked. Then check if the autoblock would + # exceed the user block. If it would exceed, then do nothing, else + # prolong block time + if ($this->mExpiry && + ($this->mExpiry < Block::getAutoblockExpiry($ipblock->mTimestamp))) { + return; + } + # Just update the timestamp + $ipblock->updateTimestamp(); + return; + } else { + $ipblock = new Block; + } + + # Make a new block object with the desired properties + wfDebug( "Autoblocking {$this->mAddress}@" . $autoblockip . "\n" ); + $ipblock->mAddress = $autoblockip; + $ipblock->mUser = 0; + $ipblock->mBy = $this->mBy; + $ipblock->mReason = wfMsgForContent( 'autoblocker', $this->mAddress, $this->mReason ); + $ipblock->mTimestamp = wfTimestampNow(); + $ipblock->mAuto = 1; + $ipblock->mCreateAccount = $this->mCreateAccount; + + # If the user is already blocked with an expiry date, we don't + # want to pile on top of that! + if($this->mExpiry) { + $ipblock->mExpiry = min ( $this->mExpiry, Block::getAutoblockExpiry( $this->mTimestamp )); + } else { + $ipblock->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp ); + } + # Insert it + return $ipblock->insert(); + } + function deleteIfExpired() { $fname = 'Block::deleteIfExpired'; @@ -449,6 +567,16 @@ class Block return $this->mNetworkBits; }*/ + /** + * @return The blocker user ID. + */ + public function getBy() { + return $this->mBy; + } + + /** + * @return The blocker user name. + */ function getByName() { if ( $this->mByName === false ) { diff --git a/includes/CacheDependency.php b/includes/CacheDependency.php new file mode 100644 index 00000000..4bb3d328 --- /dev/null +++ b/includes/CacheDependency.php @@ -0,0 +1,328 @@ +<?php + +/** + * This class stores an arbitrary value along with its dependencies. + * Users should typically only use DependencyWrapper::getFromCache(), rather + * than instantiating one of these objects directly. + */ +class DependencyWrapper { + var $value; + var $deps; + + /** + * Create an instance. + * @param mixed $value The user-supplied value + * @param mixed $deps A dependency or dependency array. All dependencies + * must be objects implementing CacheDependency. + */ + function __construct( $value = false, $deps = array() ) { + $this->value = $value; + if ( !is_array( $deps ) ) { + $deps = array( $deps ); + } + $this->deps = $deps; + } + + /** + * Returns true if any of the dependencies have expired + */ + function isExpired() { + foreach ( $this->deps as $dep ) { + if ( $dep->isExpired() ) { + return true; + } + } + return false; + } + + /** + * Initialise dependency values in preparation for storing. This must be + * called before serialization. + */ + function initialiseDeps() { + foreach ( $this->deps as $dep ) { + $dep->loadDependencyValues(); + } + } + + /** + * Get the user-defined value + */ + function getValue() { + return $this->value; + } + + /** + * Store the wrapper to a cache + */ + function storeToCache( $cache, $key, $expiry = 0 ) { + $this->initialiseDeps(); + $cache->set( $key, $this, $expiry ); + } + + /** + * Attempt to get a value from the cache. If the value is expired or missing, + * it will be generated with the callback function (if present), and the newly + * calculated value will be stored to the cache in a wrapper. + * + * @param object $cache A cache object such as $wgMemc + * @param string $key The cache key + * @param integer $expiry The expiry timestamp or interval in seconds + * @param mixed $callback The callback for generating the value, or false + * @param array $callbackParams The function parameters for the callback + * @param array $deps The dependencies to store on a cache miss. Note: these + * are not the dependencies used on a cache hit! Cache hits use the stored + * dependency array. + * + * @return mixed The value, or null if it was not present in the cache and no + * callback was defined. + */ + static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false, + $callbackParams = array(), $deps = array() ) + { + $obj = $cache->get( $key ); + if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) { + $value = $obj->value; + } elseif ( $callback ) { + $value = call_user_func_array( $callback, $callbackParams ); + # Cache the newly-generated value + $wrapper = new DependencyWrapper( $value, $deps ); + $wrapper->storeToCache( $cache, $key, $expiry ); + } else { + $value = null; + } + return $value; + } +} + +abstract class CacheDependency { + /** + * Returns true if the dependency is expired, false otherwise + */ + abstract function isExpired(); + + /** + * Hook to perform any expensive pre-serialize loading of dependency values. + */ + function loadDependencyValues() {} +} + +class FileDependency extends CacheDependency { + var $filename, $timestamp; + + /** + * Create a file dependency + * + * @param string $filename The name of the file, preferably fully qualified + * @param mixed $timestamp The unix last modified timestamp, or false if the + * file does not exist. If omitted, the timestamp will be loaded from + * the file. + * + * A dependency on a nonexistent file will be triggered when the file is + * created. A dependency on an existing file will be triggered when the + * file is changed. + */ + function __construct( $filename, $timestamp = null ) { + $this->filename = $filename; + $this->timestamp = $timestamp; + } + + function loadDependencyValues() { + if ( is_null( $this->timestamp ) ) { + if ( !file_exists( $this->filename ) ) { + # Dependency on a non-existent file + # This is a valid concept! + $this->timestamp = false; + } else { + $this->timestamp = filemtime( $this->filename ); + } + } + } + + function isExpired() { + if ( !file_exists( $this->filename ) ) { + if ( $this->timestamp === false ) { + # Still nonexistent + return false; + } else { + # Deleted + wfDebug( "Dependency triggered: {$this->filename} deleted.\n" ); + return true; + } + } else { + $lastmod = filemtime( $this->filename ); + if ( $lastmod > $this->timestamp ) { + # Modified or created + wfDebug( "Dependency triggered: {$this->filename} changed.\n" ); + return true; + } else { + # Not modified + return false; + } + } + } +} + +class TitleDependency extends CacheDependency { + var $titleObj; + var $ns, $dbk; + var $touched; + + /** + * Construct a title dependency + * @param Title $title + */ + function __construct( Title $title ) { + $this->titleObj = $title; + $this->ns = $title->getNamespace(); + $this->dbk = $title->getDBkey(); + } + + function loadDependencyValues() { + $this->touched = $this->getTitle()->getTouched(); + } + + /** + * Get rid of bulky Title object for sleep + */ + function __sleep() { + return array( 'ns', 'dbk', 'touched' ); + } + + function getTitle() { + if ( !isset( $this->titleObj ) ) { + $this->titleObj = Title::makeTitle( $this->ns, $this->dbk ); + } + return $this->titleObj; + } + + function isExpired() { + $touched = $this->getTitle()->getTouched(); + if ( $this->touched === false ) { + if ( $touched === false ) { + # Still missing + return false; + } else { + # Created + return true; + } + } elseif ( $touched === false ) { + # Deleted + return true; + } elseif ( $touched > $this->touched ) { + # Updated + return true; + } else { + # Unmodified + return false; + } + } +} + +class TitleListDependency extends CacheDependency { + var $linkBatch; + var $timestamps; + + /** + * Construct a dependency on a list of titles + */ + function __construct( LinkBatch $linkBatch ) { + $this->linkBatch = $linkBatch; + } + + function calculateTimestamps() { + # Initialise values to false + $timestamps = array(); + foreach ( $this->getLinkBatch()->data as $ns => $dbks ) { + if ( count( $dbks ) > 0 ) { + $timestamps[$ns] = array(); + foreach ( $dbks as $dbk => $value ) { + $timestamps[$ns][$dbk] = false; + } + } + } + + # Do the query + if ( count( $timestamps ) ) { + $dbr =& wfGetDB( DB_SLAVE ); + $where = $this->getLinkBatch()->constructSet( 'page', $dbr ); + $res = $dbr->select( 'page', + array( 'page_namespace', 'page_title', 'page_touched' ), + $where, __METHOD__ ); + while ( $row = $dbr->fetchObject( $res ) ) { + $timestamps[$row->page_namespace][$row->page_title] = $row->page_touched; + } + } + return $timestamps; + } + + function loadDependencyValues() { + $this->timestamps = $this->calculateTimestamps(); + } + + function __sleep() { + return array( 'timestamps' ); + } + + function getLinkBatch() { + if ( !isset( $this->linkBatch ) ){ + $this->linkBatch = new LinkBatch; + $this->linkBatch->setArray( $this->timestamps ); + } + return $this->linkBatch; + } + + function isExpired() { + $newTimestamps = $this->calculateTimestamps(); + foreach ( $this->timestamps as $ns => $dbks ) { + foreach ( $dbks as $dbk => $oldTimestamp ) { + $newTimestamp = $newTimestamps[$ns][$dbk]; + if ( $oldTimestamp === false ) { + if ( $newTimestamp === false ) { + # Still missing + } else { + # Created + return true; + } + } elseif ( $newTimestamp === false ) { + # Deleted + return true; + } elseif ( $newTimestamp > $oldTimestamp ) { + # Updated + return true; + } else { + # Unmodified + } + } + } + return false; + } +} + +class GlobalDependency extends CacheDependency { + var $name, $value; + + function __construct( $name ) { + $this->name = $name; + $this->value = $GLOBALS[$name]; + } + + function isExpired() { + return $GLOBALS[$this->name] != $this->value; + } +} + +class ConstantDependency extends CacheDependency { + var $name, $value; + + function __construct( $name ) { + $this->name = $name; + $this->value = constant( $name ); + } + + function isExpired() { + return constant( $this->name ) != $this->value; + } +} + +?> diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php index e55d2976..0086a2f9 100644 --- a/includes/CategoryPage.php +++ b/includes/CategoryPage.php @@ -236,24 +236,31 @@ class CategoryViewer { $r = ''; if( count( $this->children ) > 0 ) { # Showing subcategories + $r .= "<div id=\"mw-subcategories\">\n"; $r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n"; $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), count( $this->children) ); $r .= $this->formatList( $this->children, $this->children_start_char ); + $r .= "\n</div>"; } return $r; } function getPagesSection() { $ti = htmlspecialchars( $this->title->getText() ); - $r = '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n"; + $r = "<div id=\"mw-pages\">\n"; + $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n"; $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), count( $this->articles) ); $r .= $this->formatList( $this->articles, $this->articles_start_char ); + $r .= "\n</div>"; return $r; } function getImageSection() { if( $this->showGallery && ! $this->gallery->isEmpty() ) { - return $this->gallery->toHTML(); + return "<div id=\"mw-category-media\">\n" . + '<h2>' . wfMsg( 'category-media-header', htmlspecialchars($this->title->getText()) ) . "</h2>\n" . + wfMsgExt( 'category-media-count', array( 'parse' ), $this->gallery->count() ) . + $this->gallery->toHTML() . "\n</div>"; } else { return ''; } diff --git a/includes/ChangesList.php b/includes/ChangesList.php index 6797bb41..a2c1a265 100644 --- a/includes/ChangesList.php +++ b/includes/ChangesList.php @@ -46,10 +46,10 @@ class ChangesList { * @param $user User to fetch the list class for * @return ChangesList derivative */ - function newFromUser( &$user ) { + public static function newFromUser( &$user ) { $sk =& $user->getSkin(); $list = NULL; - if( wfRunHooks( 'FetchChangesList', array( &$user, &$skin, &$list ) ) ) { + if( wfRunHooks( 'FetchChangesList', array( &$user, &$sk, &$list ) ) ) { return $user->getOption( 'usenewrc' ) ? new EnhancedChangesList( $sk ) : new OldChangesList( $sk ); } else { return $list; @@ -184,7 +184,7 @@ class ChangesList { $s .= ' '.$articlelink; } - function insertTimestamp(&$s, &$rc) { + function insertTimestamp(&$s, $rc) { global $wgLang; # Timestamp $s .= '; ' . $wgLang->time( $rc->mAttribs['rc_timestamp'], true, true ) . ' . . '; @@ -212,8 +212,6 @@ class ChangesList { global $wgUseRCPatrol, $wgUser; return( $wgUseRCPatrol && $wgUser->isAllowed( 'patrol' ) ); } - - } @@ -225,15 +223,13 @@ class OldChangesList extends ChangesList { * Format a line using the old system (aka without any javascript). */ function recentChangesLine( &$rc, $watched = false ) { - global $wgContLang; + global $wgContLang, $wgRCShowChangedSize; $fname = 'ChangesList::recentChangesLineOld'; wfProfileIn( $fname ); - # Extract DB fields into local scope extract( $rc->mAttribs ); - $curIdEq = 'curid=' . $rc_cur_id; # Should patrol-related stuff be shown? $unpatrolled = $this->usePatrol() && $rc_patrolled == 0; @@ -246,8 +242,13 @@ class OldChangesList extends ChangesList { if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { $this->insertMove( $s, $rc ); // log entries - } elseif( $rc_namespace == NS_SPECIAL && preg_match( '!^Log/(.*)$!', $rc_title, $matches ) ) { - $this->insertLog($s, $rc->getTitle(), $matches[1]); + } elseif ( $rc_namespace == NS_SPECIAL ) { + list( $specialName, $specialSubpage ) = SpecialPage::resolveAliasWithSubpage( $rc_title ); + if ( $specialName == 'Log' ) { + $this->insertLog( $s, $rc->getTitle(), $specialSubpage ); + } else { + wfDebug( "Unexpected special page in recentchanges\n" ); + } // all other stuff } else { wfProfileIn($fname.'-page'); @@ -264,6 +265,11 @@ class OldChangesList extends ChangesList { wfProfileIn( $fname.'-rest' ); $this->insertTimestamp($s,$rc); + + if( $wgRCShowChangedSize ) { + $s .= ( $rc->getCharacterDifference() == '' ? '' : $rc->getCharacterDifference() . ' . . ' ); + } + $this->insertUserRelatedLinks($s,$rc); $this->insertComment($s, $rc); @@ -321,11 +327,16 @@ class EnhancedChangesList extends ChangesList { $msg = ( $rc_type == RC_MOVE ) ? "1movedto2" : "1movedto2_redir"; $clink = wfMsg( $msg, $this->skin->makeKnownLinkObj( $rc->getTitle(), '', 'redirect=no' ), $this->skin->makeKnownLinkObj( $rc->getMovedToTitle(), '' ) ); - } elseif( $rc_namespace == NS_SPECIAL && preg_match( '!^Log/(.*)$!', $rc_title, $matches ) ) { - # Log updates, etc - $logtype = $matches[1]; - $logname = LogPage::logName( $logtype ); - $clink = '(' . $this->skin->makeKnownLinkObj( $rc->getTitle(), $logname ) . ')'; + } elseif( $rc_namespace == NS_SPECIAL ) { + list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $rc_title ); + if ( $specialName == 'Log' ) { + # Log updates, etc + $logname = LogPage::logName( $logtype ); + $clink = '(' . $this->skin->makeKnownLinkObj( $rc->getTitle(), $logname ) . ')'; + } else { + wfDebug( "Unexpected special page in recentchanges\n" ); + $clink = ''; + } } elseif( $rc->unpatrolled && $rc_type == RC_NEW ) { # Unpatrolled new page, give rc_id in query $clink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '', "rcid={$rc_id}" ); @@ -394,7 +405,7 @@ class EnhancedChangesList extends ChangesList { * Enhanced RC group */ function recentChangesBlockGroup( $block ) { - global $wgContLang; + global $wgContLang, $wgRCShowChangedSize; $r = ''; # Collate list of users @@ -403,7 +414,6 @@ class EnhancedChangesList extends ChangesList { $userlinks = array(); foreach( $block as $rcObj ) { $oldid = $rcObj->mAttribs['rc_last_oldid']; - $newid = $rcObj->mAttribs['rc_this_oldid']; if( $rcObj->mAttribs['rc_new'] ) { $isnew = true; } @@ -447,8 +457,7 @@ class EnhancedChangesList extends ChangesList { $r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, ' ', $bot ); # Timestamp - $r .= ' '.$block[0]->timestamp.' '; - $r .= '</tt>'; + $r .= ' '.$block[0]->timestamp.' </tt>'; # Article link $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched ); @@ -459,13 +468,23 @@ class EnhancedChangesList extends ChangesList { if( $block[0]->mAttribs['rc_type'] != RC_LOG ) { # Changes $r .= ' ('.count($block).' '; + if( $isnew ) { $r .= $this->message['changes']; } else { $r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(), $this->message['changes'], $curIdEq."&diff=$currentRevision&oldid=$oldid" ); } - $r .= '; '; + + # Character difference + $chardiff = $rcObj->getCharacterDifference( $block[ count( $block ) - 1 ]->mAttribs['rc_old_len'], + $block[0]->mAttribs['rc_new_len'] ); + if( $chardiff == '' ) { + $r .= '; '; + } else { + $r .= '; ' . $chardiff . ' '; + } + # History $r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(), @@ -508,7 +527,14 @@ class EnhancedChangesList extends ChangesList { $r .= $rcObj->curlink; $r .= '; '; $r .= $rcObj->lastlink; - $r .= ') . . '.$rcObj->userlink; + $r .= ') . . '; + + # Character diff + if( $wgRCShowChangedSize ) { + $r .= ( $rcObj->getCharacterDifference() == '' ? '' : $rcObj->getCharacterDifference() . ' . . ' ) ; + } + + $r .= $rcObj->userlink; $r .= $rcObj->usertalklink; $r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() ); $r .= "<br />\n"; @@ -578,7 +604,7 @@ class EnhancedChangesList extends ChangesList { * @return string a HTML formated line (generated using $r) */ function recentChangesBlockLine( $rcObj ) { - global $wgContLang; + global $wgContLang, $wgRCShowChangedSize; # Get rc_xxxx variables extract( $rcObj->mAttribs ); @@ -606,10 +632,15 @@ class EnhancedChangesList extends ChangesList { $r .= ' ('. $rcObj->difflink .'; '; # Hist - $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), $curIdEq.'&action=history' ); + $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), $curIdEq.'&action=history' ) . ') . . '; + + # Character diff + if( $wgRCShowChangedSize ) { + $r .= ( $rcObj->getCharacterDifference() == '' ? '' : ' ' . $rcObj->getCharacterDifference() . ' . . ' ) ; + } # User/talk - $r .= ') . . '.$rcObj->userlink . $rcObj->usertalklink; + $r .= $rcObj->userlink . $rcObj->usertalklink; # Comment if( $rc_type != RC_MOVE && $rc_type != RC_MOVE_OVER_REDIRECT ) { @@ -633,7 +664,7 @@ class EnhancedChangesList extends ChangesList { return ''; } $blockOut = ''; - foreach( $this->rc_cache as $secureName => $block ) { + foreach( $this->rc_cache as $block ) { if( count( $block ) < 2 ) { $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) ); } else { diff --git a/includes/CoreParserFunctions.php b/includes/CoreParserFunctions.php index 2081b3f2..402a3ba9 100644 --- a/includes/CoreParserFunctions.php +++ b/includes/CoreParserFunctions.php @@ -65,7 +65,6 @@ class CoreParserFunctions { static function fullurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeFullURL', $s, $arg ); } static function urlFunction( $func, $s = '', $arg = null ) { - $found = false; $title = Title::newFromText( $s ); # Due to order of execution of a lot of bits, the values might be encoded # before arriving here; if that's true, then the title can't be created @@ -79,28 +78,26 @@ class CoreParserFunctions { } else { $text = $title->$func(); } - $found = true; - } - if ( $found ) { return $text; } else { return array( 'found' => false ); } } - function formatNum( $parser, $num = '' ) { + static function formatNum( $parser, $num = '' ) { return $parser->getFunctionLang()->formatNum( $num ); } - function grammar( $parser, $case = '', $word = '' ) { + static function grammar( $parser, $case = '', $word = '' ) { return $parser->getFunctionLang()->convertGrammar( $word, $case ); } - function plural( $parser, $text = '', $arg0 = null, $arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null ) { + static function plural( $parser, $text = '', $arg0 = null, $arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null ) { + $text = $parser->getFunctionLang()->parseFormattedNumber( $text ); return $parser->getFunctionLang()->convertPlural( $text, $arg0, $arg1, $arg2, $arg3, $arg4 ); } - function displaytitle( $parser, $param = '' ) { + static function displaytitle( $parser, $param = '' ) { $parserOptions = new ParserOptions; $local_parser = clone $parser; $t2 = $local_parser->parse ( $param, $parser->mTitle, $parserOptions, false ); @@ -112,7 +109,7 @@ class CoreParserFunctions { return ''; } - function isRaw( $param ) { + static function isRaw( $param ) { static $mwRaw; if ( !$mwRaw ) { $mwRaw =& MagicWord::get( 'rawsuffix' ); @@ -124,23 +121,23 @@ class CoreParserFunctions { } } - function statisticsFunction( $func, $raw = null ) { + static function statisticsFunction( $func, $raw = null ) { if ( self::isRaw( $raw ) ) { - return call_user_func( $func ); + return call_user_func( array( 'SiteStats', $func ) ); } else { global $wgContLang; - return $wgContLang->formatNum( call_user_func( $func ) ); + return $wgContLang->formatNum( call_user_func( array( 'SiteStats', $func ) ) ); } } - function numberofpages( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfPages', $raw ); } - function numberofusers( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfUsers', $raw ); } - function numberofarticles( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfArticles', $raw ); } - function numberoffiles( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfFiles', $raw ); } - function numberofadmins( $parser, $raw = null ) { return self::statisticsFunction( 'wfNumberOfAdmins', $raw ); } + static function numberofpages( $parser, $raw = null ) { return self::statisticsFunction( 'pages', $raw ); } + static function numberofusers( $parser, $raw = null ) { return self::statisticsFunction( 'users', $raw ); } + static function numberofarticles( $parser, $raw = null ) { return self::statisticsFunction( 'articles', $raw ); } + static function numberoffiles( $parser, $raw = null ) { return self::statisticsFunction( 'images', $raw ); } + static function numberofadmins( $parser, $raw = null ) { return self::statisticsFunction( 'admins', $raw ); } - function pagesinnamespace( $parser, $namespace = 0, $raw = null ) { - $count = wfPagesInNs( intval( $namespace ) ); + static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) { + $count = SiteStats::pagesInNs( intval( $namespace ) ); if ( self::isRaw( $raw ) ) { global $wgContLang; return $wgContLang->formatNum( $count ); @@ -149,13 +146,13 @@ class CoreParserFunctions { } } - function language( $parser, $arg = '' ) { + static function language( $parser, $arg = '' ) { global $wgContLang; $lang = $wgContLang->getLanguageName( strtolower( $arg ) ); return $lang != '' ? $lang : $arg; } - function pad( $string = '', $length = 0, $char = 0, $direction = STR_PAD_RIGHT ) { + static function pad( $string = '', $length = 0, $char = 0, $direction = STR_PAD_RIGHT ) { $length = min( max( $length, 0 ), 500 ); $char = substr( $char, 0, 1 ); return ( $string && (int)$length > 0 && strlen( trim( (string)$char ) ) > 0 ) @@ -163,16 +160,32 @@ class CoreParserFunctions { : $string; } - function padleft( $parser, $string = '', $length = 0, $char = 0 ) { + static function padleft( $parser, $string = '', $length = 0, $char = 0 ) { return self::pad( $string, $length, $char, STR_PAD_LEFT ); } - function padright( $parser, $string = '', $length = 0, $char = 0 ) { + static function padright( $parser, $string = '', $length = 0, $char = 0 ) { return self::pad( $string, $length, $char ); } - function anchorencode( $parser, $text ) { - return str_replace( '%', '.', str_replace('+', '_', urlencode( $text ) ) ); + static function anchorencode( $parser, $text ) { + return strtr( urlencode( $text ) , array( '%' => '.' , '+' => '_' ) ); + } + + static function special( $parser, $text ) { + $title = SpecialPage::getTitleForAlias( $text ); + if ( $title ) { + return $title->getPrefixedText(); + } else { + return wfMsgForContent( 'nosuchspecialpage' ); + } + } + + public static function defaultsort( $parser, $text ) { + $text = trim( $text ); + if( strlen( $text ) > 0 ) + $parser->setDefaultSort( $text ); + return ''; } } diff --git a/includes/Database.php b/includes/Database.php index 53e59968..eb1ee135 100644 --- a/includes/Database.php +++ b/includes/Database.php @@ -90,8 +90,8 @@ class DBConnectionError extends DBError { } function getHTML() { - global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding, $wgOutputEncoding; - global $wgSitename, $wgServer, $wgMessageCache, $wgLogo; + global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding; + global $wgSitename, $wgServer, $wgMessageCache; # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky. # Hard coding strings instead. @@ -152,7 +152,7 @@ border=\"0\" ALT=\"Google\"></A> } } - $cache = new CacheManager( $t ); + $cache = new HTMLFileCache( $t ); if( $cache->isFileCached() ) { $msg = '<p style="color: red"><b>'.$msg."<br />\n" . $cachederror . "</b></p>\n"; @@ -295,7 +295,7 @@ class Database { * Turns on (false) or off (true) the automatic generation and sending * of a "we're sorry, but there has been a database error" page on * database errors. Default is on (false). When turned off, the - * code should use wfLastErrno() and wfLastError() to handle the + * code should use lastErrno() and lastError() to handle the * situation as appropriate. */ function ignoreErrors( $ignoreErrors = NULL ) { @@ -362,6 +362,20 @@ class Database { return $this->mStrictIPs; } + /** + * Returns true if this database uses timestamps rather than integers + */ + function realTimestamps() { + return false; + } + + /** + * Returns true if this database does an implicit sort when doing GROUP BY + */ + function implicitGroupby() { + return true; + } + /**#@+ * Get function */ @@ -613,7 +627,7 @@ class Database { # Add a comment for easy SHOW PROCESSLIST interpretation if ( $fname ) { - $commentedSql = preg_replace("/\s/", " /* $fname */ ", $sql, 1); + $commentedSql = preg_replace('/\s/', " /* $fname */ ", $sql, 1); } else { $commentedSql = $sql; } @@ -679,7 +693,7 @@ class Database { * @param bool $tempIgnore */ function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { - global $wgCommandLineMode, $wgFullyInitialised, $wgColorErrors; + global $wgCommandLineMode; # Ignore errors during error handling to avoid infinite recursion $ignore = $this->ignoreErrors( true ); ++$this->mErrorCount; @@ -778,7 +792,7 @@ class Database { case '\\!': return '!'; case '\\&': return '&'; } - list( $n, $arg ) = each( $this->preparedArgs ); + list( /* $n */ , $arg ) = each( $this->preparedArgs ); switch( $matches[1] ) { case '?': return $this->addQuotes( $arg ); case '!': return $arg; @@ -981,6 +995,7 @@ class Database { if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; # Various MySQL extensions + if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */'; if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY'; if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT'; if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT'; @@ -1000,6 +1015,14 @@ class Database { /** * SELECT wrapper + * + * @param mixed $table Array or string, table name(s) (prefix auto-added) + * @param mixed $vars Array or string, field name(s) to be retrieved + * @param mixed $conds Array or string, condition(s) for WHERE + * @param string $fname Calling function name (use __METHOD__) for logs/profiling + * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')), + * see Database::makeSelectOptions code for list of supported stuff + * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure */ function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array() ) { @@ -1010,7 +1033,7 @@ class Database { $options = array( $options ); } if( is_array( $table ) ) { - if ( @is_array( $options['USE INDEX'] ) ) + if ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) $from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] ); else $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) ); @@ -1082,7 +1105,7 @@ class Database { $sql = preg_replace ('/".*"/s', "'X'", $sql); # All newlines, tabs, etc replaced by single space - $sql = preg_replace ( "/\s+/", ' ', $sql); + $sql = preg_replace ( '/\s+/', ' ', $sql); # All numbers => N $sql = preg_replace ('/-?[0-9]+/s', 'N', $sql); @@ -1143,12 +1166,15 @@ class Database { return NULL; } + $result = array(); while ( $row = $this->fetchObject( $res ) ) { if ( $row->Key_name == $index ) { - return $row; + $result[] = $row; } } - return false; + $this->freeResult($res); + + return empty($result) ? false : $result; } /** @@ -1202,7 +1228,7 @@ class Database { if ( !$indexInfo ) { return NULL; } - return !$indexInfo->Non_unique; + return !$indexInfo[0]->Non_unique; } /** @@ -1292,7 +1318,7 @@ class Database { } /** - * Makes a wfStrencoded list from an array + * Makes an encoded list of strings from an array * $mode: * LIST_COMMA - comma separated, no field names * LIST_AND - ANDed WHERE clause (without the WHERE) @@ -1321,6 +1347,8 @@ class Database { } if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) { $list .= "($value)"; + } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) { + $list .= "$value"; } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array ($value) ) { $list .= $field." IN (".$this->makeList($value).") "; } else { @@ -1379,7 +1407,7 @@ class Database { * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; */ - function tableNames() { + public function tableNames() { $inArray = func_get_args(); $retVal = array(); foreach ( $inArray as $name ) { @@ -1387,6 +1415,24 @@ class Database { } return $retVal; } + + /** + * @desc: Fetch a number of table names into an zero-indexed numerical array + * This is handy when you need to construct SQL for joins + * + * Example: + * list( $user, $watchlist ) = $dbr->tableNames('user','watchlist'); + * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user + * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; + */ + public function tableNamesN() { + $inArray = func_get_args(); + $retVal = array(); + foreach ( $inArray as $name ) { + $retVal[] = $this->tableName( $name ); + } + return $retVal; + } /** * @private @@ -1528,7 +1574,8 @@ class Database { $row = $this->fetchObject( $res ); $this->freeResult( $res ); - if ( preg_match( "/\((.*)\)/", $row->Type, $m ) ) { + $m = array(); + if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { $size = $m[1]; } else { $size = -1; @@ -1829,7 +1876,7 @@ class Database { * @return string Version information from the database */ function getServerVersion() { - return mysql_get_server_info(); + return mysql_get_server_info( $this->mConn ); } /** @@ -1852,7 +1899,6 @@ class Database { $res = $this->query( 'SHOW PROCESSLIST' ); # Find slave SQL thread. Assumed to be the second one running, which is a bit # dubious, but unfortunately there's no easy rigorous way - $slaveThreads = 0; while ( $row = $this->fetchObject( $res ) ) { /* This should work for most situations - when default db * for thread is not specified, it had no events executed, diff --git a/includes/DatabaseFunctions.php b/includes/DatabaseFunctions.php index 74b35a31..ca83b9e5 100644 --- a/includes/DatabaseFunctions.php +++ b/includes/DatabaseFunctions.php @@ -1,8 +1,7 @@ <?php /** - * Backwards compatibility wrapper for Database.php - * - * Note: $wgDatabase has ceased to exist. Destroy all references. + * Legacy database functions, for compatibility with pre-1.3 code + * NOTE: this file is no longer loaded by default. * * @package MediaWiki */ @@ -15,7 +14,6 @@ * @param $fname String: name of the php function calling */ function wfQuery( $sql, $db, $fname = '' ) { - global $wgOut; if ( !is_numeric( $db ) ) { # Someone has tried to call this the old way throw new FatalError( wfMsgNoDB( 'wrong_wfQuery_params', $db, $sql ) ); @@ -44,15 +42,6 @@ function wfSingleQuery( $sql, $dbi, $fname = '' ) { return $ret; } -/* - * @todo document function - */ -function &wfGetDB( $db = DB_LAST, $groups = array() ) { - global $wgLoadBalancer; - $ret =& $wgLoadBalancer->getConnection( $db, true, $groups ); - return $ret; -} - /** * Turns on (false) or off (true) the automatic generation and sending * of a "we're sorry, but there has been a database error" page on diff --git a/includes/DatabaseOracle.php b/includes/DatabaseOracle.php index aa1e329e..1a6f62f2 100644 --- a/includes/DatabaseOracle.php +++ b/includes/DatabaseOracle.php @@ -55,9 +55,6 @@ class DatabaseOracle extends Database { $this->mPassword = $password; $this->mDBname = $dbName; - $success = false; - - $hstring=""; $this->mConn = oci_new_connect($user, $password, $dbName, "AL32UTF8"); if ( $this->mConn === false ) { wfDebug( "DB connection error\n" ); @@ -147,7 +144,6 @@ class DatabaseOracle extends Database { for ($i = 1; $i <= $this->mNcols[$res]; $i++) { $name = $this->mFieldNames[$res][$i]; - $type = $this->mFieldTypes[$res][$i]; if (isset($this->mFetchCache[$res][$this->mFetchID[$res]][$name])) $value = $this->mFetchCache[$res][$this->mFetchID[$res]][$name]; else $value = NULL; @@ -165,7 +161,7 @@ class DatabaseOracle extends Database { return false; $i = 0; $ret = array(); - foreach ($r as $key => $value) { + foreach ($r as $value) { wfdebug("ret[$i]=[$value]\n"); $ret[$i++] = $value; } @@ -201,14 +197,19 @@ class DatabaseOracle extends Database { function lastError() { if ($this->mErr === false) { - if ($this->mLastResult !== false) $what = $this->mLastResult; - else if ($this->mConn !== false) $what = $this->mConn; - else $what = false; + if ($this->mLastResult !== false) { + $what = $this->mLastResult; + } else if ($this->mConn !== false) { + $what = $this->mConn; + } else { + $what = false; + } $err = ($what !== false) ? oci_error($what) : oci_error(); - if ($err === false) + if ($err === false) { $this->mErr = 'no error'; - else + } else { $this->mErr = $err['message']; + } } return str_replace("\n", '<br />', $this->mErr); } @@ -239,6 +240,9 @@ class DatabaseOracle extends Database { $this->freeResult($res); $row->Non_unique = !$row->uniqueness; return $row; + + // BUG: !!!! This code needs to be synced up with database.php + } function indexUnique ($table, $index, $fname = 'indexUnique') { diff --git a/includes/DatabasePostgres.php b/includes/DatabasePostgres.php index a5e02e77..803c0e26 100644 --- a/includes/DatabasePostgres.php +++ b/includes/DatabasePostgres.php @@ -1,7 +1,7 @@ <?php /** - * This is Postgres database abstraction layer. + * This is the Postgres database abstraction layer. * * As it includes more generic version for DB functions, * than MySQL ones, some of them should be moved to parent @@ -18,7 +18,7 @@ class DatabasePostgres extends Database { $failFunction = false, $flags = 0 ) { - global $wgOut, $wgDBprefix, $wgCommandLineMode; + global $wgOut; # Can't get a reference if it hasn't been set yet if ( !isset( $wgOut ) ) { $wgOut = NULL; @@ -33,6 +33,14 @@ class DatabasePostgres extends Database { } + function realTimestamps() { + return true; + } + + function implicitGroupby() { + return false; + } + static function newFromParams( $server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0) { @@ -59,7 +67,6 @@ class DatabasePostgres extends Database { $this->mPassword = $password; $this->mDBname = $dbName; - $success = false; $hstring=""; if ($server!=false && $server!="") { $hstring="host=$server "; @@ -85,13 +92,14 @@ class DatabasePostgres extends Database { $this->mOpened = true; ## If this is the initial connection, setup the schema stuff and possibly create the user if (defined('MEDIAWIKI_INSTALL')) { - global $wgDBname, $wgDBuser, $wgDBpass, $wgDBsuperuser, $wgDBmwschema, - $wgDBts2schema, $wgDBts2locale; + global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema, + $wgDBts2schema; print "OK</li>\n"; print "<li>Checking the version of Postgres..."; $version = pg_fetch_result($this->doQuery("SELECT version()"),0,0); - if (!preg_match("/PostgreSQL (\d+\.\d+)(\S+)/", $version, $thisver)) { + $thisver = array(); + if (!preg_match('/PostgreSQL (\d+\.\d+)(\S+)/', $version, $thisver)) { print "<b>FAILED</b> (could not determine the version)</li>\n"; dieout("</ul>"); } @@ -131,7 +139,7 @@ class DatabasePostgres extends Database { dieout('</ul>'); } print "<li>Creating user <b>$wgDBuser</b>..."; - $safepass = $this->addQuotes($wgDBpass); + $safepass = $this->addQuotes($wgDBpassword); $SQL = "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass"; $this->doQuery($SQL); print "OK</li>\n"; @@ -460,6 +468,9 @@ class DatabasePostgres extends Database { while ( $row = $this->fetchObject( $res ) ) { if ( $row->indexname == $index ) { return $row; + + // BUG: !!!! This code needs to be synced up with database.php + } } return false; @@ -651,9 +662,8 @@ class DatabasePostgres extends Database { return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; } - # FIXME: actually detecting deadlocks might be nice function wasDeadlock() { - return false; + return $this->lastErrno() == '40P01'; } function timestamp( $ts=0 ) { @@ -669,11 +679,21 @@ class DatabasePostgres extends Database { function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { - $message = "A database error has occurred\n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; - throw new DBUnexpectedError($this, $message); + # Ignore errors during error handling to avoid infinite recursion + $ignore = $this->ignoreErrors( true ); + ++$this->mErrorCount; + + if ($ignore || $tempIgnore) { + wfDebug("SQL ERROR (ignored): $error\n"); + $this->ignoreErrors( $ignore ); + } + else { + $message = "A database error has occurred\n" . + "Query: $sql\n" . + "Function: $fname\n" . + "Error: $errno $error\n"; + throw new DBUnexpectedError($this, $message); + } } /** @@ -800,10 +820,10 @@ class DatabasePostgres extends Database { $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES "; while ( ! feof( $f ) ) { $line = fgets($f,1024); - if (!preg_match("/^\s*(\(.+?),(\d)\)/", $line, $matches)) { + $matches = array(); + if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) { continue; } - $yesno = $matches[2]; ## ? "'true'" : "'false'"; $this->query("$SQL $matches[1],$matches[2])"); } print " (table interwiki successfully populated)...\n"; @@ -827,13 +847,66 @@ class DatabasePostgres extends Database { return "E'$s[1]'"; } return "'" . pg_escape_string($s) . "'"; - return "E'" . pg_escape_string($s) . "'"; + // Unreachable: return "E'" . pg_escape_string($s) . "'"; } function quote_ident( $s ) { return '"' . preg_replace( '/"/', '""', $s) . '"'; } -} + /* For now, does nothing */ + function selectDB( $db ) { + return true; + } + + /** + * Returns an optional USE INDEX clause to go after the table, and a + * string to go at the end of the query + * + * @private + * + * @param array $options an associative array of options to be turned into + * an SQL query, valid keys are listed in the function. + * @return array + */ + function makeSelectOptions( $options ) { + $tailOpts = ''; + $startOpts = ''; + + $noKeyOptions = array(); + foreach ( $options as $key => $option ) { + if ( is_numeric( $key ) ) { + $noKeyOptions[$option] = true; + } + } + + if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}"; + if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}"; + + if (isset($options['LIMIT'])) { + $tailOpts .= $this->limitResult('', $options['LIMIT'], + isset($options['OFFSET']) ? $options['OFFSET'] : false); + } + + if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE'; + if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE'; + if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; + + if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { + $useIndex = $this->useIndexClause( $options['USE INDEX'] ); + } else { + $useIndex = ''; + } + + return array( $startOpts, $useIndex, $tailOpts ); + } + + function ping() { + wfDebug( "Function ping() not written for DatabasePostgres.php yet"); + return true; + } + + +} // end DatabasePostgres class ?> diff --git a/includes/DateFormatter.php b/includes/DateFormatter.php index dc077fdc..c795618a 100644 --- a/includes/DateFormatter.php +++ b/includes/DateFormatter.php @@ -129,10 +129,10 @@ class DateFormatter } for ( $i=1; $i<=self::LAST; $i++ ) { $this->mSource = $i; - if ( @$this->rules[$preference][$i] ) { + if ( isset ( $this->rules[$preference][$i] ) ) { # Specific rules $this->mTarget = $this->rules[$preference][$i]; - } elseif ( @$this->rules[self::ALL][$i] ) { + } elseif ( isset ( $this->rules[self::ALL][$i] ) ) { # General rules $this->mTarget = $this->rules[self::ALL][$i]; } elseif ( $preference ) { diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index ee1ed3a0..03697b69 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -32,7 +32,7 @@ require_once( 'includes/SiteConfiguration.php' ); $wgConf = new SiteConfiguration; /** MediaWiki version number */ -$wgVersion = '1.8.3'; +$wgVersion = '1.9.0'; /** Name of the site. It must be changed in LocalSettings.php */ $wgSitename = 'MediaWiki'; @@ -82,56 +82,84 @@ if( isset( $_SERVER['SERVER_PORT'] ) /** * The path we should point to. * It might be a virtual path in case with use apache mod_rewrite for example + * + * This *needs* to be set correctly. + * + * Other paths will be set to defaults based on it unless they are directly + * set in LocalSettings.php */ $wgScriptPath = '/wiki'; /** * Whether to support URLs like index.php/Page_title - * @global bool $wgUsePathInfo + * These often break when PHP is set up in CGI mode. + * PATH_INFO *may* be correct if cgi.fix_pathinfo is + * set, but then again it may not; lighttpd converts + * incoming path data to lowercase on systems with + * case-insensitive filesystems, and there have been + * reports of problems on Apache as well. + * + * To be safe we'll continue to keep it off by default. + * + * Override this to false if $_SERVER['PATH_INFO'] + * contains unexpectedly incorrect garbage, or to + * true if it is really correct. + * + * The default $wgArticlePath will be set based on + * this value at runtime, but if you have customized + * it, having this incorrectly set to true can + * cause redirect loops when "pretty URLs" are used. + * */ -$wgUsePathInfo = ( strpos( php_sapi_name(), 'cgi' ) === false ); +$wgUsePathInfo = + ( strpos( php_sapi_name(), 'cgi' ) === false ) && + ( strpos( php_sapi_name(), 'apache2filter' ) === false ) && + ( strpos( php_sapi_name(), 'isapi' ) === false ); /**#@+ * Script users will request to get articles * ATTN: Old installations used wiki.phtml and redirect.phtml - * make sure that LocalSettings.php is correctly set! - * @deprecated - */ -/** - * @global string $wgScript - */ -$wgScript = "{$wgScriptPath}/index.php"; -/** - * @global string $wgRedirectScript + * + * Will be set based on $wgScriptPath in Setup.php if not overridden + * in LocalSettings.php. Generally you should not need to change this + * unless you don't like seeing "index.php". */ -$wgRedirectScript = "{$wgScriptPath}/redirect.php"; +$wgScript = false; /// defaults to "{$wgScriptPath}/index.php" +$wgRedirectScript = false; /// defaults to "{$wgScriptPath}/redirect.php" /**#@-*/ /**#@+ + * These various web and file path variables are set to their defaults + * in Setup.php if they are not explicitly set from LocalSettings.php. + * If you do override them, be sure to set them all! + * + * These will relatively rarely need to be set manually, unless you are + * splitting style sheets or images outside the main document root. + * * @global string */ /** * style path as seen by users - * @global string $wgStylePath */ -$wgStylePath = "{$wgScriptPath}/skins"; +$wgStylePath = false; /// defaults to "{$wgScriptPath}/skins" /** * filesystem stylesheets directory - * @global string $wgStyleDirectory */ -$wgStyleDirectory = "{$IP}/skins"; +$wgStyleDirectory = false; /// defaults to "{$IP}/skins" $wgStyleSheetPath = &$wgStylePath; -$wgArticlePath = "{$wgScript}?title=$1"; -$wgUploadPath = "{$wgScriptPath}/images"; -$wgUploadDirectory = "{$IP}/images"; +$wgArticlePath = false; /// default to "{$wgScript}/$1" or "{$wgScript}?title=$1", depending on $wgUsePathInfo +$wgVariantArticlePath = false; +$wgUploadPath = false; /// defaults to "{$wgScriptPath}/images" +$wgUploadDirectory = false; /// defaults to "{$IP}/images" $wgHashedUploadDirectory = true; -$wgLogo = "{$wgUploadPath}/wiki.png"; +$wgLogo = false; /// defaults to "{$wgStylePath}/common/images/wiki.png" $wgFavicon = '/favicon.ico'; -$wgMathPath = "{$wgUploadPath}/math"; -$wgMathDirectory = "{$wgUploadDirectory}/math"; -$wgTmpDirectory = "{$wgUploadDirectory}/tmp"; +$wgMathPath = false; /// defaults to "{$wgUploadPath}/math" +$wgMathDirectory = false; /// defaults to "{$wgUploadDirectory}/math" +$wgTmpDirectory = false; /// defaults to "{$wgUploadDirectory}/tmp" $wgUploadBaseUrl = ""; /**#@-*/ @@ -290,20 +318,23 @@ $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 / dynamic libary -* @global string $wgLoadFileinfoExtension + * This should be used only if fileinfo is installed as a shared object + * or a dynamic libary + * @global string $wgLoadFileinfoExtension */ $wgLoadFileinfoExtension= false; -/** 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. +/** 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. */ $wgMimeDetectorCommand= NULL; # use internal mime_content_type function, available since php 4.3.0 #$wgMimeDetectorCommand= "file -bi"; #use external mime detector (Linux) -/** Switch for trivial mime detection. Used by thumb.php to disable all fance things, -* because only a few types of images are needed and file extensions can be trusted. +/** Switch for trivial mime detection. Used by thumb.php to disable all fance + * things, because only a few types of images are needed and file extensions + * can be trusted. */ $wgTrivialMimeDetection= false; @@ -424,6 +455,12 @@ $wgEnableEmail = true; $wgEnableUserEmail = true; /** + * Minimum time, in hours, which must elapse between password reminder + * emails for a given account. This is to prevent abuse by mail flooding. + */ +$wgPasswordReminderResendTime = 24; + +/** * SMTP Mode * For using a direct (authenticated) SMTP server connection. * Default to false or fill an array : @@ -650,6 +687,15 @@ $wgMimeType = 'text/html'; $wgJsMimeType = 'text/javascript'; $wgDocType = '-//W3C//DTD XHTML 1.0 Transitional//EN'; $wgDTD = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'; +$wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml'; + +# Permit other namespaces in addition to the w3.org default. +# Use the prefix for the key and the namespace for the value. For +# example: +# $wgXhtmlNamespaces['svg'] = 'http://www.w3.org/2000/svg'; +# Normally we wouldn't have to define this in the root <html> +# element, but IE needs it there in some circumstances. +$wgXhtmlNamespaces = array(); /** Enable to allow rewriting dates in page text. * DOES NOT FORMAT CORRECTLY FOR MOST LANGUAGES */ @@ -664,16 +710,29 @@ $wgAmericanDates = false; */ $wgTranslateNumerals = true; - -# Translation using MediaWiki: namespace -# This will increase load times by 25-60% unless memcached is installed -# Interface messages will be loaded from the database. +/** + * Translation using MediaWiki: namespace. + * This will increase load times by 25-60% unless memcached is installed. + * Interface messages will be loaded from the database. + */ $wgUseDatabaseMessages = true; + +/** + * Expiry time for the message cache key + */ $wgMsgCacheExpiry = 86400; +/** + * Maximum entry size in the message cache, in bytes + */ +$wgMaxMsgCacheEntrySize = 10000; + # Whether to enable language variant conversion. $wgDisableLangConversion = false; +# Default variant code, if false, the default will be the language code +$wgDefaultLanguageVariant = false; + /** * Show a bar of language selection links in the user login and user * registration forms; edit the "loginlanguagelinks" message to @@ -745,7 +804,12 @@ $wgMaxArticleSize = 2048; # Maximum article size in kilobytes $wgExtraSubtitle = ''; $wgSiteSupportPage = ''; # A page where you users can receive donations -$wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR"; +/*** + * If this lock file exists, the wiki will be forced into read-only mode. + * Its contents will be shown to users as part of the read-only warning + * message. + */ +$wgReadOnlyFile = false; /// defaults to "{$wgUploadDirectory}/lock_yBgMBwiR"; /** * The debug log file should be not be publicly accessible if it is used, as it @@ -912,6 +976,7 @@ $wgGroupPermissions['emailconfirmed']['emailconfirmed'] = true; // from various log pages by default $wgGroupPermissions['bot' ]['bot'] = true; $wgGroupPermissions['bot' ]['autoconfirmed'] = true; +$wgGroupPermissions['bot' ]['nominornewtalk'] = true; // Most extra permission abilities go to this group $wgGroupPermissions['sysop']['block'] = true; @@ -923,6 +988,7 @@ $wgGroupPermissions['sysop']['import'] = true; $wgGroupPermissions['sysop']['importupload'] = true; $wgGroupPermissions['sysop']['move'] = true; $wgGroupPermissions['sysop']['patrol'] = true; +$wgGroupPermissions['sysop']['autopatrol'] = true; $wgGroupPermissions['sysop']['protect'] = true; $wgGroupPermissions['sysop']['proxyunbannable'] = true; $wgGroupPermissions['sysop']['rollback'] = true; @@ -933,6 +999,7 @@ $wgGroupPermissions['sysop']['reupload-shared'] = true; $wgGroupPermissions['sysop']['unwatchedpages'] = true; $wgGroupPermissions['sysop']['autoconfirmed'] = true; $wgGroupPermissions['sysop']['upload_by_url'] = true; +$wgGroupPermissions['sysop']['ipblock-exempt'] = true; // Permission to change users' group assignments $wgGroupPermissions['bureaucrat']['userrights'] = true; @@ -950,14 +1017,14 @@ $wgGroupPermissions['bureaucrat']['userrights'] = true; # $wgGroupPermissions['developer']['siteadmin'] = true; /** - * Set of available actions that can be restricted via Special:Protect + * Set of available actions that can be restricted via action=protect * You probably shouldn't change this. * Translated trough restriction-* messages. */ $wgRestrictionTypes = array( 'edit', 'move' ); /** - * Set of permission keys that can be selected via Special:Protect. + * Set of permission keys that can be selected via action=protect. * 'autoconfirm' allows all registerd users if $wgAutoConfirmAge is 0. */ $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ); @@ -996,7 +1063,7 @@ $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/proxy_check.php"; +$wgProxyScriptPath = "$IP/includes/proxy_check.php"; /** */ $wgProxyMemcExpiry = 86400; /** This should always be customised in LocalSettings.php */ @@ -1023,6 +1090,14 @@ $wgCachePages = true; */ $wgCacheEpoch = '20030516000000'; +/** + * Bump this number when changing the global style sheets and JavaScript. + * It should be appended in the query string of static CSS and JS includes, + * to ensure that client-side caches don't keep obsolete copies of global + * styles. + */ +$wgStyleVersion = '42'; + # Server-side caching: @@ -1032,8 +1107,9 @@ $wgCacheEpoch = '20030516000000'; * Must set $wgShowIPinHeader = false */ $wgUseFileCache = false; + /** Directory where the cached page will be saved */ -$wgFileCacheDirectory = "{$wgUploadDirectory}/cache"; +$wgFileCacheDirectory = false; /// defaults to "{$wgUploadDirectory}/cache"; /** * When using the file cache, we can store the cached HTML gzipped to save disk @@ -1069,11 +1145,20 @@ $wgEnotifRevealEditorAddress = false; # UPO; reply-to address may be filled with $wgEnotifMinorEdits = true; # UPO; false: "minor edits" on pages do not trigger notification mails. # # Attention: _every_ change on a user_talk page trigger a notification mail (if the user is not yet notified) - /** Show watching users in recent changes, watchlist and page history views */ $wgRCShowWatchingUsers = false; # UPO /** Show watching users in Page views */ $wgPageShowWatchingUsers = false; +/** Show the amount of changed characters in recent changes */ +$wgRCShowChangedSize = true; + +/** + * If the difference between the character counts of the text + * before and after the edit is below that value, the value will be + * highlighted on the RC page. + */ +$wgRCChangedSizeThreshold = -500; + /** * Show "Updated (since my last visit)" marker in RC view, watchlist and history * view for watched pages with new changes */ @@ -1124,6 +1209,7 @@ $wgMaxSquidPurgeTitles = 400; $wgHTCPPort = 4827; $wgHTCPMulticastTTL = 1; # $wgHTCPMulticastAddress = "224.0.0.85"; +$wgHTCPMulticastAddress = false; # Cookie settings: # @@ -1155,10 +1241,8 @@ $wgAllowExternalImagesFrom = ''; $wgMiserMode = false; /** Disable all query pages if miser mode is on, not just some */ $wgDisableQueryPages = false; -/** Generate a watchlist once every hour or so */ -$wgUseWatchlistCache = false; -/** The hour or so mentioned above */ -$wgWLCacheTimeout = 3600; +/** 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" */ $wgWantedPagesThreshold = 1; /** Enable slow parser functions */ @@ -1425,7 +1509,7 @@ if( !isset( $wgCommandLineMode ) ) { # Recent changes settings # -/** Log IP addresses in the recentchanges table */ +/** Log IP addresses in the recentchanges table; can be accessed only by extensions (e.g. CheckUser) or a DB admin */ $wgPutIPinRC = true; /** @@ -1999,8 +2083,11 @@ $wgNoFollowLinks = true; $wgNoFollowNsExceptions = array(); /** - * Robot policies for namespaces - * e.g. $wgNamespaceRobotPolicies = array( NS_TALK => 'noindex' ); + * Robot policies per namespaces. + * The default policy is 'index,follow', the array is made of namespace + * constants as defined in includes/Defines.php + * Example: + * $wgNamespaceRobotPolicies = array( NS_TALK => 'noindex' ); */ $wgNamespaceRobotPolicies = array(); @@ -2043,6 +2130,7 @@ $wgDisableHardRedirects = false; * Use http.dnsbl.sorbs.net to check for open proxies */ $wgEnableSorbs = false; +$wgSorbsUrl = 'http.dnsbl.sorbs.net.'; /** * Proxy whitelist, list of addresses that are assumed to be non-proxy despite what the other @@ -2224,6 +2312,13 @@ $wgAjaxSearch = false; $wgAjaxExportList = array( ); /** + * Enable watching/unwatching pages using AJAX. + * Requires $wgUseAjax to be true too. + * Causes wfAjaxWatch to be added to $wgAjaxExportList + */ +$wgAjaxWatch = false; + +/** * Allow DISPLAYTITLE to change title display */ $wgAllowDisplayTitle = false ; @@ -2232,7 +2327,12 @@ $wgAllowDisplayTitle = false ; * Array of usernames which may not be registered or logged in from * Maintenance scripts can still use these */ -$wgReservedUsernames = array( 'MediaWiki default', 'Conversion script' ); +$wgReservedUsernames = array( + 'MediaWiki default', // Default 'Main Page' and MediaWiki: message pages + 'Conversion script', // Used for the old Wikipedia software upgrade + 'Maintenance script', // ... maintenance/edit.php uses this? + 'Template namespace initialisation script', // Used in 1.2->1.3 upgrade +); /** * MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't @@ -2288,7 +2388,32 @@ $wgDjvuPostProcessor = 'ppmtojpeg'; * Enable direct access to the data API * through api.php */ -$wgEnableAPI = false; +$wgEnableAPI = true; $wgEnableWriteAPI = false; +/** + * Parser test suite files to be run by parserTests.php when no specific + * filename is passed to it. + * + * Extensions may add their own tests to this array, or site-local tests + * may be added via LocalSettings.php + * + * Use full paths. + */ +$wgParserTestFiles = array( + "$IP/maintenance/parserTests.txt", +); + +/** + * Break out of framesets. This can be used to prevent external sites from + * framing your site with ads. + */ +$wgBreakFrames = false; + +/** + * Set this to an array of special page names to prevent + * maintenance/updateSpecialPages.php from updating those pages. + */ +$wgDisableQueryPageUpdate = false; + ?> diff --git a/includes/Defines.php b/includes/Defines.php index 40727485..84bc4495 100644 --- a/includes/Defines.php +++ b/includes/Defines.php @@ -193,6 +193,7 @@ define( 'EDIT_MINOR', 4 ); define( 'EDIT_SUPPRESS_RC', 8 ); define( 'EDIT_FORCE_BOT', 16 ); define( 'EDIT_DEFER_UPDATES', 32 ); +define( 'EDIT_AUTOSUMMARY', 64 ); /**#@-*/ /** diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php index 448bcb5d..a72f0153 100644 --- a/includes/DifferenceEngine.php +++ b/includes/DifferenceEngine.php @@ -97,12 +97,10 @@ CONTROL; return; } - $t = $this->mTitle->getPrefixedText() . " (Diff: {$this->mOldid}, " . - "{$this->mNewid})"; - $mtext = wfMsg( 'missingarticle', "<nowiki>$t</nowiki>" ); - $wgOut->setArticleFlag( false ); if ( ! $this->loadRevisionData() ) { + $t = $this->mTitle->getPrefixedText() . " (Diff: {$this->mOldid}, {$this->mNewid})"; + $mtext = wfMsg( 'missingarticle', "<nowiki>$t</nowiki>" ); $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) ); $wgOut->addWikitext( $mtext ); wfProfileOut( $fname ); @@ -144,15 +142,9 @@ CONTROL; } $sk = $wgUser->getSkin(); - $talk = $wgContLang->getNsText( NS_TALK ); - $contribs = wfMsg( 'contribslink' ); if ( $this->mNewRev->isCurrent() && $wgUser->isAllowed('rollback') ) { - $username = $this->mNewRev->getUserText(); - $rollback = ' <strong>[' . $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'rollbacklink' ), - 'action=rollback&from=' . urlencode( $username ) . - '&token=' . urlencode( $wgUser->editToken( array( $this->mTitle->getPrefixedText(), $username ) ) ) ) . - ']</strong>'; + $rollback = ' ' . $sk->generateRollback( $this->mNewRev ); } else { $rollback = ''; } @@ -171,13 +163,26 @@ CONTROL; 'diff=next&oldid='.$this->mNewid, '', '', 'id="differences-nextlink"' ); } + $oldminor = ''; + $newminor = ''; + + if ($this->mOldRev->mMinorEdit == 1) { + $oldminor = wfElement( 'span', array( 'class' => 'minor' ), + wfMsg( 'minoreditletter') ) . ' '; + } + + if ($this->mNewRev->mMinorEdit == 1) { + $newminor = wfElement( 'span', array( 'class' => 'minor' ), + wfMsg( 'minoreditletter') ) . ' '; + } + $oldHeader = "<strong>{$this->mOldtitle}</strong><br />" . $sk->revUserTools( $this->mOldRev ) . "<br />" . - $sk->revComment( $this->mOldRev ) . "<br />" . + $oldminor . $sk->revComment( $this->mOldRev, true ) . "<br />" . $prevlink; $newHeader = "<strong>{$this->mNewtitle}</strong><br />" . $sk->revUserTools( $this->mNewRev ) . " $rollback<br />" . - $sk->revComment( $this->mNewRev ) . "<br />" . + $newminor . $sk->revComment( $this->mNewRev, true ) . "<br />" . $nextlink . $patrol; $this->showDiff( $oldHeader, $newHeader ); @@ -287,7 +292,8 @@ CONTROL; if ( $body === false ) { return false; } else { - return $this->addHeader( $body, $otitle, $ntitle ); + $multi = $this->getMultiNotice(); + return $this->addHeader( $body, $otitle, $ntitle, $multi ); } } @@ -426,20 +432,49 @@ CONTROL; return wfMsgExt( 'lineno', array('parseinline'), $wgLang->formatNum( $matches[1] ) ); } + + /** + * If there are revisions between the ones being compared, return a note saying so. + */ + function getMultiNotice() { + if ( !is_object($this->mOldRev) || !is_object($this->mNewRev) ) + return ''; + + if( !$this->mOldPage->equals( $this->mNewPage ) ) { + // Comparing two different pages? Count would be meaningless. + return ''; + } + + $oldid = $this->mOldRev->getId(); + $newid = $this->mNewRev->getId(); + if ( $oldid > $newid ) { + $tmp = $oldid; $oldid = $newid; $newid = $tmp; + } + + $n = $this->mTitle->countRevisionsBetween( $oldid, $newid ); + if ( !$n ) + return ''; + + return wfMsgExt( 'diff-multi', array( 'parseinline' ), $n ); + } + + /** * Add the header to a diff body */ - function addHeader( $diff, $otitle, $ntitle ) { - $out = " + function addHeader( $diff, $otitle, $ntitle, $multi = '' ) { + $header = " <table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'> <tr> <td colspan='2' width='50%' align='center' class='diff-otitle'>{$otitle}</td> <td colspan='2' width='50%' align='center' class='diff-ntitle'>{$ntitle}</td> </tr> - $diff - </table> "; - return $out; + + if ( $multi != '' ) + $header .= "<tr><td colspan='4' align='center' class='diff-multi'>{$multi}</td></tr>"; + + return $header . $diff . "</table>"; } /** @@ -488,17 +523,21 @@ CONTROL; $newLink = $this->mNewPage->escapeLocalUrl(); $this->mPagetitle = htmlspecialchars( wfMsg( 'currentrev' ) ); $newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit' ); - - $this->mNewtitle = "<strong><a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)</strong>" - . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"; + $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undo=' . $this->mNewid ); + + $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)" + . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)" + . " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)"; } else { $newLink = $this->mNewPage->escapeLocalUrl( 'oldid=' . $this->mNewid ); $newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mNewid ); + $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undo=' . $this->mNewid ); $this->mPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $timestamp ) ); - - $this->mNewtitle = "<strong><a href='$newLink'>{$this->mPagetitle}</a></strong>" - . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"; + + $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>" + . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)" + . " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)"; } // Load the old revision object @@ -527,8 +566,8 @@ CONTROL; $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true ); $oldLink = $this->mOldPage->escapeLocalUrl( 'oldid=' . $this->mOldid ); $oldEdit = $this->mOldPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mOldid ); - $this->mOldtitle = "<strong><a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) ) - . "</a></strong> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"; + $this->mOldtitle = "<a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) ) + . "</a> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"; } return true; @@ -890,7 +929,7 @@ class _DiffEngine $ymids[$k] = $ymids[$k-1]; break; } - while (list ($junk, $y) = each($matches)) { + while (list ( /* $junk */, $y) = each($matches)) { if ($y > $this->seq[$k-1]) { USE_ASSERTS && assert($y < $this->seq[$k]); // Optimization: this is a common case: @@ -1608,6 +1647,7 @@ class WordLevelDiff extends MappedDiff $words[] = $line; $stripped[] = $line; } else { + $m = array(); if (preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs', $line, $m)) { diff --git a/includes/DjVuImage.php b/includes/DjVuImage.php index 871c563b..f7297dc2 100644 --- a/includes/DjVuImage.php +++ b/includes/DjVuImage.php @@ -217,7 +217,7 @@ class DjVuImage { global $wgDjvuToXML; if ( isset( $wgDjvuToXML ) ) { $cmd = $wgDjvuToXML . ' --without-anno --without-text ' . $this->mFilename; - $xml = wfShellExec( $cmd, $retval ); + $xml = wfShellExec( $cmd ); } else { $xml = null; } diff --git a/includes/EditPage.php b/includes/EditPage.php index a1207d10..c53389cc 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -32,6 +32,7 @@ class EditPage { var $allowBlankSummary = false; var $autoSumm = ''; var $hookError = ''; + var $mPreviewTemplates; # Form values var $save = false, $preview = false, $diff = false; @@ -40,6 +41,14 @@ class EditPage { var $edittime = '', $section = '', $starttime = ''; var $oldid = 0, $editintro = '', $scrolltop = null; + # Placeholders for text injection by hooks (must be HTML) + # extensions should take care to _append_ to the present value + public $editFormPageTop; // Before even the preview + public $editFormTextTop; + public $editFormTextAfterWarn; + public $editFormTextAfterTools; + public $editFormTextBottom; + /** * @todo document * @param $article @@ -48,17 +57,25 @@ class EditPage { $this->mArticle =& $article; global $wgTitle; $this->mTitle =& $wgTitle; + + # Placeholders for text injection by hooks (empty per default) + $this->editFormPageTop = + $this->editFormTextTop = + $this->editFormTextAfterWarn = + $this->editFormTextAfterTools = + $this->editFormTextBottom = ""; } /** * Fetch initial editing page content. */ private function getContent() { - global $wgRequest, $wgParser; + global $wgOut, $wgRequest, $wgParser; # Get variables from query string :P $section = $wgRequest->getVal( 'section' ); $preload = $wgRequest->getVal( 'preload' ); + $undo = $wgRequest->getVal( 'undo' ); wfProfileIn( __METHOD__ ); @@ -79,8 +96,41 @@ class EditPage { // information. $text = $this->mArticle->getContent(); - - if( $section != '' ) { + + if ( $undo > 0 ) { + #Undoing a specific edit overrides section editing; section-editing + # doesn't work with undoing. + $undorev = Revision::newFromId($undo); + + #Sanity check, make sure it's the right page. + # Otherwise, $text will be left as-is. + if (!is_null($undorev) && $undorev->getPage() == $this->mArticle->getID()) { + $oldrev = $undorev->getPrevious(); + $undorev_text = $undorev->getText(); + $oldrev_text = $oldrev->getText(); + $currev_text = $text; + + #No use doing a merge if it's just a straight revert. + if ($currev_text != $undorev_text) { + $result = wfMerge($undorev_text, $oldrev_text, $currev_text, $text); + } else { + $text = $oldrev_text; + $result = true; + } + + if( $result ) { + # Inform the user of our success and set an automatic edit summary + $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-success' ) ); + $this->summary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() ); + $this->formtype = 'diff'; + } else { + # Warn the user that something went wrong + $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-failure' ) ); + } + + } + } + else if( $section != '' ) { if( $section == 'new' ) { $text = $this->getPreloadedText( $preload ); } else { @@ -439,6 +489,9 @@ class EditPage { # The unmarked state will be assumed to be a save, # if the form seems otherwise complete. wfDebug( "$fname: Passed token check.\n" ); + } else if ( $this->diff ) { + # Failed token check, but only requested "Show Changes". + wfDebug( "$fname: Failed token check; Show Changes requested.\n" ); } else { # Page might be a hack attempt posted from # an external site. Preview instead of saving. @@ -507,8 +560,8 @@ class EditPage { global $wgUser; if( $wgUser->isAnon() ) { # Anonymous users may not have a session - # open. Don't tokenize. - $this->mTokenOk = true; + # open. Check for suffix anyway. + $this->mTokenOk = ( EDIT_TOKEN_SUFFIX == $request->getVal( 'wpEditToken' ) ); } else { $this->mTokenOk = $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); } @@ -549,11 +602,18 @@ class EditPage { wfProfileIn( $fname ); wfProfileIn( "$fname-checks" ); + if( !wfRunHooks( 'EditPage::attemptSave', array( &$this ) ) ) + { + wfDebug( "Hook 'EditPage::attemptSave' aborted article saving" ); + return false; + } + # Reintegrate metadata if ( $this->mMetaData != '' ) $this->textbox1 .= "\n" . $this->mMetaData ; $this->mMetaData = '' ; # Check for spam + $matches = array(); if ( $wgSpamRegex && preg_match( $wgSpamRegex, $this->textbox1, $matches ) ) { $this->spamPage ( $matches[0] ); wfProfileOut( "$fname-checks" ); @@ -634,6 +694,7 @@ class EditPage { # If article is new, insert it. $aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE ); if ( 0 == $aid ) { + // Late check for create permission, just in case *PARANOIA* if ( !$this->mTitle->userCanCreate() ) { wfDebug( "$fname: no create permission\n" ); @@ -649,14 +710,6 @@ class EditPage { return false; } - # If no edit comment was given when creating a new page, and what's being - # created is a redirect, be smart and fill in a neat auto-comment - if( $this->summary == '' ) { - $rt = Title::newFromRedirect( $this->textbox1 ); - if( is_object( $rt ) ) - $this->summary = wfMsgForContent( 'autoredircomment', $rt->getPrefixedText() ); - } - $isComment=($this->section=='new'); $this->mArticle->insertNewArticle( $this->textbox1, $this->summary, $this->minoredit, $this->watchthis, false, $isComment); @@ -728,16 +781,11 @@ class EditPage { return true; } - # If no edit comment was given when turning a page into a redirect, be smart - # and fill in a neat auto-comment - if( $this->summary == '' ) { - $rt = Title::newFromRedirect( $this->textbox1 ); - if( is_object( $rt ) ) - $this->summary = wfMsgForContent( 'autoredircomment', $rt->getPrefixedText() ); - } + $oldtext = $this->mArticle->getContent(); - # Handle the user preference to force summaries here - if( $this->section != 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) { + # Handle the user preference to force summaries here, but not for null edits + if( $this->section != 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary') + && 0 != strcmp($oldtext, $text) && !Article::getRedirectAutosummary( $text )) { if( md5( $this->summary ) == $this->autoSumm ) { $this->missingSummary = true; wfProfileOut( $fname ); @@ -745,6 +793,15 @@ class EditPage { } } + #And a similar thing for new sections + if( $this->section == 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) { + if (trim($this->summary) == '') { + $this->missingSummary = true; + wfProfileOut( $fname ); + return( true ); + } + } + # All's well wfProfileIn( "$fname-sectionanchor" ); $sectionanchor = ''; @@ -802,8 +859,8 @@ class EditPage { */ function initialiseForm() { $this->edittime = $this->mArticle->getTimestamp(); - $this->textbox1 = $this->getContent(); $this->summary = ''; + $this->textbox1 = $this->getContent(); if ( !$this->mArticle->exists() && $this->mArticle->mTitle->getNamespace() == NS_MEDIAWIKI ) $this->textbox1 = wfMsgWeirdKey( $this->mArticle->mTitle->getText() ) ; wfProxyCheck(); @@ -845,6 +902,7 @@ class EditPage { $s = wfMsg('editingcomment', $this->mTitle->getPrefixedText() ); } else { $s = wfMsg('editingsection', $this->mTitle->getPrefixedText() ); + $matches = array(); if( !$this->summary && !$this->preview && !$this->diff ) { preg_match( "/^(=+)(.+)\\1/mi", $this->textbox1, @@ -863,9 +921,13 @@ class EditPage { $wgOut->addWikiText( wfMsg( 'missingcommenttext' ) ); } - if( $this->missingSummary ) { + if( $this->missingSummary && $this->section != 'new' ) { $wgOut->addWikiText( wfMsg( 'missingsummary' ) ); } + + if( $this->missingSummary && $this->section == 'new' ) { + $wgOut->addWikiText( wfMsg( 'missingcommentheader' ) ); + } if( !$this->hookError == '' ) { $wgOut->addWikiText( $this->hookError ); @@ -924,6 +986,12 @@ class EditPage { $wgOut->addWikiText( wfMsg( 'longpagewarning', $wgLang->formatNum( $this->kblength ) ) ); } + #need to parse the preview early so that we know which templates are used, + #otherwise users with "show preview after edit box" will get a blank list + if ( $this->formtype == 'preview' ) { + $previewOutput = $this->getPreviewText(); + } + $rows = $wgUser->getIntOption( 'rows' ); $cols = $wgUser->getIntOption( 'cols' ); @@ -998,10 +1066,12 @@ class EditPage { $checkboxhtml = $minoredithtml . $watchhtml; + $wgOut->addHTML( $this->editFormPageTop ); + if ( $wgUser->getOption( 'previewontop' ) ) { if ( 'preview' == $this->formtype ) { - $this->showPreview(); + $this->showPreview( $previewOutput ); } else { $wgOut->addHTML( '<div id="wikiPreview"></div>' ); } @@ -1012,22 +1082,29 @@ class EditPage { } + $wgOut->addHTML( $this->editFormTextTop ); + # if this is a comment, show a subject line at the top, which is also the edit summary. # Otherwise, show a summary field at the bottom $summarytext = htmlspecialchars( $wgContLang->recodeForEdit( $this->summary ) ); # FIXME if( $this->section == 'new' ) { $commentsubject="<span id='wpSummaryLabel'><label for='wpSummary'>{$subject}:</label></span>\n<div class='editOptions'>\n<input tabindex='1' type='text' value=\"$summarytext\" name='wpSummary' id='wpSummary' maxlength='200' size='60' /><br />"; $editsummary = ''; + $subjectpreview = $summarytext && $this->preview ? "<div class=\"mw-summary-preview\">".wfMsg('subject-preview').':'.$sk->commentBlock( $this->summary, $this->mTitle )."</div>\n" : ''; + $summarypreview = ''; } else { $commentsubject = ''; $editsummary="<span id='wpSummaryLabel'><label for='wpSummary'>{$summary}:</label></span>\n<div class='editOptions'>\n<input tabindex='2' type='text' value=\"$summarytext\" name='wpSummary' id='wpSummary' maxlength='200' size='60' /><br />"; + $summarypreview = $summarytext && $this->preview ? "<div class=\"mw-summary-preview\">".wfMsg('summary-preview').':'.$sk->commentBlock( $this->summary, $this->mTitle )."</div>\n" : ''; + $subjectpreview = ''; } # Set focus to the edit box on load, except on preview or diff, where it would interfere with the display if( !$this->preview && !$this->diff ) { $wgOut->setOnloadHandler( 'document.editform.wpTextbox1.focus()' ); } - $templates = $this->formatTemplates(); + $templates = ($this->preview || $this->section) ? $this->mPreviewTemplates : $this->mArticle->getUsedTemplates(); + $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != ''); global $wgUseMetadataEdit ; if ( $wgUseMetadataEdit ) { @@ -1138,6 +1215,7 @@ END $wgOut->addHTML( <<<END $recreate {$commentsubject} +{$subjectpreview} <textarea tabindex='1' accesskey="," name="wpTextbox1" id="wpTextbox1" rows='{$rows}' cols='{$cols}'{$ew} $hidden> END @@ -1147,9 +1225,11 @@ END " ); $wgOut->addWikiText( $copywarn ); + $wgOut->addHTML( $this->editFormTextAfterWarn ); $wgOut->addHTML( " {$metadata} {$editsummary} +{$summarypreview} {$checkboxhtml} {$safemodehtml} "); @@ -1164,26 +1244,36 @@ END </div><!-- editButtons --> </div><!-- editOptions -->"); + $wgOut->addHtml( '<div class="mw-editTools">' ); $wgOut->addWikiText( wfMsgForContent( 'edittools' ) ); + $wgOut->addHtml( '</div>' ); + + $wgOut->addHTML( $this->editFormTextAfterTools ); $wgOut->addHTML( " <div class='templatesUsed'> -{$templates} +{$formattedtemplates} </div> " ); - if ( $wgUser->isLoggedIn() ) { - /** - * To make it harder for someone to slip a user a page - * which submits an edit form to the wiki without their - * knowledge, a random token is associated with the login - * session. If it's not passed back with the submission, - * we won't save the page, or render user JavaScript and - * CSS previews. - */ + /** + * To make it harder for someone to slip a user a page + * which submits an edit form to the wiki without their + * knowledge, a random token is associated with the login + * session. If it's not passed back with the submission, + * we won't save the page, or render user JavaScript and + * CSS previews. + * + * For anon editors, who may not have a session, we just + * include the constant suffix to prevent editing from + * broken text-mangling proxies. + */ + if ( $wgUser->isLoggedIn() ) $token = htmlspecialchars( $wgUser->editToken() ); - $wgOut->addHTML( "\n<input type='hidden' value=\"$token\" name=\"wpEditToken\" />\n" ); - } + else + $token = EDIT_TOKEN_SUFFIX; + $wgOut->addHTML( "\n<input type='hidden' value=\"$token\" name=\"wpEditToken\" />\n" ); + # If a blank edit summary was previously provided, and the appropriate # user preference is active, pass a hidden tag here. This will stop the @@ -1209,11 +1299,12 @@ END $wgOut->addHTML( "<textarea tabindex=6 id='wpTextbox2' name=\"wpTextbox2\" rows='{$rows}' cols='{$cols}' wrap='virtual'>" . htmlspecialchars( $this->safeUnicodeOutput( $this->textbox2 ) ) . "\n</textarea>" ); } + $wgOut->addHTML( $this->editFormTextBottom ); $wgOut->addHTML( "</form>\n" ); if ( !$wgUser->getOption( 'previewontop' ) ) { if ( $this->formtype == 'preview') { - $this->showPreview(); + $this->showPreview( $previewOutput ); } else { $wgOut->addHTML( '<div id="wikiPreview"></div>' ); } @@ -1230,56 +1321,24 @@ END /** * Append preview output to $wgOut. * Includes category rendering if this is a category page. - * @private + * + * @param string $text The HTML to be output for the preview. */ - function showPreview() { + private function showPreview( $text ) { global $wgOut; + $wgOut->addHTML( '<div id="wikiPreview">' ); if($this->mTitle->getNamespace() == NS_CATEGORY) { $this->mArticle->openShowCategory(); } - $previewOutput = $this->getPreviewText(); - $wgOut->addHTML( $previewOutput ); + $wgOut->addHTML( $text ); if($this->mTitle->getNamespace() == NS_CATEGORY) { $this->mArticle->closeShowCategory(); } - $wgOut->addHTML( "<br style=\"clear:both;\" />\n" ); $wgOut->addHTML( '</div>' ); } /** - * Prepare a list of templates used by this page. Returns HTML. - */ - function formatTemplates() { - global $wgUser; - - $fname = 'EditPage::formatTemplates'; - wfProfileIn( $fname ); - - $sk =& $wgUser->getSkin(); - - $outText = ''; - $templates = $this->mArticle->getUsedTemplates(); - if ( count( $templates ) > 0 ) { - # Do a batch existence check - $batch = new LinkBatch; - foreach( $templates as $title ) { - $batch->addObj( $title ); - } - $batch->execute(); - - # Construct the HTML - $outText = '<br />'. wfMsgExt( 'templatesused', array( 'parseinline' ) ) . '<ul>'; - foreach ( $templates as $titleObj ) { - $outText .= '<li>' . $sk->makeLinkObj( $titleObj ) . '</li>'; - } - $outText .= '</ul>'; - } - wfProfileOut( $fname ); - return $outText; - } - - /** * Live Preview lets us fetch rendered preview page content and * add it to the page without refreshing the whole page. * If not supported by the browser it will fall through to the normal form @@ -1290,9 +1349,9 @@ END * of the preview button */ function doLivePreviewScript() { - global $wgStylePath, $wgJsMimeType, $wgOut, $wgTitle; + global $wgStylePath, $wgJsMimeType, $wgStyleVersion, $wgOut, $wgTitle; $wgOut->addHTML( '<script type="'.$wgJsMimeType.'" src="' . - htmlspecialchars( $wgStylePath . '/common/preview.js' ) . + htmlspecialchars( "$wgStylePath/common/preview.js?$wgStyleVersion" ) . '"></script>' . "\n" ); $liveAction = $wgTitle->getLocalUrl( 'action=submit&wpPreview=true&live=true' ); return "return !livePreview(" . @@ -1395,6 +1454,10 @@ END $previewHTML = $parserOutput->getText(); $wgOut->addParserOutputNoText( $parserOutput ); + foreach ( $parserOutput->getTemplates() as $ns => $template) + foreach ( array_keys( $template ) as $dbk) + $this->mPreviewTemplates[] = Title::makeTitle($ns, $dbk); + wfProfileOut( $fname ); return $previewhead . $previewHTML; } @@ -1434,7 +1497,7 @@ END global $wgUser, $wgOut; $skin = $wgUser->getSkin(); - $loginTitle = Title::makeTitle( NS_SPECIAL, 'Userlogin' ); + $loginTitle = SpecialPage::getTitleFor( 'Userlogin' ); $loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $this->mTitle->getPrefixedUrl() ); $wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) ); @@ -1508,6 +1571,7 @@ END } $currentText = $currentRevision->getText(); + $result = ''; if( wfMerge( $baseText, $editText, $currentText, $result ) ){ $editText = $result; wfProfileOut( $fname ); @@ -1583,15 +1647,15 @@ END */ $toolarray=array( array( 'image'=>'button_bold.png', - 'open' => "\'\'\'", - 'close' => "\'\'\'", + 'open' => '\\\'\\\'\\\'', + 'close' => '\\\'\\\'\\\'', 'sample'=> wfMsg('bold_sample'), 'tip' => wfMsg('bold_tip'), 'key' => 'B' ), array( 'image'=>'button_italic.png', - 'open' => "\'\'", - 'close' => "\'\'", + 'open' => '\\\'\\\'', + 'close' => '\\\'\\\'', 'sample'=> wfMsg('italic_sample'), 'tip' => wfMsg('italic_tip'), 'key' => 'I' diff --git a/includes/Exception.php b/includes/Exception.php index 56f18d5a..ac9c8a21 100644 --- a/includes/Exception.php +++ b/includes/Exception.php @@ -165,7 +165,7 @@ function wfInstallExceptionHandler() { * Report an exception to the user */ function wfReportException( Exception $e ) { - if ( is_a( $e, 'MWException' ) ) { + if ( $e instanceof MWException ) { try { $e->report(); } catch ( Exception $e2 ) { diff --git a/includes/Exif.php b/includes/Exif.php index 2ab0feb1..0860d5f7 100644 --- a/includes/Exif.php +++ b/includes/Exif.php @@ -439,7 +439,7 @@ class Exif { return false; } - if ( preg_match( "/^\s*$/", $in ) ) { + if ( preg_match( '/^\s*$/', $in ) ) { $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' ); return false; } @@ -468,7 +468,8 @@ class Exif { } function isRational( $in ) { - if ( !is_array( $in ) && @preg_match( "/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/", $in, $m ) ) { # Avoid division by zero + $m = array(); + if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero return $this->isLong( $m[1] ) && $this->isLong( $m[2] ); } else { $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' ); @@ -477,7 +478,7 @@ class Exif { } function isUndefined( $in ) { - if ( !is_array( $in ) && preg_match( "/^\d{4}$/", $in ) ) { // Allow ExifVersion and FlashpixVersion + if ( !is_array( $in ) && preg_match( '/^\d{4}$/', $in ) ) { // Allow ExifVersion and FlashpixVersion $this->debug( $in, __FUNCTION__, true ); return true; } else { @@ -497,7 +498,8 @@ class Exif { } function isSrational( $in ) { - if ( !is_array( $in ) && preg_match( "/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/", $in, $m ) ) { # Avoid division by zero + $m = array(); + if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] ); } else { $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' ); @@ -729,7 +731,9 @@ class FormatExif { case 'DateTime': case 'DateTimeOriginal': case 'DateTimeDigitized': - if( preg_match( "/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/", $val ) ) { + if( $val == '0000:00:00 00:00:00' ) { + $tags[$tag] = wfMsg('exif-unknowndate'); + } elseif( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/', $val ) ) { $tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) ); } break; @@ -1054,6 +1058,7 @@ class FormatExif { * @return mixed A floating point number or whatever we were fed */ function formatNum( $num ) { + $m = array(); if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) ) return $m[2] != 0 ? $m[1] / $m[2] : $num; else @@ -1069,6 +1074,7 @@ class FormatExif { * @return mixed A floating point number or whatever we were fed */ function formatFraction( $num ) { + $m = array(); if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) ) { $numerator = intval( $m[1] ); $denominator = intval( $m[2] ); diff --git a/includes/Export.php b/includes/Export.php index aa70e27b..b7e0f9a1 100644 --- a/includes/Export.php +++ b/includes/Export.php @@ -337,8 +337,7 @@ class XmlDumpWriter { } function homelink() { - $page = Title::newFromText( wfMsgForContent( 'mainpage' ) ); - return wfElement( 'base', array(), $page->getFullUrl() ); + return wfElement( 'base', array(), Title::newMainPage()->getFullUrl() ); } function caseSetting() { @@ -597,7 +596,7 @@ class DumpFilter { * Override for page-based filter types. * @return bool */ - function pass( $page, $string ) { + function pass( $page ) { return true; } } diff --git a/includes/Feed.php b/includes/Feed.php index 7663e820..5c14865d 100644 --- a/includes/Feed.php +++ b/includes/Feed.php @@ -149,12 +149,12 @@ class ChannelFeed extends FeedItem { * @private */ function outXmlHeader() { - global $wgServer, $wgStylePath; + global $wgServer, $wgStylePath, $wgStyleVersion; $this->httpHeaders(); echo '<?xml version="1.0" encoding="utf-8"?>' . "\n"; echo '<?xml-stylesheet type="text/css" href="' . - htmlspecialchars( "$wgServer$wgStylePath/common/feed.css" ) . '"?' . ">\n"; + htmlspecialchars( "$wgServer$wgStylePath/common/feed.css?$wgStyleVersion" ) . '"?' . ">\n"; } } diff --git a/includes/FileStore.php b/includes/FileStore.php index 35ebd554..1fd35b01 100644 --- a/includes/FileStore.php +++ b/includes/FileStore.php @@ -36,6 +36,9 @@ class FileStore { * @fixme Probably only works on MySQL. Abstract to the Database class? */ static function lock() { + global $wgDBtype; + if ($wgDBtype != 'mysql') + return true; $dbw = wfGetDB( DB_MASTER ); $lockname = $dbw->addQuotes( FileStore::lockName() ); $result = $dbw->query( "SELECT GET_LOCK($lockname, 5) AS lockstatus", __METHOD__ ); @@ -54,10 +57,13 @@ class FileStore { * Release the global file store lock. */ static function unlock() { + global $wgDBtype; + if ($wgDBtype != 'mysql') + return true; $dbw = wfGetDB( DB_MASTER ); $lockname = $dbw->addQuotes( FileStore::lockName() ); $result = $dbw->query( "SELECT RELEASE_LOCK($lockname)", __METHOD__ ); - $row = $dbw->fetchObject( $result ); + $dbw->fetchObject( $result ); $dbw->freeResult( $result ); } diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 623f9d3b..08094ca1 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -9,24 +9,16 @@ * Some globals and requires needed */ -/** - * Total number of articles - * @global integer $wgNumberOfArticles - */ +/** Total number of articles */ $wgNumberOfArticles = -1; # Unset -/** - * Total number of views - * @global integer $wgTotalViews - */ + +/** Total number of views */ $wgTotalViews = -1; -/** - * Total number of edits - * @global integer $wgTotalEdits - */ + +/** Total number of edits */ $wgTotalEdits = -1; -require_once( 'DatabaseFunctions.php' ); require_once( 'LogPage.php' ); require_once( 'normal/UtfNormalUtil.php' ); require_once( 'XmlFunctions.php' ); @@ -53,6 +45,7 @@ if( !function_exists('iconv') ) { # UTF-8 substr function based on a PHP manual comment if ( !function_exists( 'mb_substr' ) ) { function mb_substr( $str, $start ) { + $ar = array(); preg_match_all( '/./us', $str, $ar ); if( func_num_args() >= 3 ) { @@ -72,7 +65,7 @@ if ( !function_exists( 'array_diff_key' ) ) { */ function array_diff_key( $left, $right ) { $result = $left; - foreach ( $left as $key => $value ) { + foreach ( $left as $key => $unused ) { if ( isset( $right[$key] ) ) { unset( $result[$key] ); } @@ -114,7 +107,7 @@ function wfSeedRandom() { function wfRandom() { # The maximum random value is "only" 2^31-1, so get two random # values to reduce the chance of dupes - $max = mt_getrandmax(); + $max = mt_getrandmax() + 1; $rand = number_format( (mt_rand() * $max + mt_rand()) / $max / $max, 12, '.', '' ); return $rand; @@ -282,6 +275,10 @@ function wfReadOnly() { * * @param $key String: lookup key for the message, usually * defined in languages/Language.php + * + * This function also takes extra optional parameters (not + * shown in the function definition), which can by used to + * insert variable text into the predefined message. */ function wfMsg( $key ) { $args = func_get_args(); @@ -295,7 +292,7 @@ function wfMsg( $key ) { function wfMsgNoTrans( $key ) { $args = func_get_args(); array_shift( $args ); - return wfMsgReal( $key, $args, true, false ); + return wfMsgReal( $key, $args, true, false, false ); } /** @@ -371,14 +368,14 @@ function wfMsgNoDBForContent( $key ) { /** * Really get a message - * @return $key String: key to get. - * @return $args - * @return $useDB Boolean + * @param $key String: key to get. + * @param $args + * @param $useDB Boolean + * @param $transform Boolean: Whether or not to transform the message. + * @param $forContent Boolean * @return String: the requested message. */ function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform = true ) { - $fname = 'wfMsgReal'; - $message = wfMsgGetKey( $key, $useDB, $forContent, $transform ); $message = wfMsgReplaceArgs( $message, $args ); return $message; @@ -522,9 +519,10 @@ function wfMsgWikiHtml( $key ) { * <i>parseinline<i>: parses wikitext to html and removes the surrounding p's added by parser or tidy * <i>escape<i>: filters message trough htmlspecialchars * <i>replaceafter<i>: parameters are substituted after parsing or escaping + * <i>parsemag<i>: ?? */ function wfMsgExt( $key, $options ) { - global $wgOut, $wgMsgParserOptions, $wgParser; + global $wgOut, $wgParser; $args = func_get_args(); array_shift( $args ); @@ -549,12 +547,10 @@ function wfMsgExt( $key, $options ) { $string = $m[1]; } } elseif ( in_array('parsemag', $options) ) { - global $wgTitle; - $parser = new Parser(); - $parserOptions = new ParserOptions(); - $parserOptions->setInterfaceMessage( true ); - $parser->startExternalParse( $wgTitle, $parserOptions, OT_MSG ); - $string = $parser->transformMsg( $string, $parserOptions ); + global $wgMessageCache; + if ( isset( $wgMessageCache ) ) { + $string = $wgMessageCache->transform( $string ); + } } if ( in_array('escape', $options) ) { @@ -583,8 +579,8 @@ function wfAbruptExit( $error = false ){ } $called = true; - if( function_exists( 'debug_backtrace' ) ){ // PHP >= 4.3 - $bt = debug_backtrace(); + $bt = wfDebugBacktrace(); + if( $bt ) { for($i = 0; $i < count($bt) ; $i++){ $file = isset($bt[$i]['file']) ? $bt[$i]['file'] : "unknown"; $line = isset($bt[$i]['line']) ? $bt[$i]['line'] : "unknown"; @@ -666,18 +662,36 @@ function wfHostname() { return $com; } +/** + * Safety wrapper for debug_backtrace(). + * + * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat + * murky circumstances, which may be triggered in part by stub objects + * or other fancy talkin'. + * + * Will return an empty array if Zend Optimizer is detected, otherwise + * the output from debug_backtrace() (trimmed). + * + * @return array of backtrace information + */ +function wfDebugBacktrace() { + if( extension_loaded( 'Zend Optimizer' ) ) { + wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" ); + return array(); + } else { + return array_slice( debug_backtrace(), 1 ); + } +} + function wfBacktrace() { global $wgCommandLineMode; - if ( !function_exists( 'debug_backtrace' ) ) { - return false; - } if ( $wgCommandLineMode ) { $msg = ''; } else { $msg = "<ul>\n"; } - $backtrace = debug_backtrace(); + $backtrace = wfDebugBacktrace(); foreach( $backtrace as $call ) { if( isset( $call['file'] ) ) { $f = explode( DIRECTORY_SEPARATOR, $call['file'] ); @@ -801,6 +815,7 @@ function wfClientAcceptsGzip() { global $wgUseGzip; if( $wgUseGzip ) { # FIXME: we may want to blacklist some broken browsers + $m = array(); if( preg_match( '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/', $_SERVER['HTTP_ACCEPT_ENCODING'], @@ -966,6 +981,7 @@ function wfEscapeShellArg( ) { } // Double the backslashes before the end of the string, because // we will soon add a quote + $m = array(); if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) { $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] ); } @@ -1063,16 +1079,75 @@ function wfHttpError( $code, $label, $desc ) { $wgOut->sendCacheControl(); header( 'Content-type: text/html' ); - print "<html><head><title>" . + print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">". + "<html><head><title>" . htmlspecialchars( $label ) . "</title></head><body><h1>" . htmlspecialchars( $label ) . "</h1><p>" . - htmlspecialchars( $desc ) . + nl2br( htmlspecialchars( $desc ) ) . "</p></body></html>\n"; } /** + * Clear away any user-level output buffers, discarding contents. + * + * Suitable for 'starting afresh', for instance when streaming + * relatively large amounts of data without buffering, or wanting to + * output image files without ob_gzhandler's compression. + * + * The optional $resetGzipEncoding parameter controls suppression of + * the Content-Encoding header sent by ob_gzhandler; by default it + * is left. See comments for wfClearOutputBuffers() for why it would + * be used. + * + * Note that some PHP configuration options may add output buffer + * layers which cannot be removed; these are left in place. + * + * @parameter bool $resetGzipEncoding + */ +function wfResetOutputBuffers( $resetGzipEncoding=true ) { + 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. + // + // Give up, and hope the result doesn't break + // output behavior. + break; + } + 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' ) { + // Reset the 'Content-Encoding' field set by this handler + // so we can start fresh. + header( 'Content-Encoding:' ); + } + } + } +} + +/** + * More legible than passing a 'false' parameter to wfResetOutputBuffers(): + * + * Clear away output buffers, but keep the Content-Encoding header + * produced by ob_gzhandler, if any. + * + * This should be used for HTTP 304 responses, where you need to + * preserve the Content-Encoding header of the real result, but + * also need to suppress the output of ob_gzhandler to keep to spec + * and avoid breaking Firefox in rare cases where the headers and + * body are broken over two packets. + */ +function wfClearOutputBuffers() { + wfResetOutputBuffers( false ); +} + +/** * Converts an Accept-* header into an array mapping string values to quality * factors */ @@ -1089,6 +1164,7 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) { foreach( $parts as $part ) { # FIXME: doesn't deal with params like 'text/html; level=1' @list( $value, $qpart ) = explode( ';', $part ); + $match = array(); if( !isset( $qpart ) ) { $prefs[$value] = 1; } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) { @@ -1283,19 +1359,19 @@ function wfTimestamp($outputtype=TS_UNIX,$ts=0) { $da = array(); if ($ts==0) { $uts=time(); - } elseif (preg_match("/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D",$ts,$da)) { + } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) { # TS_DB $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], (int)$da[2],(int)$da[3],(int)$da[1]); - } elseif (preg_match("/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D",$ts,$da)) { + } elseif (preg_match('/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) { # TS_EXIF $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], (int)$da[2],(int)$da[3],(int)$da[1]); - } elseif (preg_match("/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D",$ts,$da)) { + } elseif (preg_match('/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D',$ts,$da)) { # TS_MW $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], (int)$da[2],(int)$da[3],(int)$da[1]); - } elseif (preg_match("/^(\d{1,13})$/D",$ts,$datearray)) { + } elseif (preg_match('/^(\d{1,13})$/D',$ts,$da)) { # TS_UNIX $uts = $ts; } elseif (preg_match('/^(\d{1,2})-(...)-(\d\d(\d\d)?) (\d\d)\.(\d\d)\.(\d\d)/', $ts, $da)) { @@ -1306,7 +1382,11 @@ function wfTimestamp($outputtype=TS_UNIX,$ts=0) { # TS_ISO_8601 $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], (int)$da[2],(int)$da[3],(int)$da[1]); - } elseif (preg_match("/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)[\+\- ](\d\d)$/",$ts,$da)) { + } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)[\+\- ](\d\d)$/',$ts,$da)) { + # TS_POSTGRES + $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], + (int)$da[2],(int)$da[3],(int)$da[1]); + } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/',$ts,$da)) { # TS_POSTGRES $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], (int)$da[2],(int)$da[3],(int)$da[1]); @@ -1383,10 +1463,21 @@ function wfGetCachedNotice( $name ) { wfProfileIn( $fname ); $needParse = false; - $notice = wfMsgForContent( $name ); - if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) { - wfProfileOut( $fname ); - return( false ); + + if( $name === 'default' ) { + // special case + global $wgSiteNotice; + $notice = $wgSiteNotice; + if( empty( $notice ) ) { + wfProfileOut( $fname ); + return false; + } + } else { + $notice = wfMsgForContentNoTrans( $name ); + if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) { + wfProfileOut( $fname ); + return( false ); + } } $cachedNotice = $parserMemc->get( wfMemcKey( $name ) ); @@ -1446,16 +1537,17 @@ function wfGetSiteNotice() { if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) { if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) { $siteNotice = wfGetCachedNotice( 'sitenotice' ); - $siteNotice = !$siteNotice ? $wgSiteNotice : $siteNotice; } else { $anonNotice = wfGetCachedNotice( 'anonnotice' ); if( !$anonNotice ) { $siteNotice = wfGetCachedNotice( 'sitenotice' ); - $siteNotice = !$siteNotice ? $wgSiteNotice : $siteNotice; } else { $siteNotice = $anonNotice; } } + if( !$siteNotice ) { + $siteNotice = wfGetCachedNotice( 'default' ); + } } wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) ); @@ -1677,7 +1769,7 @@ function wfShellExec( $cmd, &$retval=null ) { $output = array(); $retval = 1; // error by default? - $lastline = exec( $cmd, $output, $retval ); + exec( $cmd, $output, $retval ); // returns the last line of output. return implode( "\n", $output ); } @@ -1725,16 +1817,10 @@ function wfUseMW( $req_ver ) { } /** - * Escape a string to make it suitable for inclusion in a preg_replace() - * replacement parameter. - * - * @param string $string - * @return string + * @deprecated use StringUtils::escapeRegexReplacement */ function wfRegexReplacement( $string ) { - $string = str_replace( '\\', '\\\\', $string ); - $string = str_replace( '$', '\\$', $string ); - return $string; + return StringUtils::escapeRegexReplacement( $string ); } /** @@ -1749,6 +1835,7 @@ function wfRegexReplacement( $string ) { * @return string */ function wfBaseName( $path ) { + $matches = array(); if( preg_match( '#([^/\\\\]*)[/\\\\]*$#', $path, $matches ) ) { return $matches[1]; } else { @@ -1804,42 +1891,12 @@ function wfDoUpdates() } /** - * More or less "markup-safe" explode() - * Ignores any instances of the separator inside <...> - * @param string $separator - * @param string $text - * @return array + * @deprecated use StringUtils::explodeMarkup */ function wfExplodeMarkup( $separator, $text ) { - $placeholder = "\x00"; - - // Just in case... - $text = str_replace( $placeholder, '', $text ); - - // Trim stuff - $replacer = new ReplacerCallback( $separator, $placeholder ); - $cleaned = preg_replace_callback( '/(<.*?>)/', array( $replacer, 'go' ), $text ); - - $items = explode( $separator, $cleaned ); - foreach( $items as $i => $str ) { - $items[$i] = str_replace( $placeholder, $separator, $str ); - } - - return $items; + return StringUtils::explodeMarkup( $separator, $text ); } -class ReplacerCallback { - function ReplacerCallback( $from, $to ) { - $this->from = $from; - $this->to = $to; - } - - function go( $matches ) { - return str_replace( $this->from, $this->to, $matches[1] ); - } -} - - /** * Convert an arbitrarily-long digit string from one numeric base * to another, optionally zero-padding to a minimum column width. @@ -1999,7 +2056,7 @@ function wfGetPrecompiledData( $name ) { } function wfGetCaller( $level = 2 ) { - $backtrace = debug_backtrace(); + $backtrace = wfDebugBacktrace(); if ( isset( $backtrace[$level] ) ) { if ( isset( $backtrace[$level]['class'] ) ) { $caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function']; @@ -2020,7 +2077,7 @@ function wfGetAllCallers() { $frame["class"]."::".$frame["function"]: $frame["function"]; '), - array_reverse(debug_backtrace()))); + array_reverse(wfDebugBacktrace()))); } /** @@ -2063,4 +2120,19 @@ function wfWikiID() { } } +/* + * Get a Database object + * @param integer $db Index of the connection to get. May be DB_MASTER for the + * master (for write queries), DB_SLAVE for potentially lagged + * read queries, or an integer >= 0 for a particular server. + * + * @param mixed $groups Query groups. An array of group names that this query + * belongs to. May contain a single string if the query is only + * in one group. + */ +function &wfGetDB( $db = DB_LAST, $groups = array() ) { + global $wgLoadBalancer; + $ret = $wgLoadBalancer->getConnection( $db, true, $groups ); + return $ret; +} ?> diff --git a/includes/HTMLCacheUpdate.php b/includes/HTMLCacheUpdate.php index 47703b20..bda4720d 100644 --- a/includes/HTMLCacheUpdate.php +++ b/includes/HTMLCacheUpdate.php @@ -55,7 +55,6 @@ class HTMLCacheUpdate $numRows = $res->numRows(); $numBatches = ceil( $numRows / $this->mRowsPerJob ); $realBatchSize = $numRows / $numBatches; - $boundaries = array(); $start = false; $jobs = array(); do { @@ -176,7 +175,7 @@ class HTMLCacheUpdate # Update file cache if ( $wgUseFileCache ) { foreach ( $titles as $title ) { - $cm = new CacheManager($title); + $cm = new HTMLFileCache($title); @unlink($cm->fileCacheName()); } } diff --git a/includes/HTMLFileCache.php b/includes/HTMLFileCache.php new file mode 100644 index 00000000..d85a4411 --- /dev/null +++ b/includes/HTMLFileCache.php @@ -0,0 +1,159 @@ +<?php +/** + * Contain the HTMLFileCache class + * @package MediaWiki + * @subpackage Cache + */ + +/** + * Handles talking to the file cache, putting stuff in and taking it back out. + * Mostly called from Article.php, also from DatabaseFunctions.php for the + * emergency abort/fallback to cache. + * + * Global options that affect this module: + * $wgCachePages + * $wgCacheEpoch + * $wgUseFileCache + * $wgFileCacheDirectory + * $wgUseGzip + * @package MediaWiki + */ +class HTMLFileCache { + var $mTitle, $mFileCache; + + function HTMLFileCache( &$title ) { + $this->mTitle =& $title; + $this->mFileCache = ''; + } + + function fileCacheName() { + global $wgFileCacheDirectory; + if( !$this->mFileCache ) { + $key = $this->mTitle->getPrefixedDbkey(); + $hash = md5( $key ); + $key = str_replace( '.', '%2E', urlencode( $key ) ); + + $hash1 = substr( $hash, 0, 1 ); + $hash2 = substr( $hash, 0, 2 ); + $this->mFileCache = "{$wgFileCacheDirectory}/{$hash1}/{$hash2}/{$key}.html"; + + if($this->useGzip()) + $this->mFileCache .= '.gz'; + + wfDebug( " fileCacheName() - {$this->mFileCache}\n" ); + } + return $this->mFileCache; + } + + function isFileCached() { + return file_exists( $this->fileCacheName() ); + } + + function fileCacheTime() { + return wfTimestamp( TS_MW, filemtime( $this->fileCacheName() ) ); + } + + function isFileCacheGood( $timestamp ) { + global $wgCacheEpoch; + + if( !$this->isFileCached() ) return false; + + $cachetime = $this->fileCacheTime(); + $good = (( $timestamp <= $cachetime ) && + ( $wgCacheEpoch <= $cachetime )); + + wfDebug(" isFileCacheGood() - cachetime $cachetime, touched {$timestamp} epoch {$wgCacheEpoch}, good $good\n"); + return $good; + } + + function useGzip() { + global $wgUseGzip; + return $wgUseGzip; + } + + /* In handy string packages */ + function fetchRawText() { + return file_get_contents( $this->fileCacheName() ); + } + + function fetchPageText() { + if( $this->useGzip() ) { + /* Why is there no gzfile_get_contents() or gzdecode()? */ + return implode( '', gzfile( $this->fileCacheName() ) ); + } else { + return $this->fetchRawText(); + } + } + + /* Working directory to/from output */ + function loadFromFileCache() { + global $wgOut, $wgMimeType, $wgOutputEncoding, $wgContLanguageCode; + wfDebug(" loadFromFileCache()\n"); + + $filename=$this->fileCacheName(); + $wgOut->sendCacheControl(); + + header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" ); + header( "Content-language: $wgContLanguageCode" ); + + if( $this->useGzip() ) { + if( wfClientAcceptsGzip() ) { + header( 'Content-Encoding: gzip' ); + } else { + /* Send uncompressed */ + readgzfile( $filename ); + return; + } + } + readfile( $filename ); + } + + function checkCacheDirs() { + $filename = $this->fileCacheName(); + $mydir2=substr($filename,0,strrpos($filename,'/')); # subdirectory level 2 + $mydir1=substr($mydir2,0,strrpos($mydir2,'/')); # subdirectory level 1 + + if(!file_exists($mydir1)) { mkdir($mydir1,0775); } # create if necessary + if(!file_exists($mydir2)) { mkdir($mydir2,0775); } + } + + function saveToFileCache( $origtext ) { + $text = $origtext; + if(strcmp($text,'') == 0) return ''; + + wfDebug(" saveToFileCache()\n", false); + + $this->checkCacheDirs(); + + $f = fopen( $this->fileCacheName(), 'w' ); + if($f) { + $now = wfTimestampNow(); + if( $this->useGzip() ) { + $rawtext = str_replace( '</html>', + '<!-- Cached/compressed '.$now." -->\n</html>", + $text ); + $text = gzencode( $rawtext ); + } else { + $text = str_replace( '</html>', + '<!-- Cached '.$now." -->\n</html>", + $text ); + } + fwrite( $f, $text ); + fclose( $f ); + if( $this->useGzip() ) { + if( wfClientAcceptsGzip() ) { + header( 'Content-Encoding: gzip' ); + return $text; + } else { + return $rawtext; + } + } else { + return $text; + } + } + return $text; + } + +} + +?> diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php index 3ee85859..189e5c79 100644 --- a/includes/HTMLForm.php +++ b/includes/HTMLForm.php @@ -99,7 +99,7 @@ class HTMLForm { if ( $this->mRequest->wasPosted() ) { $arr = $this->mRequest->getArray( $varname ); if ( is_array( $arr ) ) { - foreach ( $_POST[$varname] as $index => $element ) { + foreach ( $_POST[$varname] as $element ) { $s .= htmlspecialchars( $element )."\n"; } } diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php index 357c1d48..a06b620d 100644 --- a/includes/HistoryBlob.php +++ b/includes/HistoryBlob.php @@ -226,7 +226,7 @@ class HistoryBlobStub { $flags = explode( ',', $row->old_flags ); if( in_array( 'external', $flags ) ) { $url=$row->old_text; - @list($proto,$path)=explode('://',$url,2); + @list( /* $proto */ ,$path)=explode('://',$url,2); if ($path=="") { wfProfileOut( $fname ); return false; diff --git a/includes/Hooks.php b/includes/Hooks.php index 575a28c5..2eecfd72 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -31,7 +31,6 @@ function wfRunHooks($event, $args = null) { global $wgHooks; - $fname = 'wfRunHooks'; if (!is_array($wgHooks)) { throw new MWException("Global hooks array is not an array!\n"); diff --git a/includes/IP.php b/includes/IP.php index f3ff3427..edf4af7a 100644 --- a/includes/IP.php +++ b/includes/IP.php @@ -10,11 +10,15 @@ // Some regex definition to "play" with IP address and IP address blocks // An IP is made of 4 bytes from x00 to xFF which is d0 to d255 -define( 'RE_IP_BYTE', '(25[0-5]|2[0-4]\d|1?\d{1,2})'); +define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])'); define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE ); // An IP block is an IP address and a prefix (d1 to d32) -define( 'RE_IP_PREFIX' , '(3[0-2]|[12]?\d)'); +define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)'); define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX); +// For IPv6 canonicalization (NOT for strict validation; these are quite lax!) +define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' ); +define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' ); +define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' ); class IP { @@ -23,7 +27,7 @@ class IP { * @return boolean True if it is valid. */ public static function isValid( $ip ) { - return preg_match( '/^' . RE_IP_ADD . '$/', $ip, $matches) ; + return preg_match( '/^' . RE_IP_ADD . '$/', $ip) ; } /** @@ -74,12 +78,13 @@ class IP { /** * Split out an IP block as an array of 4 bytes and a mask, - * return false if it cant be determined + * return false if it can't be determined * * @parameter $ip string A quad dotted IP address * @return array */ public static function toArray( $ipblock ) { + $matches = array(); if(! preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) { return false; } else { @@ -206,6 +211,50 @@ class IP { } else { return array( $start, $end ); } - } + } + + /** + * Determine if a given integer IPv4 address is in a given CIDR network + * @param $addr The address to check against the given range. + * @param $range The range to check the given address against. + * @return bool Whether or not the given address is in the given range. + */ + public static function isInRange( $addr, $range ) { + $unsignedIP = IP::toUnsigned($addr); + list( $start, $end ) = IP::parseRange($range); + + $start = hexdec($start); + $end = hexdec($end); + + return (($unsignedIP >= $start) && ($unsignedIP <= $end)); + } + + /** + * Convert some unusual representations of IPv4 addresses to their + * canonical dotted quad representation. + * + * This currently only checks a few IPV4-to-IPv6 related cases. More + * unusual representations may be added later. + * + * @param $addr something that might be an IP address + * @return valid dotted quad IPv4 address or null + */ + public static function canonicalize( $addr ) { + if ( IP::isValid( $addr ) ) + return $addr; + + // IPv6 loopback address + if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) + return '127.0.0.1'; + + // IPv4-mapped and IPv4-compatible IPv6 addresses + if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) + return $m[1]; + if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD . ':' . RE_IPV6_WORD . '$/i', $addr, $m ) ) + return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) ); + + return null; // give up + } } + ?> diff --git a/includes/Image.php b/includes/Image.php index 55e53e26..1f3895c6 100644 --- a/includes/Image.php +++ b/includes/Image.php @@ -58,7 +58,7 @@ class Image * @param string $name name of the image, used to create a title object using Title::makeTitleSafe * @public */ - function newFromName( $name ) { + public static function newFromName( $name ) { $title = Title::makeTitleSafe( NS_IMAGE, $name ); if ( is_object( $title ) ) { return new Image( $title ); @@ -235,7 +235,7 @@ class Image * Load metadata from the file itself */ function loadFromFile() { - global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang, $wgShowEXIF; + global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang; wfProfileIn( __METHOD__ ); $this->imagePath = $this->getFullPath(); $this->fileExists = file_exists( $this->imagePath ); @@ -925,7 +925,7 @@ class Image if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) { $url = $this->getURL(); } else { - list( $isScriptUrl, $url ) = $this->thumbUrl( $width ); + list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width ); } $thumb = new ThumbnailImage( $url, $width, $height ); } else { @@ -1360,15 +1360,17 @@ class Image $dir = wfImageThumbDir( $this->name, $shared ); $urls = array(); foreach ( $files as $file ) { + $m = array(); if ( preg_match( '/^(\d+)px/', $file, $m ) ) { - $urls[] = $this->thumbUrl( $m[1], $this->fromSharedDirectory ); + list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] ); + $urls[] = $url; @unlink( "$dir/$file" ); } } // Purge the squid if ( $wgUseSquid ) { - $urls[] = $this->getViewURL(); + $urls[] = $this->getURL(); foreach ( $archiveFiles as $file ) { $urls[] = wfImageArchiveUrl( $file ); } @@ -1461,7 +1463,7 @@ class Image array( 'img_name' => $this->title->getDBkey() ), __METHOD__ ); - if ( 0 == wfNumRows( $this->historyRes ) ) { + if ( 0 == $dbr->numRows( $this->historyRes ) ) { return FALSE; } } else if ( $this->historyLine == 1 ) { @@ -1701,7 +1703,7 @@ class Image } $linkCache =& LinkCache::singleton(); - extract( $db->tableNames( 'page', 'imagelinks' ) ); + list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' ); $encName = $db->addQuotes( $this->name ); $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options"; $res = $db->query( $sql, __METHOD__ ); diff --git a/includes/ImageFunctions.php b/includes/ImageFunctions.php index d182d527..931fdff1 100644 --- a/includes/ImageFunctions.php +++ b/includes/ImageFunctions.php @@ -126,6 +126,7 @@ function wfScaleSVGUnit( $length ) { '' => 1.0, // "User units" pixels by default '%' => 2.0, // Fake it! ); + $matches = array(); if( preg_match( '/^(\d+(?:\.\d+)?)(em|ex|px|pt|pc|cm|mm|in|%|)$/', $length, $matches ) ) { $length = floatval( $matches[1] ); $unit = $matches[2]; @@ -156,6 +157,7 @@ function wfGetSVGsize( $filename ) { fclose( $f ); // Uber-crappy hack! Run through a real XML parser. + $matches = array(); if( !preg_match( '/<svg\s*([^>]*)\s*>/s', $chunk, $matches ) ) { return false; } @@ -198,7 +200,7 @@ function wfIsBadImage( $name, $contextTitle = false ) { if( !$badImages ) { # Build the list now $badImages = array(); - $lines = explode( "\n", wfMsgForContent( 'bad_image_list' ) ); + $lines = explode( "\n", wfMsgForContentNoTrans( 'bad_image_list' ) ); foreach( $lines as $line ) { # List items only if ( substr( $line, 0, 1 ) !== '*' ) { @@ -206,6 +208,7 @@ function wfIsBadImage( $name, $contextTitle = false ) { } # Find all links + $m = array(); if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) { continue; } diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php index 7ff456b6..9d58b7f6 100644 --- a/includes/ImageGallery.php +++ b/includes/ImageGallery.php @@ -42,11 +42,20 @@ class ImageGallery } /** - * Set the caption + * Set the caption (as plain text) * * @param $caption Caption */ function setCaption( $caption ) { + $this->mCaption = htmlspecialchars( $caption ); + } + + /** + * Set the caption (as HTML) + * + * @param $caption Caption + */ + function setCaptionHtml( $caption ) { $this->mCaption = $caption; } @@ -134,20 +143,19 @@ class ImageGallery * */ function toHTML() { - global $wgLang, $wgIgnoreImageErrors, $wgGenerateThumbnailOnParse; + global $wgLang, $wgGenerateThumbnailOnParse; $sk = $this->getSkin(); $s = '<table class="gallery" cellspacing="0" cellpadding="0">'; if( $this->mCaption ) - $s .= '<td class="galleryheader" colspan="4"><big>' . htmlspecialchars( $this->mCaption ) . '</big></td>'; + $s .= '<td class="galleryheader" colspan="4"><big>' . $this->mCaption . '</big></td>'; $i = 0; foreach ( $this->mImages as $pair ) { $img =& $pair[0]; $text = $pair[1]; - $name = $img->getName(); $nt = $img->getTitle(); if( $nt->getNamespace() != NS_IMAGE ) { @@ -206,6 +214,13 @@ class ImageGallery return $s; } + + /** + * @return int Number of images in the gallery + */ + public function count() { + return count( $this->mImages ); + } } //class ?> diff --git a/includes/ImagePage.php b/includes/ImagePage.php index 908dd5cc..43b99130 100644 --- a/includes/ImagePage.php +++ b/includes/ImagePage.php @@ -71,13 +71,13 @@ class ImagePage extends Article { $this->imageHistory(); $this->imageLinks(); if( $exif ) { - global $wgStylePath; + global $wgStylePath, $wgStyleVersion; $expand = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-expand' ) ) ); $collapse = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-collapse' ) ) ); $wgOut->addHTML( "<h2 id=\"metadata\">" . wfMsgHtml( 'metadata' ) . "</h2>\n" ); $wgOut->addWikiText( $this->makeMetadataTable( $exif ) ); $wgOut->addHTML( - "<script type=\"text/javascript\" src=\"$wgStylePath/common/metadata.js\"></script>\n" . + "<script type=\"text/javascript\" src=\"$wgStylePath/common/metadata.js?$wgStyleVersion\"></script>\n" . "<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" ); } } else { @@ -142,6 +142,7 @@ class ImagePage extends Article { $fields = array(); $lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) ); foreach( $lines as $line ) { + $matches = array(); if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) { $fields[] = $matches[1]; } @@ -169,12 +170,8 @@ class ImagePage extends Article { $full_url = $this->img->getURL(); $anchoropen = ''; $anchorclose = ''; + $sizeSel = intval( $wgUser->getOption( 'imagesize') ); - if( $wgUser->getOption( 'imagesize' ) == '' ) { - $sizeSel = User::getDefaultOption( 'imagesize' ); - } else { - $sizeSel = intval( $wgUser->getOption( 'imagesize' ) ); - } if( !isset( $wgImageLimits[$sizeSel] ) ) { $sizeSel = User::getDefaultOption( 'imagesize' ); } @@ -247,7 +244,7 @@ class ImagePage extends Article { $wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen . "<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" . - htmlspecialchars( $wgRequest->getVal( 'image' ) ).'" />' . $anchorclose . '</div>' ); + htmlspecialchars( $this->img->getTitle()->getPrefixedText() ).'" />' . $anchorclose . '</div>' ); if ( $this->img->isMultipage() ) { $count = $this->img->pageCount(); @@ -300,9 +297,15 @@ class ImagePage extends Article { if ($showLink) { $filename = wfEscapeWikiText( $this->img->getName() ); + // Hacky workaround: for some reason we use the incorrect MIME type + // image/svg for SVG. This should be fixed internally, but at least + // make the displayed type right. + $mime = $this->img->getMimeType(); + if ($mime == 'image/svg') $mime = 'image/svg+xml'; + $info = wfMsg( 'fileinfo', ceil($this->img->getSize()/1024.0), - $this->img->getMimeType() ); + $mime ); global $wgContLang; $dirmark = $wgContLang->getDirMark(); @@ -333,7 +336,7 @@ END } else { # Image does not exist - $title = Title::makeTitle( NS_SPECIAL, 'Upload' ); + $title = SpecialPage::getTitleFor( 'Upload' ); $link = $sk->makeKnownLinkObj($title, wfMsgHtml('noimage-linktext'), 'wpDestFile=' . urlencode( $this->img->getName() ) ); $wgOut->addHTML( wfMsgWikiHtml( 'noimage', $link ) ); @@ -348,7 +351,7 @@ END if ($wgRepositoryBaseUrl && !$wgFetchCommonsDescriptions) { $sk = $wgUser->getSkin(); - $title = Title::makeTitle( NS_SPECIAL, 'Upload' ); + $title = SpecialPage::getTitleFor( 'Upload' ); $link = $sk->makeKnownLinkObj($title, wfMsgHtml('shareduploadwiki-linktext'), array( 'wpDestFile' => urlencode( $this->img->getName() ))); $sharedtext .= " " . wfMsgWikiHtml('shareduploadwiki', $link); @@ -365,7 +368,7 @@ END function getUploadUrl() { global $wgServer; - $uploadTitle = Title::makeTitle( NS_SPECIAL, 'Upload' ); + $uploadTitle = SpecialPage::getTitleFor( 'Upload' ); return $wgServer . $uploadTitle->getLocalUrl( 'wpDestFile=' . urlencode( $this->img->getName() ) ); } @@ -530,15 +533,10 @@ END * @param $reason User provided reason for deletion. */ function doDelete( $reason ) { - global $wgOut, $wgRequest, $wgUseSquid; - global $wgPostCommitUpdateList; - - $fname = 'ImagePage::doDelete'; + global $wgOut, $wgRequest; $oldimage = $wgRequest->getVal( 'oldimage' ); - $dbw =& wfGetDB( DB_MASTER ); - if ( !is_null( $oldimage ) ) { if ( strlen( $oldimage ) < 16 ) { $wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars($oldimage) ); diff --git a/includes/Licenses.php b/includes/Licenses.php index aaa44052..dd1308b4 100644 --- a/includes/Licenses.php +++ b/includes/Licenses.php @@ -63,12 +63,14 @@ class Licenses { $obj = new License( $line ); $this->stackItem( $this->licenses, $levels, $obj ); } else { - if ( $level < count( $levels ) ) + if ( $level < count( $levels ) ) { $levels = array_slice( $levels, 0, $level ); - if ( $level == count( $levels ) ) + } + if ( $level == count( $levels ) ) { $levels[$level - 1] = $line; - else if ( $level > count( $levels ) ) + } else if ( $level > count( $levels ) ) { $levels[] = $line; + } } } } diff --git a/includes/LinkBatch.php b/includes/LinkBatch.php index 061f1b19..61e1c040 100644 --- a/includes/LinkBatch.php +++ b/includes/LinkBatch.php @@ -97,7 +97,7 @@ class LinkBatch { // The remaining links in $data are bad links, register them as such foreach ( $remaining as $ns => $dbkeys ) { - foreach ( $dbkeys as $dbkey => $nothing ) { + foreach ( $dbkeys as $dbkey => $unused ) { $title = Title::makeTitle( $ns, $dbkey ); $cache->addBadLinkObj( $title ); $ids[$title->getPrefixedDBkey()] = 0; @@ -112,7 +112,6 @@ class LinkBatch { */ function doQuery() { $fname = 'LinkBatch::doQuery'; - $namespaces = array(); if ( $this->isEmpty() ) { return false; @@ -161,7 +160,7 @@ class LinkBatch { $sql .= "({$prefix}_namespace=$ns AND {$prefix}_title IN ("; $firstTitle = true; - foreach( $dbkeys as $dbkey => $nothing ) { + foreach( $dbkeys as $dbkey => $unused ) { if ( $firstTitle ) { $firstTitle = false; } else { diff --git a/includes/Linker.php b/includes/Linker.php index d34971ff..0eabab2f 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -16,7 +16,6 @@ * @package MediaWiki */ class Linker { - function Linker() {} /** @@ -39,7 +38,6 @@ class Linker { function getInterwikiLinkAttributes( $link, $text, $class='' ) { global $wgContLang; - $same = ($link == $text); $link = urldecode( $link ); $link = $wgContLang->checkTitleEncoding( $link ); $link = preg_replace( '/[\\x00-\\x1f]/', ' ', $link ); @@ -180,12 +178,14 @@ class Linker { * call this lots of times, pre-fill the link cache with a LinkBatch, otherwise each * call to this will result in a DB query. * - * @param $title String: the text of the title + * @param $nt Title: the title object to make the link from, e.g. from + * Title::newFromText. * @param $text String: link text * @param $query String: optional query part * @param $trail String: optional trail. Alphabetic characters at the start of this string will * be included in the link text. Other characters will be appended after * the end of the link. + * @param $prefix String: optional prefix. As trail, only before instead of after. */ function makeLinkObj( $nt, $text= '', $query = '', $trail = '', $prefix = '' ) { global $wgUser; @@ -199,8 +199,6 @@ class Linker { return "<!-- ERROR -->{$prefix}{$text}{$trail}"; } - $ns = $nt->getNamespace(); - $dbkey = $nt->getDBkey(); if ( $nt->isExternal() ) { $u = $nt->getFullURL(); $link = $nt->getPrefixedURL(); @@ -209,27 +207,12 @@ class Linker { $inside = ''; if ( '' != $trail ) { + $m = array(); if ( preg_match( '/^([a-z]+)(.*)$$/sD', $trail, $m ) ) { $inside = $m[1]; $trail = $m[2]; } } - - # Check for anchors, normalize the anchor - - $parts = explode( '#', $u, 2 ); - if ( count( $parts ) == 2 ) { - $anchor = urlencode( Sanitizer::decodeCharReferences( str_replace(' ', '_', $parts[1] ) ) ); - $replacearray = array( - '%3A' => ':', - '%' => '.' - ); - $u = $parts[0] . '#' . - str_replace( array_keys( $replacearray ), - array_values( $replacearray ), - $anchor ); - } - $t = "<a href=\"{$u}\"{$style}>{$text}{$inside}</a>"; wfProfileOut( $fname ); @@ -308,12 +291,7 @@ class Linker { $text = htmlspecialchars( $nt->getFragment() ); } } - $anchor = urlencode( Sanitizer::decodeCharReferences( str_replace( ' ', '_', $nt->getFragment() ) ) ); - $replacearray = array( - '%3A' => ':', - '%' => '.' - ); - $u .= '#' . str_replace(array_keys($replacearray),array_values($replacearray),$anchor); + $u .= $nt->getFragmentForURL(); } if ( $text == '' ) { $text = htmlspecialchars( $nt->getPrefixedText() ); @@ -380,8 +358,6 @@ class Linker { * the end of the link. */ function makeStubLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { - $link = $nt->getPrefixedURL(); - $u = $nt->escapeLocalURL( $query ); if ( '' == $text ) { @@ -535,6 +511,8 @@ class Linker { $url = $thumb->getUrl(); } else { $error = htmlspecialchars( $img->getLastError() ); + // Do client-side scaling... + $height = intval( $img->getHeight() * $width / $img->getWidth() ); } } } else { @@ -627,10 +605,14 @@ class Linker { $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right'; $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : ''; - $s = "<div class=\"thumb t{$align}\"><div style=\"width:{$oboxwidth}px;\">"; + $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$oboxwidth}px;\">"; if( $thumbUrl == '' ) { // Couldn't generate thumbnail? Scale the image client-side. $thumbUrl = $img->getViewURL(); + if( $boxheight == -1 ) { + // Approximate... + $boxheight = intval( $height * $boxwidth / $width ); + } } if ( $error ) { $s .= htmlspecialchars( $error ); @@ -642,14 +624,14 @@ class Linker { $s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'. '<img src="'.$thumbUrl.'" alt="'.$alt.'" ' . 'width="'.$boxwidth.'" height="'.$boxheight.'" ' . - 'longdesc="'.$u.'" /></a>'; + 'longdesc="'.$u.'" class="thumbimage" /></a>'; if ( $framed ) { $zoomicon=""; } else { $zoomicon = '<div class="magnify" style="float:'.$magnifyalign.'">'. '<a href="'.$u.'" class="internal" title="'.$more.'">'. '<img src="'.$wgStylePath.'/common/images/magnify-clip.png" ' . - 'width="15" height="11" alt="'.$more.'" /></a></div>'; + 'width="15" height="11" alt="" /></a></div>'; } } $s .= ' <div class="thumbcaption"'.$textalign.'>'.$zoomicon.$label."</div></div></div>"; @@ -673,7 +655,7 @@ class Linker { if ( '' != $query ) { $q .= "&$query"; } - $uploadTitle = Title::makeTitle( NS_SPECIAL, 'Upload' ); + $uploadTitle = SpecialPage::getTitleFor( 'Upload' ); $url = $uploadTitle->escapeLocalURL( $q ); if ( '' == $text ) { @@ -710,13 +692,12 @@ class Linker { ### HOTFIX. Instead of breaking, return empty string. return $text; } else { - $name = $title->getDBKey(); $img = new Image( $title ); if( $img->exists() ) { $url = $img->getURL(); $class = 'internal'; } else { - $upload = Title::makeTitle( NS_SPECIAL, 'Upload' ); + $upload = SpecialPage::getTitleFor( 'Upload' ); $url = $upload->getLocalUrl( 'wpDestFile=' . urlencode( $img->getName() ) ); $class = 'new'; } @@ -763,9 +744,9 @@ class Linker { function userLink( $userId, $userText ) { $encName = htmlspecialchars( $userText ); if( $userId == 0 ) { - $contribsPage = Title::makeTitle( NS_SPECIAL, 'Contributions' ); + $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText ); return $this->makeKnownLinkObj( $contribsPage, - $encName, 'target=' . urlencode( $userText ) ); + $encName); } else { $userPage = Title::makeTitle( NS_USER, $userText ); return $this->makeLinkObj( $userPage, $encName ); @@ -788,9 +769,9 @@ class Linker { $items[] = $this->userTalkLink( $userId, $userText ); } if( $userId ) { - $contribsPage = Title::makeTitle( NS_SPECIAL, 'Contributions' ); - $items[] = $this->makeKnownLinkObj( $contribsPage, - wfMsgHtml( 'contribslink' ), 'target=' . urlencode( $userText ) ); + $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText ); + $items[] = $this->makeKnownLinkObj( $contribsPage , + wfMsgHtml( 'contribslink' ) ); } if( $blockable && $wgUser->isAllowed( 'block' ) ) { $items[] = $this->blockLink( $userId, $userText ); @@ -825,9 +806,9 @@ class Linker { * @private */ function blockLink( $userId, $userText ) { - $blockPage = Title::makeTitle( NS_SPECIAL, 'Blockip' ); + $blockPage = SpecialPage::getTitleFor( 'Blockip', $userText ); $blockLink = $this->makeKnownLinkObj( $blockPage, - wfMsgHtml( 'blocklink' ), 'ip=' . urlencode( $userText ) ); + wfMsgHtml( 'blocklink' ) ); return $blockLink; } @@ -873,17 +854,18 @@ class Linker { * comments. It escapes any HTML in the comment, but adds some CSS to format * auto-generated comments (from section editing) and formats [[wikilinks]]. * - * The $title parameter must be a title OBJECT. It is used to generate a - * direct link to the section in the autocomment. * @author Erik Moeller <moeller@scireview.de> * * Note: there's not always a title to pass to this function. * Since you can't set a default parameter for a reference, I've turned it * temporarily to a value pass. Should be adjusted further. --brion + * + * $param string $comment + * @param mixed $title Title object (to generate link to the section in autocomment) or null + * @param bool $local Whether section links should refer to local page */ - function formatComment($comment, $title = NULL) { - $fname = 'Linker::formatComment'; - wfProfileIn( $fname ); + function formatComment($comment, $title = NULL, $local = false) { + wfProfileIn( __METHOD__ ); global $wgContLang; $comment = str_replace( "\n", " ", $comment ); @@ -893,6 +875,7 @@ class Linker { # some nasty regex. # We look for all comments, match any text before and after the comment, # add a separator where needed and format the comment itself with CSS + $match = array(); while (preg_match('/(.*)\/\*\s*(.*?)\s*\*\/(.*)/', $comment,$match)) { $pre=$match[1]; $auto=$match[2]; @@ -909,8 +892,12 @@ class Linker { $section = str_replace( '[[:', '', $section ); $section = str_replace( '[[', '', $section ); $section = str_replace( ']]', '', $section ); - $sectionTitle = wfClone( $title ); - $sectionTitle->mFragment = $section; + if ( $local ) { + $sectionTitle = Title::newFromText( '#' . $section); + } else { + $sectionTitle = wfClone( $title ); + $sectionTitle->mFragment = $section; + } $link = $this->makeKnownLinkObj( $sectionTitle, wfMsg( 'sectionlink' ) ); } $sep='-'; @@ -923,14 +910,16 @@ class Linker { # format regular and media links - all other wiki formatting # is ignored - $medians = $wgContLang->getNsText( NS_MEDIA ) . ':'; - while(preg_match('/\[\[(.*?)(\|(.*?))*\]\](.*)$/',$comment,$match)) { + $medians = '(?:' . preg_quote( Namespace::getCanonicalName( NS_MEDIA ), '/' ) . '|'; + $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):'; + while(preg_match('/\[\[:?(.*?)(\|(.*?))*\]\](.*)$/',$comment,$match)) { # Handle link renaming [[foo|text]] will show link as "text" if( "" != $match[3] ) { $text = $match[3]; } else { $text = $match[1]; } + $submatch = array(); if( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) { # Media link; trail not supported. $linkRegexp = '/\[\[(.*?)\]\]/'; @@ -943,13 +932,13 @@ class Linker { $trail = ""; } $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/'; - if ($match[1][0] == ':') + if (isset($match[1][0]) && $match[1][0] == ':') $match[1] = substr($match[1], 1); $thelink = $this->makeLink( $match[1], $text, "", $trail ); } - $comment = preg_replace( $linkRegexp, wfRegexReplacement( $thelink ), $comment, 1 ); + $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 ); } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $comment; } @@ -957,19 +946,20 @@ class Linker { * Wrap a comment in standard punctuation and formatting if * it's non-empty, otherwise return empty string. * - * @param $comment String: the comment. - * @param $title Title object. + * @param string $comment + * @param mixed $title Title object (to generate link to section in autocomment) or null + * @param bool $local Whether section links should refer to local page * * @return string */ - function commentBlock( $comment, $title = NULL ) { + function commentBlock( $comment, $title = NULL, $local = false ) { // '*' used to be the comment inserted by the software way back // in antiquity in case none was provided, here for backwards // compatability, acc. to brion -ævar if( $comment == '' || $comment == '*' ) { return ''; } else { - $formatted = $this->formatComment( $comment, $title ); + $formatted = $this->formatComment( $comment, $title, $local ); return " <span class=\"comment\">($formatted)</span>"; } } @@ -977,12 +967,14 @@ class Linker { /** * Wrap and format the given revision's comment block, if the current * user is allowed to view it. - * @param $rev Revision object. + * + * @param Revision $rev + * @param bool $local Whether section links should refer to local page * @return string HTML */ - function revComment( $rev ) { + function revComment( Revision $rev, $local = false ) { if( $rev->userCan( Revision::DELETED_COMMENT ) ) { - $block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle() ); + $block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle(), $local ); } else { $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>"; @@ -1039,44 +1031,46 @@ class Linker { } /** @todo document */ - function editSectionLinkForOther( $title, $section ) { + public function editSectionLinkForOther( $title, $section ) { global $wgContLang; $title = Title::newFromText( $title ); $editurl = '§ion='.$section; $url = $this->makeKnownLinkObj( $title, wfMsg('editsection'), 'action=edit'.$editurl ); - if( $wgContLang->isRTL() ) { - $farside = 'left'; - $nearside = 'right'; - } else { - $farside = 'right'; - $nearside = 'left'; - } - return "<div class=\"editsection\" style=\"float:$farside;margin-$nearside:5px;\">[".$url."]</div>"; + return "<span class=\"editsection\">[".$url."]</span>"; } - /** + /** * @param $title Title object. * @param $section Integer: section number. * @param $hint Link String: title, or default if omitted or empty */ - function editSectionLink( $nt, $section, $hint='' ) { + public function editSectionLink( $nt, $section, $hint='' ) { global $wgContLang; $editurl = '§ion='.$section; $hint = ( $hint=='' ) ? '' : ' title="' . wfMsgHtml( 'editsectionhint', htmlspecialchars( $hint ) ) . '"'; $url = $this->makeKnownLinkObj( $nt, wfMsg('editsection'), 'action=edit'.$editurl, '', '', '', $hint ); - if( $wgContLang->isRTL() ) { - $farside = 'left'; - $nearside = 'right'; - } else { - $farside = 'right'; - $nearside = 'left'; - } - return "<div class=\"editsection\" style=\"float:$farside;margin-$nearside:5px;\">[".$url."]</div>"; + return "<span class=\"editsection\">[".$url."]</span>"; + } + + /** + * Create a headline for content + * + * @param int $level The level of the headline (1-6) + * @param string $attribs Any attributes for the headline, starting with a space and ending with '>' + * This *must* be at least '>' for no attribs + * @param string $anchor The anchor to give the headline (the bit after the #) + * @param string $text The text of the header + * @param string $link HTML to add for the section edit link + * + * @return string HTML headline + */ + public function makeHeadline( $level, $attribs, $anchor, $text, $link ) { + return "<a name=\"$anchor\"></a><h$level$attribs$link <span class=\"mw-headline\">$text</span></h$level>"; } /** @@ -1093,6 +1087,7 @@ class Linker { } $inside = ''; if ( '' != $trail ) { + $m = array(); if ( preg_match( $regex, $trail, $m ) ) { $inside = $m[1]; $trail = $m[2]; @@ -1101,5 +1096,112 @@ class Linker { return array( $inside, $trail ); } + /** + * Generate a rollback link for a given revision. Currently it's the + * caller's responsibility to ensure that the revision is the top one. If + * it's not, of course, the user will get an error message. + * + * If the calling page is called with the parameter &bot=1, all rollback + * links also get that parameter. It causes the edit itself and the rollback + * to be marked as "bot" edits. Bot edits are hidden by default from recent + * changes, so this allows sysops to combat a busy vandal without bothering + * other users. + * + * @param Revision $rev + */ + function generateRollback( $rev ) { + global $wgUser, $wgRequest; + $title = $rev->getTitle(); + + $extraRollback = $wgRequest->getBool( 'bot' ) ? '&bot=1' : ''; + $extraRollback .= '&token=' . urlencode( + $wgUser->editToken( array( $title->getPrefixedText(), $rev->getUserText() ) ) ); + return '<span class="mw-rollback-link">['. $this->makeKnownLinkObj( $title, + wfMsg('rollbacklink'), + 'action=rollback&from=' . urlencode( $rev->getUserText() ) . $extraRollback ) .']</span>'; + } + + /** + * Returns HTML for the "templates used on this page" list. + * + * @param array $templates Array of templates from Article::getUsedTemplate + * or similar + * @param bool $preview Whether this is for a preview + * @param bool $section Whether this is for a section edit + * @return string HTML output + */ + public function formatTemplates( $templates, $preview = false, $section = false) { + global $wgUser; + wfProfileIn( __METHOD__ ); + + $sk =& $wgUser->getSkin(); + + $outText = ''; + if ( count( $templates ) > 0 ) { + # Do a batch existence check + $batch = new LinkBatch; + foreach( $templates as $title ) { + $batch->addObj( $title ); + } + $batch->execute(); + + # Construct the HTML + $outText = '<div class="mw-templatesUsedExplanation">'; + if ( $preview ) { + $outText .= wfMsgExt( 'templatesusedpreview', array( 'parse' ) ); + } elseif ( $section ) { + $outText .= wfMsgExt( 'templatesusedsection', array( 'parse' ) ); + } else { + $outText .= wfMsgExt( 'templatesused', array( 'parse' ) ); + } + $outText .= '</div><ul>'; + + foreach ( $templates as $titleObj ) { + $r = $titleObj->getRestrictions( 'edit' ); + if ( in_array( 'sysop', $r ) ) { + $protected = wfMsgExt( 'template-protected', array( 'parseinline' ) ); + } elseif ( in_array( 'autoconfirmed', $r ) ) { + $protected = wfMsgExt( 'template-semiprotected', array( 'parseinline' ) ); + } else { + $protected = ''; + } + $outText .= '<li>' . $sk->makeLinkObj( $titleObj ) . ' ' . $protected . '</li>'; + } + $outText .= '</ul>'; + } + wfProfileOut( __METHOD__ ); + return $outText; + } + + /** + * Format a size in bytes for output, using an appropriate + * unit (B, KB, MB or GB) according to the magnitude in question + * + * @param $size Size to format + * @return string + */ + public function formatSize( $size ) { + global $wgLang; + if( $size > 1024 ) { + $size = $size / 1024; + if( $size > 1024 ) { + $size = $size / 1024; + if( $size > 1024 ) { + $size = $size / 1024; + $msg = 'size-gigabytes'; + } else { + $msg = 'size-megabytes'; + } + } else { + $msg = 'size-kilobytes'; + } + } else { + $msg = 'size-bytes'; + } + $size = round( $size, 0 ); + return wfMsgHtml( $msg, $wgLang->formatNum( $size ) ); + } + } + ?> diff --git a/includes/LoadBalancer.php b/includes/LoadBalancer.php index 3e81aea9..396ef865 100644 --- a/includes/LoadBalancer.php +++ b/includes/LoadBalancer.php @@ -141,6 +141,9 @@ class LoadBalancer { $i = false; if ( $this->mForce >= 0 ) { $i = $this->mForce; + } elseif ( count( $this->mServers ) == 1 ) { + # Skip the load balancing if there's only one server + $i = 0; } else { if ( $this->mReadIndex >= 0 ) { $i = $this->mReadIndex; @@ -171,10 +174,9 @@ class LoadBalancer { unset( $loads[$i] ); $sleepTime = 0; } else { - $status = $this->mConnections[$i]->getStatus("Thread%"); - if ( isset( $this->mServers[$i]['max threads'] ) && - $status['Threads_running'] > $this->mServers[$i]['max threads'] ) - { + if ( isset( $this->mServers[$i]['max threads'] ) ) { + $status = $this->mConnections[$i]->getStatus("Thread%"); + if ( $status['Threads_running'] > $this->mServers[$i]['max threads'] ) { # Too much load, back off and wait for a while. # The sleep time is scaled by the number of threads connected, # to produce a roughly constant global poll rate. @@ -182,9 +184,13 @@ class LoadBalancer { # If we reach the timeout and exit the loop, don't use it $i = false; - } else { + } else { $done = true; $sleepTime = 0; + } + } else { + $done = true; + $sleepTime = 0; } } } else { diff --git a/includes/LogPage.php b/includes/LogPage.php index 954b178f..dd395126 100644 --- a/includes/LogPage.php +++ b/includes/LogPage.php @@ -74,7 +74,7 @@ class LogPage { # And update recentchanges if ( $this->updateRecentChanges ) { - $titleObj = Title::makeTitle( NS_SPECIAL, 'Log/' . $this->type ); + $titleObj = SpecialPage::getTitleFor( 'Log', $this->type ); $rcComment = $this->actionText; if( '' != $this->comment ) { if ($rcComment == '') @@ -107,7 +107,7 @@ class LogPage { /** * @static */ - function logName( $type ) { + public static function logName( $type ) { global $wgLogNames; if( isset( $wgLogNames[$type] ) ) { @@ -150,7 +150,7 @@ class LogPage { $titleLink = $title->getText(); } else { $titleLink = $skin->makeLinkObj( $title, $title->getText() ); - $titleLink .= ' (' . $skin->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions/' . $title->getDBkey() ), wfMsg( 'contribslink' ) ) . ')'; + $titleLink .= ' (' . $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() ), wfMsg( 'contribslink' ) ) . ')'; } break; case 'rights': diff --git a/includes/MagicWord.php b/includes/MagicWord.php index 68cbe345..60bfd0f4 100644 --- a/includes/MagicWord.php +++ b/includes/MagicWord.php @@ -101,6 +101,7 @@ class MagicWord { 'contentlanguage', 'pagesinnamespace', 'numberofadmins', + 'defaultsort', ); static public $mObjects = array(); @@ -289,7 +290,7 @@ class MagicWord { * Used in matchAndRemove() * @private **/ - function pregRemoveAndRecord( $match ) { + function pregRemoveAndRecord( ) { $this->mFound = true; return ''; } @@ -298,7 +299,7 @@ class MagicWord { * Replaces the word with something else */ function replace( $replacement, $subject, $limit=-1 ) { - $res = preg_replace( $this->getRegex(), wfRegexReplacement( $replacement ), $subject, $limit ); + $res = preg_replace( $this->getRegex(), StringUtils::escapeRegexReplacement( $replacement ), $subject, $limit ); $this->mModified = !($res === $subject); return $res; } diff --git a/includes/Math.php b/includes/Math.php index a8b33984..9fa631f7 100644 --- a/includes/Math.php +++ b/includes/Math.php @@ -39,6 +39,9 @@ class MathRenderer { # No need to render or parse anything more! return ('$ '.htmlspecialchars( $this->tex ).' $'); } + if( $this->tex == '' ) { + return; # bug 8372 + } if( !$this->_recall() ) { # Ensure that the temp and output directories are available before continuing... @@ -75,12 +78,13 @@ class MathRenderer { $retval = substr ($contents, 0, 1); $errmsg = ''; if (($retval == 'C') || ($retval == 'M') || ($retval == 'L')) { - if ($retval == 'C') + if ($retval == 'C') { $this->conservativeness = 2; - else if ($retval == 'M') + } else if ($retval == 'M') { $this->conservativeness = 1; - else + } else { $this->conservativeness = 0; + } $outdata = substr ($contents, 33); $i = strpos($outdata, "\000"); @@ -89,12 +93,13 @@ class MathRenderer { $this->mathml = substr($outdata, $i+1); } else if (($retval == 'c') || ($retval == 'm') || ($retval == 'l')) { $this->html = substr ($contents, 33); - if ($retval == 'c') + if ($retval == 'c') { $this->conservativeness = 2; - else if ($retval == 'm') + } else if ($retval == 'm') { $this->conservativeness = 1; - else + } else { $this->conservativeness = 0; + } $this->mathml = NULL; } else if ($retval == 'X') { $this->html = NULL; @@ -118,7 +123,7 @@ class MathRenderer { $this->hash = substr ($contents, 1, 32); } - $res = wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) ); + wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) ); if ( $errmsg ) { return $errmsg; diff --git a/includes/MessageCache.php b/includes/MessageCache.php index 9cab222b..a269c620 100644 --- a/includes/MessageCache.php +++ b/includes/MessageCache.php @@ -11,10 +11,11 @@ define( 'MSG_LOAD_TIMEOUT', 60); define( 'MSG_LOCK_TIMEOUT', 10); define( 'MSG_WAIT_TIMEOUT', 10); +define( 'MSG_CACHE_VERSION', 1 ); /** * Message cache - * Performs various useful MediaWiki namespace-related functions + * Performs various MediaWiki namespace-related functions * * @package MediaWiki */ @@ -79,7 +80,7 @@ class MessageCache { if ( $hash == $localHash ) { // All good, get the rest of it $serialized = fread( $file, 10000000 ); - $this->mCache = unserialize( $serialized ); + $this->setCache( unserialize( $serialized ) ); } fclose( $file ); } @@ -130,6 +131,7 @@ class MessageCache { return; } require("$wgLocalMessageCache/messages-" . wfWikiID()); + $this->setCache( $this->mCache); } function saveToScript($array, $hash) { @@ -162,6 +164,17 @@ class MessageCache { } /** + * Set the cache to $cache, if it is valid. Otherwise set the cache to false. + */ + function setCache( $cache ) { + if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) { + $this->mCache = $cache; + } else { + $this->mCache = false; + } + } + + /** * Loads messages either from memcached or the database, if not disabled * On error, quietly switches to a fallback mode * Returns false for a reportable error, true otherwise @@ -177,110 +190,104 @@ class MessageCache { } return true; } + if ( !$this->mUseCache ) { + $this->mDeferred = false; + return true; + } + $fname = 'MessageCache::load'; wfProfileIn( $fname ); $success = true; - if ( $this->mUseCache ) { - $this->mCache = false; + $this->mCache = false; - # Try local cache - wfProfileIn( $fname.'-fromlocal' ); - $hash = $this->mMemc->get( "{$this->mMemcKey}-hash" ); - if ( $hash ) { - if ($wgLocalMessageCacheSerialized) { - $this->loadFromLocal( $hash ); - } else { - $this->loadFromScript( $hash ); - } - if ( $this->mCache ) { - wfDebug( "MessageCache::load(): got from local cache\n" ); - } + # Try local cache + wfProfileIn( $fname.'-fromlocal' ); + $hash = $this->mMemc->get( "{$this->mMemcKey}-hash" ); + if ( $hash ) { + if ($wgLocalMessageCacheSerialized) { + $this->loadFromLocal( $hash ); + } else { + $this->loadFromScript( $hash ); } - wfProfileOut( $fname.'-fromlocal' ); - - # Try memcached - if ( !$this->mCache ) { - wfProfileIn( $fname.'-fromcache' ); - $this->mCache = $this->mMemc->get( $this->mMemcKey ); - if ( $this->mCache ) { - wfDebug( "MessageCache::load(): got from global cache\n" ); - # Save to local cache - if ( $wgLocalMessageCache !== false ) { - $serialized = serialize( $this->mCache ); - if ( !$hash ) { - $hash = md5( $serialized ); - $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry ); - } - if ($wgLocalMessageCacheSerialized) { - $this->saveToLocal( $serialized,$hash ); - } else { - $this->saveToScript( $this->mCache, $hash ); - } - } - } - wfProfileOut( $fname.'-fromcache' ); + if ( $this->mCache ) { + wfDebug( "MessageCache::load(): got from local cache\n" ); } - - - # If there's nothing in memcached, load all the messages from the database - if ( !$this->mCache ) { - wfDebug( "MessageCache::load(): loading all messages\n" ); - $this->lock(); - # Other threads don't need to load the messages if another thread is doing it. - $success = $this->mMemc->add( $this->mMemcKey.'-status', "loading", MSG_LOAD_TIMEOUT ); - if ( $success ) { - wfProfileIn( $fname.'-load' ); - $this->loadFromDB(); - wfProfileOut( $fname.'-load' ); - - # Save in memcached - # Keep trying if it fails, this is kind of important - wfProfileIn( $fname.'-save' ); - for ($i=0; $i<20 && - !$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry ); - $i++ ) { - usleep(mt_rand(500000,1500000)); - } - - # Save to local cache - if ( $wgLocalMessageCache !== false ) { - $serialized = serialize( $this->mCache ); + } + wfProfileOut( $fname.'-fromlocal' ); + + # Try memcached + if ( !$this->mCache ) { + wfProfileIn( $fname.'-fromcache' ); + $this->setCache( $this->mMemc->get( $this->mMemcKey ) ); + if ( $this->mCache ) { + wfDebug( "MessageCache::load(): got from global cache\n" ); + # Save to local cache + if ( $wgLocalMessageCache !== false ) { + $serialized = serialize( $this->mCache ); + if ( !$hash ) { $hash = md5( $serialized ); $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry ); - if ($wgLocalMessageCacheSerialized) { - $this->saveToLocal( $serialized,$hash ); - } else { - $this->saveToScript( $this->mCache, $hash ); - } } - - wfProfileOut( $fname.'-save' ); - if ( $i == 20 ) { - $this->mMemc->set( $this->mMemcKey.'-status', 'error', 60*5 ); - wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" ); + if ($wgLocalMessageCacheSerialized) { + $this->saveToLocal( $serialized,$hash ); + } else { + $this->saveToScript( $this->mCache, $hash ); } } - $this->unlock(); } + wfProfileOut( $fname.'-fromcache' ); + } + + + # If there's nothing in memcached, load all the messages from the database + if ( !$this->mCache ) { + wfDebug( "MessageCache::load(): cache is empty\n" ); + $this->lock(); + # Other threads don't need to load the messages if another thread is doing it. + $success = $this->mMemc->add( $this->mMemcKey.'-status', "loading", MSG_LOAD_TIMEOUT ); + if ( $success ) { + wfProfileIn( $fname.'-load' ); + wfDebug( "MessageCache::load(): loading all messages from DB\n" ); + $this->loadFromDB(); + wfProfileOut( $fname.'-load' ); + + # Save in memcached + # Keep trying if it fails, this is kind of important + wfProfileIn( $fname.'-save' ); + for ($i=0; $i<20 && + !$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry ); + $i++ ) { + usleep(mt_rand(500000,1500000)); + } - if ( !is_array( $this->mCache ) ) { - wfDebug( "MessageCache::load(): individual message mode\n" ); - # If it is 'loading' or 'error', switch to individual message mode, otherwise disable - # Causing too much DB load, disabling -- TS - $this->mDisable = true; - /* - if ( $this->mCache == "loading" ) { - $this->mUseCache = false; - } elseif ( $this->mCache == "error" ) { - $this->mUseCache = false; - $success = false; + # Save to local cache + if ( $wgLocalMessageCache !== false ) { + $serialized = serialize( $this->mCache ); + $hash = md5( $serialized ); + $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry ); + if ($wgLocalMessageCacheSerialized) { + $this->saveToLocal( $serialized,$hash ); + } else { + $this->saveToScript( $this->mCache, $hash ); + } + } + + wfProfileOut( $fname.'-save' ); + if ( $i == 20 ) { + $this->mMemc->set( $this->mMemcKey.'-status', 'error', 60*5 ); + wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" ); } else { - $this->mDisable = true; - $success = false; - }*/ - $this->mCache = false; + $this->mMemc->delete( $this->mMemcKey.'-status' ); + } } + $this->unlock(); + } + + if ( !is_array( $this->mCache ) ) { + wfDebug( "MessageCache::load(): unable to load cache, disabled\n" ); + $this->mDisable = true; + $this->mCache = false; } wfProfileOut( $fname ); $this->mDeferred = false; @@ -291,50 +298,42 @@ class MessageCache { * Loads all or main part of cacheable messages from the database */ function loadFromDB() { - global $wgLang; + global $wgLang, $wgMaxMsgCacheEntrySize; - $fname = 'MessageCache::loadFromDB'; + wfProfileIn( __METHOD__ ); $dbr =& wfGetDB( DB_SLAVE ); - if ( !$dbr ) { - throw new MWException( 'Invalid database object' ); - } - $conditions = array( 'page_is_redirect' => 0, - 'page_namespace' => NS_MEDIAWIKI); - $res = $dbr->select( array( 'page', 'revision', 'text' ), - array( 'page_title', 'old_text', 'old_flags' ), - 'page_is_redirect=0 AND page_namespace='.NS_MEDIAWIKI.' AND page_latest=rev_id AND rev_text_id=old_id', - $fname - ); - $this->mCache = array(); - for ( $row = $dbr->fetchObject( $res ); $row; $row = $dbr->fetchObject( $res ) ) { - $this->mCache[$row->page_title] = Revision::getRevisionText( $row ); - } - # Negative caching - # Go through the language array and the extension array and make a note of - # any keys missing from the cache - $allMessages = Language::getMessagesFor( 'en' ); - foreach ( $allMessages as $key => $value ) { - $uckey = $wgLang->ucfirst( $key ); - if ( !array_key_exists( $uckey, $this->mCache ) ) { - $this->mCache[$uckey] = false; - } + # Load titles for all oversized pages in the MediaWiki namespace + $res = $dbr->select( 'page', 'page_title', + array( + 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ), + 'page_is_redirect' => 0, + 'page_namespace' => NS_MEDIAWIKI, + ), + __METHOD__ ); + while ( $row = $dbr->fetchObject( $res ) ) { + $this->mCache[$row->page_title] = '!TOO BIG'; } + $dbr->freeResult( $res ); - # Make sure all extension messages are available - MessageCache::loadAllMessages(); + # Load text for the remaining pages + $res = $dbr->select( array( 'page', 'revision', 'text' ), + array( 'page_title', 'old_text', 'old_flags' ), + array( + 'page_is_redirect' => 0, + 'page_namespace' => NS_MEDIAWIKI, + 'page_latest=rev_id', + 'rev_text_id=old_id', + 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ), + __METHOD__ ); - # Add them to the cache - foreach ( $this->mExtensionMessages as $key => $value ) { - $uckey = $wgLang->ucfirst( $key ); - if ( !array_key_exists( $uckey, $this->mCache ) && - ( isset( $this->mExtensionMessages[$key][$wgLang->getCode()] ) || isset( $this->mExtensionMessages[$key]['en'] ) ) ) { - $this->mCache[$uckey] = false; - } + for ( $row = $dbr->fetchObject( $res ); $row; $row = $dbr->fetchObject( $res ) ) { + $this->mCache[$row->page_title] = ' ' . Revision::getRevisionText( $row ); } - + $this->mCache['VERSION'] = MSG_CACHE_VERSION; $dbr->freeResult( $res ); + wfProfileOut( __METHOD__ ); } /** @@ -345,7 +344,7 @@ class MessageCache { if ( !$this->mKeys ) { $this->mKeys = array(); $allMessages = Language::getMessagesFor( 'en' ); - foreach ( $allMessages as $key => $value ) { + foreach ( $allMessages as $key => $unused ) { $title = $wgContLang->ucfirst( $key ); array_push( $this->mKeys, $title ); } @@ -353,21 +352,26 @@ class MessageCache { return $this->mKeys; } - /** - * @deprecated - */ - function isCacheable( $key ) { - return true; - } - function replace( $title, $text ) { global $wgLocalMessageCache, $wgLocalMessageCacheSerialized, $parserMemc; + global $wgMaxMsgCacheEntrySize; + wfProfileIn( __METHOD__ ); $this->lock(); $this->load(); $parserMemc->delete(wfMemcKey('sidebar')); if ( is_array( $this->mCache ) ) { - $this->mCache[$title] = $text; + if ( $text === false ) { + # Article was deleted + unset( $this->mCache[$title] ); + $this->mMemc->delete( "$this->mMemcKey:{$title}" ); + } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) { + $this->mCache[$title] = '!TOO BIG'; + $this->mMemc->set( "$this->mMemcKey:{$title}", ' '.$text, $this->mExpiry ); + } else { + $this->mCache[$title] = ' ' . $text; + $this->mMemc->delete( "$this->mMemcKey:{$title}" ); + } $this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry ); # Save to local cache @@ -381,10 +385,9 @@ class MessageCache { $this->saveToScript( $this->mCache, $hash ); } } - - } $this->unlock(); + wfProfileOut( __METHOD__ ); } /** @@ -413,9 +416,18 @@ class MessageCache { $this->mMemc->delete( $lockKey ); } - function get( $key, $useDB = true, $forcontent = true, $isfullkey = false ) { + /** + * Get a message from either the content language or the user language. + * + * @param string $key The message cache key + * @param bool $useDB Get the message from the DB, false to use only the localisation + * @param bool $forContent Get the message from the content language rather than the + * user language + * @param bool $isFullKey Specifies whether $key is a two part key "lang/msg". + */ + function get( $key, $useDB = true, $forContent = true, $isFullKey = false ) { global $wgContLanguageCode, $wgContLang, $wgLang; - if( $forcontent ) { + if( $forContent ) { $lang =& $wgContLang; } else { $lang =& $wgLang; @@ -431,37 +443,32 @@ class MessageCache { } $message = false; + + # Normalise title-case input + $lckey = $wgContLang->lcfirst( $key ); + $lckey = str_replace( ' ', '_', $lckey ); + + # Try the MediaWiki namespace if( !$this->mDisable && $useDB ) { - $title = $wgContLang->ucfirst( $key ); - if(!$isfullkey && ($langcode != $wgContLanguageCode) ) { + $title = $wgContLang->ucfirst( $lckey ); + if(!$isFullKey && ($langcode != $wgContLanguageCode) ) { $title .= '/' . $langcode; } - $message = $this->getFromCache( $title ); + $message = $this->getMsgFromNamespace( $title ); } # Try the extension array - if( $message === false && array_key_exists( $key, $this->mExtensionMessages ) ) { - if ( isset( $this->mExtensionMessages[$key][$langcode] ) ) { - $message = $this->mExtensionMessages[$key][$langcode]; - } elseif ( isset( $this->mExtensionMessages[$key]['en'] ) ) { - $message = $this->mExtensionMessages[$key]['en']; - } + if( $message === false && isset( $this->mExtensionMessages[$langcode][$lckey] ) ) { + $message = $this->mExtensionMessages[$langcode][$lckey]; + } + if ( $message === false && isset( $this->mExtensionMessages['en'][$lckey] ) ) { + $message = $this->mExtensionMessages['en'][$lckey]; } # Try the array in the language object if( $message === false ) { #wfDebug( "Trying language object for message $key\n" ); wfSuppressWarnings(); - $message = $lang->getMessage( $key ); - wfRestoreWarnings(); - if ( is_null( $message ) ) { - $message = false; - } - } - - # Try the English array - if( $message === false && $langcode != 'en' ) { - wfSuppressWarnings(); - $message = Language::getMessage( $key ); + $message = $lang->getMessage( $lckey ); wfRestoreWarnings(); if ( is_null( $message ) ) { $message = false; @@ -469,8 +476,8 @@ class MessageCache { } # Try the array of another language - if( $message === false && strpos( $key, '/' ) ) { - $message = explode( '/', $key ); + if( $message === false && strpos( $lckey, '/' ) ) { + $message = explode( '/', $lckey ); if ( $message[1] ) { wfSuppressWarnings(); $message = Language::getMessageFor( $message[0], $message[1] ); @@ -486,8 +493,8 @@ class MessageCache { # Is this a custom message? Try the default language in the db... if( ($message === false || $message === '-' ) && !$this->mDisable && $useDB && - !$isfullkey && ($langcode != $wgContLanguageCode) ) { - $message = $this->getFromCache( $wgContLang->ucfirst( $key ) ); + !$isFullKey && ($langcode != $wgContLanguageCode) ) { + $message = $this->getMsgFromNamespace( $wgContLang->ucfirst( $lckey ) ); } # Final fallback @@ -500,46 +507,70 @@ class MessageCache { return $message; } - function getFromCache( $title ) { + /** + * Get a message from the MediaWiki namespace, with caching. The key must + * first be converted to two-part lang/msg form if necessary. + * + * @param string $title Message cache key with initial uppercase letter + */ + function getMsgFromNamespace( $title ) { $message = false; + $type = false; # Try the cache - if( $this->mUseCache && is_array( $this->mCache ) && array_key_exists( $title, $this->mCache ) ) { - return $this->mCache[$title]; + if( $this->mUseCache && isset( $this->mCache[$title] ) ) { + $entry = $this->mCache[$title]; + $type = substr( $entry, 0, 1 ); + if ( $type == ' ' ) { + return substr( $entry, 1 ); + } + } + + # Call message hooks, in case they are defined + wfRunHooks('MessagesPreLoad', array( $title, &$message ) ); + if ( $message !== false ) { + return $message; + } + + # If there is no cache entry and no placeholder, it doesn't exist + if ( $type != '!' && $message === false ) { + return false; } - # Try individual message cache + $memcKey = $this->mMemcKey . ':' . $title; + + # Try the individual message cache if ( $this->mUseCache ) { - $message = $this->mMemc->get( $this->mMemcKey . ':' . $title ); - if ( $message == '###NONEXISTENT###' ) { - $this->mCache[$title] = false; - return false; - } elseif( !is_null( $message ) ) { - $this->mCache[$title] = $message; - return $message; - } else { - $message = false; + $entry = $this->mMemc->get( $memcKey ); + if ( $entry ) { + $type = substr( $entry, 0, 1 ); + + if ( $type == ' ' ) { + $message = substr( $entry, 1 ); + $this->mCache[$title] = $message; + return $message; + } elseif ( $entry == '!NONEXISTENT' ) { + return false; + } else { + # Corrupt/obsolete entry, delete it + $this->mMemc->delete( $memcKey ); + } + } } - # Call message Hooks, in case they are defined - wfRunHooks('MessagesPreLoad',array($title,&$message)); - - # If it wasn't in the cache, load each message from the DB individually + # Try loading it from the DB $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) ); if( $revision ) { $message = $revision->getText(); if ($this->mUseCache) { - $this->mCache[$title]=$message; - /* individual messages may be often - recached until proper purge code exists - */ - $this->mMemc->set( $this->mMemcKey . ':' . $title, $message, 300 ); + $this->mCache[$title] = ' ' . $message; + $this->mMemc->set( $memcKey, $message, $this->mExpiry ); } } else { # Negative caching # Use some special text instead of false, because false gets converted to '' somewhere - $this->mMemc->set( $this->mMemcKey . ':' . $title, '###NONEXISTENT###', $this->mExpiry ); + $this->mMemc->set( $memcKey, '!NONEXISTENT', $this->mExpiry ); $this->mCache[$title] = false; } @@ -577,7 +608,7 @@ class MessageCache { * @param string $lang The messages language, English by default */ function addMessage( $key, $value, $lang = 'en' ) { - $this->mExtensionMessages[$key][$lang] = $value; + $this->mExtensionMessages[$lang][$key] = $value; } /** @@ -588,8 +619,24 @@ class MessageCache { */ function addMessages( $messages, $lang = 'en' ) { wfProfileIn( __METHOD__ ); + if ( isset( $this->mExtensionMessages[$lang] ) ) { + $this->mExtensionMessages[$lang] = $messages + $this->mExtensionMessages[$lang]; + } else { + $this->mExtensionMessages[$lang] = $messages; + } + wfProfileOut( __METHOD__ ); + } + + /** + * Add a 2-D array of messages by lang. Useful for extensions. + * Introduced in 1.9. Please do not use it for now, for backwards compatibility. + * + * @param array $messages The array to be added + */ + function addMessagesByLang( $messages ) { + wfProfileIn( __METHOD__ ); foreach ( $messages as $key => $value ) { - $this->addMessage( $key, $value, $lang ); + $this->addMessages( $value, $key ); } wfProfileOut( __METHOD__ ); } @@ -602,12 +649,11 @@ class MessageCache { function getExtensionMessagesFor( $lang = 'en' ) { wfProfileIn( __METHOD__ ); $messages = array(); - foreach( $this->mExtensionMessages as $key => $message ) { - if ( isset( $message[$lang] ) ) { - $messages[$key] = $message[$lang]; - } elseif ( isset( $message['en'] ) ) { - $messages[$key] = $message['en']; - } + if ( isset( $this->mExtensionMessages[$lang] ) ) { + $messages = $this->mExtensionMessages[$lang]; + } + if ( $lang != 'en' ) { + $messages = $messages + $this->mExtensionMessages['en']; } wfProfileOut( __METHOD__ ); return $messages; diff --git a/includes/Metadata.php b/includes/Metadata.php index af40ab21..b48ced0d 100644 --- a/includes/Metadata.php +++ b/includes/Metadata.php @@ -22,7 +22,8 @@ */ /** - * + * TODO: Perhaps make this file into a Metadata class, with static methods (declared + * as private where indicated), to move these functions out of the global namespace? */ define('RDF_TYPE_PREFS', "application/rdf+xml,text/xml;q=0.7,application/xml;q=0.5,text/rdf;q=0.1"); @@ -142,7 +143,7 @@ function dcBasics($article) { dcPerson('contributor', $user_parts[0], $user_parts[1], $user_parts[2]); } - dcRights($article); + dcRights(); } /** @@ -291,7 +292,7 @@ function dcPerson($name, $id, $user_name='', $user_real_name='') { * different pages. * @private */ -function dcRights($article) { +function dcRights() { global $wgRightsPage, $wgRightsUrl, $wgRightsText; @@ -316,7 +317,11 @@ function ccGetTerms($url) { return $wgLicenseTerms; } else { $known = getKnownLicenses(); - return $known[$url]; + if( isset( $known[$url] ) ) { + return $known[$url]; + } else { + return array(); + } } } diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php index dd197c31..ca05dbb3 100644 --- a/includes/MimeMagic.php +++ b/includes/MimeMagic.php @@ -403,8 +403,6 @@ class MimeMagic { elseif ($tag==="svg") $mime= "image/svg"; elseif (strpos($doctype,"-//W3C//DTD XHTML")===0) $mime= "text/html"; elseif ($tag==="html") $mime= "text/html"; - - $test_more= false; } } diff --git a/includes/Namespace.php b/includes/Namespace.php index 73dc2969..78493902 100644 --- a/includes/Namespace.php +++ b/includes/Namespace.php @@ -11,7 +11,7 @@ $wgCanonicalNamespaceNames = array( NS_MEDIA => 'Media', NS_SPECIAL => 'Special', - NS_TALK => 'Talk', + NS_TALK => 'Talk', NS_USER => 'User', NS_USER_TALK => 'User_talk', NS_PROJECT => 'Project', @@ -24,7 +24,7 @@ $wgCanonicalNamespaceNames = array( NS_TEMPLATE_TALK => 'Template_talk', NS_HELP => 'Help', NS_HELP_TALK => 'Help_talk', - NS_CATEGORY => 'Category', + NS_CATEGORY => 'Category', NS_CATEGORY_TALK => 'Category_talk', ); diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 0d55c2e0..4ca9e88a 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -54,18 +54,34 @@ class OutputPage { $this->mNewSectionLink = false; } - function redirect( $url, $responsecode = '302' ) { + public function redirect( $url, $responsecode = '302' ) { # Strip newlines as a paranoia check for header injection in PHP<5.1.2 $this->mRedirect = str_replace( "\n", '', $url ); $this->mRedirectCode = $responsecode; } + /** + * Set the HTTP status code to send with the output. + * + * @param int $statusCode + * @return nothing + */ function setStatusCode( $statusCode ) { $this->mStatusCode = $statusCode; } # To add an http-equiv meta tag, precede the name with "http:" function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); } function addKeyword( $text ) { array_push( $this->mKeywords, $text ); } function addScript( $script ) { $this->mScripts .= $script; } + + /** + * Add a self-contained script tag with the given contents + * @param string $script JavaScript text, no <script> tags + */ + function addInlineScript( $script ) { + global $wgJsMimeType; + $this->mScripts .= "<script type=\"$wgJsMimeType\"><!--\n$script\n--></script>"; + } + function getScript() { return $this->mScripts; } function setETag($tag) { $this->mETag = $tag; } @@ -88,8 +104,9 @@ class OutputPage { /** * checkLastModified tells the client to use the client-cached page if * possible. If sucessful, the OutputPage is disabled so that - * any future call to OutputPage->output() have no effect. The method - * returns true iff cache-ok headers was sent. + * any future call to OutputPage->output() have no effect. + * + * @return bool True iff cache-ok headers was sent. */ function checkLastModified ( $timestamp ) { global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest; @@ -127,7 +144,12 @@ class OutputPage { $this->sendCacheControl(); wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false ); $this->disable(); - @ob_end_clean(); // Don't output compressed blob + + // Don't output a compressed blob when using ob_gzhandler; + // it's technically against HTTP spec and seems to confuse + // Firefox when the response gets split over two packets. + wfClearOutputBuffers(); + return true; } else { wfDebug( "$fname: READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false ); @@ -162,9 +184,9 @@ class OutputPage { } } - function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; } - function setHTMLTitle( $name ) {$this->mHTMLtitle = $name; } - function setPageTitle( $name ) { + public function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; } + public function setHTMLTitle( $name ) {$this->mHTMLtitle = $name; } + public function setPageTitle( $name ) { global $action, $wgContLang; $name = $wgContLang->convert($name, true); $this->mPagetitle = $name; @@ -177,50 +199,50 @@ class OutputPage { $this->setHTMLTitle( wfMsg( 'pagetitle', $name ) ); } - function getHTMLTitle() { return $this->mHTMLtitle; } - function getPageTitle() { return $this->mPagetitle; } - function setSubtitle( $str ) { $this->mSubtitle = /*$this->parse(*/$str/*)*/; } // @bug 2514 - function getSubtitle() { return $this->mSubtitle; } - function isArticle() { return $this->mIsarticle; } - function setPrintable() { $this->mPrintable = true; } - function isPrintable() { return $this->mPrintable; } - function setSyndicated( $show = true ) { $this->mShowFeedLinks = $show; } - function isSyndicated() { return $this->mShowFeedLinks; } - function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; } - function getOnloadHandler() { return $this->mOnloadHandler; } - function disable() { $this->mDoNothing = true; } - - function setArticleRelated( $v ) { + public function getHTMLTitle() { return $this->mHTMLtitle; } + public function getPageTitle() { return $this->mPagetitle; } + public function setSubtitle( $str ) { $this->mSubtitle = /*$this->parse(*/$str/*)*/; } // @bug 2514 + public function getSubtitle() { return $this->mSubtitle; } + public function isArticle() { return $this->mIsarticle; } + public function setPrintable() { $this->mPrintable = true; } + public function isPrintable() { return $this->mPrintable; } + public function setSyndicated( $show = true ) { $this->mShowFeedLinks = $show; } + public function isSyndicated() { return $this->mShowFeedLinks; } + public function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; } + public function getOnloadHandler() { return $this->mOnloadHandler; } + public function disable() { $this->mDoNothing = true; } + + public function setArticleRelated( $v ) { $this->mIsArticleRelated = $v; if ( !$v ) { $this->mIsarticle = false; } } - function setArticleFlag( $v ) { + public function setArticleFlag( $v ) { $this->mIsarticle = $v; if ( $v ) { $this->mIsArticleRelated = $v; } } - function isArticleRelated() { return $this->mIsArticleRelated; } + public function isArticleRelated() { return $this->mIsArticleRelated; } - function getLanguageLinks() { return $this->mLanguageLinks; } - function addLanguageLinks($newLinkArray) { + public function getLanguageLinks() { return $this->mLanguageLinks; } + public function addLanguageLinks($newLinkArray) { $this->mLanguageLinks += $newLinkArray; } - function setLanguageLinks($newLinkArray) { + public function setLanguageLinks($newLinkArray) { $this->mLanguageLinks = $newLinkArray; } - function getCategoryLinks() { + public function getCategoryLinks() { return $this->mCategoryLinks; } /** * Add an array of categories, with names in the keys */ - function addCategoryLinks($categories) { + public function addCategoryLinks($categories) { global $wgUser, $wgContLang; if ( !is_array( $categories ) ) { @@ -233,32 +255,32 @@ class OutputPage { $lb->execute(); $sk =& $wgUser->getSkin(); - foreach ( $categories as $category => $arbitrary ) { + foreach ( $categories as $category => $unused ) { $title = Title::makeTitleSafe( NS_CATEGORY, $category ); $text = $wgContLang->convertHtml( $title->getText() ); $this->mCategoryLinks[] = $sk->makeLinkObj( $title, $text ); } } - function setCategoryLinks($categories) { + public function setCategoryLinks($categories) { $this->mCategoryLinks = array(); $this->addCategoryLinks($categories); } - function suppressQuickbar() { $this->mSuppressQuickbar = true; } - function isQuickbarSuppressed() { return $this->mSuppressQuickbar; } + public function suppressQuickbar() { $this->mSuppressQuickbar = true; } + public function isQuickbarSuppressed() { return $this->mSuppressQuickbar; } - function addHTML( $text ) { $this->mBodytext .= $text; } - function clearHTML() { $this->mBodytext = ''; } - function getHTML() { return $this->mBodytext; } - function debug( $text ) { $this->mDebugtext .= $text; } + public function addHTML( $text ) { $this->mBodytext .= $text; } + public function clearHTML() { $this->mBodytext = ''; } + public function getHTML() { return $this->mBodytext; } + public function debug( $text ) { $this->mDebugtext .= $text; } /* @deprecated */ - function setParserOptions( $options ) { + public function setParserOptions( $options ) { return $this->parserOptions( $options ); } - function parserOptions( $options = null ) { + public function parserOptions( $options = null ) { if ( !$this->mParserOptions ) { $this->mParserOptions = new ParserOptions; } @@ -271,7 +293,7 @@ class OutputPage { * @param mixed $revid an integer, or NULL * @return mixed previous value */ - function setRevisionId( $revid ) { + public function setRevisionId( $revid ) { $val = is_null( $revid ) ? null : intval( $revid ); return wfSetVar( $this->mRevisionId, $val ); } @@ -280,17 +302,20 @@ class OutputPage { * Convert wikitext to HTML and add it to the buffer * Default assumes that the current page title will * be used. + * + * @param string $text + * @param bool $linestart */ - function addWikiText( $text, $linestart = true ) { + public function addWikiText( $text, $linestart = true ) { global $wgTitle; $this->addWikiTextTitle($text, $wgTitle, $linestart); } - function addWikiTextWithTitle($text, &$title, $linestart = true) { + public function addWikiTextWithTitle($text, &$title, $linestart = true) { $this->addWikiTextTitle($text, $title, $linestart); } - function addWikiTextTitle($text, &$title, $linestart) { + private function addWikiTextTitle($text, &$title, $linestart) { global $wgParser; $fname = 'OutputPage:addWikiTextTitle'; wfProfileIn($fname); @@ -301,7 +326,11 @@ class OutputPage { wfProfileOut($fname); } - function addParserOutputNoText( &$parserOutput ) { + /** + * @todo document + * @param ParserOutput object &$parserOutput + */ + public function addParserOutputNoText( &$parserOutput ) { $this->mLanguageLinks += $parserOutput->getLanguageLinks(); $this->addCategoryLinks( $parserOutput->getCategories() ); $this->mNewSectionLink = $parserOutput->getNewSection(); @@ -319,6 +348,10 @@ class OutputPage { wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); } + /** + * @todo document + * @param ParserOutput &$parserOutput + */ function addParserOutput( &$parserOutput ) { $this->addParserOutputNoText( $parserOutput ); $text = $parserOutput->getText(); @@ -328,9 +361,13 @@ class OutputPage { /** * Add wikitext to the buffer, assuming that this is the primary text for a page view - * Saves the text into the parser cache if possible + * Saves the text into the parser cache if possible. + * + * @param string $text + * @param Article $article + * @param bool $cache */ - function addPrimaryWikiText( $text, $article, $cache = true ) { + public function addPrimaryWikiText( $text, $article, $cache = true ) { global $wgParser, $wgUser; $popts = $this->parserOptions(); @@ -348,8 +385,11 @@ class OutputPage { /** * For anything that isn't primary text or interface message + * + * @param string $text + * @param bool $linestart Is this the start of a line? */ - function addSecondaryWikiText( $text, $linestart = true ) { + public function addSecondaryWikiText( $text, $linestart = true ) { global $wgTitle; $popts = $this->parserOptions(); $popts->setTidy(true); @@ -360,9 +400,10 @@ class OutputPage { /** * Add the output of a QuickTemplate to the output buffer + * * @param QuickTemplate $template */ - function addTemplate( &$template ) { + public function addTemplate( &$template ) { ob_start(); $template->execute(); $this->addHTML( ob_get_contents() ); @@ -371,8 +412,12 @@ class OutputPage { /** * Parse wikitext and return the HTML. + * + * @param string $text + * @param bool $linestart Is this the start of a line? + * @param bool $interface ?? */ - function parse( $text, $linestart = true, $interface = false ) { + public function parse( $text, $linestart = true, $interface = false ) { global $wgParser, $wgTitle; $popts = $this->parserOptions(); if ( $interface) { $popts->setInterfaceMessage(true); } @@ -383,12 +428,12 @@ class OutputPage { } /** - * @param $article - * @param $user + * @param Article $article + * @param User $user * - * @return bool + * @return bool True if successful, else false. */ - function tryParserCache( &$article, $user ) { + public function tryParserCache( &$article, $user ) { $parserCache =& ParserCache::singleton(); $parserOutput = $parserCache->get( $article, $user ); if ( $parserOutput !== false ) { @@ -400,18 +445,17 @@ class OutputPage { } /** - * Set the maximum cache time on the Squid in seconds - * @param $maxage + * @param int $maxage Maximum cache time on the Squid, in seconds. */ - function setSquidMaxage( $maxage ) { + public function setSquidMaxage( $maxage ) { $this->mSquidMaxage = $maxage; } /** * Use enableClientCache(false) to force it to send nocache headers - * @param $state + * @param $state ?? */ - function enableClientCache( $state ) { + public function enableClientCache( $state ) { return wfSetVar( $this->mEnableClientCache, $state ); } @@ -421,7 +465,7 @@ class OutputPage { && $wgRequest->getText('uselang', false) === false; } - function sendCacheControl() { + public function sendCacheControl() { global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest; $fname = 'OutputPage::sendCacheControl'; @@ -477,10 +521,11 @@ class OutputPage { * Finally, all the text has been munged and accumulated into * the object, let's actually output it: */ - function output() { + public function output() { global $wgUser, $wgOutputEncoding, $wgRequest; global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType; - global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgScriptPath, $wgServer; + global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgAjaxWatch; + global $wgServer, $wgStyleVersion; if( $this->mDoNothing ){ return; @@ -490,12 +535,15 @@ class OutputPage { $sk = $wgUser->getSkin(); if ( $wgUseAjax ) { - $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajax.js\"></script>\n" ); - } + $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajax.js?$wgStyleVersion\"></script>\n" ); + if( $wgAjaxSearch ) { + $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js\"></script>\n" ); + $this->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", sajax_onload);</script>\n" ); + } - if ( $wgUseAjax && $wgAjaxSearch ) { - $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js\"></script>\n" ); - $this->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", sajax_onload);</script>\n" ); + if( $wgAjaxWatch && $wgUser->isLoggedIn() ) { + $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxwatch.js\"></script>\n" ); + } } if ( '' != $this->mRedirect ) { @@ -601,7 +649,11 @@ class OutputPage { wfProfileOut( $fname ); } - function out( $ins ) { + /** + * @todo document + * @param string $ins + */ + public function out( $ins ) { global $wgInputEncoding, $wgOutputEncoding, $wgContLang; if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) { $outs = $ins; @@ -612,7 +664,10 @@ class OutputPage { print $outs; } - function setEncodings() { + /** + * @todo document + */ + public static function setEncodings() { global $wgInputEncoding, $wgOutputEncoding; global $wgUser, $wgContLang; @@ -626,19 +681,20 @@ class OutputPage { } /** - * Returns a HTML comment with the elapsed time since request. - * This method has no side effects. - * Use wfReportTime() instead. + * Deprecated, use wfReportTime() instead. * @return string * @deprecated */ - function reportTime() { + public function reportTime() { $time = wfReportTime(); return $time; } /** - * Produce a "user is blocked" page + * Produce a "user is blocked" page. + * + * @param bool $return Whether to have a "return to $wgTitle" message or not. + * @return nothing */ function blockedPage( $return = true ) { global $wgUser, $wgContLang, $wgTitle; @@ -658,7 +714,9 @@ class OutputPage { } $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]"; - $this->addWikiText( wfMsg( 'blockedtext', $link, $reason, $ip, $name ) ); + $blockid = $wgUser->mBlock->mId; + + $this->addWikiText( wfMsg( 'blockedtext', $link, $reason, $ip, $name, $blockid ) ); # Don't auto-return to special pages if( $return ) { @@ -668,9 +726,13 @@ class OutputPage { } /** - * Note: these arguments are keys into wfMsg(), not text! + * Outputs a pretty page to explain why the request exploded. + * + * @param string $title Message key for page title. + * @param string $msg Message key for page text. + * @return nothing */ - function showErrorPage( $title, $msg ) { + public function showErrorPage( $title, $msg ) { global $wgTitle; $this->mDebugtext .= 'Original title: ' . @@ -688,7 +750,7 @@ class OutputPage { } /** @obsolete */ - function errorpage( $title, $msg ) { + public function errorpage( $title, $msg ) { throw new ErrorPageError( $title, $msg ); } @@ -698,7 +760,7 @@ class OutputPage { * * @param mixed $version The version of MediaWiki needed to use the page */ - function versionRequired( $version ) { + public function versionRequired( $version ) { $this->setPageTitle( wfMsg( 'versionrequired', $version ) ); $this->setHTMLTitle( wfMsg( 'versionrequired', $version ) ); $this->setRobotpolicy( 'noindex,nofollow' ); @@ -711,9 +773,10 @@ class OutputPage { /** * Display an error page noting that a given permission bit is required. + * * @param string $permission key required */ - function permissionRequired( $permission ) { + public function permissionRequired( $permission ) { global $wgGroupPermissions, $wgUser; $this->setPageTitle( wfMsg( 'badaccess' ) ); @@ -751,23 +814,25 @@ class OutputPage { } /** + * Use permissionRequired. * @deprecated */ - function sysopRequired() { + public function sysopRequired() { throw new MWException( "Call to deprecated OutputPage::sysopRequired() method\n" ); } /** + * Use permissionRequired. * @deprecated */ - function developerRequired() { + public function developerRequired() { throw new MWException( "Call to deprecated OutputPage::developerRequired() method\n" ); } /** * Produce the stock "please login to use the wiki" page */ - function loginToUse() { + public function loginToUse() { global $wgUser, $wgTitle, $wgContLang; if( $wgUser->isLoggedIn() ) { @@ -782,40 +847,45 @@ class OutputPage { $this->setRobotPolicy( 'noindex,nofollow' ); $this->setArticleFlag( false ); - $loginTitle = Title::makeTitle( NS_SPECIAL, 'Userlogin' ); + $loginTitle = SpecialPage::getTitleFor( 'Userlogin' ); $loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $wgTitle->getPrefixedUrl() ); $this->addHtml( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) ); $this->addHtml( "\n<!--" . $wgTitle->getPrefixedUrl() . "-->" ); # Don't return to the main page if the user can't read it # otherwise we'll end up in a pointless loop - $mainPage = Title::newFromText( wfMsgForContent( 'mainpage' ) ); + $mainPage = Title::newMainPage(); if( $mainPage->userCanRead() ) $this->returnToMain( true, $mainPage ); } /** @obsolete */ - function databaseError( $fname, $sql, $error, $errno ) { + public function databaseError( $fname, $sql, $error, $errno ) { throw new MWException( "OutputPage::databaseError is obsolete\n" ); } - function readOnlyPage( $source = null, $protected = false ) { + /** + * @todo document + * @param bool $protected Is the reason the page can't be reached because it's protected? + * @param mixed $source + */ + public function readOnlyPage( $source = null, $protected = false ) { global $wgUser, $wgReadOnlyFile, $wgReadOnly, $wgTitle; + $skin = $wgUser->getSkin(); $this->setRobotpolicy( 'noindex,nofollow' ); $this->setArticleRelated( false ); if( $protected ) { - $skin = $wgUser->getSkin(); $this->setPageTitle( wfMsg( 'viewsource' ) ); $this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) ); - + # Determine if protection is due to the page being a system message # and show an appropriate explanation if( $wgTitle->getNamespace() == NS_MEDIAWIKI && !$wgUser->isAllowed( 'editinterface' ) ) { $this->addWikiText( wfMsg( 'protectedinterface' ) ); } else { - $this->addWikiText( wfMsg( 'protectedtext' ) ); + $this->addWikiText( wfMsg( 'protectedpagetext' ) ); } } else { $this->setPageTitle( wfMsg( 'readonly' ) ); @@ -828,7 +898,8 @@ class OutputPage { } if( is_string( $source ) ) { - if( strcmp( $source, '' ) == 0 ) { + $this->addWikiText( wfMsg( 'viewsourcetext' ) ); + if( $source === '' ) { global $wgTitle; if ( $wgTitle->getNamespace() == NS_MEDIAWIKI ) { $source = wfMsgWeirdKey ( $wgTitle->getText() ); @@ -838,46 +909,48 @@ class OutputPage { } $rows = $wgUser->getIntOption( 'rows' ); $cols = $wgUser->getIntOption( 'cols' ); - + $text = "\n<textarea name='wpTextbox1' id='wpTextbox1' cols='$cols' rows='$rows' readonly='readonly'>" . htmlspecialchars( $source ) . "\n</textarea>"; $this->addHTML( $text ); } + $article = new Article($wgTitle); + $this->addHTML( $skin->formatTemplates($article->getUsedTemplates()) ); $this->returnToMain( false ); } /** @obsolete */ - function fatalError( $message ) { + public function fatalError( $message ) { throw new FatalError( $message ); } /** @obsolete */ - function unexpectedValueError( $name, $val ) { + public function unexpectedValueError( $name, $val ) { throw new FatalError( wfMsg( 'unexpected', $name, $val ) ); } /** @obsolete */ - function fileCopyError( $old, $new ) { + public function fileCopyError( $old, $new ) { throw new FatalError( wfMsg( 'filecopyerror', $old, $new ) ); } /** @obsolete */ - function fileRenameError( $old, $new ) { + public function fileRenameError( $old, $new ) { throw new FatalError( wfMsg( 'filerenameerror', $old, $new ) ); } /** @obsolete */ - function fileDeleteError( $name ) { + public function fileDeleteError( $name ) { throw new FatalError( wfMsg( 'filedeleteerror', $name ) ); } /** @obsolete */ - function fileNotFoundError( $name ) { + public function fileNotFoundError( $name ) { throw new FatalError( wfMsg( 'filenotfound', $name ) ); } - function showFatalError( $message ) { + public function showFatalError( $message ) { $this->setPageTitle( wfMsg( "internalerror" ) ); $this->setRobotpolicy( "noindex,nofollow" ); $this->setArticleRelated( false ); @@ -886,23 +959,23 @@ class OutputPage { $this->mBodytext = $message; } - function showUnexpectedValueError( $name, $val ) { + public function showUnexpectedValueError( $name, $val ) { $this->showFatalError( wfMsg( 'unexpected', $name, $val ) ); } - function showFileCopyError( $old, $new ) { + public function showFileCopyError( $old, $new ) { $this->showFatalError( wfMsg( 'filecopyerror', $old, $new ) ); } - function showFileRenameError( $old, $new ) { + public function showFileRenameError( $old, $new ) { $this->showFatalError( wfMsg( 'filerenameerror', $old, $new ) ); } - function showFileDeleteError( $name ) { + public function showFileDeleteError( $name ) { $this->showFatalError( wfMsg( 'filedeleteerror', $name ) ); } - function showFileNotFoundError( $name ) { + public function showFileNotFoundError( $name ) { $this->showFatalError( wfMsg( 'filenotfound', $name ) ); } @@ -911,7 +984,7 @@ class OutputPage { * @param $auto automatically redirect the user after 10 seconds * @param $returnto page title to return to. Default is Main Page. */ - function returnToMain( $auto = true, $returnto = NULL ) { + public function returnToMain( $auto = true, $returnto = NULL ) { global $wgUser, $wgOut, $wgRequest; if ( $returnto == NULL ) { @@ -919,7 +992,7 @@ class OutputPage { } if ( '' === $returnto ) { - $returnto = wfMsgForContent( 'mainpage' ); + $returnto = Title::newMainPage(); } if ( is_object( $returnto ) ) { @@ -944,8 +1017,10 @@ class OutputPage { /** * This function takes the title (first item of mGoodLinks), categories, existing and broken links for the page * and uses the first 10 of them for META keywords + * + * @param ParserOutput &$parserOutput */ - function addKeywords( &$parserOutput ) { + private function addKeywords( &$parserOutput ) { global $wgTitle; $this->addKeyword( $wgTitle->getPrefixedText() ); $count = 1; @@ -953,8 +1028,8 @@ class OutputPage { if ( !is_array( $links2d ) ) { return; } - foreach ( $links2d as $ns => $dbkeys ) { - foreach( $dbkeys as $dbkey => $id ) { + foreach ( $links2d as $dbkeys ) { + foreach( $dbkeys as $dbkey => $unused ) { $this->addKeyword( $dbkey ); if ( ++$count > 10 ) { break 2; @@ -964,12 +1039,12 @@ class OutputPage { } /** - * @access private - * @return string + * @return string The doctype, opening <html>, and head element. */ - function headElement() { + public function headElement() { global $wgDocType, $wgDTD, $wgContLanguageCode, $wgOutputEncoding, $wgMimeType; - global $wgUser, $wgContLang, $wgUseTrackbacks, $wgTitle; + global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces; + global $wgUser, $wgContLang, $wgUseTrackbacks, $wgTitle, $wgStyleVersion; if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) { $ret = "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?>\n"; @@ -984,7 +1059,11 @@ class OutputPage { } $rtl = $wgContLang->isRTL() ? " dir='RTL'" : ''; - $ret .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"$wgContLanguageCode\" lang=\"$wgContLanguageCode\" $rtl>\n"; + $ret .= "<html xmlns=\"{$wgXhtmlDefaultNamespace}\" "; + foreach($wgXhtmlNamespaces as $tag => $ns) { + $ret .= "xmlns:{$tag}=\"{$ns}\" "; + } + $ret .= "xml:lang=\"$wgContLanguageCode\" lang=\"$wgContLanguageCode\" $rtl>\n"; $ret .= "<head>\n<title>" . htmlspecialchars( $this->getHTMLTitle() ) . "</title>\n"; array_push( $this->mMetatags, array( "http:Content-type", "$wgMimeType; charset={$wgOutputEncoding}" ) ); @@ -995,7 +1074,7 @@ class OutputPage { } else { $media = "media='print'"; } - $printsheet = htmlspecialchars( "$wgStylePath/common/wikiprintable.css" ); + $printsheet = htmlspecialchars( "$wgStylePath/common/wikiprintable.css?$wgStyleVersion" ); $ret .= "<link rel='stylesheet' type='text/css' $media href='$printsheet' />\n"; $sk = $wgUser->getSkin(); @@ -1010,7 +1089,10 @@ class OutputPage { return $ret; } - function getHeadLinks() { + /** + * @return string HTML tag links to be put in the header. + */ + public function getHeadLinks() { global $wgRequest; $ret = ''; foreach ( $this->mMetatags as $tag ) { @@ -1060,9 +1142,8 @@ class OutputPage { * Turn off regular page output and return an error reponse * for when rate limiting has triggered. * @todo i18n - * @access public */ - function rateLimited() { + public function rateLimited() { global $wgOut; $wgOut->disable(); wfHttpError( 500, 'Internal Server Error', @@ -1075,9 +1156,8 @@ class OutputPage { * * @return bool True if the parser output instructs us to add one */ - function showNewSectionLink() { + public function showNewSectionLink() { return $this->mNewSectionLink; } - } ?> diff --git a/includes/PageHistory.php b/includes/PageHistory.php index d7f426fc..aea0f0ed 100644 --- a/includes/PageHistory.php +++ b/includes/PageHistory.php @@ -70,7 +70,7 @@ class PageHistory { $wgOut->setRobotpolicy( 'noindex,nofollow' ); $wgOut->setSyndicated( true ); - $logPage = Title::makeTitle( NS_SPECIAL, 'Log' ); + $logPage = SpecialPage::getTitleFor( 'Log' ); $logLink = $this->mSkin->makeKnownLinkObj( $logPage, wfMsgHtml( 'viewpagelogs' ), 'page=' . $this->mTitle->getPrefixedUrl() ); $subtitle = wfMsgHtml( 'revhistory' ) . '<br />' . $logLink; @@ -106,12 +106,11 @@ class PageHistory { * Do the list */ $pager = new PageHistoryPager( $this ); - $navbar = $pager->getNavigationBar(); $this->linesonpage = $pager->getNumRows(); $wgOut->addHTML( $pager->getNavigationBar() . $this->beginHistoryList() . - $pager->getBody() . + $pager->getBody() . $this->endHistoryList() . $pager->getNavigationBar() ); @@ -166,7 +165,19 @@ class PageHistory { : ''; } - /** @todo document */ + /** + * Returns a row from the history printout. + * + * @todo document some more, and maybe clean up the code (some params redundant?) + * + * @param object $row The database row corresponding to the line (or is it the previous line?). + * @param object $next The database row corresponding to the next line (or is it this one?). + * @param int $counter Apparently a counter of what row number we're at, counted from the top row = 1. + * @param $notificationtimestamp + * @param bool $latest Whether this row corresponds to the page's latest revision. + * @param bool $firstInList Whether this row corresponds to the first displayed on this history page. + * @return string HTML output for the row + */ function historyLine( $row, $next, $counter = '', $notificationtimestamp = false, $latest = false, $firstInList = false ) { global $wgUser; $rev = new Revision( $row ); @@ -184,7 +195,7 @@ class PageHistory { $s .= "($curlink) ($lastlink) $arbitrary"; if( $wgUser->isAllowed( 'deleterevision' ) ) { - $revdel = Title::makeTitle( NS_SPECIAL, 'Revisiondelete' ); + $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); if( $firstInList ) { // We don't currently handle well changing the top revision's settings $del = wfMsgHtml( 'rev-delundel' ); @@ -210,6 +221,9 @@ class PageHistory { if( $row->rev_deleted & Revision::DELETED_TEXT ) { $s .= ' ' . wfMsgHtml( 'deletedrev' ); } + if( $wgUser->isAllowed( 'rollback' ) && $latest ) { + $s .= ' '.$this->mSkin->generateRollback( $rev ); + } $s .= "</li>\n"; return $s; diff --git a/includes/Pager.php b/includes/Pager.php index b14aa8ca..0987cc06 100644 --- a/includes/Pager.php +++ b/includes/Pager.php @@ -323,7 +323,7 @@ abstract class IndexPager implements Pager { $next = array( 'offset' => $this->mLastShown, 'limit' => $urlLimit ); $last = array( 'dir' => 'prev', 'limit' => $urlLimit ); } - return compact( 'prev', 'next', 'first', 'last' ); + return array( 'prev' => $prev, 'next' => $next, 'first' => $first, 'last' => $last ); } /** @@ -487,7 +487,7 @@ abstract class TablePager extends IndexPager { } function getEndBody() { - return '</tbody></table>'; + return "</tbody></table>\n"; } function getEmptyBody() { @@ -553,7 +553,7 @@ abstract class TablePager extends IndexPager { 'next' => $wgContLang->isRTL() ? 'arrow_disabled_left_25.png' : 'arrow_disabled_right_25.png', 'last' => $wgContLang->isRTL() ? 'arrow_disabled_first_25.png' : 'arrow_disabled_last_25.png', ); - + $linkTexts = array(); $disabledTexts = array(); foreach ( $labels as $type => $label ) { @@ -564,12 +564,12 @@ abstract class TablePager extends IndexPager { $links = $this->getPagingLinks( $linkTexts, $disabledTexts ); $navClass = htmlspecialchars( $this->getNavClass() ); - $s = "<table class=\"$navClass\" align=\"center\" cellpadding=\"3\"><tr>"; + $s = "<table class=\"$navClass\" align=\"center\" cellpadding=\"3\"><tr>\n"; $cellAttrs = 'valign="top" align="center" width="' . 100 / count( $links ) . '%"'; foreach ( $labels as $type => $label ) { $s .= "<td $cellAttrs>{$links[$type]}</td>\n"; } - $s .= '</tr></table>'; + $s .= "</tr></table>\n"; return $s; } diff --git a/includes/Parser.php b/includes/Parser.php index 76783448..8d67279d 100644 --- a/includes/Parser.php +++ b/includes/Parser.php @@ -62,13 +62,15 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 ); * Processes wiki markup * * <pre> - * There are three main entry points into the Parser class: + * There are four main entry points into the Parser class: * parse() * produces HTML output * preSaveTransform(). * produces altered wiki markup. * transformMsg() * performs brace substitution on MediaWiki messages + * preprocess() + * removes HTML comments and expands templates * * Globals used: * objects: $wgLang, $wgContLang @@ -78,7 +80,7 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 ); * settings: * $wgUseTex*, $wgUseDynamicDates*, $wgInterwikiMagic*, * $wgNamespacesWithSubpages, $wgAllowExternalImages*, - * $wgLocaltimezone, $wgAllowSpecialInclusion*, + * $wgLocaltimezone, $wgAllowSpecialInclusion*, * $wgMaxArticleSize* * * * only within ParserOptions @@ -95,10 +97,10 @@ class Parser var $mTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables; # Cleared with clearState(): - var $mOutput, $mAutonumber, $mDTopen, $mStripState = array(); + var $mOutput, $mAutonumber, $mDTopen, $mStripState; var $mIncludeCount, $mArgStack, $mLastSection, $mInPre; var $mInterwikiLinkHolders, $mLinkHolders, $mUniqPrefix; - var $mIncludeSizes; + var $mIncludeSizes, $mDefaultSort; var $mTemplates, // cache of already loaded templates, avoids // multiple SQL queries for the same string $mTemplatePath; // stores an unsorted hash of all the templates already loaded @@ -110,7 +112,9 @@ class Parser $mTitle, // Title context, used for self-link rendering and similar things $mOutputType, // Output type, one of the OT_xxx constants $ot, // Shortcut alias, see setOutputType() - $mRevisionId; // ID to display in {{REVISIONID}} tags + $mRevisionId, // ID to display in {{REVISIONID}} tags + $mRevisionTimestamp, // The timestamp of the specified revision ID + $mRevIdForTs; // The revision ID which was used to fetch the timestamp /**#@-*/ @@ -162,6 +166,8 @@ class Parser $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH ); $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH ); $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH ); + $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) ); + $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH ); if ( $wgAllowDisplayTitle ) { $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH ); @@ -169,12 +175,11 @@ class Parser if ( $wgAllowSlowParserFunctions ) { $this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH ); } - - $this->initialiseVariables(); + $this->initialiseVariables(); $this->mFirstCall = false; wfProfileOut( __METHOD__ ); - } + } /** * Clear Parser state @@ -191,7 +196,7 @@ class Parser $this->mLastSection = ''; $this->mDTopen = false; $this->mIncludeCount = array(); - $this->mStripState = array(); + $this->mStripState = new StripState; $this->mArgStack = array(); $this->mInPre = false; $this->mInterwikiLinkHolders = array( @@ -205,7 +210,7 @@ class Parser 'texts' => array(), 'titles' => array() ); - $this->mRevisionId = null; + $this->mRevisionTimestamp = $this->mRevisionId = null; /** * Prefix for temporary replacement strings for the multipass parser. @@ -227,6 +232,7 @@ class Parser 'post-expand' => 0, 'arg' => 0 ); + $this->mDefaultSort = false; wfRunHooks( 'ParserClearState', array( &$this ) ); wfProfileOut( __METHOD__ ); @@ -273,6 +279,7 @@ class Parser global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang; $fname = 'Parser::parse-' . wfGetCaller(); + wfProfileIn( __METHOD__ ); wfProfileIn( $fname ); if ( $clearState ) { @@ -282,22 +289,17 @@ class Parser $this->mOptions = $options; $this->mTitle =& $title; $oldRevisionId = $this->mRevisionId; + $oldRevisionTimestamp = $this->mRevisionTimestamp; if( $revid !== null ) { $this->mRevisionId = $revid; + $this->mRevisionTimestamp = null; } $this->setOutputType( OT_HTML ); - - //$text = $this->strip( $text, $this->mStripState ); - // VOODOO MAGIC FIX! Sometimes the above segfaults in PHP5. - $x =& $this->mStripState; - - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) ); - $text = $this->strip( $text, $x ); - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) ); - + wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); + $text = $this->strip( $text, $this->mStripState ); + wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); $text = $this->internalParse( $text ); - - $text = $this->unstrip( $text, $this->mStripState ); + $text = $this->mStripState->unstripGeneral( $text ); # Clean up special characters, only run once, next-to-last before doBlockLevels $fixtags = array( @@ -320,7 +322,7 @@ class Parser # Side-effects: this calls $this->mOutput->setTitleText() $text = $wgContLang->parserConvert( $text, $this ); - $text = $this->unstripNoWiki( $text, $this->mStripState ); + $text = $this->mStripState->unstripNoWiki( $text ); wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) ); @@ -370,7 +372,9 @@ class Parser } $this->mOutput->setText( $text ); $this->mRevisionId = $oldRevisionId; + $this->mRevisionTimestamp = $oldRevisionTimestamp; wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $this->mOutput; } @@ -381,10 +385,9 @@ class Parser */ function recursiveTagParse( $text ) { wfProfileIn( __METHOD__ ); - $x =& $this->mStripState; - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) ); - $text = $this->strip( $text, $x ); - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) ); + wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); + $text = $this->strip( $text, $this->mStripState ); + wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); $text = $this->internalParse( $text ); wfProfileOut( __METHOD__ ); return $text; @@ -400,16 +403,14 @@ class Parser $this->setOutputType( OT_PREPROCESS ); $this->mOptions = $options; $this->mTitle = $title; - $x =& $this->mStripState; - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$x ) ); - $text = $this->strip( $text, $x ); - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$x ) ); + wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); + $text = $this->strip( $text, $this->mStripState ); + wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); if ( $this->mOptions->getRemoveComments() ) { $text = Sanitizer::removeHTMLcomments( $text ); } $text = $this->replaceVariables( $text ); - $text = $this->unstrip( $text, $x ); - $text = $this->unstripNowiki( $text, $x ); + $text = $this->mStripState->unstripBoth( $text ); wfProfileOut( __METHOD__ ); return $text; } @@ -503,7 +504,7 @@ class Parser $text = $q[2]; } } - + $matches[$marker] = array( $element, $content, Sanitizer::decodeTagAttributes( $attributes ), @@ -516,25 +517,29 @@ class Parser * Strips and renders nowiki, pre, math, hiero * If $render is set, performs necessary rendering operations on plugins * Returns the text, and fills an array with data needed in unstrip() - * If the $state is already a valid strip state, it adds to the state + * + * @param StripState $state * * @param bool $stripcomments when set, HTML comments <!-- like this --> * will be stripped in addition to other tags. This is important * for section editing, where these comments cause confusion when * counting the sections in the wikisource - * + * * @param array dontstrip contains tags which should not be stripped; * used to prevent stipping of <gallery> when saving (fixes bug 2700) * * @private */ - function strip( $text, &$state, $stripcomments = false , $dontstrip = array () ) { + function strip( $text, $state, $stripcomments = false , $dontstrip = array () ) { + global $wgContLang; wfProfileIn( __METHOD__ ); $render = ($this->mOutputType == OT_HTML); $uniq_prefix = $this->mUniqPrefix; - $commentState = array(); - + $commentState = new ReplacementArray; + $nowikiItems = array(); + $generalItems = array(); + $elements = array_merge( array( 'nowiki', 'gallery' ), array_keys( $this->mTagHooks ) ); @@ -545,13 +550,13 @@ class Parser if( $this->mOptions->getUseTeX() ) { $elements[] = 'math'; } - + # Removing $dontstrip tags from $elements list (currently only 'gallery', fixing bug 2700) foreach ( $elements AS $k => $v ) { if ( !in_array ( $v , $dontstrip ) ) continue; unset ( $elements[$k] ); } - + $matches = array(); $text = Parser::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix ); @@ -578,10 +583,10 @@ class Parser } // Shouldn't happen otherwise. :) case 'nowiki': - $output = wfEscapeHTMLTagsOnly( $content ); + $output = Xml::escapeTagsOnly( $content ); break; case 'math': - $output = MathRenderer::renderMath( $content ); + $output = $wgContLang->armourMath( MathRenderer::renderMath( $content ) ); break; case 'gallery': $output = $this->renderImageGallery( $content, $params ); @@ -600,18 +605,22 @@ class Parser $output = $tag; } - // Unstrip the output, because unstrip() is no longer recursive so - // it won't do it itself - $output = $this->unstrip( $output, $state ); + // Unstrip the output, to support recursive strip() calls + $output = $state->unstripBoth( $output ); if( !$stripcomments && $element == '!--' ) { - $commentState[$marker] = $output; + $commentState->setPair( $marker, $output ); } elseif ( $element == 'html' || $element == 'nowiki' ) { - $state['nowiki'][$marker] = $output; + $nowikiItems[$marker] = $output; } else { - $state['general'][$marker] = $output; + $generalItems[$marker] = $output; } } + # Add the new items to the state + # We do this after the loop instead of during it to avoid slowing + # down the recursive unstrip + $state->nowiki->mergeArray( $nowikiItems ); + $state->general->mergeArray( $generalItems ); # Unstrip comments unless explicitly told otherwise. # (The comments are always stripped prior to this point, so as to @@ -619,7 +628,7 @@ class Parser # a comment.) if ( !$stripcomments ) { // Put them all back and forget them - $text = strtr( $text, $commentState ); + $text = $commentState->replace( $text ); } wfProfileOut( __METHOD__ ); @@ -631,35 +640,27 @@ class Parser * * always call unstripNoWiki() after this one * @private + * @deprecated use $this->mStripState->unstrip() */ - function unstrip( $text, &$state ) { - if ( !isset( $state['general'] ) ) { - return $text; - } - - wfProfileIn( __METHOD__ ); - # TODO: good candidate for FSS - $text = strtr( $text, $state['general'] ); - wfProfileOut( __METHOD__ ); - return $text; + function unstrip( $text, $state ) { + return $state->unstripGeneral( $text ); } /** * Always call this after unstrip() to preserve the order * * @private + * @deprecated use $this->mStripState->unstrip() */ - function unstripNoWiki( $text, &$state ) { - if ( !isset( $state['nowiki'] ) ) { - return $text; - } + function unstripNoWiki( $text, $state ) { + return $state->unstripNoWiki( $text ); + } - wfProfileIn( __METHOD__ ); - # TODO: good candidate for FSS - $text = strtr( $text, $state['nowiki'] ); - wfProfileOut( __METHOD__ ); - - return $text; + /** + * @deprecated use $this->mStripState->unstripBoth() + */ + function unstripForHTML( $text ) { + return $this->mStripState->unstripBoth( $text ); } /** @@ -671,10 +672,7 @@ class Parser */ function insertStripItem( $text, &$state ) { $rnd = $this->mUniqPrefix . '-item' . Parser::getRandomString(); - if ( !$state ) { - $state = array(); - } - $state['general'][$rnd] = $text; + $state->general->setPair( $rnd, $text ); return $rnd; } @@ -791,135 +789,191 @@ class Parser * * @private */ - function doTableStuff ( $t ) { + function doTableStuff ( $text ) { $fname = 'Parser::doTableStuff'; wfProfileIn( $fname ); - $t = explode ( "\n" , $t ) ; - $td = array () ; # Is currently a td tag open? - $ltd = array () ; # Was it TD or TH? - $tr = array () ; # Is currently a tr tag open? - $ltr = array () ; # tr attributes - $has_opened_tr = array(); # Did this table open a <tr> element? - $indent_level = 0; # indent level of the table - foreach ( $t AS $k => $x ) + $lines = explode ( "\n" , $text ); + $td_history = array (); // Is currently a td tag open? + $last_tag_history = array (); // Save history of last lag activated (td, th or caption) + $tr_history = array (); // Is currently a tr tag open? + $tr_attributes = array (); // history of tr attributes + $has_opened_tr = array(); // Did this table open a <tr> element? + $indent_level = 0; // indent level of the table + foreach ( $lines as $key => $line ) { - $x = trim ( $x ) ; - $fc = substr ( $x , 0 , 1 ) ; - if ( preg_match( '/^(:*)\{\|(.*)$/', $x, $matches ) ) { + $line = trim ( $line ); + + if( $line == '' ) { // empty line, go to next line + continue; + } + $first_character = $line{0}; + $matches = array(); + + if ( preg_match( '/^(:*)\{\|(.*)$/' , $line , $matches ) ) { + // First check if we are starting a new table $indent_level = strlen( $matches[1] ); - $attributes = $this->unstripForHTML( $matches[2] ); + $attributes = $this->mStripState->unstripBoth( $matches[2] ); + $attributes = Sanitizer::fixTagAttributes ( $attributes , 'table' ); + + $lines[$key] = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>"; + array_push ( $td_history , false ); + array_push ( $last_tag_history , '' ); + array_push ( $tr_history , false ); + array_push ( $tr_attributes , '' ); + array_push ( $has_opened_tr , false ); + } else if ( count ( $td_history ) == 0 ) { + // Don't do any of the following + continue; + } else if ( substr ( $line , 0 , 2 ) == '|}' ) { + // We are ending a table + $line = '</table>' . substr ( $line , 2 ); + $last_tag = array_pop ( $last_tag_history ); - $t[$k] = str_repeat( '<dl><dd>', $indent_level ) . - '<table' . Sanitizer::fixTagAttributes ( $attributes, 'table' ) . '>' ; - array_push ( $td , false ) ; - array_push ( $ltd , '' ) ; - array_push ( $tr , false ) ; - array_push ( $ltr , '' ) ; - array_push ( $has_opened_tr, false ); - } - else if ( count ( $td ) == 0 ) { } # Don't do any of the following - else if ( '|}' == substr ( $x , 0 , 2 ) ) { - $z = "</table>" . substr ( $x , 2); - $l = array_pop ( $ltd ) ; - if ( !array_pop ( $has_opened_tr ) ) $z = "<tr><td></td></tr>" . $z ; - if ( array_pop ( $tr ) ) $z = '</tr>' . $z ; - if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ; - array_pop ( $ltr ) ; - $t[$k] = $z . str_repeat( '</dd></dl>', $indent_level ); - } - else if ( '|-' == substr ( $x , 0 , 2 ) ) { # Allows for |--------------- - $x = substr ( $x , 1 ) ; - while ( $x != '' && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ; - $z = '' ; - $l = array_pop ( $ltd ) ; + if ( !array_pop ( $has_opened_tr ) ) { + $line = "<tr><td></td></tr>{$line}"; + } + + if ( array_pop ( $tr_history ) ) { + $line = "</tr>{$line}"; + } + + if ( array_pop ( $td_history ) ) { + $line = "</{$last_tag}>{$line}"; + } + array_pop ( $tr_attributes ); + $lines[$key] = $line . str_repeat( '</dd></dl>' , $indent_level ); + } else if ( substr ( $line , 0 , 2 ) == '|-' ) { + // Now we have a table row + $line = preg_replace( '#^\|-+#', '', $line ); + + // Whats after the tag is now only attributes + $attributes = $this->mStripState->unstripBoth( $line ); + $attributes = Sanitizer::fixTagAttributes ( $attributes , 'tr' ); + array_pop ( $tr_attributes ); + array_push ( $tr_attributes , $attributes ); + + $line = ''; + $last_tag = array_pop ( $last_tag_history ); array_pop ( $has_opened_tr ); - array_push ( $has_opened_tr , true ) ; - if ( array_pop ( $tr ) ) $z = '</tr>' . $z ; - if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ; - array_pop ( $ltr ) ; - $t[$k] = $z ; - array_push ( $tr , false ) ; - array_push ( $td , false ) ; - array_push ( $ltd , '' ) ; - $attributes = $this->unstripForHTML( $x ); - array_push ( $ltr , Sanitizer::fixTagAttributes ( $attributes, 'tr' ) ) ; + array_push ( $has_opened_tr , true ); + + if ( array_pop ( $tr_history ) ) { + $line = '</tr>'; + } + + if ( array_pop ( $td_history ) ) { + $line = "</{$last_tag}>{$line}"; + } + + $lines[$key] = $line; + array_push ( $tr_history , false ); + array_push ( $td_history , false ); + array_push ( $last_tag_history , '' ); } - else if ( '|' == $fc || '!' == $fc || '|+' == substr ( $x , 0 , 2 ) ) { # Caption - # $x is a table row - if ( '|+' == substr ( $x , 0 , 2 ) ) { - $fc = '+' ; - $x = substr ( $x , 1 ) ; + else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 ) == '|+' ) { + // This might be cell elements, td, th or captions + if ( substr ( $line , 0 , 2 ) == '|+' ) { + $first_character = '+'; + $line = substr ( $line , 1 ); + } + + $line = substr ( $line , 1 ); + + if ( $first_character == '!' ) { + $line = str_replace ( '!!' , '||' , $line ); } - $after = substr ( $x , 1 ) ; - if ( $fc == '!' ) $after = str_replace ( '!!' , '||' , $after ) ; // Split up multiple cells on the same line. - // FIXME: This can result in improper nesting of tags processed + // FIXME : This can result in improper nesting of tags processed // by earlier parser steps, but should avoid splitting up eg // attribute values containing literal "||". - $after = wfExplodeMarkup( '||', $after ); + $cells = StringUtils::explodeMarkup( '||' , $line ); - $t[$k] = '' ; + $lines[$key] = ''; - # Loop through each table cell - foreach ( $after AS $theline ) + // Loop through each table cell + foreach ( $cells as $cell ) { - $z = '' ; - if ( $fc != '+' ) + $previous = ''; + if ( $first_character != '+' ) { - $tra = array_pop ( $ltr ) ; - if ( !array_pop ( $tr ) ) $z = '<tr'.$tra.">\n" ; - array_push ( $tr , true ) ; - array_push ( $ltr , '' ) ; + $tr_after = array_pop ( $tr_attributes ); + if ( !array_pop ( $tr_history ) ) { + $previous = "<tr{$tr_after}>\n"; + } + array_push ( $tr_history , true ); + array_push ( $tr_attributes , '' ); array_pop ( $has_opened_tr ); - array_push ( $has_opened_tr , true ) ; + array_push ( $has_opened_tr , true ); + } + + $last_tag = array_pop ( $last_tag_history ); + + if ( array_pop ( $td_history ) ) { + $previous = "</{$last_tag}>{$previous}"; } - $l = array_pop ( $ltd ) ; - if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ; - if ( $fc == '|' ) $l = 'td' ; - else if ( $fc == '!' ) $l = 'th' ; - else if ( $fc == '+' ) $l = 'caption' ; - else $l = '' ; - array_push ( $ltd , $l ) ; - - # Cell parameters - $y = explode ( '|' , $theline , 2 ) ; - # Note that a '|' inside an invalid link should not - # be mistaken as delimiting cell parameters - if ( strpos( $y[0], '[[' ) !== false ) { - $y = array ($theline); + if ( $first_character == '|' ) { + $last_tag = 'td'; + } else if ( $first_character == '!' ) { + $last_tag = 'th'; + } else if ( $first_character == '+' ) { + $last_tag = 'caption'; + } else { + $last_tag = ''; } - if ( count ( $y ) == 1 ) - $y = "{$z}<{$l}>{$y[0]}" ; + + array_push ( $last_tag_history , $last_tag ); + + // A cell could contain both parameters and data + $cell_data = explode ( '|' , $cell , 2 ); + + // Bug 553: Note that a '|' inside an invalid link should not + // be mistaken as delimiting cell parameters + if ( strpos( $cell_data[0], '[[' ) !== false ) { + $cell = "{$previous}<{$last_tag}>{$cell}"; + } else if ( count ( $cell_data ) == 1 ) + $cell = "{$previous}<{$last_tag}>{$cell_data[0]}"; else { - $attributes = $this->unstripForHTML( $y[0] ); - $y = "{$z}<{$l}".Sanitizer::fixTagAttributes($attributes, $l).">{$y[1]}" ; + $attributes = $this->mStripState->unstripBoth( $cell_data[0] ); + $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag ); + $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}"; } - $t[$k] .= $y ; - array_push ( $td , true ) ; + + $lines[$key] .= $cell; + array_push ( $td_history , true ); } } } - # Closing open td, tr && table - while ( count ( $td ) > 0 ) + // Closing open td, tr && table + while ( count ( $td_history ) > 0 ) { - $l = array_pop ( $ltd ) ; - if ( array_pop ( $td ) ) $t[] = '</td>' ; - if ( array_pop ( $tr ) ) $t[] = '</tr>' ; - if ( !array_pop ( $has_opened_tr ) ) $t[] = "<tr><td></td></tr>" ; - $t[] = '</table>' ; + if ( array_pop ( $td_history ) ) { + $lines[] = '</td>' ; + } + if ( array_pop ( $tr_history ) ) { + $lines[] = '</tr>' ; + } + if ( !array_pop ( $has_opened_tr ) ) { + $lines[] = "<tr><td></td></tr>" ; + } + + $lines[] = '</table>' ; + } + + $output = implode ( "\n" , $lines ) ; + + // special case: don't return empty table + if( $output == "<table>\n<tr><td></td></tr>\n</table>" ) { + $output = ''; } - $t = implode ( "\n" , $t ) ; - # special case: don't return empty table - if($t == "<table>\n<tr><td></td></tr>\n</table>") - $t = ''; wfProfileOut( $fname ); - return $t ; + + return $output; } /** @@ -935,7 +989,7 @@ class Parser wfProfileIn( $fname ); # Hook to suspend the parser in this state - if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$x ) ) ) { + if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) { wfProfileOut( $fname ); return $text ; } @@ -943,7 +997,7 @@ class Parser # Remove <noinclude> tags and <includeonly> sections $text = strtr( $text, array( '<onlyinclude>' => '' , '</onlyinclude>' => '' ) ); $text = strtr( $text, array( '<noinclude>' => '', '</noinclude>' => '') ); - $text = preg_replace( '/<includeonly>.*?<\/includeonly>/s', '', $text ); + $text = StringUtils::delimiterReplace( '<includeonly>', '</includeonly>', '', $text ); $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ) ); @@ -987,15 +1041,19 @@ class Parser */ function &doMagicLinks( &$text ) { wfProfileIn( __METHOD__ ); - $text = preg_replace_callback( + $text = preg_replace_callback( '!(?: # Start cases <a.*?</a> | # Skip link text <.*?> | # Skip stuff inside HTML elements (?:RFC|PMID)\s+([0-9]+) | # RFC or PMID, capture number as m[1] - ISBN\s+([0-9Xx-]+) # ISBN, capture number as m[2] + ISBN\s+(\b # ISBN, capture number as m[2] + (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix + (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters + [0-9Xx] # check digit + \b) )!x', array( &$this, 'magicLinkCallback' ), $text ); wfProfileOut( __METHOD__ ); - return $text; + return $text; } function magicLinkCallback( $m ) { @@ -1004,12 +1062,12 @@ class Parser return $m[0]; } elseif ( substr( $m[0], 0, 4 ) == 'ISBN' ) { $isbn = $m[2]; - $num = strtr( $isbn, array( + $num = strtr( $isbn, array( '-' => '', ' ' => '', 'x' => 'X', )); - $titleObj = Title::makeTitle( NS_SPECIAL, 'Booksources' ); + $titleObj = SpecialPage::getTitleFor( 'Booksources' ); $text = '<a href="' . $titleObj->escapeLocalUrl( "isbn=$num" ) . "\" class=\"internal\">ISBN $isbn</a>"; @@ -1023,10 +1081,10 @@ class Parser $urlmsg = 'pubmedurl'; $id = $m[1]; } else { - throw new MWException( __METHOD__.': unrecognised match type "' . + throw new MWException( __METHOD__.': unrecognised match type "' . substr($m[0], 0, 20 ) . '"' ); } - + $url = wfMsg( $urlmsg, $id); $sk =& $this->mOptions->getSkin(); $la = $sk->getExternalLinkAttributes( $url, $keyword.$id ); @@ -1106,9 +1164,9 @@ class Parser } # Count the number of occurrences of bold and italics mark-ups. # We are not counting sequences of five apostrophes. - if ( strlen( $arr[$i] ) == 2 ) $numitalics++; else - if ( strlen( $arr[$i] ) == 3 ) $numbold++; else - if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; } + if ( strlen( $arr[$i] ) == 2 ) { $numitalics++; } + else if ( strlen( $arr[$i] ) == 3 ) { $numbold++; } + else if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; } } $i++; } @@ -1264,6 +1322,7 @@ class Parser # The characters '<' and '>' (which were escaped by # removeHTMLtags()) should not be included in # URLs, per RFC 2396. + $m2 = array(); if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) { $text = substr($url, $m2[0][1]) . ' ' . $text; $url = substr($url, 0, $m2[0][1]); @@ -1299,7 +1358,7 @@ class Parser } $text = $wgContLang->markNoConversion($text); - + $url = Sanitizer::cleanUrl( $url ); # Process the trail (i.e. everything after this link up until start of the next link), @@ -1342,6 +1401,7 @@ class Parser $protocol = $bits[$i++]; $remainder = $bits[$i++]; + $m = array(); if ( preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $remainder, $m ) ) { # Found some characters after the protocol that look promising $url = $protocol . $m[1]; @@ -1349,10 +1409,10 @@ class Parser # special case: handle urls as url args: # http://www.example.com/foo?=http://www.example.com/bar - if(strlen($trail) == 0 && + if(strlen($trail) == 0 && isset($bits[$i]) && preg_match('/^'. wfUrlProtocols() . '$/S', $bits[$i]) && - preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $bits[$i + 1], $m )) + preg_match( '/^('.EXT_LINK_URL_CLASS.'+)(.*)$/s', $bits[$i + 1], $m )) { # add protocol, arg $url .= $bits[$i] . $m[1]; # protocol, url as arg to previous link @@ -1363,6 +1423,7 @@ class Parser # The characters '<' and '>' (which were escaped by # removeHTMLtags()) should not be included in # URLs, per RFC 2396. + $m2 = array(); if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) { $trail = substr($url, $m2[0][1]) . $trail; $url = substr($url, 0, $m2[0][1]); @@ -1498,6 +1559,7 @@ class Parser $nottalk = !$this->mTitle->isTalkPage(); if ( $useLinkPrefixExtension ) { + $m = array(); if ( preg_match( $e2, $s, $m ) ) { $first_prefix = $m[2]; } else { @@ -1507,7 +1569,10 @@ class Parser $prefix = ''; } - $selflink = $this->mTitle->getPrefixedText(); + if($wgContLang->hasVariants()) + $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText()); + else + $selflink = array($this->mTitle->getPrefixedText()); $useSubpages = $this->areSubpagesAllowed(); wfProfileOut( $fname.'-setup' ); @@ -1543,10 +1608,10 @@ class Parser # Still some problems for cases where the ] is meant to be outside punctuation, # and no image is in sight. See bug 2095. # - if( $text !== '' && - substr( $m[3], 0, 1 ) === ']' && - strpos($text, '[') !== false - ) + if( $text !== '' && + substr( $m[3], 0, 1 ) === ']' && + strpos($text, '[') !== false + ) { $text .= ']'; # so that replaceExternalLinks($text) works later $m[3] = substr( $m[3], 1 ); @@ -1595,7 +1660,7 @@ class Parser wfProfileOut( "$fname-misc" ); wfProfileIn( "$fname-title" ); - $nt = Title::newFromText( $this->unstripNoWiki($link, $this->mStripState) ); + $nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) ); if( !$nt ) { $s .= $prefix . '[[' . $line; wfProfileOut( "$fname-title" ); @@ -1605,7 +1670,7 @@ class Parser $ns = $nt->getNamespace(); $iw = $nt->getInterWiki(); wfProfileOut( "$fname-title" ); - + if ($might_be_img) { # if this is actually an invalid link wfProfileIn( "$fname-might_be_img" ); if ($ns == NS_IMAGE && $noforce) { #but might be an image @@ -1693,11 +1758,7 @@ class Parser $s = rtrim($s . "\n"); # bug 87 if ( $wasblank ) { - if ( $this->mTitle->getNamespace() == NS_CATEGORY ) { - $sortkey = $this->mTitle->getText(); - } else { - $sortkey = $this->mTitle->getPrefixedText(); - } + $sortkey = $this->getDefaultSort(); } else { $sortkey = $text; } @@ -1717,7 +1778,7 @@ class Parser } } - if( ( $nt->getPrefixedText() === $selflink ) && + if( ( in_array( $nt->getPrefixedText(), $selflink ) ) && ( $nt->getFragment() === '' ) ) { # Self-links are handled specially; generally de-link and change to bold. $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail ); @@ -1819,7 +1880,7 @@ class Parser * @return string less-or-more HTML with NOPARSE bits */ function armorLinks( $text ) { - return preg_replace( "/\b(" . wfUrlProtocols() . ')/', + return preg_replace( '/\b(' . wfUrlProtocols() . ')/', "{$this->mUniqPrefix}NOPARSE$1", $text ); } @@ -2073,10 +2134,10 @@ class Parser wfProfileIn( "$fname-paragraph" ); # No prefix (not in list)--go to paragraph mode // XXX: use a stack for nestable elements like span, table and div - $openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/center|<\\/tr|<\\/td|<\\/th)/iS', $t ); + $openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t ); $closematch = preg_match( '/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'. - '<td|<th|<div|<\\/div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<center)/iS', $t ); + '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t ); if ( $openmatch or $closematch ) { $paragraphStack = false; # TODO bug 5718: paragraph closed @@ -2326,9 +2387,11 @@ class Parser * expensive to check many times. */ static $varCache = array(); - if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) ) - if ( isset( $varCache[$index] ) ) + if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) ) { + if ( isset( $varCache[$index] ) ) { return $varCache[$index]; + } + } $ts = time(); wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) ); @@ -2416,15 +2479,15 @@ class Parser case 'revisionid': return $this->mRevisionId; case 'revisionday': - return intval( substr( wfRevisionTimestamp( $this->mRevisionId ), 6, 2 ) ); + return intval( substr( $this->getRevisionTimestamp(), 6, 2 ) ); case 'revisionday2': - return substr( wfRevisionTimestamp( $this->mRevisionId ), 6, 2 ); + return substr( $this->getRevisionTimestamp(), 6, 2 ); case 'revisionmonth': - return intval( substr( wfRevisionTimestamp( $this->mRevisionId ), 4, 2 ) ); + return intval( substr( $this->getRevisionTimestamp(), 4, 2 ) ); case 'revisionyear': - return substr( wfRevisionTimestamp( $this->mRevisionId ), 0, 4 ); + return substr( $this->getRevisionTimestamp(), 0, 4 ); case 'revisiontimestamp': - return wfRevisionTimestamp( $this->mRevisionId ); + return $this->getRevisionTimestamp(); case 'namespace': return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) ); case 'namespacee': @@ -2466,15 +2529,15 @@ class Parser case 'localdow': return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek ); case 'numberofarticles': - return $varCache[$index] = $wgContLang->formatNum( wfNumberOfArticles() ); + return $varCache[$index] = $wgContLang->formatNum( SiteStats::articles() ); case 'numberoffiles': - return $varCache[$index] = $wgContLang->formatNum( wfNumberOfFiles() ); + return $varCache[$index] = $wgContLang->formatNum( SiteStats::images() ); case 'numberofusers': - return $varCache[$index] = $wgContLang->formatNum( wfNumberOfUsers() ); + return $varCache[$index] = $wgContLang->formatNum( SiteStats::users() ); case 'numberofpages': - return $varCache[$index] = $wgContLang->formatNum( wfNumberOfPages() ); + return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() ); case 'numberofadmins': - return $varCache[$index] = $wgContLang->formatNum( wfNumberOfAdmins() ); + return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() ); case 'currenttimestamp': return $varCache[$index] = wfTimestampNow(); case 'localtimestamp': @@ -2543,7 +2606,7 @@ class Parser $lastOpeningBrace = -1; # last not closed parentheses $validOpeningBraces = implode( '', array_keys( $callbacks ) ); - + $i = 0; while ( $i < strlen( $text ) ) { # Find next opening brace, closing brace or pipe @@ -2597,13 +2660,13 @@ class Parser $maxCount = $openingBraceStack[$lastOpeningBrace]['count']; $count = strspn( $text, $text[$i], $i, $maxCount ); - # check for maximum matching characters (if there are 5 closing + # check for maximum matching characters (if there are 5 closing # characters, we will probably need only 3 - depending on the rules) $matchingCount = 0; $matchingCallback = null; $cbType = $callbacks[$openingBraceStack[$lastOpeningBrace]['brace']]; if ( $count > $cbType['max'] ) { - # The specified maximum exists in the callback array, unless the caller + # The specified maximum exists in the callback array, unless the caller # has made an error $matchingCount = $cbType['max']; } else { @@ -2624,12 +2687,12 @@ class Parser # let's set a title or last part (if '|' was found) if (null === $openingBraceStack[$lastOpeningBrace]['parts']) { - $openingBraceStack[$lastOpeningBrace]['title'] = - substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], + $openingBraceStack[$lastOpeningBrace]['title'] = + substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']); } else { - $openingBraceStack[$lastOpeningBrace]['parts'][] = - substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], + $openingBraceStack[$lastOpeningBrace]['parts'][] = + substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']); } @@ -2679,13 +2742,13 @@ class Parser } elseif ( $found == 'pipe' ) { # lets set a title if it is a first separator, or next part otherwise if (null === $openingBraceStack[$lastOpeningBrace]['parts']) { - $openingBraceStack[$lastOpeningBrace]['title'] = - substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], + $openingBraceStack[$lastOpeningBrace]['title'] = + substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']); $openingBraceStack[$lastOpeningBrace]['parts'] = array(); } else { - $openingBraceStack[$lastOpeningBrace]['parts'][] = - substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], + $openingBraceStack[$lastOpeningBrace]['parts'][] = + substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], $i - $openingBraceStack[$lastOpeningBrace]['partStart']); } $openingBraceStack[$lastOpeningBrace]['partStart'] = ++$i; @@ -2731,15 +2794,15 @@ class Parser $braceCallbacks[3] = array( &$this, 'argSubstitution' ); } if ( $braceCallbacks ) { - $callbacks = array( + $callbacks = array( '{' => array( 'end' => '}', 'cb' => $braceCallbacks, 'min' => $argsOnly ? 3 : 2, 'max' => isset( $braceCallbacks[3] ) ? 3 : 2, ), - '[' => array( - 'end' => ']', + '[' => array( + 'end' => ']', 'cb' => array(2=>null), 'min' => 2, 'max' => 2, @@ -2789,31 +2852,30 @@ class Parser return $text; } - # Split template arguments - function getTemplateArgs( $argsString ) { - if ( $argsString === '' ) { - return array(); - } - - $args = explode( '|', substr( $argsString, 1 ) ); - - # If any of the arguments contains a '[[' but no ']]', it needs to be - # merged with the next arg because the '|' character between belongs - # to the link syntax and not the template parameter syntax. - $argc = count($args); - - for ( $i = 0; $i < $argc-1; $i++ ) { - if ( substr_count ( $args[$i], '[[' ) != substr_count ( $args[$i], ']]' ) ) { - $args[$i] .= '|'.$args[$i+1]; - array_splice($args, $i+1, 1); - $i--; - $argc--; + + /// Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. + static function createAssocArgs( $args ) { + $assocArgs = array(); + $index = 1; + foreach( $args as $arg ) { + $eqpos = strpos( $arg, '=' ); + if ( $eqpos === false ) { + $assocArgs[$index++] = $arg; + } else { + $name = trim( substr( $arg, 0, $eqpos ) ); + $value = trim( substr( $arg, $eqpos+1 ) ); + if ( $value === false ) { + $value = ''; + } + if ( $name !== false ) { + $assocArgs[$name] = $value; + } } } - - return $args; + + return $assocArgs; } - + /** * Return the text of a template, after recursively * replacing any variables or templates within the template. @@ -2826,7 +2888,7 @@ class Parser * @private */ function braceSubstitution( $piece ) { - global $wgContLang, $wgLang, $wgAllowDisplayTitle, $action; + global $wgContLang, $wgLang, $wgAllowDisplayTitle; $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/; wfProfileIn( $fname ); wfProfileIn( __METHOD__.'-setup' ); @@ -2837,6 +2899,7 @@ class Parser $noparse = false; # Unsafe HTML tags should not be stripped, etc. $noargs = false; # Don't replace triple-brace arguments in $text $replaceHeadings = false; # Make the edit section links go to the template not the article + $headingOffset = 0; # Skip headings when number, to account for those that weren't transcluded. $isHTML = false; # $text is HTML, armour it against wikitext transformation $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered @@ -2845,7 +2908,7 @@ class Parser $linestart = ''; - + # $part1 is the bit before the first |, and must contain only title characters # $args is a list of arguments, starting from index 0, not including $part1 @@ -2863,7 +2926,6 @@ class Parser } $args = (null == $piece['parts']) ? array() : $piece['parts']; - $argc = count( $args ); wfProfileOut( __METHOD__.'-setup' ); # SUBST @@ -2893,7 +2955,7 @@ class Parser $mwMsg =& MagicWord::get( 'msg' ); $mwMsg->matchStartAndRemove( $part1 ); } - + # Check for RAW: $mwRaw =& MagicWord::get( 'raw' ); if ( $mwRaw->matchStartAndRemove( $part1 ) ) { @@ -2902,10 +2964,13 @@ class Parser } wfProfileOut( __METHOD__.'-modifiers' ); + //save path level before recursing into functions & templates. + $lastPathLevel = $this->mTemplatePath; + # Parser functions if ( !$found ) { wfProfileIn( __METHOD__ . '-pfunc' ); - + $colonPos = strpos( $part1, ':' ); if ( $colonPos !== false ) { # Case sensitive functions @@ -2970,7 +3035,6 @@ class Parser } # Load from database - $lastPathLevel = $this->mTemplatePath; if ( !$found ) { wfProfileIn( __METHOD__ . '-loadtpl' ); $ns = NS_TEMPLATE; @@ -2987,9 +3051,8 @@ class Parser if ( !is_null( $title ) ) { $titleText = $title->getPrefixedText(); - $checkVariantLink = sizeof($wgContLang->getVariants())>1; # Check for language variants if the template is not found - if($checkVariantLink && $title->getArticleID() == 0){ + if($wgContLang->hasVariants() && $title->getArticleID() == 0){ $wgContLang->findVariantLink($part1, $title); } @@ -3062,24 +3125,7 @@ class Parser $assocArgs = array(); } else { # Clean up argument array - $assocArgs = array(); - $index = 1; - foreach( $args as $arg ) { - $eqpos = strpos( $arg, '=' ); - if ( $eqpos === false ) { - $assocArgs[$index++] = $arg; - } else { - $name = trim( substr( $arg, 0, $eqpos ) ); - $value = trim( substr( $arg, $eqpos+1 ) ); - if ( $value === false ) { - $value = ''; - } - if ( $name !== false ) { - $assocArgs[$name] = $value; - } - } - } - + $assocArgs = self::createAssocArgs($args); # Add a new element to the templace recursion path $this->mTemplatePath[$part1] = 1; } @@ -3087,13 +3133,13 @@ class Parser if ( !$noparse ) { # If there are any <onlyinclude> tags, only include them if ( in_string( '<onlyinclude>', $text ) && in_string( '</onlyinclude>', $text ) ) { - preg_match_all( '/<onlyinclude>(.*?)\n?<\/onlyinclude>/s', $text, $m ); - $text = ''; - foreach ($m[1] as $piece) - $text .= $piece; + $replacer = new OnlyIncludeReplacer; + StringUtils::delimiterReplaceCallback( '<onlyinclude>', '</onlyinclude>', + array( &$replacer, 'replace' ), $text ); + $text = $replacer->output; } # Remove <noinclude> sections and <includeonly> tags - $text = preg_replace( '/<noinclude>.*?<\/noinclude>/s', '', $text ); + $text = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $text ); $text = strtr( $text, array( '<includeonly>' => '' , '</includeonly>' => '' ) ); if( $this->ot['html'] || $this->ot['pre'] ) { @@ -3109,7 +3155,7 @@ class Parser # If the template begins with a table or block-level # element, it should be treated as beginning a new line. - if (!$piece['lineStart'] && preg_match('/^({\\||:|;|#|\*)/', $text)) /*}*/{ + if (!$piece['lineStart'] && preg_match('/^({\\||:|;|#|\*)/', $text)) /*}*/{ $text = "\n" . $text; } } elseif ( !$noargs ) { @@ -3151,7 +3197,7 @@ class Parser $m = preg_split('/(^={1,6}.*?={1,6}\s*?$)/m', $text, -1, PREG_SPLIT_DELIM_CAPTURE); $text = ''; - $nsec = 0; + $nsec = $headingOffset; for( $i = 0; $i < count($m); $i += 2 ) { $text .= $m[$i]; if (!isset($m[$i + 1]) || $m[$i + 1] == "") continue; @@ -3160,6 +3206,7 @@ class Parser $text .= $hl; continue; } + $m2 = array(); preg_match('/^(={1,6})(.*?)(={1,6})\s*?$/m', $hl, $m2); $text .= $m2[1] . $m2[2] . "<!--MWTEMPLATESECTION=" . $encodedname . "&" . base64_encode("$nsec") . "-->" . $m2[3]; @@ -3192,10 +3239,19 @@ class Parser for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) { $rev = Revision::newFromTitle( $title ); $this->mOutput->addTemplate( $title, $title->getArticleID() ); - if ( !$rev ) { + if ( $rev ) { + $text = $rev->getText(); + } elseif( $title->getNamespace() == NS_MEDIAWIKI ) { + global $wgLang; + $message = $wgLang->lcfirst( $title->getText() ); + $text = wfMsgForContentNoTrans( $message ); + if( wfEmptyMsg( $message, $text ) ) { + $text = false; + break; + } + } else { break; } - $text = $rev->getText(); if ( $text === false ) { break; } @@ -3209,22 +3265,13 @@ class Parser * Transclude an interwiki link. */ function interwikiTransclude( $title, $action ) { - global $wgEnableScaryTranscluding, $wgCanonicalNamespaceNames; + global $wgEnableScaryTranscluding; if (!$wgEnableScaryTranscluding) return wfMsg('scarytranscludedisabled'); - // The namespace will actually only be 0 or 10, depending on whether there was a leading : - // But we'll handle it generally anyway - if ( $title->getNamespace() ) { - // Use the canonical namespace, which should work anywhere - $articleName = $wgCanonicalNamespaceNames[$title->getNamespace()] . ':' . $title->getDBkey(); - } else { - $articleName = $title->getDBkey(); - } - - $url = str_replace('$1', urlencode($articleName), Title::getInterwikiLink($title->getInterwiki())); - $url .= "?action=$action"; + $url = $title->getFullUrl( "action=$action" ); + if (strlen($url) > 255) return wfMsg('scarytranscludetoolong'); return $this->fetchScaryTemplateMaybeFromCache($url); @@ -3267,7 +3314,7 @@ class Parser if ( array_key_exists( $arg, $inputArgs ) ) { $text = $inputArgs[$arg]; - } else if (($this->mOutputType == OT_HTML || $this->mOutputType == OT_PREPROCESS ) && + } else if (($this->mOutputType == OT_HTML || $this->mOutputType == OT_PREPROCESS ) && null != $matches['parts'] && count($matches['parts']) > 0) { $text = $matches['parts'][0]; } @@ -3362,7 +3409,8 @@ class Parser # Get all headlines for numbering them and adding funky stuff like [edit] # links - this is for later, but we need the number of headlines right now - $numMatches = preg_match_all( '/<H([1-6])(.*?'.'>)(.*?)<\/H[1-6] *>/i', $text, $matches ); + $matches = array(); + $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?'.'>)(?P<header>.*?)<\/H[1-6] *>/i', $text, $matches ); # if there are fewer than 4 headlines in the article, do not show TOC # unless it's been explicitly enabled. @@ -3413,7 +3461,7 @@ class Parser $templatetitle = ''; $templatesection = 0; $numbering = ''; - + $mat = array(); if (preg_match("/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", $headline, $mat)) { $istemplate = 1; $templatetitle = base64_decode($mat[1]); @@ -3486,8 +3534,7 @@ class Parser # The canonized header is a version of the header text safe to use for links # Avoid insertion of weird stuff like <math> by expanding the relevant sections - $canonized_headline = $this->unstrip( $headline, $this->mStripState ); - $canonized_headline = $this->unstripNoWiki( $canonized_headline, $this->mStripState ); + $canonized_headline = $this->mStripState->unstripBoth( $headline ); # Remove link placeholders by the link text. # <!--LINK number--> @@ -3509,7 +3556,7 @@ class Parser $refers[$headlineCount] = $canonized_headline; # count how many in assoc. array so we can track dupes in anchors - @$refers[$canonized_headline]++; + isset( $refers[$canonized_headline] ) ? $refers[$canonized_headline]++ : $refers[$canonized_headline] = 1; $refcount[$headlineCount]=$refers[$canonized_headline]; # Don't number the heading if it is the only one (looks silly) @@ -3526,18 +3573,16 @@ class Parser if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) { $toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel); } + # give headline the correct <h#> tag if( $showEditLink && ( !$istemplate || $templatetitle !== "" ) ) { - if ( empty( $head[$headlineCount] ) ) { - $head[$headlineCount] = ''; - } if( $istemplate ) - $head[$headlineCount] .= $sk->editSectionLinkForOther($templatetitle, $templatesection); + $editlink = $sk->editSectionLinkForOther($templatetitle, $templatesection); else - $head[$headlineCount] .= $sk->editSectionLink($this->mTitle, $sectionCount+1, $headline_hint); + $editlink = $sk->editSectionLink($this->mTitle, $sectionCount+1, $headline_hint); + } else { + $editlink = ''; } - - # give headline the correct <h#> tag - @$head[$headlineCount] .= "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount] .$headline.'</h'.$level.'>'; + $head[$headlineCount] = $sk->makeHeadline( $level, $matches['attrib'][$headlineCount], $anchor, $headline, $editlink ); $headlineCount++; if( !$istemplate ) @@ -3595,7 +3640,7 @@ class Parser * @return string the altered wiki markup * @public */ - function preSaveTransform( $text, &$title, &$user, $options, $clearState = true ) { + function preSaveTransform( $text, &$title, $user, $options, $clearState = true ) { $this->mOptions = $options; $this->mTitle =& $title; $this->setOutputType( OT_WIKI ); @@ -3604,15 +3649,14 @@ class Parser $this->clearState(); } - $stripState = false; + $stripState = new StripState; $pairs = array( "\r\n" => "\n", ); $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text ); $text = $this->strip( $text, $stripState, true, array( 'gallery' ) ); $text = $this->pstPass2( $text, $stripState, $user ); - $text = $this->unstrip( $text, $stripState ); - $text = $this->unstripNoWiki( $text, $stripState ); + $text = $stripState->unstripBoth( $text ); return $text; } @@ -3620,7 +3664,7 @@ class Parser * Pre-save transform helper function * @private */ - function pstPass2( $text, &$stripState, &$user ) { + function pstPass2( $text, &$stripState, $user ) { global $wgContLang, $wgLocaltimezone; /* Note: This is the timestamp saved as hardcoded wikitext to @@ -3668,6 +3712,7 @@ class Parser $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text ); $t = $this->mTitle->getText(); + $m = array(); if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) { $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text ); } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && '' != "$m[1]$m[2]" ) { @@ -3834,7 +3879,7 @@ class Parser */ function setHook( $tag, $callback ) { $tag = strtolower( $tag ); - $oldVal = @$this->mTagHooks[$tag]; + $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null; $this->mTagHooks[$tag] = $callback; return $oldVal; @@ -3845,10 +3890,10 @@ class Parser * The callback function should have the form: * function myParserFunction( &$parser, $arg1, $arg2, $arg3 ) { ... } * - * The callback may either return the text result of the function, or an array with the text - * in element 0, and a number of flags in the other elements. The names of the flags are + * The callback may either return the text result of the function, or an array with the text + * in element 0, and a number of flags in the other elements. The names of the flags are * specified in the keys. Valid flags are: - * found The text returned is valid, stop processing the template. This + * found The text returned is valid, stop processing the template. This * is on by default. * nowiki Wiki markup in the return value should be escaped * noparse Unsafe HTML tags should not be stripped, etc. @@ -3859,13 +3904,13 @@ class Parser * * @param string $id The magic word ID * @param mixed $callback The callback function (and object) to use - * @param integer $flags a combination of the following flags: + * @param integer $flags a combination of the following flags: * SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}} * * @return The old callback function for this name, if any */ function setFunctionHook( $id, $callback, $flags = 0 ) { - $oldVal = @$this->mFunctionHooks[$id]; + $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id] : null; $this->mFunctionHooks[$id] = $callback; # Add to function cache @@ -3914,8 +3959,7 @@ class Parser */ function replaceLinkHolders( &$text, $options = 0 ) { global $wgUser; - global $wgOutputReplace; - global $wgContLang, $wgLanguageCode; + global $wgContLang; $fname = 'Parser::replaceLinkHolders'; wfProfileIn( $fname ); @@ -3936,6 +3980,7 @@ class Parser # Generate query $query = false; + $current = null; foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { # Make title object $title = $this->mLinkHolders['titles'][$key]; @@ -4006,10 +4051,14 @@ class Parser } wfProfileOut( $fname.'-check' ); - # Do a second query for different language variants of links (if needed) + # Do a second query for different language variants of links and categories if($wgContLang->hasVariants()){ - $linkBatch = new LinkBatch(); - $variantMap = array(); // maps $pdbkey_Variant => $pdbkey_original + $linkBatch = new LinkBatch(); + $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) + $categoryMap = array(); // maps $category_variant => $category (dbkeys) + $varCategories = array(); // category replacements oldDBkey => newDBkey + + $categories = $this->mOutput->getCategoryLinks(); // Add variants of links to link batch foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { @@ -4018,21 +4067,37 @@ class Parser continue; $pdbk = $title->getPrefixedDBkey(); + $titleText = $title->getText(); // generate all variants of the link title text - $allTextVariants = $wgContLang->convertLinkToAllVariants($title->getText()); + $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText); // if link was not found (in first query), add all variants to query if ( !isset($colours[$pdbk]) ){ foreach($allTextVariants as $textVariant){ - $variantTitle = Title::makeTitle( $ns, $textVariant ); + if($textVariant != $titleText){ + $variantTitle = Title::makeTitle( $ns, $textVariant ); + if(is_null($variantTitle)) continue; + $linkBatch->addObj( $variantTitle ); + $variantMap[$variantTitle->getPrefixedDBkey()][] = $key; + } + } + } + } + + // process categories, check if a category exists in some variant + foreach( $categories as $category){ + $variants = $wgContLang->convertLinkToAllVariants($category); + foreach($variants as $variant){ + if($variant != $category){ + $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) ); if(is_null($variantTitle)) continue; $linkBatch->addObj( $variantTitle ); - $variantMap[$variantTitle->getPrefixedDBkey()][] = $key; + $categoryMap[$variant] = $category; } } } - + if(!$linkBatch->isEmpty()){ // construct query @@ -4055,13 +4120,17 @@ class Parser $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title ); $varPdbk = $variantTitle->getPrefixedDBkey(); - $linkCache->addGoodLinkObj( $s->page_id, $variantTitle ); - $this->mOutput->addLink( $variantTitle, $s->page_id ); + $vardbk = $variantTitle->getDBkey(); - $holderKeys = $variantMap[$varPdbk]; + $holderKeys = array(); + if(isset($variantMap[$varPdbk])){ + $holderKeys = $variantMap[$varPdbk]; + $linkCache->addGoodLinkObj( $s->page_id, $variantTitle ); + $this->mOutput->addLink( $variantTitle, $s->page_id ); + } // loop over link holders - foreach($holderKeys as $key){ + foreach($holderKeys as $key){ $title = $this->mLinkHolders['titles'][$key]; if ( is_null( $title ) ) continue; @@ -4071,7 +4140,7 @@ class Parser // found link in some of the variants, replace the link holder data $this->mLinkHolders['titles'][$key] = $variantTitle; $this->mLinkHolders['dbkeys'][$key] = $variantTitle->getDBkey(); - + // set pdbk and colour $pdbks[$key] = $varPdbk; if ( $threshold > 0 ) { @@ -4081,19 +4150,39 @@ class Parser } else { $colours[$varPdbk] = 2; } - } + } else { $colours[$varPdbk] = 1; - } + } } } + + // check if the object is a variant of a category + if(isset($categoryMap[$vardbk])){ + $oldkey = $categoryMap[$vardbk]; + if($oldkey != $vardbk) + $varCategories[$oldkey]=$vardbk; + } + } + + // rebuild the categories in original order (if there are replacements) + if(count($varCategories)>0){ + $newCats = array(); + $originalCats = $this->mOutput->getCategories(); + foreach($originalCats as $cat => $sortkey){ + // make the replacement + if( array_key_exists($cat,$varCategories) ) + $newCats[$varCategories[$cat]] = $sortkey; + else $newCats[$cat] = $sortkey; + } + $this->mOutput->setCategoryLinks($newCats); } } } # Construct search and replace arrays wfProfileIn( $fname.'-construct' ); - $wgOutputReplace = array(); + $replacePairs = array(); foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { $pdbk = $pdbks[$key]; $searchkey = "<!--LINK $key-->"; @@ -4102,27 +4191,27 @@ class Parser $linkCache->addBadLinkObj( $title ); $colours[$pdbk] = 0; $this->mOutput->addLink( $title, 0 ); - $wgOutputReplace[$searchkey] = $sk->makeBrokenLinkObj( $title, + $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title, $this->mLinkHolders['texts'][$key], $this->mLinkHolders['queries'][$key] ); } elseif ( $colours[$pdbk] == 1 ) { - $wgOutputReplace[$searchkey] = $sk->makeKnownLinkObj( $title, + $replacePairs[$searchkey] = $sk->makeKnownLinkObj( $title, $this->mLinkHolders['texts'][$key], $this->mLinkHolders['queries'][$key] ); } elseif ( $colours[$pdbk] == 2 ) { - $wgOutputReplace[$searchkey] = $sk->makeStubLinkObj( $title, + $replacePairs[$searchkey] = $sk->makeStubLinkObj( $title, $this->mLinkHolders['texts'][$key], $this->mLinkHolders['queries'][$key] ); } } + $replacer = new HashtableReplacer( $replacePairs, 1 ); wfProfileOut( $fname.'-construct' ); # Do the thing wfProfileIn( $fname.'-replace' ); - $text = preg_replace_callback( '/(<!--LINK .*?-->)/', - "wfOutputReplaceMatches", + $replacer->cb(), $text); wfProfileOut( $fname.'-replace' ); @@ -4133,15 +4222,16 @@ class Parser if ( !empty( $this->mInterwikiLinkHolders['texts'] ) ) { wfProfileIn( $fname.'-interwiki' ); # Make interwiki link HTML - $wgOutputReplace = array(); + $replacePairs = array(); foreach( $this->mInterwikiLinkHolders['texts'] as $key => $link ) { $title = $this->mInterwikiLinkHolders['titles'][$key]; - $wgOutputReplace[$key] = $sk->makeLinkObj( $title, $link ); + $replacePairs[$key] = $sk->makeLinkObj( $title, $link ); } + $replacer = new HashtableReplacer( $replacePairs, 1 ); $text = preg_replace_callback( '/<!--IWLINK (.*?)-->/', - "wfOutputReplaceMatches", + $replacer->cb(), $text ); wfProfileOut( $fname.'-interwiki' ); } @@ -4192,13 +4282,13 @@ class Parser /** * Tag hook handler for 'pre'. */ - function renderPreTag( $text, $attribs, $parser ) { + function renderPreTag( $text, $attribs ) { // Backwards-compatibility hack - $content = preg_replace( '!<nowiki>(.*?)</nowiki>!is', '\\1', $text ); + $content = StringUtils::delimiterReplace( '<nowiki>', '</nowiki>', '$1', $text, 'i' ); $attribs = Sanitizer::validateTagAttributes( $attribs, 'pre' ); return wfOpenElement( 'pre', $attribs ) . - wfEscapeHTMLTagsOnly( $content ) . + Xml::escapeTagsOnly( $content ) . '</pre>'; } @@ -4218,13 +4308,18 @@ class Parser $ig->setParsing(); $ig->useSkin( $this->mOptions->getSkin() ); - if( isset( $params['caption'] ) ) - $ig->setCaption( $params['caption'] ); + if( isset( $params['caption'] ) ) { + $caption = $params['caption']; + $caption = htmlspecialchars( $caption ); + $caption = $this->replaceInternalLinks( $caption ); + $ig->setCaptionHtml( $caption ); + } $lines = explode( "\n", $text ); foreach ( $lines as $line ) { # match lines like these: # Image:someimage.jpg|This is some image + $matches = array(); preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches ); # Skip empty lines if ( count( $matches ) == 0 ) { @@ -4263,7 +4358,7 @@ class Parser /** * Parse image options text and use it to make an image */ - function makeImage( &$nt, $options ) { + function makeImage( $nt, $options ) { global $wgUseImageResize, $wgDjvuRenderer; $align = ''; @@ -4295,7 +4390,7 @@ class Parser $page = null; $manual_thumb = '' ; - foreach( $part as $key => $val ) { + foreach( $part as $val ) { if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) { $thumb=true; } elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) { @@ -4318,9 +4413,10 @@ class Parser && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) { # Select a page in a multipage document $page = $match; - } elseif ( $wgUseImageResize && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) { + } elseif ( $wgUseImageResize && !$width && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) { wfDebug( "img_width match: $match\n" ); # $match is the image width in pixels + $m = array(); if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) { $width = intval( $m[1] ); $height = intval( $m[2] ); @@ -4339,7 +4435,7 @@ class Parser # make sure there are no placeholders in thumbnail attributes # that are later expanded to html- so expand them now and # remove the tags - $alt = $this->unstrip($alt, $this->mStripState); + $alt = $this->mStripState->unstripBoth( $alt ); $alt = Sanitizer::stripAllTags( $alt ); # Linker does the rest @@ -4366,15 +4462,10 @@ class Parser */ function attributeStripCallback( &$text, $args ) { $text = $this->replaceVariables( $text, $args ); - $text = $this->unstripForHTML( $text ); + $text = $this->mStripState->unstripBoth( $text ); return $text; } - function unstripForHTML( $text ) { - $text = $this->unstrip( $text, $this->mStripState ); - $text = $this->unstripNoWiki( $text, $this->mStripState ); - return $text; - } /**#@-*/ /**#@+ @@ -4410,14 +4501,14 @@ class Parser private function extractSections( $text, $section, $mode, $newtext='' ) { # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML # comments to be stripped as well) - $striparray = array(); + $stripState = new StripState; $oldOutputType = $this->mOutputType; $oldOptions = $this->mOptions; $this->mOptions = new ParserOptions(); $this->setOutputType( OT_WIKI ); - $striptext = $this->strip( $text, $striparray, true ); + $striptext = $this->strip( $text, $stripState, true ); $this->setOutputType( $oldOutputType ); $this->mOptions = $oldOptions; @@ -4524,9 +4615,7 @@ class Parser } } # reinsert stripped tags - $rv = $this->unstrip( $rv, $striparray ); - $rv = $this->unstripNoWiki( $rv, $striparray ); - $rv = trim( $rv ); + $rv = trim( $stripState->unstripBoth( $rv ) ); return $rv; } @@ -4549,6 +4638,62 @@ class Parser return $this->extractSections( $oldtext, $section, "replace", $text ); } + /** + * Get the timestamp associated with the current revision, adjusted for + * the default server-local timestamp + */ + function getRevisionTimestamp() { + if ( is_null( $this->mRevisionTimestamp ) ) { + wfProfileIn( __METHOD__ ); + global $wgContLang; + $dbr =& wfGetDB( DB_SLAVE ); + $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', + array( 'rev_id' => $this->mRevisionId ), __METHOD__ ); + + // Normalize timestamp to internal MW format for timezone processing. + // This has the added side-effect of replacing a null value with + // the current time, which gives us more sensible behavior for + // previews. + $timestamp = wfTimestamp( TS_MW, $timestamp ); + + // The cryptic '' timezone parameter tells to use the site-default + // timezone offset instead of the user settings. + // + // Since this value will be saved into the parser cache, served + // to other users, and potentially even used inside links and such, + // it needs to be consistent for all visitors. + $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' ); + + wfProfileOut( __METHOD__ ); + } + return $this->mRevisionTimestamp; + } + + /** + * Mutator for $mDefaultSort + * + * @param $sort New value + */ + public function setDefaultSort( $sort ) { + $this->mDefaultSort = $sort; + } + + /** + * Accessor for $mDefaultSort + * Will use the title/prefixed title if none is set + * + * @return string + */ + public function getDefaultSort() { + if( $this->mDefaultSort !== false ) { + return $this->mDefaultSort; + } else { + return $this->mTitle->getNamespace() == NS_CATEGORY + ? $this->mTitle->getText() + : $this->mTitle->getPrefixedText(); + } + } + } /** @@ -4619,7 +4764,7 @@ class ParserOutput function addImage( $name ) { $this->mImages[$name] = 1; } function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; } function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; } - + function setNewSection( $value ) { $this->mNewSection = (bool)$value; } @@ -4674,7 +4819,7 @@ class ParserOutput */ class ParserOptions { - # All variables are private + # All variables are supposed to be private in theory, although in practise this is not the case. var $mUseTeX; # Use texvc to expand <math> tags var $mUseDynamicDates; # Use DateFormatter to format dates var $mInterwikiMagic; # Interlanguage links are removed and returned in an array @@ -4712,7 +4857,7 @@ class ParserOptions return $this->mSkin; } - function getDateFormat() { + function getDateFormat() { if ( !isset( $this->mDateFormat ) ) { $this->mDateFormat = $this->mUser->getDatePreference(); } @@ -4729,7 +4874,7 @@ class ParserOptions function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); } function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); } function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); } - function setSkin( &$x ) { $this->mSkin =& $x; } + function setSkin( $x ) { $this->mSkin = $x; } function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); } function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); } function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); } @@ -4758,7 +4903,6 @@ class ParserOptions $user = $wgUser; } else { $user = new User; - $user->setLoaded( true ); } } else { $user =& $userInput; @@ -4784,152 +4928,47 @@ class ParserOptions } } -/** - * Callback function used by Parser::replaceLinkHolders() - * to substitute link placeholders. - */ -function &wfOutputReplaceMatches( $matches ) { - global $wgOutputReplace; - return $wgOutputReplace[$matches[1]]; -} - -/** - * Return the total number of articles - */ -function wfNumberOfArticles() { - global $wgNumberOfArticles; +class OnlyIncludeReplacer { + var $output = ''; - wfLoadSiteStats(); - return $wgNumberOfArticles; -} - -/** - * Return the number of files - */ -function wfNumberOfFiles() { - $fname = 'wfNumberOfFiles'; - - wfProfileIn( $fname ); - $dbr =& wfGetDB( DB_SLAVE ); - $numImages = $dbr->selectField('site_stats', 'ss_images', array(), $fname ); - wfProfileOut( $fname ); - - return $numImages; -} - -/** - * Return the number of user accounts - * @return integer - */ -function wfNumberOfUsers() { - wfProfileIn( 'wfNumberOfUsers' ); - $dbr =& wfGetDB( DB_SLAVE ); - $count = $dbr->selectField( 'site_stats', 'ss_users', array(), 'wfNumberOfUsers' ); - wfProfileOut( 'wfNumberOfUsers' ); - return (int)$count; + function replace( $matches ) { + if ( substr( $matches[1], -1 ) == "\n" ) { + $this->output .= substr( $matches[1], 0, -1 ); + } else { + $this->output .= $matches[1]; + } + } } -/** - * Return the total number of pages - * @return integer - */ -function wfNumberOfPages() { - wfProfileIn( 'wfNumberOfPages' ); - $dbr =& wfGetDB( DB_SLAVE ); - $count = $dbr->selectField( 'site_stats', 'ss_total_pages', array(), 'wfNumberOfPages' ); - wfProfileOut( 'wfNumberOfPages' ); - return (int)$count; -} +class StripState { + var $general, $nowiki; -/** - * Return the total number of admins - * - * @return integer - */ -function wfNumberOfAdmins() { - static $admins = -1; - wfProfileIn( 'wfNumberOfAdmins' ); - if( $admins == -1 ) { - $dbr =& wfGetDB( DB_SLAVE ); - $admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), 'wfNumberOfAdmins' ); + function __construct() { + $this->general = new ReplacementArray; + $this->nowiki = new ReplacementArray; } - wfProfileOut( 'wfNumberOfAdmins' ); - return (int)$admins; -} -/** - * Count the number of pages in a particular namespace - * - * @param $ns Namespace - * @return integer - */ -function wfPagesInNs( $ns ) { - static $pageCount = array(); - wfProfileIn( 'wfPagesInNs' ); - if( !isset( $pageCount[$ns] ) ) { - $dbr =& wfGetDB( DB_SLAVE ); - $pageCount[$ns] = $dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $ns ), 'wfPagesInNs' ); + function unstripGeneral( $text ) { + wfProfileIn( __METHOD__ ); + $text = $this->general->replace( $text ); + wfProfileOut( __METHOD__ ); + return $text; } - wfProfileOut( 'wfPagesInNs' ); - return (int)$pageCount[$ns]; -} -/** - * Get various statistics from the database - * @private - */ -function wfLoadSiteStats() { - global $wgNumberOfArticles, $wgTotalViews, $wgTotalEdits; - $fname = 'wfLoadSiteStats'; - - if ( -1 != $wgNumberOfArticles ) return; - $dbr =& wfGetDB( DB_SLAVE ); - $s = $dbr->selectRow( 'site_stats', - array( 'ss_total_views', 'ss_total_edits', 'ss_good_articles' ), - array( 'ss_row_id' => 1 ), $fname - ); - - if ( $s === false ) { - return; - } else { - $wgTotalViews = $s->ss_total_views; - $wgTotalEdits = $s->ss_total_edits; - $wgNumberOfArticles = $s->ss_good_articles; + function unstripNoWiki( $text ) { + wfProfileIn( __METHOD__ ); + $text = $this->nowiki->replace( $text ); + wfProfileOut( __METHOD__ ); + return $text; } -} - -/** - * Get revision timestamp from the database considering timecorrection - * - * @param $id Int: page revision id - * @return integer - */ -function wfRevisionTimestamp( $id ) { - global $wgContLang; - $fname = 'wfRevisionTimestamp'; - - wfProfileIn( $fname ); - $dbr =& wfGetDB( DB_SLAVE ); - $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', - array( 'rev_id' => $id ), __METHOD__ ); - $timestamp = $wgContLang->userAdjust( $timestamp ); - wfProfileOut( $fname ); - - return $timestamp; -} -/** - * Escape html tags - * Basically replacing " > and < with HTML entities ( ", >, <) - * - * @param $in String: text that might contain HTML tags. - * @return string Escaped string - */ -function wfEscapeHTMLTagsOnly( $in ) { - return str_replace( - array( '"', '>', '<' ), - array( '"', '>', '<' ), - $in ); + function unstripBoth( $text ) { + wfProfileIn( __METHOD__ ); + $text = $this->general->replace( $text ); + $text = $this->nowiki->replace( $text ); + wfProfileOut( __METHOD__ ); + return $text; + } } ?> diff --git a/includes/ParserCache.php b/includes/ParserCache.php index 1f2e2aaf..37a42b7f 100644 --- a/includes/ParserCache.php +++ b/includes/ParserCache.php @@ -56,8 +56,6 @@ class ParserCache { $fname = 'ParserCache::get'; wfProfileIn( $fname ); - $hash = $user->getPageRenderingHash(); - $pageid = intval( $article->getID() ); $key = $this->getKey( $article, $user ); wfDebug( "Trying parser cache $key\n" ); diff --git a/includes/Profiler.php b/includes/Profiler.php index 78003e02..30cda63f 100644 --- a/includes/Profiler.php +++ b/includes/Profiler.php @@ -164,7 +164,7 @@ class Profiler { } function getCallTreeLine($entry) { - list ($fname, $level, $start, $x, $end) = $entry; + list ($fname, $level, $start, /* $x */, $end) = $entry; $delta = $end - $start; $space = str_repeat(' ', $level); @@ -208,7 +208,6 @@ class Profiler { # First, subtract the overhead! foreach ($this->mStack as $entry) { $fname = $entry[0]; - $thislevel = $entry[1]; $start = $entry[2]; $end = $entry[4]; $elapsed = $end - $start; @@ -229,7 +228,6 @@ class Profiler { # Collate foreach ($this->mStack as $index => $entry) { $fname = $entry[0]; - $thislevel = $entry[1]; $start = $entry[2]; $end = $entry[4]; $elapsed = $end - $start; @@ -351,7 +349,7 @@ class Profiler { } static function getCaller( $level ) { - $backtrace = debug_backtrace(); + $backtrace = wfDebugBacktrace(); if ( isset( $backtrace[$level] ) ) { if ( isset( $backtrace[$level]['class'] ) ) { $caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function']; diff --git a/includes/ProfilerSimple.php b/includes/ProfilerSimple.php index d5bdaf94..e69bfc47 100644 --- a/includes/ProfilerSimple.php +++ b/includes/ProfilerSimple.php @@ -12,6 +12,7 @@ require_once(dirname(__FILE__).'/Profiler.php'); class ProfilerSimple extends Profiler { var $mMinimumTime = 0; + var $mProfileID = false; function ProfilerSimple() { global $wgRequestTime,$wgRUstart; @@ -24,7 +25,7 @@ class ProfilerSimple extends Profiler { $entry =& $this->mCollated["-setup"]; if (!is_array($entry)) { $entry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0); - $this->mCollated[$functionname] =& $entry; + $this->mCollated["-setup"] =& $entry; } $entry['cpu'] += $elapsedcpu; @@ -39,6 +40,18 @@ class ProfilerSimple extends Profiler { $this->mMinimumTime = $min; } + function setProfileID( $id ) { + $this->mProfileID = $id; + } + + function getProfileID() { + if ( $this->mProfileID === false ) { + return wfWikiID(); + } else { + return $this->mProfileID; + } + } + function profileIn($functionname) { global $wgDebugFunctionEntry; if ($wgDebugFunctionEntry) { @@ -48,15 +61,13 @@ class ProfilerSimple extends Profiler { } function profileOut($functionname) { - $memory = memory_get_usage(); - global $wgDebugFunctionEntry; if ($wgDebugFunctionEntry) { $this->debug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n"); } - list($ofname,$ocount,$ortime,$octime) = array_pop($this->mWorkStack); + list($ofname, /* $ocount */ ,$ortime,$octime) = array_pop($this->mWorkStack); if (!$ofname) { $this->debug("Profiling error: $functionname\n"); diff --git a/includes/ProfilerSimpleUDP.php b/includes/ProfilerSimpleUDP.php index e0490512..a8527c38 100644 --- a/includes/ProfilerSimpleUDP.php +++ b/includes/ProfilerSimpleUDP.php @@ -21,7 +21,7 @@ class ProfilerSimpleUDP extends ProfilerSimple { $plength=0; $packet=""; foreach ($this->mCollated as $entry=>$pfdata) { - $pfline=sprintf ("%s %s %d %f %f %f %f %s\n", wfWikiID(),"-",$pfdata['count'], + $pfline=sprintf ("%s %s %d %f %f %f %f %s\n", $this->getProfileID(),"-",$pfdata['count'], $pfdata['cpu'],$pfdata['cpu_sq'],$pfdata['real'],$pfdata['real_sq'],$entry); $length=strlen($pfline); /* printf("<!-- $pfline -->"); */ diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php index fd1bc81e..f96262fe 100644 --- a/includes/ProtectionForm.php +++ b/includes/ProtectionForm.php @@ -212,9 +212,9 @@ class ProtectionForm { } function buildScript() { - global $wgStylePath; + global $wgStylePath, $wgStyleVersion; return '<script type="text/javascript" src="' . - htmlspecialchars( $wgStylePath . "/common/protect.js" ) . + htmlspecialchars( $wgStylePath . "/common/protect.js?$wgStyleVersion" ) . '"></script>'; } diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php index 7974c882..22ea4947 100644 --- a/includes/ProxyTools.php +++ b/includes/ProxyTools.php @@ -23,7 +23,7 @@ function wfGetForwardedFor() { /** Work out the IP address based on various globals */ function wfGetIP() { - global $wgSquidServers, $wgSquidServersNoPurge, $wgIP; + global $wgIP; # Return cached result if ( !empty( $wgIP ) ) { @@ -33,34 +33,31 @@ function wfGetIP() { /* collect the originating ips */ # Client connecting to this webserver if ( isset( $_SERVER['REMOTE_ADDR'] ) ) { - $ipchain = array( $_SERVER['REMOTE_ADDR'] ); + $ipchain = array( IP::canonicalize( $_SERVER['REMOTE_ADDR'] ) ); } else { # Running on CLI? $ipchain = array( '127.0.0.1' ); } $ip = $ipchain[0]; - # Get list of trusted proxies - # Flipped for quicker access - $trustedProxies = array_flip( array_merge( $wgSquidServers, $wgSquidServersNoPurge ) ); - if ( count( $trustedProxies ) ) { - # Append XFF on to $ipchain - $forwardedFor = wfGetForwardedFor(); - if ( isset( $forwardedFor ) ) { - $xff = array_map( 'trim', explode( ',', $forwardedFor ) ); - $xff = array_reverse( $xff ); - $ipchain = array_merge( $ipchain, $xff ); - } - # Step through XFF list and find the last address in the list which is a trusted server - # Set $ip to the IP address given by that trusted server, unless the address is not sensible (e.g. private) - foreach ( $ipchain as $i => $curIP ) { - if ( array_key_exists( $curIP, $trustedProxies ) ) { - if ( isset( $ipchain[$i + 1] ) && IP::isPublic( $ipchain[$i + 1] ) ) { - $ip = $ipchain[$i + 1]; - } - } else { - break; + # Append XFF on to $ipchain + $forwardedFor = wfGetForwardedFor(); + if ( isset( $forwardedFor ) ) { + $xff = array_map( 'trim', explode( ',', $forwardedFor ) ); + $xff = array_reverse( $xff ); + $ipchain = array_merge( $ipchain, $xff ); + } + + # Step through XFF list and find the last address in the list which is a trusted server + # Set $ip to the IP address given by that trusted server, unless the address is not sensible (e.g. private) + foreach ( $ipchain as $i => $curIP ) { + $curIP = IP::canonicalize( $curIP ); + if ( wfIsTrustedProxy( $curIP ) ) { + if ( isset( $ipchain[$i + 1] ) && IP::isPublic( $ipchain[$i + 1] ) ) { + $ip = $ipchain[$i + 1]; } + } else { + break; } } @@ -69,6 +66,21 @@ function wfGetIP() { return $ip; } +function wfIsTrustedProxy( $ip ) { + global $wgSquidServers, $wgSquidServersNoPurge; + + if ( in_array( $ip, $wgSquidServers ) || + in_array( $ip, $wgSquidServersNoPurge ) || + wfIsAOLProxy( $ip ) + ) { + $trusted = true; + } else { + $trusted = false; + } + wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) ); + return $trusted; +} + /** * Forks processes to scan the originating IP for an open proxy server * MemCached can be used to skip IPs that have already been scanned @@ -96,7 +108,7 @@ function wfProxyCheck() { # Fork the processes if ( !$skip ) { - $title = Title::makeTitle( NS_SPECIAL, 'Blockme' ); + $title = SpecialPage::getTitleFor( 'Blockme' ); $iphash = md5( $ip . $wgProxyKey ); $url = $title->getFullURL( 'ip='.$iphash ); @@ -154,6 +166,51 @@ function wfIsLocallyBlockedProxy( $ip ) { return $ret; } +/** + * TODO: move this list to the database in a global IP info table incorporating + * trusted ISP proxies, blocked IP addresses and open proxies. + */ +function wfIsAOLProxy( $ip ) { + $ranges = array( + '64.12.96.0/19', + '149.174.160.0/20', + '152.163.240.0/21', + '152.163.248.0/22', + '152.163.252.0/23', + '152.163.96.0/22', + '152.163.100.0/23', + '195.93.32.0/22', + '195.93.48.0/22', + '195.93.64.0/19', + '195.93.96.0/19', + '195.93.16.0/20', + '198.81.0.0/22', + '198.81.16.0/20', + '198.81.8.0/23', + '202.67.64.128/25', + '205.188.192.0/20', + '205.188.208.0/23', + '205.188.112.0/20', + '205.188.146.144/30', + '207.200.112.0/21', + ); + + static $parsedRanges; + if ( is_null( $parsedRanges ) ) { + $parsedRanges = array(); + foreach ( $ranges as $range ) { + $parsedRanges[] = IP::parseRange( $range ); + } + } + + $hex = IP::toHex( $ip ); + foreach ( $parsedRanges as $range ) { + if ( $hex >= $range[0] && $hex <= $range[1] ) { + return true; + } + } + return false; +} diff --git a/includes/QueryPage.php b/includes/QueryPage.php index 7d6dc900..ff6355e7 100644 --- a/includes/QueryPage.php +++ b/includes/QueryPage.php @@ -92,7 +92,7 @@ class QueryPage { * @return Title */ function getTitle() { - return Title::makeTitle( NS_SPECIAL, $this->getName() ); + return SpecialPage::getTitleFor( $this->getName() ); } /** @@ -282,13 +282,15 @@ class QueryPage { $sname = $this->getName(); $fname = get_class($this) . '::doQuery'; - $sql = $this->getSQL(); $dbr =& wfGetDB( DB_SLAVE ); - $querycache = $dbr->tableName( 'querycache' ); $wgOut->setSyndicated( $this->isSyndicated() ); - if ( $this->isCached() ) { + if ( !$this->isCached() ) { + $sql = $this->getSQL(); + } else { + # Get the cached result + $querycache = $dbr->tableName( 'querycache' ); $type = $dbr->strencode( $sname ); $sql = "SELECT qc_type as type, qc_namespace as namespace,qc_title as title, qc_value as value @@ -310,6 +312,14 @@ class QueryPage { } $wgOut->addWikiText( $cacheNotice ); + + # If updates on this page have been disabled, let the user know + # that the data set won't be refreshed for now + global $wgDisableQueryPageUpdate; + if( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) { + $wgOut->addWikiText( wfMsg( 'querypage-no-updates' ) ); + } + } } @@ -339,7 +349,7 @@ class QueryPage { if ( $num > 0 ) { $s = array(); if ( ! $this->listoutput ) - $s[] = "<ol start='" . ( $offset + 1 ) . "' class='special'>"; + $s[] = $this->openList( $offset ); # Only read at most $num rows, because $res may contain the whole 1000 for ( $i = 0; $i < $num && $obj = $dbr->fetchObject( $res ); $i++ ) { @@ -364,7 +374,7 @@ class QueryPage { $dbr->freeResult( $res ); if ( ! $this->listoutput ) - $s[] = '</ol>'; + $s[] = $this->closeList(); $str = $this->listoutput ? $wgContLang->listToText( $s ) : implode( '', $s ); $wgOut->addHTML( $str ); } @@ -373,12 +383,20 @@ class QueryPage { } return $num; } + + function openList( $offset ) { + return "<ol start='" . ( $offset + 1 ) . "' class='special'>"; + } + + function closeList() { + return '</ol>'; + } /** * Do any necessary preprocessing of the result object. - * You should pass this by reference: &$db , &$res + * You should pass this by reference: &$db , &$res [although probably no longer necessary in PHP5] */ - function preprocessResults( $db, $res ) {} + function preprocessResults( &$db, &$res ) {} /** * Similar to above, but packaging in a syndicated feed instead of a web page @@ -459,7 +477,7 @@ class QueryPage { } function feedUrl() { - $title = Title::MakeTitle( NS_SPECIAL, $this->getName() ); + $title = SpecialPage::getTitleFor( $this->getName() ); return $title->getFullURL(); } } diff --git a/includes/RecentChange.php b/includes/RecentChange.php index ebd4b335..1c7791c2 100644 --- a/includes/RecentChange.php +++ b/includes/RecentChange.php @@ -24,6 +24,8 @@ * rc_ip IP address of the user in dotted quad notation * rc_new obsolete, use rc_type==RC_NEW * rc_patrolled boolean whether or not someone has marked this edit as patrolled + * rc_old_len integer byte length of the text before the edit + * rc_new_len the same after the edit * * mExtra: * prefixedDBkey prefixed db key, used by external app via msg queue @@ -54,7 +56,7 @@ class RecentChange return $rc; } - /* static */ function newFromCurRow( $row, $rc_this_oldid = 0 ) + public static function newFromCurRow( $row, $rc_this_oldid = 0 ) { $rc = new RecentChange; $rc->loadFromCurRow( $row, $rc_this_oldid ); @@ -62,6 +64,24 @@ class RecentChange $rc->numberofWatchingusers = false; return $rc; } + + /** + * Obtain the recent change with a given rc_id value + * + * @param $rcid rc_id value to retrieve + * @return RecentChange + */ + public static function newFromId( $rcid ) { + $dbr =& wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'recentchanges', '*', array( 'rc_id' => $rcid ), __METHOD__ ); + if( $res && $dbr->numRows( $res ) > 0 ) { + $row = $dbr->fetchObject( $res ); + $dbr->freeResult( $res ); + return self::newFromRow( $row ); + } else { + return NULL; + } + } # Accessors @@ -95,7 +115,7 @@ class RecentChange # Writes the data in this object to the database function save() { - global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPPort, $wgRC2UDPPrefix, $wgUseRCPatrol; + global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPPort, $wgRC2UDPPrefix; $fname = 'RecentChange::save'; $dbw =& wfGetDB( DB_MASTER ); @@ -212,6 +232,7 @@ class RecentChange $oldId, $lastTimestamp, $bot = "default", $ip = '', $oldSize = 0, $newSize = 0, $newId = 0) { + if ( $bot === 'default' ) { $bot = $user->isAllowed( 'bot' ); } @@ -240,9 +261,11 @@ class RecentChange 'rc_bot' => $bot ? 1 : 0, 'rc_moved_to_ns' => 0, 'rc_moved_to_title' => '', - 'rc_ip' => $ip, - 'rc_patrolled' => 0, - 'rc_new' => 0 # obsolete + 'rc_ip' => $ip, + 'rc_patrolled' => 0, + 'rc_new' => 0, # obsolete + 'rc_old_len' => $oldSize, + 'rc_new_len' => $newSize ); $rc->mExtra = array( @@ -294,7 +317,9 @@ class RecentChange 'rc_moved_to_title' => '', 'rc_ip' => $ip, 'rc_patrolled' => 0, - 'rc_new' => 1 # obsolete + 'rc_new' => 1, # obsolete + 'rc_old_len' => 0, + 'rc_new_len' => $size ); $rc->mExtra = array( @@ -336,7 +361,9 @@ class RecentChange 'rc_moved_to_title' => $newTitle->getDBkey(), 'rc_ip' => $ip, 'rc_new' => 0, # obsolete - 'rc_patrolled' => 1 + 'rc_patrolled' => 1, + 'rc_old_len' => NULL, + 'rc_new_len' => NULL, ); $rc->mExtra = array( @@ -386,7 +413,9 @@ class RecentChange 'rc_moved_to_title' => '', 'rc_ip' => $ip, 'rc_patrolled' => 1, - 'rc_new' => 0 # obsolete + 'rc_new' => 0, # obsolete + 'rc_old_len' => NULL, + 'rc_new_len' => NULL, ); $rc->mExtra = array( 'prefixedDBkey' => $title->getPrefixedDBkey(), @@ -408,7 +437,7 @@ class RecentChange $this->mExtra = array(); } - # Makes a pseudo-RC entry from a cur row, for watchlists and things + # Makes a pseudo-RC entry from a cur row function loadFromCurRow( $row ) { $this->mAttribs = array( @@ -430,12 +459,23 @@ class RecentChange 'rc_ip' => '', 'rc_id' => $row->rc_id, 'rc_patrolled' => $row->rc_patrolled, - 'rc_new' => $row->page_is_new # obsolete + 'rc_new' => $row->page_is_new, # obsolete + 'rc_old_len' => $row->rc_old_len, + 'rc_new_len' => $row->rc_new_len, ); $this->mExtra = array(); } + /** + * Get an attribute value + * + * @param $name Attribute name + * @return mixed + */ + public function getAttribute( $name ) { + return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : NULL; + } /** * Gets the end part of the diff URL associated with this object @@ -522,5 +562,37 @@ class RecentChange return $fullString; } + /** + * Returns the change size (HTML). + * The lengths can be given optionally. + */ + function getCharacterDifference( $old = 0, $new = 0 ) { + global $wgRCChangedSizeThreshold, $wgLang; + + if( $old === 0 ) { + $old = $this->mAttribs['rc_old_len']; + } + if( $new === 0 ) { + $new = $this->mAttribs['rc_new_len']; + } + + if( $old === NULL || $new === NULL ) { + return ''; + } + + $szdiff = $new - $old; + $formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape'), + $wgLang->formatNum($szdiff) ); + + if( $szdiff < $wgRCChangedSizeThreshold ) { + return '<strong class=\'mw-plusminus-neg\'>(' . $formatedSize . ')</strong>'; + } elseif( $szdiff === 0 ) { + return '<span class=\'mw-plusminus-null\'>(' . $formatedSize . ')</span>'; + } elseif( $szdiff > 0 ) { + return '<span class=\'mw-plusminus-pos\'>(+' . $formatedSize . ')</span>'; + } else { + return '<span class=\'mw-plusminus-neg\'>(' . $formatedSize . ')</span>'; + } + } } ?> diff --git a/includes/Revision.php b/includes/Revision.php index bd68e05a..c5235e22 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -297,6 +297,7 @@ class Revision { // Enforce spacing trimming on supplied text $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null; $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null; + $this->mTextRow = null; $this->mTitle = null; # Load on demand if needed $this->mCurrent = false; @@ -507,7 +508,7 @@ class Revision { * @param string $prefix table prefix (default 'old_') * @return string $text|false the text requested */ - function getRevisionText( $row, $prefix = 'old_' ) { + public static function getRevisionText( $row, $prefix = 'old_' ) { $fname = 'Revision::getRevisionText'; wfProfileIn( $fname ); @@ -531,7 +532,7 @@ class Revision { # Use external methods for external objects, text in table is URL-only then if ( in_array( 'external', $flags ) ) { $url=$text; - @list($proto,$path)=explode('://',$url,2); + @list(/* $proto */,$path)=explode('://',$url,2); if ($path=="") { wfProfileOut( $fname ); return false; @@ -801,6 +802,7 @@ class Revision { * @param integer $id */ static function getTimestampFromID( $id ) { + $dbr =& wfGetDB( DB_SLAVE ); $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', array( 'rev_id' => $id ), __METHOD__ ); if ( $timestamp === false ) { diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php index 185679f6..0c0f7244 100644 --- a/includes/Sanitizer.php +++ b/includes/Sanitizer.php @@ -390,11 +390,13 @@ class Sanitizer { if(!$wgUseTidy) { $tagstack = $tablestack = array(); foreach ( $bits as $x ) { - $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) ); - preg_match( '!^(/?)(\\w+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs ); - list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs; - error_reporting( $prev ); - + $regs = array(); + if( preg_match( '!^(/?)(\\w+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs ) ) { + list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs; + } else { + $slash = $t = $params = $brace = $rest = null; + } + $badtag = 0 ; if ( isset( $htmlelements[$t = strtolower( $t )] ) ) { # Check our stack @@ -487,7 +489,7 @@ class Sanitizer { foreach ( $bits as $x ) { preg_match( '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/', $x, $regs ); - @list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs; + @list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs; if ( isset( $htmlelements[$t = strtolower( $t )] ) ) { if( is_callable( $processCallback ) ) { call_user_func_array( $processCallback, array( &$params, $args ) ); @@ -603,7 +605,8 @@ class Sanitizer { $stripped = Sanitizer::decodeCharReferences( $value ); // Remove any comments; IE gets token splitting wrong - $stripped = preg_replace( '!/\\*.*?\\*/!S', ' ', $stripped ); + $stripped = StringUtils::delimiterReplace( '/*', '*/', ' ', $stripped ); + $value = $stripped; // ... and continue checks @@ -737,6 +740,25 @@ class Sanitizer { } /** + * Given a value, escape it so that it can be used as a CSS class and + * return it. + * + * TODO: For extra validity, input should be validated UTF-8. + * + * @link http://www.w3.org/TR/CSS21/syndata.html Valid characters/format + * + * @param string $class + * @return string + */ + static function escapeClass( $class ) { + // Convert ugly stuff to underscores and kill underscores in ugly places + return rtrim(preg_replace( + array('/(^[0-9\\-])|[\\x00-\\x20!"#$%&\'()*+,.\\/:;<=>?@[\\]^`{|}~]|\\xC2\\xA0/','/_+/'), + '_', + $class ), '_'); + } + + /** * Regex replace callback for armoring links against further processing. * @param array $matches * @return string @@ -1159,7 +1181,7 @@ class Sanitizer { */ static function stripAllTags( $text ) { # Actual <tags> - $text = preg_replace( '/ < .*? > /x', '', $text ); + $text = StringUtils::delimiterReplace( '<', '>', '', $text ); # Normalize &entities and whitespace $text = Sanitizer::normalizeAttributeValue( $text ); @@ -1203,8 +1225,9 @@ class Sanitizer { $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url ); # Validate hostname portion + $matches = array(); if( preg_match( '!^([^:]+:)(//[^/]+)?(.*)$!iD', $url, $matches ) ) { - list( $whole, $protocol, $host, $rest ) = $matches; + list( /* $whole */, $protocol, $host, $rest ) = $matches; // Characters that will be ignored in IDNs. // http://tools.ietf.org/html/3454#section-3.1 diff --git a/includes/SearchEngine.php b/includes/SearchEngine.php index 5e598883..cec40c91 100644 --- a/includes/SearchEngine.php +++ b/includes/SearchEngine.php @@ -116,7 +116,7 @@ class SearchEngine { # Entering an IP address goes to the contributions page if ( ( $title->getNamespace() == NS_USER && User::isIP($title->getText() ) ) || User::isIP( trim( $searchterm ) ) ) { - return Title::makeTitle( NS_SPECIAL, "Contributions/" . $title->getDbkey() ); + return SpecialPage::getTitleFor( 'Contributions', $title->getDbkey() ); } @@ -126,6 +126,7 @@ class SearchEngine { } # Quoted term? Try without the quotes... + $matches = array(); if( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) { return SearchEngine::getNearMatch( $matches[1] ); } diff --git a/includes/SearchMySQL4.php b/includes/SearchMySQL4.php index dcc1f685..c20e3f8e 100644 --- a/includes/SearchMySQL4.php +++ b/includes/SearchMySQL4.php @@ -43,6 +43,7 @@ class SearchMySQL4 extends SearchMySQL { $this->searchTerms = array(); # FIXME: This doesn't handle parenthetical expressions. + $m = array(); if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/', $filteredText, $m, PREG_SET_ORDER ) ) { foreach( $m as $terms ) { @@ -60,7 +61,7 @@ class SearchMySQL4 extends SearchMySQL { $this->searchTerms[] = $regexp; } wfDebug( "Would search with '$searchon'\n" ); - wfDebug( "Match with /\b" . implode( '\b|\b', $this->searchTerms ) . "\b/\n" ); + wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" ); } else { wfDebug( "Can't understand search query '{$filteredText}'\n" ); } diff --git a/includes/SearchPostgres.php b/includes/SearchPostgres.php index faf53f02..457636b4 100644 --- a/includes/SearchPostgres.php +++ b/includes/SearchPostgres.php @@ -60,6 +60,7 @@ class SearchPostgres extends SearchEngine { $this->searchTerms = array(); # FIXME: This doesn't handle parenthetical expressions. + $m = array(); if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/', $filteredText, $m, PREG_SET_ORDER ) ) { foreach( $m as $terms ) { @@ -77,7 +78,7 @@ class SearchPostgres extends SearchEngine { $this->searchTerms[] = $regexp; } wfDebug( "Would search with '$searchon'\n" ); - wfDebug( "Match with /\b" . implode( '\b|\b', $this->searchTerms ) . "\b/\n" ); + wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" ); } else { wfDebug( "Can't understand search query '{$this->filteredText}'\n" ); } @@ -97,7 +98,8 @@ class SearchPostgres extends SearchEngine { $match = $this->parseQuery( $filteredTerm, $fulltext ); - $query = "SELECT page_id, page_namespace, page_title, old_text AS page_text ". + $query = "SELECT page_id, page_namespace, page_title, old_text AS page_text, ". + "rank(titlevector, to_tsquery('default','$match')) AS rnk ". "FROM page p, revision r, pagecontent c WHERE p.page_latest = r.rev_id " . "AND r.rev_text_id = c.old_id AND $fulltext @@ to_tsquery('default','$match')"; @@ -113,7 +115,7 @@ class SearchPostgres extends SearchEngine { $query .= " AND page_namespace IN ($namespaces)"; } - $query .= " ORDER BY rank($fulltext, to_tsquery('default','$fulltext')) DESC"; + $query .= " ORDER BY rnk DESC, page_id DESC"; $query .= $this->db->limitResult( '', $this->limit, $this->offset ); diff --git a/includes/SearchTsearch2.php b/includes/SearchTsearch2.php index a8f354b3..1fca9899 100644 --- a/includes/SearchTsearch2.php +++ b/includes/SearchTsearch2.php @@ -47,6 +47,7 @@ class SearchTsearch2 extends SearchEngine { $this->searchTerms = array(); # FIXME: This doesn't handle parenthetical expressions. + $m = array(); if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/', $filteredText, $m, PREG_SET_ORDER ) ) { foreach( $m as $terms ) { @@ -64,7 +65,7 @@ class SearchTsearch2 extends SearchEngine { $this->searchTerms[] = $regexp; } wfDebug( "Would search with '$searchon'\n" ); - wfDebug( "Match with /\b" . implode( '\b|\b', $this->searchTerms ) . "\b/\n" ); + wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" ); } else { wfDebug( "Can't understand search query '{$this->filteredText}'\n" ); } @@ -112,7 +113,7 @@ class SearchTsearch2 extends SearchEngine { $dbw=& wfGetDB(DB_MASTER); $searchindex = $dbw->tableName( 'searchindex' ); $sql = "UPDATE $searchindex SET si_title=to_tsvector('" . - $db->strencode( $title ) . + $dbw->strencode( $title ) . "') WHERE si_page={$id}"; $dbw->query( $sql, "SearchMySQL4::updateTitle" ); diff --git a/includes/Setup.php b/includes/Setup.php index 8fe9ef71..80a5b48a 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -28,6 +28,33 @@ if ( !isset( $wgVersion ) ) { die( 1 ); } +// Set various default paths sensibly... +if( $wgScript === false ) $wgScript = "$wgScriptPath/index.php"; +if( $wgRedirectScript === false ) $wgRedirectScript = "$wgScriptPath/redirect.php"; + +if( $wgArticlePath === false ) { + if( $wgUsePathInfo ) { + $wgArticlePath = "$wgScript/$1"; + } else { + $wgArticlePath = "$wgScript?title=$1"; + } +} + +if( $wgStylePath === false ) $wgStylePath = "$wgScriptPath/skins"; +if( $wgStyleDirectory === false) $wgStyleDirectory = "$IP/skins"; + +if( $wgLogo === false ) $wgLogo = "$wgStylePath/common/images/wiki.png"; + +if( $wgUploadPath === false ) $wgUploadPath = "$wgScriptPath/images"; +if( $wgUploadDirectory === false ) $wgUploadDirectory = "$IP/images"; + +if( $wgMathPath === false ) $wgMathPath = "{$wgUploadPath}/math"; +if( $wgMathDirectory === false ) $wgMathDirectory = "{$wgUploadDirectory}/math"; +if( $wgTmpDirectory === false ) $wgTmpDirectory = "{$wgUploadDirectory}/tmp"; + +if( $wgReadOnlyFile === false ) $wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR"; +if( $wgFileCacheDirectory === false ) $wgFileCacheDirectory = "{$wgUploadDirectory}/cache"; + require_once( "$IP/includes/AutoLoader.php" ); wfProfileIn( $fname.'-exception' ); @@ -160,7 +187,9 @@ foreach ( $wgSkinExtensionFunctions as $func ) { if( !is_object( $wgAuth ) ) { $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' ); + wfRunHooks( 'AuthPluginSetup', array( &$wgAuth ) ); } + wfProfileOut( $fname.'-User' ); wfProfileIn( $fname.'-misc2' ); @@ -169,6 +198,7 @@ $wgDeferredUpdateList = array(); $wgPostCommitUpdateList = array(); if ( $wgAjaxSearch ) $wgAjaxExportList[] = 'wfSajaxSearch'; +if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch'; wfSeedRandom(); diff --git a/includes/SiteStats.php b/includes/SiteStats.php new file mode 100644 index 00000000..e2774a14 --- /dev/null +++ b/includes/SiteStats.php @@ -0,0 +1,168 @@ +<?php + +/** + * Static accessor class for site_stats and related things + * @package MediaWiki + */ +class SiteStats { + static $row, $loaded = false; + static $admins; + static $pageCount = array(); + + static function recache() { + self::load( true ); + } + + static function load( $recache = false ) { + if ( self::$loaded && !$recache ) { + return; + } + + $dbr =& wfGetDB( DB_SLAVE ); + self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ ); + + # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS + if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) { + # Update schema + $u = new SiteStatsUpdate( 0, 0, 0 ); + $u->doUpdate(); + self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ ); + } + } + + static function views() { + self::load(); + return self::$row->ss_total_views; + } + + static function edits() { + self::load(); + return self::$row->ss_total_edits; + } + + static function articles() { + self::load(); + return self::$row->ss_good_articles; + } + + static function pages() { + self::load(); + return self::$row->ss_total_pages; + } + + static function users() { + self::load(); + return self::$row->ss_users; + } + + static function images() { + self::load(); + return self::$row->ss_images; + } + + static function admins() { + if ( !isset( self::$admins ) ) { + $dbr =& wfGetDB( DB_SLAVE ); + self::$admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), __METHOD__ ); + } + return self::$admins; + } + + static function pagesInNs( $ns ) { + wfProfileIn( __METHOD__ ); + if( !isset( self::$pageCount[$ns] ) ) { + $dbr =& wfGetDB( DB_SLAVE ); + $pageCount[$ns] = (int)$dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $ns ), __METHOD__ ); + } + wfProfileOut( __METHOD__ ); + return $pageCount[$ns]; + } + +} + + +/** + * + * @package MediaWiki + */ +class SiteStatsUpdate { + + var $mViews, $mEdits, $mGood, $mPages, $mUsers; + + function SiteStatsUpdate( $views, $edits, $good, $pages = 0, $users = 0 ) { + $this->mViews = $views; + $this->mEdits = $edits; + $this->mGood = $good; + $this->mPages = $pages; + $this->mUsers = $users; + } + + function appendUpdate( &$sql, $field, $delta ) { + if ( $delta ) { + if ( $sql ) { + $sql .= ','; + } + if ( $delta < 0 ) { + $sql .= "$field=$field-1"; + } else { + $sql .= "$field=$field+1"; + } + } + } + + function doUpdate() { + $fname = 'SiteStatsUpdate::doUpdate'; + $dbw =& wfGetDB( DB_MASTER ); + + # First retrieve the row just to find out which schema we're in + $row = $dbw->selectRow( 'site_stats', '*', false, $fname ); + + $updates = ''; + + $this->appendUpdate( $updates, 'ss_total_views', $this->mViews ); + $this->appendUpdate( $updates, 'ss_total_edits', $this->mEdits ); + $this->appendUpdate( $updates, 'ss_good_articles', $this->mGood ); + + if ( isset( $row->ss_total_pages ) ) { + # Update schema if required + if ( $row->ss_total_pages == -1 && !$this->mViews ) { + $dbr =& wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow') ); + list( $page, $user ) = $dbr->tableNamesN( 'page', 'user' ); + + $sql = "SELECT COUNT(page_namespace) AS total FROM $page"; + $res = $dbr->query( $sql, $fname ); + $pageRow = $dbr->fetchObject( $res ); + $pages = $pageRow->total + $this->mPages; + + $sql = "SELECT COUNT(user_id) AS total FROM $user"; + $res = $dbr->query( $sql, $fname ); + $userRow = $dbr->fetchObject( $res ); + $users = $userRow->total + $this->mUsers; + + if ( $updates ) { + $updates .= ','; + } + $updates .= "ss_total_pages=$pages, ss_users=$users"; + } else { + $this->appendUpdate( $updates, 'ss_total_pages', $this->mPages ); + $this->appendUpdate( $updates, 'ss_users', $this->mUsers ); + } + } + if ( $updates ) { + $site_stats = $dbw->tableName( 'site_stats' ); + $sql = $dbw->limitResultForUpdate("UPDATE $site_stats SET $updates", 1); + $dbw->begin(); + $dbw->query( $sql, $fname ); + $dbw->commit(); + } + + /* + global $wgDBname, $wgTitle; + if ( $this->mGood && $wgDBname == 'enwiki' ) { + $good = $dbw->selectField( 'site_stats', 'ss_good_articles', '', $fname ); + error_log( $good . ' ' . $wgTitle->getPrefixedDBkey() . "\n", 3, '/home/wikipedia/logs/million.log' ); + } + */ + } +} +?> diff --git a/includes/Skin.php b/includes/Skin.php index ffbe27c7..3e4f5d3c 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -23,6 +23,7 @@ class Skin extends Linker { var $rc_cache ; # Cache for Enhanced Recent Changes var $rcCacheIndex ; # Recent Changes Cache Counter for visibility toggle var $rcMoveIndex; + var $mWatchLinkNum = 0; // Appended to end of watch link id's /**#@-*/ /** Constructor, call parent constructor */ @@ -48,6 +49,7 @@ class Skin extends Linker { # while code from www.php.net while (false !== ($file = $skinDir->read())) { // Skip non-PHP files, hidden files, and '.dep' includes + $matches = array(); if(preg_match('/^([^.]*)\.php$/',$file, $matches)) { $aSkin = $matches[1]; $wgValidSkinNames[strtolower($aSkin)] = $aSkin; @@ -116,10 +118,9 @@ class Skin extends Linker { $skinName = $skinNames[$key]; # Grab the skin class and initialise it. - wfSuppressWarnings(); // Preload base classes to work around APC/PHP5 bug - include_once( "{$wgStyleDirectory}/{$skinName}.deps.php" ); - wfRestoreWarnings(); + $deps = "{$wgStyleDirectory}/{$skinName}.deps.php"; + if( file_exists( $deps ) ) include_once( $deps ); require_once( "{$wgStyleDirectory}/{$skinName}.php" ); # Check if we got if not failback to default skin @@ -139,7 +140,7 @@ class Skin extends Linker { /** @return string path to the skin stylesheet */ function getStylesheet() { - return 'common/wikistandard.css?1'; + return 'common/wikistandard.css'; } /** @return string skin name */ @@ -151,8 +152,7 @@ class Skin extends Linker { global $wgOut, $wgUser; if ( $wgOut->isQuickbarSuppressed() ) { return 0; } - $q = $wgUser->getOption( 'quickbar' ); - if ( '' == $q ) { $q = 0; } + $q = $wgUser->getOption( 'quickbar', 0 ); return $q; } @@ -270,60 +270,71 @@ class Skin extends Linker { $out->out( "\n</body></html>" ); } - static function makeGlobalVariablesScript( $data ) { - $r = '<script type= "' . $data['jsmimetype'] . '"> - var skin = "' . Xml::escapeJsString( $data['skinname'] ) . '"; - var stylepath = "' . Xml::escapeJsString( $data['stylepath'] ) . '"; - - var wgArticlePath = "' . Xml::escapeJsString( $data['articlepath'] ) . '"; - var wgScriptPath = "' . Xml::escapeJsString( $data['scriptpath'] ) . '"; - var wgServer = "' . Xml::escapeJsString( $data['serverurl'] ) . '"; - - var wgCanonicalNamespace = "' . Xml::escapeJsString( $data['nscanonical'] ) . '"; - var wgNamespaceNumber = ' . (int)$data['nsnumber'] . '; - var wgPageName = "' . Xml::escapeJsString( $data['titleprefixeddbkey'] ) . '"; - var wgTitle = "' . Xml::escapeJsString( $data['titletext'] ) . '"; - var wgArticleId = ' . (int)$data['articleid'] . '; - var wgIsArticle = ' . ( $data['isarticle'] ? 'true' : 'false' ) . '; - - var wgUserName = ' . ( $data['username'] == NULL ? 'null' : ( '"' . Xml::escapeJsString( $data['username'] ) . '"' ) ) . '; - var wgUserLanguage = "' . Xml::escapeJsString( $data['userlang'] ) . '"; - var wgContentLanguage = "' . Xml::escapeJsString( $data['lang'] ) . '"; - </script> - '; - + static function makeVariablesScript( $data ) { + global $wgJsMimeType; + + $r = "<script type= \"$wgJsMimeType\">/*<![CDATA[*/\n"; + foreach ( $data as $name => $value ) { + $encValue = Xml::encodeJsVar( $value ); + $r .= "var $name = $encValue;\n"; + } + $r .= "/*]]>*/</script>\n"; + return $r; } - function getHeadScripts() { - global $wgStylePath, $wgUser, $wgAllowUserJs, $wgJsMimeType; + /** + * Make a <script> tag containing global variables + * @param array $data Associative array containing one element: + * skinname => the skin name + * The odd calling convention is for backwards compatibility + */ + static function makeGlobalVariablesScript( $data ) { + global $wgStylePath, $wgUser; global $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgLang; - global $wgTitle, $wgCanonicalNamespaceNames, $wgOut; - - $nsname = @$wgCanonicalNamespaceNames[ $wgTitle->getNamespace() ]; - if ( $nsname === NULL ) $nsname = $wgTitle->getNsText(); + global $wgTitle, $wgCanonicalNamespaceNames, $wgOut, $wgArticle; + global $wgBreakFrames; + $ns = $wgTitle->getNamespace(); + $nsname = isset( $wgCanonicalNamespaceNames[ $ns ] ) ? $wgCanonicalNamespaceNames[ $ns ] : $wgTitle->getNsText(); + $vars = array( - 'jsmimetype' => $wgJsMimeType, - 'skinname' => $this->getSkinName(), + 'skin' => $data['skinname'], 'stylepath' => $wgStylePath, - 'articlepath' => $wgArticlePath, - 'scriptpath' => $wgScriptPath, - 'serverurl' => $wgServer, - 'nscanonical' => $nsname, - 'nsnumber' => $wgTitle->getNamespace(), - 'titleprefixeddbkey' => $wgTitle->getPrefixedDBKey(), - 'titletext' => $wgTitle->getText(), - 'articleid' => $wgTitle->getArticleId(), - 'isarticle' => $wgOut->isArticle(), - 'username' => $wgUser->isAnon() ? NULL : $wgUser->getName(), - 'userlang' => $wgLang->getCode(), - 'lang' => $wgContLang->getCode(), + 'wgArticlePath' => $wgArticlePath, + 'wgScriptPath' => $wgScriptPath, + 'wgServer' => $wgServer, + 'wgCanonicalNamespace' => $nsname, + 'wgCanonicalSpecialPageName' => SpecialPage::resolveAlias( $wgTitle->getDBKey() ), + 'wgNamespaceNumber' => $wgTitle->getNamespace(), + 'wgPageName' => $wgTitle->getPrefixedDBKey(), + 'wgTitle' => $wgTitle->getText(), + 'wgArticleId' => $wgTitle->getArticleId(), + 'wgIsArticle' => $wgOut->isArticle(), + 'wgUserName' => $wgUser->isAnon() ? NULL : $wgUser->getName(), + 'wgUserLanguage' => $wgLang->getCode(), + 'wgContentLanguage' => $wgContLang->getCode(), + 'wgBreakFrames' => $wgBreakFrames, + 'wgCurRevisionId' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0, ); - $r = self::makeGlobalVariablesScript( $vars ); + return self::makeVariablesScript( $vars ); + } + + function getHeadScripts() { + global $wgStylePath, $wgUser, $wgAllowUserJs, $wgJsMimeType, $wgStyleVersion; + + $r = self::makeGlobalVariablesScript( array( 'skinname' => $this->getSkinName() ) ); - $r .= "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/wikibits.js\"></script>\n"; + $r .= "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/wikibits.js?$wgStyleVersion\"></script>\n"; + global $wgUseSiteJs; + if ($wgUseSiteJs) { + if ($wgUser->isLoggedIn()) { + $r .= "<script type=\"$wgJsMimeType\" src=\"".htmlspecialchars(self::makeUrl('-','action=raw&smaxage=0&gen=js'))."\"><!-- site js --></script>\n"; + } else { + $r .= "<script type=\"$wgJsMimeType\" src=\"".htmlspecialchars(self::makeUrl('-','action=raw&gen=js'))."\"><!-- site js --></script>\n"; + } + } if( $wgAllowUserJs && $wgUser->isLoggedIn() ) { $userpage = $wgUser->getUserPage(); $userjs = htmlspecialchars( self::makeUrl( @@ -360,11 +371,11 @@ class Skin extends Linker { # get the user/site-specific stylesheet, SkinTemplate loads via RawPage.php (settings are cached that way) function getUserStylesheet() { - global $wgStylePath, $wgRequest, $wgContLang, $wgSquidMaxage; + global $wgStylePath, $wgRequest, $wgContLang, $wgSquidMaxage, $wgStyleVersion; $sheet = $this->getStylesheet(); - $action = $wgRequest->getText('action'); - $s = "@import \"$wgStylePath/$sheet\";\n"; - if($wgContLang->isRTL()) $s .= "@import \"$wgStylePath/common/common_rtl.css\";\n"; + $s = "@import \"$wgStylePath/common/common.css?$wgStyleVersion\";\n"; + $s .= "@import \"$wgStylePath/$sheet?$wgStyleVersion\";\n"; + if($wgContLang->isRTL()) $s .= "@import \"$wgStylePath/common/common_rtl.css?$wgStyleVersion\";\n"; $query = "usemsgcache=yes&action=raw&ctype=text/css&smaxage=$wgSquidMaxage"; $s .= '@import "' . self::makeNSUrl( 'Common.css', $query, NS_MEDIAWIKI ) . "\";\n" . @@ -375,9 +386,40 @@ class Skin extends Linker { } /** - * placeholder, returns generated js in monobook + * This returns MediaWiki:Common.js. For some bizarre reason, it does + * *not* return any custom user JS from user subpages. Huh? + * + * @return string */ - function getUserJs() { return; } + function getUserJs() { + $fname = 'Skin::getUserJs'; + wfProfileIn( __METHOD__ ); + + global $wgStylePath; + $s = "/* generated javascript */\n"; + $s .= "var skin = '{$this->skinname}';\nvar stylepath = '{$wgStylePath}';"; + $s .= "\n\n/* MediaWiki:Common.js */\n"; + $commonJs = wfMsgForContent('common.js'); + if ( !wfEmptyMsg ( 'common.js', $commonJs ) ) { + $s .= $commonJs; + } + + global $wgUseAjax, $wgAjaxWatch; + if($wgUseAjax && $wgAjaxWatch) { + $s .= " + +/* AJAX (un)watch (see /skins/common/ajaxwatch.js) */ +var wgAjaxWatch = { + watchMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'watch', array() ) )."', + unwatchMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'unwatch', array() ) )."', + watchingMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'watching', array() ) )."', + unwatchingMsg: '". str_replace( array("'", "\n"), array("\\'", ' '), wfMsgExt( 'unwatching', array() ) )."' +};"; + } + + wfProfileOut( __METHOD__ ); + return $s; + } /** * Return html code that include User stylesheets @@ -464,7 +506,6 @@ END; else $a = array( 'bgcolor' => '#FFFFFF' ); if($wgOut->isArticle() && $wgUser->getOption('editondblclick') && $wgTitle->userCanEdit() ) { - $t = wfMsg( 'editthispage' ); $s = $wgTitle->getFullURL( $this->editUrlOptions() ); $s = 'document.location = "' .wfEscapeJSString( $s ) .'";'; $a += array ('ondblclick' => $s); @@ -477,7 +518,8 @@ END; } $a['onload'] .= 'setupRightClickEdit()'; } - $a['class'] = 'ns-'.$wgTitle->getNamespace().' '.($wgContLang->isRTL() ? "rtl" : "ltr"); + $a['class'] = 'ns-'.$wgTitle->getNamespace().' '.($wgContLang->isRTL() ? "rtl" : "ltr"). + ' '.Sanitizer::escapeId( 'page-'.$wgTitle->getPrefixedText() ); return $a; } @@ -576,9 +618,8 @@ END; $pop = '</span>'; $t = $embed . implode ( "{$pop} {$sep} {$embed}" , $wgOut->mCategoryLinks ) . $pop; - $msg = wfMsgExt('categories', array('parsemag', 'escape'), count( $wgOut->mCategoryLinks )); - $s = $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Categories' ), - $msg, 'article=' . urlencode( $wgTitle->getPrefixedDBkey() ) ) + $msg = wfMsgExt( 'pagecategories', array( 'parsemag', 'escape' ), count( $wgOut->mCategoryLinks ) ); + $s = $this->makeLinkObj( Title::newFromText( wfMsgForContent('pagecategorieslink') ), $msg ) . ': ' . $t; # optional 'dmoz-like' category browser. Will be shown under the list @@ -670,7 +711,8 @@ END; function pageTitleLinks() { global $wgOut, $wgTitle, $wgUser, $wgRequest; - extract( $wgRequest->getValues( 'oldid', 'diff' ) ); + $oldid = $wgRequest->getVal( 'oldid' ); + $diff = $wgRequest->getVal( 'diff' ); $action = $wgRequest->getText( 'action' ); $s = $this->printableLink(); @@ -731,8 +773,8 @@ END; $msg = 'viewdeleted'; } return wfMsg( $msg, - $this->makeKnownLink( - $wgContLang->SpecialPage( 'Undelete/' . $wgTitle->getPrefixedDBkey() ), + $this->makeKnownLinkObj( + SpecialPage::getTitleFor( 'Undelete', $wgTitle->getPrefixedDBkey() ), wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $n ) ) ); } return ''; @@ -812,7 +854,6 @@ END; function nameAndLogin() { global $wgUser, $wgTitle, $wgLang, $wgContLang, $wgShowIPinHeader; - $li = $wgContLang->specialPage( 'Userlogin' ); $lo = $wgContLang->specialPage( 'Userlogout' ); $s = ''; @@ -834,7 +875,7 @@ END; } else { $q = "returnto={$rt}"; } $s .= "\n<br />" . $this->makeKnownLinkObj( - Title::makeTitle( NS_SPECIAL, 'Userlogin' ), + SpecialPage::getTitleFor( 'Userlogin' ), wfMsg( 'login' ), $q ); } else { $n = $wgUser->getName(); @@ -846,7 +887,7 @@ END; $s .= $this->makeKnownLinkObj( $wgUser->getUserPage(), $n ) . "{$tl}<br />" . - $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Userlogout' ), wfMsg( 'logout' ), + $this->makeKnownLinkObj( SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ), "returnto={$rt}" ) . ' | ' . $this->specialLink( 'preferences' ); } @@ -857,7 +898,7 @@ END; } function getSearchLink() { - $searchPage =& Title::makeTitle( NS_SPECIAL, 'Search' ); + $searchPage = SpecialPage::getTitleFor( 'Search' ); return $searchPage->getLocalURL(); } @@ -892,7 +933,38 @@ END; } # Many people don't like this dropdown box #$s .= $sep . $this->specialPagesList(); + + $s .= $this->variantLinks(); + + $s .= $this->extensionTabLinks(); + return $s; + } + + /** + * Compatibility for extensions adding functionality through tabs. + * Eventually these old skins should be replaced with SkinTemplate-based + * versions, sigh... + * @return string + */ + function extensionTabLinks() { + $tabs = array(); + $s = ''; + wfRunHooks( 'SkinTemplateTabs', array( $this, &$tabs ) ); + foreach( $tabs as $tab ) { + $s .= ' | ' . Xml::element( 'a', + array( 'href' => $tab['href'] ), + $tab['text'] ); + } + return $s; + } + + /** + * Language/charset variant links for classic-style skins + * @return string + */ + function variantLinks() { + $s = ''; /* show links to different language variants */ global $wgDisableLangConversion, $wgContLang, $wgTitle; $variants = $wgContLang->getVariants(); @@ -901,10 +973,9 @@ END; $varname = $wgContLang->getVariantname( $code ); if( $varname == 'disable' ) continue; - $s .= ' | <a href="' . $wgTitle->getLocalUrl( 'variant=' . $code ) . '">' . $varname . '</a>'; + $s .= ' | <a href="' . $wgTitle->escapeLocalUrl( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>'; } } - return $s; } @@ -955,7 +1026,8 @@ END; global $wgOut, $wgLang, $wgArticle, $wgRequest, $wgUser; global $wgDisableCounters, $wgMaxCredits, $wgShowCreditsIfMax, $wgTitle, $wgPageShowWatchingUsers; - extract( $wgRequest->getValues( 'oldid', 'diff' ) ); + $oldid = $wgRequest->getVal( 'oldid' ); + $diff = $wgRequest->getVal( 'diff' ); if ( ! $wgOut->isArticle() ) { return ''; } if ( isset( $oldid ) || isset( $diff ) ) { return ''; } if ( 0 == $wgArticle->getID() ) { return ''; } @@ -977,7 +1049,7 @@ END; if ($wgPageShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) { $dbr =& wfGetDB( DB_SLAVE ); - extract( $dbr->tableNames( 'watchlist' ) ); + $watchlist = $dbr->tableName( 'watchlist' ); $sql = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" . $dbr->strencode($wgTitle->getDBKey()) . "' AND wl_namespace=" . $wgTitle->getNamespace() ; @@ -1045,7 +1117,7 @@ END; function getPoweredBy() { global $wgStylePath; $url = htmlspecialchars( "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" ); - $img = '<a href="http://www.mediawiki.org/"><img src="'.$url.'" alt="MediaWiki" /></a>'; + $img = '<a href="http://www.mediawiki.org/"><img src="'.$url.'" alt="Powered by MediaWiki" /></a>'; return $img; } @@ -1071,12 +1143,8 @@ END; else { $a = ''; } $mp = wfMsg( 'mainpage' ); - $titleObj = Title::newFromText( $mp ); - if ( is_object( $titleObj ) ) { - $url = $titleObj->escapeLocalURL(); - } else { - $url = ''; - } + $mptitle = Title::newMainPage(); + $url = ( is_object($mptitle) ? $mptitle->escapeLocalURL() : '' ); $logourl = $this->getLogo(); $s = "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>"; @@ -1088,7 +1156,6 @@ END; */ function specialPagesList() { global $wgUser, $wgContLang, $wgServer, $wgRedirectScript; - $a = array(); $pages = array_merge( SpecialPage::getRegularPages(), SpecialPage::getRestrictedPages() ); foreach ( $pages as $name => $page ) { $pages[$name] = $page->getDescription(); @@ -1115,9 +1182,7 @@ END; } function mainPageLink() { - $mp = wfMsgForContent( 'mainpage' ); - $mptxt = wfMsg( 'mainpage'); - $s = $this->makeKnownLink( $mp, $mptxt ); + $s = $this->makeKnownLinkObj( Title::newMainPage(), wfMsg( 'mainpage' ) ); return $s; } @@ -1221,16 +1286,19 @@ END; function watchThisPage() { global $wgOut, $wgTitle; + ++$this->mWatchLinkNum; if ( $wgOut->isArticleRelated() ) { if ( $wgTitle->userIsWatching() ) { $t = wfMsg( 'unwatchthispage' ); $q = 'action=unwatch'; + $id = "mw-unwatch-link".$this->mWatchLinkNum; } else { $t = wfMsg( 'watchthispage' ); $q = 'action=watch'; + $id = 'mw-watch-link'.$this->mWatchLinkNum; } - $s = $this->makeKnownLinkObj( $wgTitle, $t, $q ); + $s = $this->makeKnownLinkObj( $wgTitle, $t, $q, '', '', " id=\"$id\"" ); } else { $s = wfMsg( 'notanarticle' ); } @@ -1241,7 +1309,7 @@ END; global $wgTitle; if ( $wgTitle->userCanMove() ) { - return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Movepage' ), + return $this->makeKnownLinkObj( SpecialPage::getTitleFor( 'Movepage' ), wfMsg( 'movethispage' ), 'target=' . $wgTitle->getPrefixedURL() ); } else { // no message if page is protected - would be redundant @@ -1259,15 +1327,17 @@ END; function whatLinksHere() { global $wgTitle; - return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Whatlinkshere' ), - wfMsg( 'whatlinkshere' ), 'target=' . $wgTitle->getPrefixedURL() ); + return $this->makeKnownLinkObj( + SpecialPage::getTitleFor( 'Whatlinkshere', $wgTitle->getPrefixedDBkey() ), + wfMsg( 'whatlinkshere' ) ); } function userContribsLink() { global $wgTitle; - return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), - wfMsg( 'contributions' ), 'target=' . $wgTitle->getPartialURL() ); + return $this->makeKnownLinkObj( + SpecialPage::getTitleFor( 'Contributions', $wgTitle->getDBkey() ), + wfMsg( 'contributions' ) ); } function showEmailUser( $id ) { @@ -1284,8 +1354,9 @@ END; function emailUserLink() { global $wgTitle; - return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Emailuser' ), - wfMsg( 'emailuser' ), 'target=' . $wgTitle->getPartialURL() ); + return $this->makeKnownLinkObj( + SpecialPage::getTitleFor( 'Emailuser', $wgTitle->getDBkey() ), + wfMsg( 'emailuser' ) ); } function watchPageLinksLink() { @@ -1294,9 +1365,9 @@ END; if ( ! $wgOut->isArticleRelated() ) { return '(' . wfMsg( 'notanarticle' ) . ')'; } else { - return $this->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, - 'Recentchangeslinked' ), wfMsg( 'recentchangeslinked' ), - 'target=' . $wgTitle->getPrefixedURL() ); + return $this->makeKnownLinkObj( + SpecialPage::getTitleFor( 'Recentchangeslinked', $wgTitle->getPrefixedDBkey() ), + wfMsg( 'recentchangeslinked' ) ); } } @@ -1437,8 +1508,19 @@ END; } /* these are used extensively in SkinTemplate, but also some other places */ + static function makeMainPageUrl( $urlaction = '' ) { + $title = Title::newMainPage(); + self::checkTitle( $title, $name ); + return $title->getLocalURL( $urlaction ); + } + static function makeSpecialUrl( $name, $urlaction = '' ) { - $title = Title::makeTitle( NS_SPECIAL, $name ); + $title = SpecialPage::getTitleFor( $name ); + return $title->getLocalURL( $urlaction ); + } + + static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) { + $title = SpecialPage::getSafeTitleFor( $name, $subpage ); return $title->getLocalURL( $urlaction ); } @@ -1547,7 +1629,19 @@ END; $text = $line[1]; if (wfEmptyMsg($line[0], $link)) $link = $line[0]; - $href = self::makeInternalOrExternalUrl( $link ); + + if ( preg_match( '/^(?:' . wfUrlProtocols() . ')/', $link ) ) { + $href = $link; + } else { + $title = Title::newFromText( $link ); + if ( $title ) { + $title = $title->fixSpecialName(); + $href = $title->getLocalURL(); + } else { + $href = 'INVALID-TITLE'; + } + } + $bar[$heading][] = array( 'text' => $text, 'href' => $href, @@ -1558,7 +1652,7 @@ END; } } if ($cacheSidebar) - $cachednotice = $parserMemc->set( $key, $bar, 86400 ); + $parserMemc->set( $key, $bar, 86400 ); wfProfileOut( $fname ); return $bar; } diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php index 482680e6..ff095477 100644 --- a/includes/SkinTemplate.php +++ b/includes/SkinTemplate.php @@ -54,6 +54,7 @@ class MediaWiki_I18N { $value = wfMsg( $value ); // interpolate variables + $m = array(); while (preg_match('/\$([0-9]*?)/sm', $value, $m)) { list($src, $var) = $m; wfSuppressWarnings(); @@ -134,6 +135,7 @@ class SkinTemplate extends Skin { global $wgTitle, $wgArticle, $wgUser, $wgLang, $wgContLang, $wgOut; global $wgScript, $wgStylePath, $wgContLanguageCode; global $wgMimeType, $wgJsMimeType, $wgOutputEncoding, $wgRequest; + global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces; global $wgDisableCounters, $wgLogo, $action, $wgFeedClasses, $wgHideInterlanguageLinks; global $wgMaxCredits, $wgShowCreditsIfMax; global $wgPageShowWatchingUsers; @@ -147,7 +149,8 @@ class SkinTemplate extends Skin { // adding of CSS or Javascript by extensions. wfRunHooks( 'BeforePageDisplay', array( &$out ) ); - extract( $wgRequest->getValues( 'oldid', 'diff' ) ); + $oldid = $wgRequest->getVal( 'oldid' ); + $diff = $wgRequest->getVal( 'diff' ); wfProfileIn( "$fname-init" ); $this->initPage( $out ); @@ -190,17 +193,21 @@ class SkinTemplate extends Skin { $tpl->set( 'title', $wgOut->getPageTitle() ); $tpl->set( 'pagetitle', $wgOut->getHTMLTitle() ); $tpl->set( 'displaytitle', $wgOut->mPageLinkTitle ); + $tpl->set( 'pageclass', Sanitizer::escapeClass( 'page-'.$wgTitle->getPrefixedText() ) ); + + $nsname = isset( $wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ] ) ? + $wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ] : + $this->mTitle->getNsText(); - $nsname = @$wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ]; - if ( $nsname === NULL ) $nsname = $this->mTitle->getNsText(); - $tpl->set( 'nscanonical', $nsname ); $tpl->set( 'nsnumber', $this->mTitle->getNamespace() ); $tpl->set( 'titleprefixeddbkey', $this->mTitle->getPrefixedDBKey() ); $tpl->set( 'titletext', $this->mTitle->getText() ); $tpl->set( 'articleid', $this->mTitle->getArticleId() ); + $tpl->set( 'currevisionid', isset( $wgArticle ) ? $wgArticle->getLatest() : 0 ); + $tpl->set( 'isarticle', $wgOut->isArticle() ); - + $tpl->setRef( "thispage", $this->thispage ); $subpagestr = $this->subPageSubtitle(); $tpl->set( @@ -219,8 +226,14 @@ class SkinTemplate extends Skin { if( $wgOut->isSyndicated() ) { $feeds = array(); foreach( $wgFeedClasses as $format => $class ) { + $linktext = $format; + if ( $format == "atom" ) { + $linktext = wfMsg( 'feed-atom' ); + } else if ( $format == "rss" ) { + $linktext = wfMsg( 'feed-rss' ); + } $feeds[$format] = array( - 'text' => $format, + 'text' => $linktext, 'href' => $wgRequest->appendQuery( "feed=$format" ) ); } @@ -228,9 +241,14 @@ class SkinTemplate extends Skin { } else { $tpl->set( 'feeds', false ); } - if ($wgUseTrackbacks && $out->isArticleRelated()) - $tpl->set( 'trackbackhtml', $wgTitle->trackbackRDF()); + if ($wgUseTrackbacks && $out->isArticleRelated()) { + $tpl->set( 'trackbackhtml', $wgTitle->trackbackRDF() ); + } else { + $tpl->set( 'trackbackhtml', null ); + } + $tpl->setRef( 'xhtmldefaultnamespace', $wgXhtmlDefaultNamespace ); + $tpl->set( 'xhtmlnamespaces', $wgXhtmlNamespaces ); $tpl->setRef( 'mimetype', $wgMimeType ); $tpl->setRef( 'jsmimetype', $wgJsMimeType ); $tpl->setRef( 'charset', $wgOutputEncoding ); @@ -337,7 +355,7 @@ class SkinTemplate extends Skin { if ($wgPageShowWatchingUsers) { $dbr =& wfGetDB( DB_SLAVE ); - extract( $dbr->tableNames( 'watchlist' ) ); + $watchlist = $dbr->tableName( 'watchlist' ); $sql = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" . $dbr->strencode($this->mTitle->getDBKey()) . "' AND wl_namespace=" . $this->mTitle->getNamespace() ; @@ -502,17 +520,20 @@ class SkinTemplate extends Skin { 'href' => $href, 'active' => ( $href == $pageurl ) ); - $href = self::makeSpecialUrl( "Contributions/$this->username" ); + $href = self::makeSpecialUrlSubpage( 'Contributions', $this->username ); $personal_urls['mycontris'] = array( 'text' => wfMsg( 'mycontris' ), - 'href' => $href - # FIXME # 'active' => ( $href == $pageurl . '/' . $this->username ) + 'href' => $href, + // FIXME # 'active' was disabed in r11346 with message: "disable bold link to my contributions; link was bold on all + // Special:Contributions, not just current user's (fix me please!)". Until resolved, explicitly setting active to false. + 'active' => false # ( ( $href == $pageurl . '/' . $this->username ) ); $personal_urls['logout'] = array( 'text' => wfMsg( 'userlogout' ), 'href' => self::makeSpecialUrl( 'Userlogout', - $wgTitle->getNamespace() === NS_SPECIAL && $wgTitle->getText() === 'Preferences' ? '' : "returnto={$this->thisurl}" - ) + $wgTitle->isSpecial( 'Preferences' ) ? '' : "returnto={$this->thisurl}" + ), + 'active' => false ); } else { if( $wgShowIPinHeader && isset( $_COOKIE[ini_get("session.name")] ) ) { @@ -534,14 +555,14 @@ class SkinTemplate extends Skin { $personal_urls['anonlogin'] = array( 'text' => wfMsg('userlogin'), 'href' => self::makeSpecialUrl( 'Userlogin', 'returnto=' . $this->thisurl ), - 'active' => ( NS_SPECIAL == $wgTitle->getNamespace() && 'Userlogin' == $wgTitle->getDBkey() ) + 'active' => $wgTitle->isSpecial( 'Userlogin' ) ); } else { $personal_urls['login'] = array( 'text' => wfMsg('userlogin'), 'href' => self::makeSpecialUrl( 'Userlogin', 'returnto=' . $this->thisurl ), - 'active' => ( NS_SPECIAL == $wgTitle->getNamespace() && 'Userlogin' == $wgTitle->getDBkey() ) + 'active' => $wgTitle->isSpecial( 'Userlogin' ) ); } } @@ -696,18 +717,18 @@ class SkinTemplate extends Skin { ); } if ( $this->mTitle->userCanMove()) { - $moveTitle = Title::makeTitle( NS_SPECIAL, 'Movepage' ); + $moveTitle = SpecialPage::getTitleFor( 'Movepage', $this->thispage ); $content_actions['move'] = array( - 'class' => ($this->mTitle->getDbKey() == 'Movepage' and $this->mTitle->getNamespace == NS_SPECIAL) ? 'selected' : false, + 'class' => $this->mTitle->isSpecial( 'Movepage' ) ? 'selected' : false, 'text' => wfMsg('move'), - 'href' => $moveTitle->getLocalUrl( 'target=' . urlencode( $this->thispage ) ) + 'href' => $moveTitle->getLocalUrl() ); } } else { //article doesn't exist or is deleted if( $wgUser->isAllowed( 'delete' ) ) { if( $n = $this->mTitle->isDeleted() ) { - $undelTitle = Title::makeTitle( NS_SPECIAL, 'Undelete' ); + $undelTitle = SpecialPage::getTitleFor( 'Undelete' ); $content_actions['undelete'] = array( 'class' => false, 'text' => wfMsgExt( 'undelete_short', array( 'parsemag' ), $n ), @@ -739,7 +760,7 @@ class SkinTemplate extends Skin { } else { /* show special page tab */ - $content_actions['article'] = array( + $content_actions[$this->mTitle->getNamespaceKey()] = array( 'class' => 'selected', 'text' => wfMsg('specialpage'), 'href' => $wgRequest->getRequestURL(), // @bug 2457, 2510 @@ -753,9 +774,6 @@ class SkinTemplate extends Skin { $variants = $wgContLang->getVariants(); if( !$wgDisableLangConversion && sizeof( $variants ) > 1 ) { $preferred = $wgContLang->getPreferredVariant(); - $actstr = ''; - if( $action ) - $actstr = 'action=' . $action . '&'; $vcount=0; foreach( $variants as $code ) { $varname = $wgContLang->getVariantname( $code ); @@ -765,7 +783,7 @@ class SkinTemplate extends Skin { $content_actions['varlang-' . $vcount] = array( 'class' => $selected, 'text' => $varname, - 'href' => $this->mTitle->getLocalUrl( $actstr . 'variant=' . urlencode( $code ) ) + 'href' => $this->mTitle->getLocalURL('',$code) ); $vcount ++; } @@ -795,10 +813,9 @@ class SkinTemplate extends Skin { $action = $wgRequest->getText( 'action' ); $oldid = $wgRequest->getVal( 'oldid' ); - $diff = $wgRequest->getVal( 'diff' ); $nav_urls = array(); - $nav_urls['mainpage'] = array( 'href' => self::makeI18nUrl( 'mainpage') ); + $nav_urls['mainpage'] = array( 'href' => self::makeMainPageUrl() ); if( $wgEnableUploads ) { if ($wgUploadNavigationUrl) { $nav_urls['upload'] = array( 'href' => $wgUploadNavigationUrl ); @@ -813,7 +830,9 @@ class SkinTemplate extends Skin { } $nav_urls['specialpages'] = array( 'href' => self::makeSpecialUrl( 'Specialpages' ) ); - + // default permalink to being off, will override it as required below. + $nav_urls['permalink'] = false; + // A print stylesheet is attached to all pages, but nobody ever // figures that out. :) Add a link... if( $this->iscontent && ($action == '' || $action == 'view' || $action == 'purge' ) ) { @@ -842,15 +861,17 @@ class SkinTemplate extends Skin { } if( $this->mTitle->getNamespace() != NS_SPECIAL ) { - $wlhTitle = Title::makeTitle( NS_SPECIAL, 'Whatlinkshere' ); + $wlhTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage ); $nav_urls['whatlinkshere'] = array( - 'href' => $wlhTitle->getLocalUrl( 'target=' . urlencode( $this->thispage ) ) + 'href' => $wlhTitle->getLocalUrl() ); if( $this->mTitle->getArticleId() ) { - $rclTitle = Title::makeTitle( NS_SPECIAL, 'Recentchangeslinked' ); + $rclTitle = SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage ); $nav_urls['recentchangeslinked'] = array( - 'href' => $rclTitle->getLocalUrl( 'target=' . urlencode( $this->thispage ) ) + 'href' => $rclTitle->getLocalUrl() ); + } else { + $nav_urls['recentchangeslinked'] = false; } if ($wgUseTrackbacks) $nav_urls['trackbacklink'] = array( @@ -868,19 +889,23 @@ class SkinTemplate extends Skin { if($id || $ip) { # both anons and non-anons have contri list $nav_urls['contributions'] = array( - 'href' => self::makeSpecialUrl( 'Contributions/' . $this->mTitle->getText() ) + 'href' => self::makeSpecialUrlSubpage( 'Contributions', $this->mTitle->getText() ) ); - if ( $wgUser->isAllowed( 'block' ) ) + if ( $wgUser->isAllowed( 'block' ) ) { $nav_urls['blockip'] = array( - 'href' => self::makeSpecialUrl( 'Blockip/' . $this->mTitle->getText() ) - ); + 'href' => self::makeSpecialUrlSubpage( 'Blockip', $this->mTitle->getText() ) + ); + } else { + $nav_urls['blockip'] = false; + } } else { $nav_urls['contributions'] = false; + $nav_urls['blockip'] = false; } $nav_urls['emailuser'] = false; if( $this->showEmailUser( $id ) ) { $nav_urls['emailuser'] = array( - 'href' => self::makeSpecialUrl( 'Emailuser/' . $this->mTitle->getText() ) + 'href' => self::makeSpecialUrlSubpage( 'Emailuser', $this->mTitle->getText() ) ); } wfProfileOut( $fname ); @@ -932,7 +957,10 @@ class SkinTemplate extends Skin { $siteargs .= '&ts=' . $wgUser->mTouched; } - if ($wgContLang->isRTL()) $sitecss .= '@import "' . $wgStylePath . '/' . $this->stylename . '/rtl.css";' . "\n"; + if( $wgContLang->isRTL() ) { + global $wgStyleVersion; + $sitecss .= "@import \"$wgStylePath/$this->stylename/rtl.css?$wgStyleVersion\";\n"; + } # If we use the site's dynamic CSS, throw that in, too if ( $wgUseSiteCss ) { @@ -1003,16 +1031,23 @@ class SkinTemplate extends Skin { } /** - * @public + * This returns MediaWiki:Common.js and MediaWiki:[Skinname].js concate- + * nated together. For some bizarre reason, it does *not* return any + * custom user JS from subpages. Huh? + * + * There's absolutely no reason to have separate Monobook/Common JSes. + * Any JS that cares can just check the skin variable generated at the + * top. For now Monobook.js will be maintained, but it should be consi- + * dered deprecated. + * + * @return string */ - function getUserJs() { + public function getUserJs() { $fname = 'SkinTemplate::getUserJs'; wfProfileIn( $fname ); - global $wgStylePath; - $s = '/* generated javascript */'; - $s .= "var skin = '{$this->skinname}';\nvar stylepath = '{$wgStylePath}';"; - $s .= '/* MediaWiki:'.ucfirst($this->skinname)." */\n"; + $s = parent::getUserJs(); + $s .= "\n\n/* MediaWiki:".ucfirst($this->skinname).".js (deprecated; migrate to Common.js!) */\n"; // avoid inclusion of non defined user JavaScript (with custom skins only) // by checking for default message content @@ -1123,7 +1158,7 @@ class QuickTemplate { * @private */ function haveData( $str ) { - return $this->data[$str]; + return isset( $this->data[$str] ); } /** diff --git a/includes/SpecialAllmessages.php b/includes/SpecialAllmessages.php index 6e3f6588..a28ab3c2 100644 --- a/includes/SpecialAllmessages.php +++ b/includes/SpecialAllmessages.php @@ -1,12 +1,12 @@ <?php /** - * Provide functions to generate a special page + * Use this special page to get a list of the MediaWiki system messages. * @package MediaWiki * @subpackage SpecialPage */ /** - * + * Constructor. */ function wfSpecialAllmessages() { global $wgOut, $wgRequest, $wgMessageCache, $wgTitle; @@ -18,10 +18,9 @@ function wfSpecialAllmessages() { return; } - $fname = "wfSpecialAllMessages"; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); - wfProfileIn( "$fname-setup"); + wfProfileIn( __METHOD__ . '-setup' ); $ot = $wgRequest->getText( 'ot' ); $navText = wfMsg( 'allmessagestext' ); @@ -29,7 +28,6 @@ function wfSpecialAllmessages() { # Make sure all extension messages are available MessageCache::loadAllMessages(); - $first = true; $sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) ); ksort( $sortedArray ); $messages = array(); @@ -42,89 +40,84 @@ function wfSpecialAllmessages() { } $wgMessageCache->enableTransform(); - wfProfileOut( "$fname-setup" ); + wfProfileOut( __METHOD__ . '-setup' ); - wfProfileIn( "$fname-output" ); - if ($ot == 'php') { - $navText .= makePhp($messages); - $wgOut->addHTML('PHP | <a href="'.$wgTitle->escapeLocalUrl('ot=html').'">HTML</a><pre>'.htmlspecialchars($navText).'</pre>'); + wfProfileIn( __METHOD__ . '-output' ); + if ( $ot == 'php' ) { + $navText .= makePhp( $messages ); + $wgOut->addHTML( 'PHP | <a href="' . $wgTitle->escapeLocalUrl( 'ot=html' ) . '">HTML</a><pre>' . htmlspecialchars( $navText ) . '</pre>' ); } else { - $wgOut->addHTML( '<a href="'.$wgTitle->escapeLocalUrl('ot=php').'">PHP</a> | HTML' ); + $wgOut->addHTML( '<a href="' . $wgTitle->escapeLocalUrl( 'ot=php' ) . '">PHP</a> | HTML' ); $wgOut->addWikiText( $navText ); $wgOut->addHTML( makeHTMLText( $messages ) ); } - wfProfileOut( "$fname-output" ); + wfProfileOut( __METHOD__ . '-output' ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); } /** - * + * Create the messages array, formatted in PHP to copy to language files. + * @param $messages Messages array. + * @return The PHP messages array. + * @todo Make suitable for language files. */ -function makePhp($messages) { +function makePhp( $messages ) { global $wgLang; $txt = "\n\n\$messages = array(\n"; foreach( $messages as $key => $m ) { - if($wgLang->getCode() != 'en' and $m['msg'] == $m['enmsg'] ) { - //if (strstr($m['msg'],"\n")) { - // $txt.='/* '; - // $comment=' */'; - //} else { - // $txt .= '#'; - // $comment = ''; - //} + if( $wgLang->getCode() != 'en' && $m['msg'] == $m['enmsg'] ) { continue; - } elseif ( wfEmptyMsg( $key, $m['msg'] ) ) { + } else if ( wfEmptyMsg( $key, $m['msg'] ) ) { $m['msg'] = ''; $comment = ' #empty'; } else { $comment = ''; } - $txt .= "'$key' => '" . preg_replace( "/(?<!\\\\)'/", "\'", $m['msg']) . "',$comment\n"; + $txt .= "'$key' => '" . preg_replace( '/(?<!\\\\)\'/', "\'", $m['msg']) . "',$comment\n"; } $txt .= ');'; return $txt; } /** - * + * Create a list of messages, formatted in HTML as a list of messages and values and showing differences between the default language file message and the message in MediaWiki: namespace. + * @param $messages Messages array. + * @return The HTML list of messages. */ function makeHTMLText( $messages ) { global $wgLang, $wgContLang, $wgUser; - $fname = "makeHTMLText"; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); $sk =& $wgUser->getSkin(); $talk = $wgLang->getNsText( NS_TALK ); - $mwnspace = $wgLang->getNsText( NS_MEDIAWIKI ); - $mwtalk = $wgLang->getNsText( NS_MEDIAWIKI_TALK ); $input = wfElement( 'input', array( 'type' => 'text', 'id' => 'allmessagesinput', - 'onkeyup' => 'allmessagesfilter()',), - ''); + 'onkeyup' => 'allmessagesfilter()' + ), '' ); $checkbox = wfElement( 'input', array( 'type' => 'button', 'value' => wfMsgHtml( 'allmessagesmodified' ), 'id' => 'allmessagescheckbox', - 'onclick' => 'allmessagesmodified()',), - ''); + 'onclick' => 'allmessagesmodified()' + ), '' ); - $txt = '<span id="allmessagesfilter" style="display:none;">' . - wfMsgHtml('allmessagesfilter') . " {$input}{$checkbox} " . '</span>'; + $txt = '<span id="allmessagesfilter" style="display: none;">' . wfMsgHtml( 'allmessagesfilter' ) . " {$input}{$checkbox} " . '</span>'; - $txt .= " -<table border='1' cellspacing='0' width='100%' id='allmessagestable'> + $txt .= ' +<table border="1" cellspacing="0" width="100%" id="allmessagestable"> <tr> - <th rowspan='2'>" . wfMsgHtml('allmessagesname') . "</th> - <th>" . wfMsgHtml('allmessagesdefault') . "</th> + <th rowspan="2">' . wfMsgHtml( 'allmessagesname' ) . '</th> + <th>' . wfMsgHtml( 'allmessagesdefault' ) . '</th> </tr> <tr> - <th>" . wfMsgHtml('allmessagescurrent') . "</th> - </tr>"; + <th>' . wfMsgHtml( 'allmessagescurrent' ) . '</th> + </tr>'; + + wfProfileIn( __METHOD__ . "-check" ); - wfProfileIn( "$fname-check" ); # This is a nasty hack to avoid doing independent existence checks # without sending the links and table through the slow wiki parser. $pageExists = array( @@ -139,31 +132,29 @@ function makeHTMLText( $messages ) { $pageExists[$s->page_namespace][$s->page_title] = true; } $dbr->freeResult( $res ); - wfProfileOut( "$fname-check" ); + wfProfileOut( __METHOD__ . "-check" ); - wfProfileIn( "$fname-output" ); + wfProfileIn( __METHOD__ . "-output" ); $i = 0; foreach( $messages as $key => $m ) { - $title = $wgLang->ucfirst( $key ); - if($wgLang->getCode() != $wgContLang->getCode()) - $title.= '/' . $wgLang->getCode(); + if( $wgLang->getCode() != $wgContLang->getCode() ) { + $title .= '/' . $wgLang->getCode(); + } $titleObj =& Title::makeTitle( NS_MEDIAWIKI, $title ); $talkPage =& Title::makeTitle( NS_MEDIAWIKI_TALK, $title ); - $changed = ($m['statmsg'] != $m['msg']); + $changed = ( $m['statmsg'] != $m['msg'] ); $message = htmlspecialchars( $m['statmsg'] ); $mw = htmlspecialchars( $m['msg'] ); - #$pageLink = $sk->makeLinkObj( $titleObj, htmlspecialchars( $key ) ); - #$talkLink = $sk->makeLinkObj( $talkPage, htmlspecialchars( $talk ) ); if( isset( $pageExists[NS_MEDIAWIKI][$title] ) ) { - $pageLink = $sk->makeKnownLinkObj( $titleObj, "<span id='sp-allmessages-i-$i'>" . htmlspecialchars( $key ) . "</span>" ); + $pageLink = $sk->makeKnownLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . htmlspecialchars( $key ) . '</span>' ); } else { - $pageLink = $sk->makeBrokenLinkObj( $titleObj, "<span id='sp-allmessages-i-$i'>" . htmlspecialchars( $key ) . "</span>" ); + $pageLink = $sk->makeBrokenLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . htmlspecialchars( $key ) . '</span>' ); } if( isset( $pageExists[NS_MEDIAWIKI_TALK][$title] ) ) { $talkLink = $sk->makeKnownLinkObj( $talkPage, htmlspecialchars( $talk ) ); @@ -174,38 +165,35 @@ function makeHTMLText( $messages ) { $anchor = 'msg_' . htmlspecialchars( strtolower( $title ) ); $anchor = "<a id=\"$anchor\" name=\"$anchor\"></a>"; - if($changed) { - + if( $changed ) { $txt .= " - <tr class='orig' id='sp-allmessages-r1-$i'> - <td rowspan='2'> + <tr class=\"orig\" id=\"sp-allmessages-r1-$i\"> + <td rowspan=\"2\"> $anchor$pageLink<br />$talkLink </td><td> $message </td> - </tr><tr class='new' id='sp-allmessages-r2-$i'> + </tr><tr class=\"new\" id=\"sp-allmessages-r2-$i\"> <td> $mw </td> </tr>"; } else { - $txt .= " - <tr class='def' id='sp-allmessages-r1-$i'> + <tr class=\"def\" id=\"sp-allmessages-r1-$i\"> <td> $anchor$pageLink<br />$talkLink </td><td> $mw </td> </tr>"; - } - $i++; + $i++; } - $txt .= "</table>"; - wfProfileOut( "$fname-output" ); + $txt .= '</table>'; + wfProfileOut( __METHOD__ . '-output' ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $txt; } diff --git a/includes/SpecialAllpages.php b/includes/SpecialAllpages.php index 345c48e6..737e6834 100644 --- a/includes/SpecialAllpages.php +++ b/includes/SpecialAllpages.php @@ -24,7 +24,7 @@ function wfSpecialAllpages( $par=NULL, $specialPage ) { $namespace = 0; $wgOut->setPagetitle( $namespace > 0 ? - wfMsg( 'allinnamespace', $namespaces[$namespace] ) : + wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) : wfMsg( 'allarticles' ) ); @@ -51,7 +51,7 @@ class SpecialAllpages { */ function namespaceForm ( $namespace = NS_MAIN, $from = '' ) { global $wgScript; - $t = Title::makeTitle( NS_SPECIAL, $this->name ); + $t = SpecialPage::getTitleFor( $this->name ); $namespaceselect = HTMLnamespaceselector($namespace, null); @@ -83,8 +83,7 @@ function namespaceForm ( $namespace = NS_MAIN, $from = '' ) { * @param integer $namespace (default NS_MAIN) */ function showToplevel ( $namespace = NS_MAIN, $including = false ) { - global $wgOut, $wgUser; - $sk = $wgUser->getSkin(); + global $wgOut; $fname = "indexShowToplevel"; # TODO: Either make this *much* faster or cache the title index points @@ -185,14 +184,10 @@ function showToplevel ( $namespace = NS_MAIN, $including = false ) { * @param integer $namespace (Default NS_MAIN) */ function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) { - global $wgUser; - $sk = $wgUser->getSkin(); - $dbr =& wfGetDB( DB_SLAVE ); - $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) ); $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) ); $queryparams = ($namespace ? "namespace=$namespace" : ''); - $special = Title::makeTitle( NS_SPECIAL, $this->name . '/' . $inpoint ); + $special = SpecialPage::getTitleFor( $this->name, $inpoint ); $link = $special->escapeLocalUrl( $queryparams ); $out = wfMsgHtml( @@ -215,7 +210,8 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) { $sk = $wgUser->getSkin(); $fromList = $this->getNamespaceKeyAndText($namespace, $from); - + $n = 0; + if ( !$fromList ) { $out = wfMsgWikiHtml( 'allpagesbadtitle' ); } else { @@ -236,12 +232,8 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) { ) ); - ### FIXME: side link to previous - - $n = 0; $out = '<table style="background: inherit;" border="0" width="100%">'; - $namespaces = $wgContLang->getFormattedNamespaces(); while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { $t = Title::makeTitle( $s->page_namespace, $s->page_title ); if( $t ) { @@ -269,21 +261,71 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) { if ( $including ) { $out2 = ''; } else { + + # Get the last title from previous chunk + $dbr =& wfGetDB( DB_SLAVE ); + $res_prev = $dbr->select( + 'page', + 'page_title', + array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ), + $fname, + array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) ) + ); + + # Get first title of previous complete chunk + if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) { + $pt = $dbr->fetchObject( $res_prev ); + $prevTitle = Title::makeTitle( $namespace, $pt->page_title ); + } else { + # The previous chunk is not complete, need to link to the very first title + # available in the database + $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title', array( 'page_namespace' => $namespace ), $fname, array( 'LIMIT' => 1) ); + + # Show the previous link if it s not the current requested chunk + if( $from != $reallyFirstPage_title ) { + $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title ); + } else { + $prevTitle = null; + } + } + $nsForm = $this->namespaceForm ( $namespace, $from ); $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; $out2 .= '<tr valign="top"><td align="left">' . $nsForm; $out2 .= '</td><td align="right" style="font-size: smaller; margin-bottom: 1em;">' . $sk->makeKnownLink( $wgContLang->specialPage( "Allpages" ), wfMsgHtml ( 'allpages' ) ); - if ( isset($dbr) && $dbr && ($n == $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { - $self = Title::makeTitle( NS_SPECIAL, 'Allpages' ); + + $self = SpecialPage::getTitleFor( 'Allpages' ); + + # Do we put a previous link ? + if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) { + $q = 'from=' . $prevTitle->getPartialUrl() . ( $namespace ? '&namespace=' . $namespace : '' ); + $prevLink = $sk->makeKnownLinkObj( $self, wfMsgHTML( 'prevpage', $pt ), $q ); + $out2 .= ' | ' . $prevLink; + } + + if( $n == $this->maxPerPage && $s = $dbr->fetchObject($res) ) { + # $s is the first link of the next chunk + $t = Title::MakeTitle($namespace, $s->page_title); $q = 'from=' . $t->getPartialUrl() . ( $namespace ? '&namespace=' . $namespace : '' ); - $out2 .= ' | ' . $sk->makeKnownLinkObj( $self, wfMsgHtml( 'nextpage', $t->getText() ), $q ); + $nextLink = $sk->makeKnownLinkObj( $self, wfMsgHtml( 'nextpage', $t->getText() ), $q ); + $out2 .= ' | ' . $nextLink; } $out2 .= "</td></tr></table><hr />"; } $wgOut->addHtml( $out2 . $out ); + if( isset($prevLink) or isset($nextLink) ) { + $wgOut->addHtml( '<hr/><p style="font-size: smaller; float: right;">' ); + if( isset( $prevLink ) ) + $wgOut->addHTML( $prevLink . ' | '); + if( isset( $nextLink ) ) + $wgOut->addHTML( $nextLink ); + $wgOut->addHTML( '</p>' ); + + } + } /** @@ -298,18 +340,20 @@ function getNamespaceKeyAndText ($ns, $text) { return array( $ns, '', '' ); # shortcut for common case $t = Title::makeTitleSafe($ns, $text); - if ( $t && $t->isLocal() ) + if ( $t && $t->isLocal() ) { return array( $t->getNamespace(), $t->getDBkey(), $t->getText() ); - else if ( $t ) + } else if ( $t ) { return NULL; + } # try again, in case the problem was an empty pagename $text = preg_replace('/(#|$)/', 'X$1', $text); $t = Title::makeTitleSafe($ns, $text); - if ( $t && $t->isLocal() ) + if ( $t && $t->isLocal() ) { return array( $t->getNamespace(), '', '' ); - else + } else { return NULL; + } } } diff --git a/includes/SpecialBlockip.php b/includes/SpecialBlockip.php index 4eb4957a..626922bb 100644 --- a/includes/SpecialBlockip.php +++ b/includes/SpecialBlockip.php @@ -46,15 +46,13 @@ class IPBlockForm { $this->BlockReason = $wgRequest->getText( 'wpBlockReason' ); $this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') ); $this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' ); - $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly' ); # Unchecked checkboxes are not included in the form data at all, so having one # that is true by default is a bit tricky - if ( $wgRequest->wasPosted() ) { - $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', false ); - } else { - $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', true ); - } + $byDefault = !$wgRequest->wasPosted(); + $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault ); + $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault ); + $this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault ); } function showForm( $err ) { @@ -73,7 +71,7 @@ class IPBlockForm { $mIpbothertime = wfMsgHtml( 'ipbotheroption' ); $mIpbreason = wfMsgHtml( 'ipbreason' ); $mIpbsubmit = wfMsgHtml( 'ipbsubmit' ); - $titleObj = Title::makeTitle( NS_SPECIAL, 'Blockip' ); + $titleObj = SpecialPage::getTitleFor( 'Blockip' ); $action = $titleObj->escapeLocalURL( "action=submit" ); if ( "" != $err ) { @@ -82,7 +80,6 @@ class IPBlockForm { } $scBlockAddress = htmlspecialchars( $this->BlockAddress ); - $scBlockExpiry = htmlspecialchars( $this->BlockExpiry ); $scBlockReason = htmlspecialchars( $this->BlockReason ); $scBlockOtherTime = htmlspecialchars( $this->BlockOther ); $scBlockExpiryOptions = htmlspecialchars( wfMsgForContent( 'ipboptions' ) ); @@ -155,10 +152,18 @@ class IPBlockForm { array( 'tabindex' => 5 ) ) . " </td> </tr> + <tr> + <td> </td> + <td align=\"left\"> + " . wfCheckLabel( wfMsg( 'ipbenableautoblock' ), + 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock, + array( 'tabindex' => 6 ) ) . " + </td> + </tr> <tr> <td style='padding-top: 1em'> </td> <td style='padding-top: 1em' align=\"left\"> - <input tabindex='5' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" /> + <input tabindex='7' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" /> </td> </tr> </table> @@ -183,6 +188,7 @@ class IPBlockForm { # Check for invalid specifications if ( ! preg_match( "/^$rxIP$/", $this->BlockAddress ) ) { + $matches = array(); if ( preg_match( "/^($rxIP)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) { if ( $wgSysopRangeBans ) { if ( $matches[2] > 31 || $matches[2] < 16 ) { @@ -242,7 +248,7 @@ class IPBlockForm { $block = new Block( $this->BlockAddress, $userId, $wgUser->getID(), $this->BlockReason, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, - $this->BlockCreateAccount ); + $this->BlockCreateAccount, $this->BlockEnableAutoblock ); if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) { @@ -260,7 +266,7 @@ class IPBlockForm { $this->BlockReason, $expirestr ); # Report to the user - $titleObj = Title::makeTitle( NS_SPECIAL, 'Blockip' ); + $titleObj = SpecialPage::getTitleFor( 'Blockip' ); $wgOut->redirect( $titleObj->getFullURL( 'action=success&ip=' . urlencode( $this->BlockAddress ) ) ); } @@ -275,7 +281,7 @@ class IPBlockForm { $wgOut->addWikiText( $text ); } - function showLogFragment( &$out, &$title ) { + function showLogFragment( $out, $title ) { $out->addHtml( wfElement( 'h2', NULL, LogPage::logName( 'block' ) ) ); $request = new FauxRequest( array( 'page' => $title->getPrefixedText(), 'type' => 'block' ) ); $viewer = new LogViewer( new LogReader( $request ) ); diff --git a/includes/SpecialBooksources.php b/includes/SpecialBooksources.php index 960f6224..5c047fbe 100644 --- a/includes/SpecialBooksources.php +++ b/includes/SpecialBooksources.php @@ -1,109 +1,110 @@ <?php -/** - * ISBNs in wiki pages will create links to this page, with the ISBN passed - * in via the query string. - * - * @package MediaWiki - * @subpackage SpecialPage - */ - -/** - * Constructor - */ -function wfSpecialBooksources( $par ) { - global $wgRequest; - - $isbn = $par; - if( empty( $par ) ) { - $isbn = $wgRequest->getVal( 'isbn' ); - } - $isbn = preg_replace( '/[^0-9X]/', '', $isbn ); - - $bsl = new BookSourceList( $isbn ); - $bsl->show(); -} /** + * Special page outputs information on sourcing a book with a particular ISBN + * The parser creates links to this page when dealing with ISBNs in wikitext * * @package MediaWiki - * @subpackage SpecialPage + * @subpackage Special pages + * @author Rob Church <robchur@gmail.com> + * @todo Validate ISBNs using the standard check-digit method */ -class BookSourceList { - var $mIsbn; +class SpecialBookSources extends SpecialPage { - function BookSourceList( $isbn ) { - $this->mIsbn = $isbn; + /** + * ISBN passed to the page, if any + */ + private $isbn = ''; + + /** + * Constructor + */ + public function __construct() { + parent::__construct( 'Booksources' ); } - - function show() { - global $wgOut; - - $wgOut->setPagetitle( wfMsg( "booksources" ) ); - if( $this->mIsbn == '' ) { - $this->askForm(); - } else { + + /** + * Show the special page + * + * @param $isbn ISBN passed as a subpage parameter + */ + public function execute( $isbn = false ) { + global $wgOut, $wgRequest; + $this->setHeaders(); + $this->isbn = $this->cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) ); + $wgOut->addWikiText( wfMsgNoTrans( 'booksources-summary' ) ); + $wgOut->addHtml( $this->makeForm() ); + if( strlen( $this->isbn) > 0 ) $this->showList(); - } } - - function showList() { + + /** + * Trim ISBN and remove characters which aren't required + * + * @param $isbn Unclean ISBN + * @return string + */ + private function cleanIsbn( $isbn ) { + return trim( preg_replace( '![^0-9X]!', '', $isbn ) ); + } + + /** + * Generate a form to allow users to enter an ISBN + * + * @return string + */ + private function makeForm() { + global $wgScript; + $title = self::getTitleFor( 'Booksources' ); + $form = '<fieldset><legend>' . wfMsgHtml( 'booksources-search-legend' ) . '</legend>'; + $form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); + $form .= Xml::hidden( 'title', $title->getPrefixedText() ); + $form .= '<p>' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn ); + $form .= ' ' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>'; + $form .= Xml::closeElement( 'form' ); + $form .= '</fieldset>'; + return $form; + } + + /** + * Determine where to get the list of book sources from, + * format and output them + * + * @return string + */ + private function showList() { global $wgOut, $wgContLang; - $fname = "BookSourceList::showList()"; - - # First, see if we have a custom list setup in - # [[Wikipedia:Book sources]] or equivalent. - $bstitle = Title::makeTitleSafe( NS_PROJECT, wfMsg( "booksources" ) ); - if( $bstitle ) { - $revision = Revision::newFromTitle( $bstitle ); - if( $revision ) { - $bstext = $revision->getText(); - if( $bstext ) { - $bstext = str_replace( "MAGICNUMBER", $this->mIsbn, $bstext ); - $wgOut->addWikiText( $bstext ); - return; - } - } + + # Check for a local page such as Project:Book_sources and use that if available + $title = Title::makeTitleSafe( NS_PROJECT, wfMsg( 'booksources' ) ); # Should this be wfMsgForContent()? -- RC + if( is_object( $title ) && $title->exists() ) { + $rev = Revision::newFromTitle( $title ); + $wgOut->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) ); + return true; } - - # Otherwise, use the list of links in the default Language.php file. - $s = wfMsgWikiHtml( 'booksourcetext' ) . "<ul>\n"; - $bs = $wgContLang->getBookstoreList() ; - $bsn = array_keys ( $bs ) ; - foreach ( $bsn as $name ) { - $adr = $bs[$name] ; - if ( ! $this->mIsbn ) { - $adr = explode( ":" , $adr , 2 ); - $adr = explode( "/" , $adr[1] ); - $a = ""; - while ( $a == "" ) { - $a = array_shift( $adr ); - } - $adr = "http://".$a ; - } else { - $adr = str_replace ( "$1" , $this->mIsbn , $adr ) ; - } - $name = htmlspecialchars( $name ); - $adr = htmlspecialchars( $adr ); - $s .= "<li><a href=\"{$adr}\" class=\"external\">{$name}</a></li>\n" ; - } - $s .= "</ul>\n"; - - $wgOut->addHTML( $s ); + + # Fall back to the defaults given in the language file + $wgOut->addWikiText( wfMsgNoTrans( 'booksources-text' ) ); + $wgOut->addHtml( '<ul>' ); + $items = $wgContLang->getBookstoreList(); + foreach( $items as $label => $url ) + $wgOut->addHtml( $this->makeListItem( $label, $url ) ); + $wgOut->addHtml( '</ul>' ); + return true; } - - function askForm() { - global $wgOut, $wgTitle; - $fname = "BookSourceList::askForm()"; - - $action = $wgTitle->escapeLocalUrl(); - $isbn = htmlspecialchars( wfMsg( "isbn" ) ); - $go = htmlspecialchars( wfMsg( "go" ) ); - $out = "<form action=\"$action\" method='post'> - $isbn: <input name='isbn' id='isbn' /> - <input type='submit' value=\"$go\" /> - </form>"; - $wgOut->addHTML( $out ); + + /** + * Format a book source list item + * + * @param $label Book source label + * @param $url Book source URL + * @return string + */ + private function makeListItem( $label, $url ) { + $url = str_replace( '$1', $this->isbn, $url ); + return '<li><a href="' . htmlspecialchars( $url ) . '">' . htmlspecialchars( $label ) . '</a></li>'; } + } ?> diff --git a/includes/SpecialBrokenRedirects.php b/includes/SpecialBrokenRedirects.php index 653e13e2..50935654 100644 --- a/includes/SpecialBrokenRedirects.php +++ b/includes/SpecialBrokenRedirects.php @@ -27,7 +27,7 @@ class BrokenRedirectsPage extends PageQueryPage { function getSQL() { $dbr =& wfGetDB( DB_SLAVE ); - extract( $dbr->tableNames( 'page', 'pagelinks' ) ); + list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); $sql = "SELECT 'BrokenRedirects' AS type, p1.page_namespace AS namespace, diff --git a/includes/SpecialCategories.php b/includes/SpecialCategories.php index 89cff20a..346eac63 100644 --- a/includes/SpecialCategories.php +++ b/includes/SpecialCategories.php @@ -30,10 +30,11 @@ class CategoriesPage extends QueryPage { $NScat = NS_CATEGORY; $dbr =& wfGetDB( DB_SLAVE ); $categorylinks = $dbr->tableName( 'categorylinks' ); + $implicit_groupby = $dbr->implicitGroupby() ? '1' : 'cl_to'; $s= "SELECT 'Categories' as type, {$NScat} as namespace, cl_to as title, - 1 as value, + $implicit_groupby as value, COUNT(*) as count FROM $categorylinks GROUP BY 1,2,3,4"; diff --git a/includes/SpecialConfirmemail.php b/includes/SpecialConfirmemail.php index 72567609..e64232aa 100644 --- a/includes/SpecialConfirmemail.php +++ b/includes/SpecialConfirmemail.php @@ -36,8 +36,8 @@ class EmailConfirmation extends SpecialPage { $wgOut->addWikiText( wfMsg( 'confirmemail_noemail' ) ); } } else { - $title = Title::makeTitle( NS_SPECIAL, 'Userlogin' ); - $self = Title::makeTitle( NS_SPECIAL, 'Confirmemail' ); + $title = SpecialPage::getTitleFor( 'Userlogin' ); + $self = SpecialPage::getTitleFor( 'Confirmemail' ); $skin = $wgUser->getSkin(); $llink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $self->getPrefixedUrl() ); $wgOut->addHtml( wfMsgWikiHtml( 'confirmemail_needlogin', $llink ) ); @@ -54,15 +54,21 @@ class EmailConfirmation extends SpecialPage { global $wgOut, $wgUser, $wgLang, $wgRequest; if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) { $ok = $wgUser->sendConfirmationMail(); - $message = WikiError::isError( $ok ) ? 'confirmemail_sendfailed' : 'confirmemail_sent'; - $wgOut->addWikiText( wfMsg( $message ) ); + if ( WikiError::isError( $ok ) ) { + $wgOut->addWikiText( wfMsg( 'confirmemail_sendfailed', $ok->toString() ) ); + } else { + $wgOut->addWikiText( wfMsg( 'confirmemail_sent' ) ); + } } else { if( $wgUser->isEmailConfirmed() ) { $time = $wgLang->timeAndDate( $wgUser->mEmailAuthenticated, true ); $wgOut->addWikiText( wfMsg( 'emailauthenticated', $time ) ); } + if( $wgUser->isEmailConfirmationPending() ) { + $wgOut->addWikiText( wfMsg( 'confirmemail_pending' ) ); + } $wgOut->addWikiText( wfMsg( 'confirm |