tag
+* Add list of sections to action=parse output
+* Added action=logout
+* Added cascade flag to prop=info&inprop=protections
+* Added wlshow parameter to list=watchlist, similar to rcshow
+ (list=recentchanges)
+* Added support for image thumbnailing to prop=imageinfo
+* action={login,block,delete,move,protect,rollback,unblock,undelete} now must be
+ POSTed
+* prop=imageinfo interface changed: iihistory replaced by iilimit, iistart and
+ iiend parameters
+* Added amlang parameter to meta=allmessages
+* Added apfilterlanglinks parameter to list=allpages, replacing
+ query.php?what=nolanglinks
+* (bug 12718) Added action=paraminfo module that provides information about API
+ modules and their parameters
+* Added iiurlwidth and iiurlheight parameters to prop=imageinfo
+* Added format=txt and format=dbg, imported from query.php
+* Added uiprop=editcount to meta=userinfo
+* Added list=users which fetches user information
+* Added list=random which fetches a list of random pages
+* Added page parameter to action=parse to facilitate parsing of existing pages
+* Added uiprop=ratelimits to meta=userinfo
+* Added siprop=namespacealiases to meta=siteinfo
+* Made multiple values for ucuser possible in list=usercontribs
+* (bug 12944) Added cmstart and cmend parameters to list=categorymembers
+* Allow queries to have a where range that does not match the range field
+
+== MediaWiki 1.11 ==
+
+This is the Summer 2007 branch release of MediaWiki.
+
+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 developments
+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
+
+== Configuration changes since 1.10 ==
+
+* $wgThumbUpright - Adjust width of upright images when parameter 'upright' is
+ used
+* $wgAddGroups, $wgRemoveGroups - Finer control over who can assign which
+ usergroups
+* $wgEnotifImpersonal, $wgEnotifUseJobQ - Bulk mail options for large sites
+* $wgShowHostnames - Expose server host names through the API and HTML comments
+* $wgSaveDeletedFiles has been removed, the feature is now enabled unconditionally
+
+== New features since 1.10 ==
+
+* (bug 8868) Separate "blocked" message for autoblocks
+* Adding expiry of block to block messages
+* Links to redirect pages in categories are wrapped in
+
+* Introduced 'ImageOpenShowImageInlineBefore' hook; see docs/hooks.txt for
+ more information
+* (bug 9628) Show warnings about slave lag on Special:Contributions,
+ Special:Watchlist
+* (bug 8818) Expose "wpDestFile" as parameter $1 to "uploaddisabledtext"
+* Introducing new image keyword 'upright' and corresponding variable
+ $wgThumbUpright. This allows better proportional view of upright images
+ related to landscape images on a page without nailing the width of upright
+ images to a fix value which makes views for anon unproportional and user
+ preferences useless
+* (bug 6072) Introducing 'border' keyword to the [[Image:]] syntax
+* Introducing 'frameless' keyword to [[Image:]] syntax which respects the
+ user preferences for image width like 'thumb' but without a frame.
+* (bug 7960) Link to "what links here" for each "what links here" entry
+* Added support for configuration of an arbitrary number of commons-style
+ file repositories.
+* Added a Content-Disposition header to thumb.php output
+* Improved thumb.php error handling
+* Display file history on local image description pages of shared images
+* Added $wgArticleRobotPolicies
+* (bug 10076) Additional parameter $7 added to MediaWiki:Blockedtext
+ containing, the ip, ip range, or username whose block is affecting the
+* (bug 7691) Show relevant lines from the deletion log when re-creating a
+ previously deleted article
+* Added variables 'wgRestrictionEdit' and 'wgRestrictionMove' for JS to header
+* (bug 9898) Allow viewing all namespaces in Special:Newpages
+* (bug 10139) Introduce 'EditSectionLink' and 'EditSectionLinkForOther' hooks;
+ see docs/hooks.txt for details
+* (bug 9769) Provide "watch this page" toggle on protection form
+* (bug 9886) Provide clear example "stub link" in Special:Preferences
+* (bug 10055) Populate email address and real name properties of User objects
+ passed to the 'AbortNewAccount' hook
+* Show result of Special:Booksources in wiki content language always, it's
+ normally better maintained than the generic list from the standard message
+ files
+* (bug 7997) Allow users to be blocked from using Special:Emailuser
+* (bug 8989) Blacklist 'mhtml' and 'mht' files from upload
+* (bug 8760) Allow wiki links in "protectexpiry" message
+* (bug 5908) Add "DEFAULTSORTKEY" and "DEFAULTCATEGORYSORT" aliases for
+ "DEFAULTSORT" magic word
+* (bug 10181) Support the XCache object caching mechanism
+* (bug 9058) Introduce '--aconf' option for all maintenance scripts, to provide
+ a path to the AdminSettings.php file
+* (bug 8781) Remind users to check file permissions for LocalSettings.php
+ post-installation
+* Use shared.css for all skins and oldshared.css in place of common.css for
+ pre-Monobook skins. As always, modifications should go in-wiki to MediaWiki:
+ Common.css and MediaWiki:Monobook.css.
+* (bug 8869) Introduce Special:Uncategorizedtemplates
+* (bug 8734) Different log message when article protection level is changed
+* (bug 8458, 10338) Limit custom signature length to $wgMaxSigChars Unicode
+ characters
+* (bug 10096) Added an ability to query interwiki map table
+* On reupload, add a null revision to the image description page
+* Group log output by date
+* Kurdish interface latin/arabic writing system with transliteration
+* Support wiki text in all query page headers
+* Add 'Orphanedpages' as an alias to Special:Lonelypages
+* (bug 9328) Use "revision-info-current" message in place of "revision-info"
+ when viewing the current revision of a page, if available
+* (bug 8890) Enable wiki text for "license" message
+* Throw a showstopper exception when a hook function fails to return a value.
+ Forgetting to give a 'true' return value is a very common error which tends
+ to cause hard-to-track-down interactions between extensions.
+* Use $wgJobClasses to determine the correct Job to instantiate for a particular
+ queued task; allows extensions to introduce custom jobs
+* (bug 10326) AJAX-based page watching and unwatching has been cleaned up and
+ enabled by default.
+* Added option to install to MyISAM
+* (bug 9250) Remove hardcoded minimum image name length of three characters
+* Fixed DISPLAYTITLE behaviour to reject titles which don't normalise to the
+ same title as the current page, and enabled per default
+* Wrap site CSS and JavaScript in a tag, like user JS/CSS
+* (bug 10196) Add classes and dir="ltr" to the s on CSS and JS pages (new
+ classes: mw-code, mw-css, mw-js)
+* (bug 6711) Add $wgAddGroups and $wgRemoveGroups to allow finer control over
+ usergroup assignment.
+* Introduce 'UserEffectiveGroups' hook; see docs/hooks.txt for more information
+* (bug 10387) Detect and handle '.php5' extension environments at install time
+* Introduce 'ShowRawCssJs' hook; see docs/hooks.txt for more information
+* (bug 10404) Show rights log for the selected user in Special:Userrights
+* New javascript for upload page that will show a warning if a file with the
+ "destination filename" already exists.
+* Add 'editsection-brackets' message to allow localization (or removal) of the
+ brackets in the "[edit]" link for sections
+* (bug 10437) Move texvc styling to shared.css
+* Introduce "raw editing" mode for the watchlist, to allow bulk additions,
+ removals, and convenient exporting of watchlist contents
+* Show "undo" links in page histories
+* Option to jump to specified time period in user contributions
+* Improved feedback on "rollback success" page
+* Show distinct 'namespaceprotected' message to users when namespace protection
+ prevents page editing
+* (bug 9936) Per-edit suppression of preview-on-first edit with "preview=no"
+* Allow showing a one-off preview on first edit with "preview=yes"
+* (bug 9151) Remove timed redirects on "Return to X" pages for accessibility.
+* Link to user logs in toolbox when viewing a user page
+* (bug 10508) Allow HTML attributes on
+* (bug 1962) Allow HTML attributes on
+* (bug 10530) Introduce optional "sp-contributions-explain" message for
+ additional explanation in Special:Contributions
+* (bug 10520) Preview licences during upload via AJAX (toggle with
+ $wgAjaxLicensePreview)
+* New Parser::setTransparentTagHook for parser extension and template
+ compatibility
+* Introduced 'ContributionsToolLinks' hook; see docs/hooks.txt for more
+ information
+* Add a message if category is empty
+* Add CSS compatibility for Opera 9.5
+* Remove largely untested handheld stylesheet, which was causing more trouble
+ than good. Proper handheld support will be added at a future date. For now,
+ display should be acceptable either with CSS turned off or when using a so-
+ phisticated handheld browser.
+* (bug 3173) Option to offer exported pages as a download, rather than displaying
+ inline, as in most browsers
+* Pass the user as an argument to 'isValidPassword' hook callbacks; see
+ docs/hooks.txt for more information
+* Introduce 'UserGetRights' hook; see docs/hooks.txt for more information
+* (bug 9595) Pass new Revision to the 'ArticleInsertComplete' and
+ 'ArticleSaveComplete' hooks; see docs/hooks.txt for more information
+* (bug 9575) Accept upload description from GET parameters
+* Skip the difference engine cache when 'action=purge' is used while requesting
+ a difference page, to allow refreshing the cache in case of errors
+* (bug 10701) Link to Special:Listusers in default Special:Statistics messages
+* Improved file history presentation
+* (bug 10739) Users can now enter comments when reverting files
+* Improved handling of permissions errors
+* (bug 10793) "Mark patrolled" links will now be shown for users with
+ patrol permissions on all eligible diff pages
+* (bug 10655) Show standard tool links for blocked users in block log messages
+* Show standard tool links for blocked users in Special:Ipblocklist
+* Miscellaneous aesthetic improvements to Special:Ipblocklist
+* (bug 10826) Added link trail with Cyrillic characters for Mongolian language
+* (bug 10859) Introduce 'UserGetImplicitGroups' hook; see docs/hooks.txt for
+ more information
+* (bug 10832) Include user information when viewing a deleted revision
+* (bug 10872) Fall back to sane defaults when generating protection selector
+ labels for custom restriction levels
+* Show edit count in user preferences
+* Improved support for audio/video extensions
+* (bug 10937) Distinguish overwritten files in upload log
+* Introduce 'ArticleUpdateBeforeRedirect' hook; see docs/hooks.txt for more
+ information
+* Confirmation is now required when deleting old versions of files
+* (bug 7535) Users can now enter comments when deleting old versions of files
+* (bug 11001) Submit Special:Newpages as a GET, rather than a POST request
+* The around links to watched pages in change lists now
+ has a class - "mw-watched"
+* (bug 9002) Provide a "view/restore deleted edits" link on Special:Upload
+ when a destination filename is provided that corresponds with previous
+ deleted files
+* Make the "invalid special page" message clearer
+* Add accesskey 's' and tooltip to 'upload file' button at Special:Upload
+* Introduced 'SkinAfterBottomScripts' hook; see docs/hooks.txt for
+ more information
+* (bug 11095) Honour "preview on first edit" preference when preloading
+ text for a non-existent page
+* (bug 11022) Use a more accurate page title for Special:Whatlinkshere and
+ Special:Recentchangeslinked
+* Add link to user contributions in normal watchlist edit mode
+* (bug 9426) Add 'newsectionheaderdefaultlevel' message to allow
+ modification of the heading formatting for new sections when section=new
+ argument is supplied
+* (bug 10836) Add 'newsectionsummary' message to allow modification of the
+ text that prefixes a new section link in Recent Changes
+
+== Bugfixes since 1.10 ==
+
+* (bug 9712) Use Arabic comma in date/time formats for Arabic and Farsi
+* (bug 9670) Follow redirects when render edit section links to transcluded
+ templates.
+* (bug 6204) Fix incorrect unindentation with $wgMaxTocLevel
+* (bug 3431) Suppress "next page" link in Special:Search at end of results
+* Don't show unblock form if the user doesn't have permission to use it
+ (cosmetic change, no vulnerabilities existed)
+* Subtitle success message when unblocking a block ID instead of a pseudo link
+ like [[User:#123|#123]]
+* Use the standard HTTP fetch functions when retrieving remote wiki pages
+ through transwiki, so we can take advantage of cURL goodies if available
+* Disable user JavaScript on Special:Userlogin, Special:Resetpass and
+ Special:Preferences, to avoid a compromised script sniffing passwords, etc.
+* (bug 9854, 3770) Clip overflow text in gallery boxes for visual cleanliness
+ instead of letting it flow outside the box or trigger ugly scroll bars.
+* Tooltips for print version and permalink
+* Links to the MediaWiki namespace for system messages having their default
+ values are no longer shown as nonexistent (e.g., in red)
+* Special:Ipblocklist differentiates between empty list and no search results.
+* (bug 5375) profiling does not respect read-only mode.
+* (bug 7070) monobook/user.gif has antialias artifacts
+* (bug 9123) Safer way when applying $wgLocalTZoffset
+* (bug 9896) Documentation for $wgSquidServers and X-FORWARDED-FOR
+* (bug 9417) Uploading new versions of images when using Postgres no longer
+ throws warnings.
+* (bug 9908) Using tsearch2 with Postgres 8.1 no longer gives an error.
+* (bug 1438) Fix for diff table layout on very wide lines.
+ Diff style rules have been broken out to common/diff.css,
+ and the dupes removed from the default skin files.
+ Skins can still override the default rules.
+* (bug 1229) Balance columns in diff display evenly
+* Right-align diff line numbers in RTL language display
+* (bug 9332) Fix instructions in tests/README
+* (bug 9813) Reject usernames containing '#' to avoid silent truncation
+ of fragments during the normalisation process
+* (bug 7989) RSS feeds content now use black text when using white background.
+* (bug 9971) Typo in a french language message.
+* (bug 9973) Changed size was shown in advanced recentchanges collapsible items
+ with $wgRCShowChangedSized = false.
+* Fix PHP strict standards warning in enhanced recent changes.
+* (bug 5850) Added hexadecimal html entities comments for $digitTransformTable
+ entries.
+* (bug 7432) Change language name for Aromanian (roa-rup)
+* (bug 908) Unexistent special pages now generate a red link.
+* (bug 7899) Added \hline and \vline to the list of allowed TeX commands
+* (bug 7993) support mathematical symbol classes
+* (bug 10007) Allow Block IP to work with Postgrs again.
+* Add Google Wireless Transcoder to the Unicode editing blacklist
+* (bug 10083) Fix for Special:Version breakage on PHP 5.2 with some hooks
+* (bug 3624) TeX: \ker, \hom, \arg, \dim treated like \sin & \cos
+* (bug 10132, 10134) Restore back-compatibility Image::imageUrl() function
+* (bug 10113) Fix double-click for view source on protected pages
+* (bug 10117) Special:Wantedpages doesn't handle invalid titles in result
+ set [now prints out a warning]
+* (bug 10118) Introduced Special:Mostlinkedtemplates, report which lists
+ templates with a high number of inclusion links
+* (bug 10104) Fixed Database::getLag() for PostgreSQL and Oracle
+* (bug 9820) session.save_path check no longer halts installation, but
+ warns of possible bad values
+* (bug 9978) Fixed session.save_path validation when using extended
+ configuration format, e.g. "5;/tmp"
+* Don't generate a diff link in the patrol log if the page doesn't exist
+* (bug 10067) Translations for former skins removed from message files
+* (bug 9993) Force $wgShowExceptionDetails on during installation
+* (bug 9980) Validate administrator username and password during
+ installation
+* (bug 9383) Don't set a default value for BLOB column in rc-deleted
+ database patch
+* (bug 10149) Don't show full template list on section-0 edit
+* (bug 9909) Ensure access to binary fields in the math table use encodeBlob()
+ and decodeBlob()
+* (bug 6743) Don't link broken image links to the upload form when uploads
+ are disabled
+* (bug 9679) Improve documentation for $wgSiteNotice
+* (bug 10215) Show custom editing introduction when editing existing pages
+* (bug 10223) Fix edit link in noarticletext localizations for fr, oc
+* (bug 10247) Fix IP address regex to avoid false positive IPv6 matches
+* (bug 9948) Workaround for diff regression with old Mozilla versions
+* (bug 10265) Fix regression in category image gallery paging
+* (bug 8577) Fix some weird misapplications of time zones.
+ {{CURRENT*}} functions now consistently use UTC as intended, while
+ {{LOCAL*}} functions return local time per server config or $wgLocaltimezone.
+ Signature dates for Japanese and other languages including weekday now show
+ the correct day to match the rest of the time in local time.
+* Escape the output of magic variables that return page name or part of it
+* (bug 10309) Initialise parser state properly in extractSections(), fixes
+ some cases where section edits broke because tags were improperly stripped
+* Avoid PHP notice errors when doing HTTP proxy purges for an empty list
+* As intended, *skip* the HTTP proxy purges when doing HTCP purges
+* (bug 9696) Fix handling of brace transformations in "pagemovedtext"
+* (bug 10325) Fix regression in form action on Special:Listusers
+* Fixed installation on MyISAM or old InnoDB with charset=utf8, was giving
+ overlong key errors.
+* Fixed zero-padding issues with MySQL 5 binary schema
+* (bug 10344) Don't follow a redirect after changing its protection level
+* (bug 10333) Correct date format in Slovenian
+* (bug 10160) Show error message for unknown namespace on Special:Allpages and
+ Special:Prefixindex; making forms prettier for RTL wikis.
+* (bug 10334) Replace normal spaces before percent (%) signs with non-breaking
+ spaces
+* (bug 10372) namespaceDupes.php no longer ignores namespace aliases
+* (bug 10198) namespaceDupes.php no longer ignores interwiki prefixes
+* namespaceDupes.php should work better for initial-lowercase wikis
+* (bug 10377) "Permanent links" to revisions still work if the page is moved
+ and the redirect deleted
+* (bug 7071) Properly handle an 'oldid' passed to view or edit that doesn't
+ match the given title. Fixes inconsistencies with talk, history, edit links.
+* (bug 10397) Fix AJAX watch error fallback when we receive a bogus result
+* (bug 10396) Fix AJAX error when $wgScriptPath/index.php is not valid;
+ using $wgScript now included in JS info
+* Use native XMLHttpRequest class in preference to ActiveX on IE 7; this
+ avoids the "ActiveX "Do you want to allow ActiveX?" prompt when something
+ security settings are cranked this way and AJAX-y gets used.
+* Delay AJAX watch initialization until click so IE 6 with ugly security
+ settings doesn't prompt you until you use the link.
+* (bug 10401) Provide non-redirecting link to original title in Special:Movepage
+* Fix broken handling of log views for page titles consisting of one
+ or more zeros, e.g. "0", "00" etc.
+* Fix read permission check for special pages with subpage parameters, e.g.
+ Special:Confirmemail
+* Fix read permission check for unreadable page titles which are numerically
+ equivalent to a whitelisted title
+* '?>' closing tag removed from all files to help avoid problems with extraneous
+ whitespace (broken XML feeds, etc.)
+* Don't use garbled parser cache output when viewing custom CSS or JavaScript
+ pages
+* (bug 10406) Fix Special:Listusers filter form for non-ASCII localizations
+* Fix empty message checks for message names containing &
+ This corrects some odd behavior with sidebar items and custom namespaces
+ containing ampersands.
+* (bug 10375) Change thousands separator character to for Latin (la)
+* (bug 10477) Fix AJAX watch for Farsi on Firefox: JavaScript encoding tweak
+* (bug 10496) Fix broken DISTINCT option logic in database backend
+* Fix CSS media declaration for "screen, projection"; was causing some
+ validation issues
+* (bug 10495) $wgMemcachedDebug set twice in includes/DefaultSettings.php
+* (bug 10316) Prevent inconsistent cached skin settings in gen=js by setting
+ the intended skin directly in the URL.
+* (bug 9903) Don't mark redirects in categories as stubs
+* (bug 6965) Cannot include "Template:R" with {{R}} (magic word conflict)
+* Padding parser functions now work with strings like '0' that evaluate to false
+* (bug 10332) Title->userCan( 'edit' ) may return false positive
+* Fix bug with in front of links for wikis where linkPrefixExtension is true
+* (bug 10552) Suppress rollback link in history for single-revision pages
+* (bug 10538) Gracefully handle invalid input on move success page
+* Fix for Esperanto double-x-encoding in move success page
+* (bug 10526) Fix toolbar/insertTags behavior for IE 6/7 and Opera (8+)
+ Now matches the selection behavior on Mozilla / Safari.
+ Patch by Alex Smotrov.
+* Don't show non-functional toolbar buttons on Opera 7 anymore
+* (bug 9151) Fix relative subpage links with section fragments
+* (bug 10560) Adding a space between category letter heading and "continues"
+* (bug 4650) Keep impossibly large/small counts off Special:Statistics
+* (bug 10608) PHP notice when installing with PostgreSQL
+* (bug 10615) Fix for transwiki import when CURL not available
+* (bug 8054) Return search page for empty search requests with ugly URLs
+* (bug 10572) Force refresh after clearing visitation timestamps on watchlist
+* (bug 10631) Warn when illegal characters are removed from filename at upload
+* Fix several JavaScript bugs under MSIE 5/Macintosh
+* (bug 10591) Use Arabic numerals (0,1,2...) for the Malayam language
+* (bug 10642) Fix shift-click checkbox behavior for Opera 9.0+ and 6.0
+* Work around Safari bug with pages ending in ".gz" or ".tgz"
+* Removed obsolete maintenance/changeuser.sql script; use RenameUser extension
+* (bug 2735) "Preview" shown in title bar for action=submit on special pages
+* Removed "restore" links from the deletion log embedded in Special:Undelete
+* Improved error reporting and robustness for file delete/undelete.
+* Improved speed of file delete by storing the SHA-1 hash in image/oldimage
+* Fixed leading zero in base 36 SHA-1 hash
+* Protection form no longer produces JavaScript errors
+* (bug 10741) File histories show "delete" links for non-sysops
+* (bug 10744) Treat "noarticletext" and "noarticletextanon" as wiki text when
+ used on a non-existent page with "action=info"
+* Fix escaping of raw message text when used on a non-existent page with
+ "action=info"
+* (bug 10683) Fix inconsistent handling of URL-encoded titles in links
+ used in redirects (i.e. they now work)
+* (bug 8878) Changes to $dateFormats in German localization (removing unused,
+ nonexistent formats, putting time after date)
+* (bug 10769) Database::update() should return boolean result
+* Fix preference checkbox display for right-to-left languages which caused
+ them to be hidden in IE in some cases
+* Fix upload form display in right-to-left languages
+* Fixed regression in blocking of username '0'
+* (bug 9437) Don't overwrite edit form submission handler when setting up
+ edit box scroll position preserve/restore behaviour
+* (bug 10805) Fix "undo" link when viewing the diff of the most recent
+ change to a page using "diff=0"
+* (bug 10765) img_auth.php will now refuse logged-out requests where
+ $wgWhitelistRead is undefined, instead of (incorrectly) honouring them
+* Fixed img_auth.php file name extraction for whitelist checking
+* Tweak spacing of email preference display
+* Table sorting JavaScript prefers textContent over innerText to allow hidden
+ sort keys to work on Safari
+* (bug 4530) Fix local name of Kurdish language
+* (bug 10830) Fix local name of Haitian Creole language
+* Fix invalid XHTML in Special:Protectedpages
+* Fix comments in contributions and log pages for right-to-left languages
+* Make installer include_path-independent, so it should work on hosts which
+ disable user setting of PHP include_path setting
+* glob() is horribly unreliable and doesn't work on some systems, including
+ free.fr shared hosting. No longer using it in Language::getLanguageNames()
+* (bug 10763) Fix multi-insert logic for PostgreSQL
+* Fix invalid XHTML when viewing a deleted revision
+* Fix syntax error in translations of magic words in Romanian language
+* (bug 8737) Fix warnings caused by incorrect use of `/dev/null` when piping
+ process error output under Windows
+* (bug 7890) Don't list redirects to special pages in Special:BrokenRedirects
+* (bug 10783) Resizing PNG-24 images with GD no longer causes all alpha
+ channel transparency to be lost and transparent pixels to be turned black
+* (bug 9339) General error pages were transforming messages and their parameters
+ in the wrong order
+* (bug 9026) Incorrect heading numbering when viewing Special:Statistics with
+ "auto-numbered headings" enabled
+* Fixed invalid XHTML in Special:Upload
+* (bug 11013) Make sure dl() is available before attempting to use it to check
+ available databases in installer
+* Resizing transparent GIF images with GD now retains transparency by skipping
+ resampling
+* (bug 11065) Fix regression in handling of wiki-formatted EXIF metadata
+* Double encoding broke Special:Newpages for some languages
+* Adding a newline before the statistics footer, to prevent parsing problems
+* Preventing the TOC from appearing in Special:Statistics
+* (bug 11082) Fix check for fully-specced table names in Database::tableName
+* (bug 11067) Fix regression in upload conflict thumbnail display
+* (bug 10985) Resolved cached entries on Special:DoubleRedirects were being
+ supressed, breaking paging - now strikes out "fixed" results
+* (bug 8393) and need to be preserved (without attributes) for
+ entries in the table of contents
+* (bug 11114) Fix regression in read-only mode error display during editing
+* Force non-MySQL databases to use an ORDER BY in SpecialAllpages to ensure
+ that the first page_title is truly the first page title.
+* (bug 10836) Change the summary on creating of new section
+* Inclusion of Special:Wantedpages now works again
+
+== API changes since 1.10 ==
+
+Full API documentation is available at http://www.mediawiki.org/wiki/API
+
+* New properties: links, templates, images, langlinks, categories, external
+ links
+* Breaking Change: imagelinks renamed into imageusage (il->iu)
+* Bug fix: incorrect generator behavior in some cases
+* JSON format allows an optional callback function to wrap the result.
+* Login module disabled until a more secure solution can be implemented
+* (bug 9938) Querying by revision identifier returns the most recent revision
+ for the corresponding page, rather than the requested revision
+* (bug 8772) Filter page revision queries by user
+* (bug 9927) User contributions queries do not accept IP addresses
+* Watchlist feed now reports a proper feed item when the user is not logged in
+* Watchlist feed date bug fixed - automatically shows one last day
+* Watchlist feed now allows to specify number of hours to monitor
+* list=allpages now returns a list instead of a map in JSON format
+* Breaking Change: in json, revisions are now returned as a list, not as a map.
+* Add: prop=info can show page is new flag, current page length, and visit
+ counter.
+* Change: Query watchlist now shows flags only when explicitly requested with
+ wlparam=flags
+* rc_this_oldid (textid) is no longer accessible from query watchlist
+* action=usercontribs: additional filtering by ucshow=; selection of needed
+ fields with ucprop=; the textid (rev_text_id) is no longer being exposed
+* (bug 9970) Breaking Change: backlinks, embeddedin and imageusage now return
+ lists in JSON instead of a map, and do not return anything when titles do
+ not exist
+* (bug 9121) Introduced indexpageids query parameter to list the page_id
+ values of all returned page items
+* (bug 10147) Now interwiki titles are not processed but added to a separate
+ "interwiki" section of the output.
+* Added categorymembers list to query for pages in a category.
+* (bug 10260) Show page protection status
+* (bug 10392) Include MediaWiki version details in version output
+* (bug 10411) Site language in meta=siteinfo
+* (bug 10391) action=help doesn't return help if format is fancy markup
+* backlinks, embeddedin and imageusage lists should use (bl|ei|iu)title parameter
+ instead of titles. Titles for these lists is obsolete and might stop working soon.
+* Added prop=imageinfo - gets image properties and upload history
+* (bug 10211) Added db server replication lag information in meta=siteinfo
+* Added external url search within wiki pages (list=exturlusage)
+* Added link enumeration (list=alllinks)
+* Added registered users enumeration (list=allusers)
+* Added full text search in titles and content (list=search)
+* (bug 10684) Expanded list=allusers functionality
+* Possible breaking change: prop=revisions no longer includes pageid for rvprop=ids
+* Added rvprop=size to prop=revisions (The size will not be shown if it is NULL in the database)
+* list=allpages now allows to filter by article min/max size and protection status
+* Added site statistics (siprop=statistics for meta=siteinfo)
+* (bug 10902) Unable to fetch user contributions from IP addresses
+* `list=usercontribs` no longer requires that the user exist
+* (bug 10971) `aufrom` parameter doesn't work with spaces
+* Fix username handling issue with `auprefix` parameter
+* Treat underscores as spaces for `aufrom` and `auprefix` parameters
+* Added edit/delete/... token retrieval to prop=info
+* Added meta=userinfo - logged-in user information, group membership, rights
+* (bug 11072) Fix regression in API image history query
+* (bug 11115) Adding SHA1 hash to imageinfo query
+* (bug 10898) API does not return an edit token for non-existent pages
+* (bug 10890) Timestamp support for categorymembers query
+* (bug 10980) Add exclude redirects on backlinks
+* IPv6 titles in User namespace are normalized (run cleanupTitles.php to fix any old stray pages)
+
+== Maintenance script changes since 1.10 ==
+
+* Add support for wgMaxTocLevel option in parserTests
+* (bug 6823) Disable article view counter in maintenance/dumpHTML.php
+* Fix maintenance/importImages.php so it doesn't barf PHP errors when no
+ suitable files are found, and make the list of extensions an option (defaults
+ to $wgFileExtensions)
+* Add option to maintenance/createAndPromote.php to give the user bureaucrat
+ permissions (--bureaucrat)
+* Allow overwriting existing files with a conflicting name using
+ maintenance/importImages.php
+* (bug 10266) Use native newlines when rebuilding a messages file.
+
+== Languages updated since 1.10 ==
+
+* Afrikaans (af)
+* Arabic (ar)
+* Bikol (bcl)
+* Bulgarian (bg)
+* Catalan (ca)
+* Danish (da)
+* German (de)
+* Greek (el)
+* Esperanto (eo)
+* Spanish (es)
+* Estonian (et)
+* Extremaduran (ext)
+* Farsi (fa)
+* Finnish (fi)
+* Vöro (fiu-vro)
+* French (fr)
+* Français Cadien (frc) (new)
+* Franco-Provençal/Arpetan (frp)
+* Galician (gl)
+* Hakka (hak)
+* Hebrew (he)
+* Upper Sorbian (hsb)
+* Haitian (ht)
+* Indonesian (id)
+* Icelandic (is)
+* Italian (it)
+* Japanese (ja)
+* Georgian (ka)
+* Kabyle (kab)
+* Kazakh (kk)
+* Korean (ko)
+* Kinaray-a (krj) (new)
+* Kurdish (ku)
+* Latin (la)
+* Lao (lo)
+* Lithuanian (lt)
+* Latviešu (lv)
+* Malayalam (ml)
+* Bahasa Melayu (ms)
+* Burmese (my)
+* Low German (nds)
+* Dutch (nl)
+* Norwegian (no)
+* Occitan (oc)
+* Punjabi (Gurmukhi) (pa)
+* Polish (pl)
+* Piedmontese (pms)
+* Portuguese (pt)
+* Romani (rmy)
+* Romanian (ro)
+* Aromanian (roa-rup)
+* Russian (ru)
+* Sakha (sah)
+* Sango (se) (new)
+* Slovak (sk)
+* Slovenian (sl)
+* Shona (sn)
+* Somali (so)
+* Albanian (sq)
+* Sundanese (su)
+* Swedish (sv)
+* Tamil (ta)
+* Thai (th)
+* Tigrinya (ti)
+* Setswana (tn)
+* Tok Pisin (tpi)
+* Uyghur (ug)
+* Volapük (vo)
+* Winaray (war) (new)
+* Yiddish (yi)
+* Old Chinese / Late Middle Chinese (zh-classical)
+* Chinese (PRC) (zh-cn)
+* Chinese (Taiwan) (zh-tw)
+* Cantonese (zh-yue)
+
== MediaWiki 1.10 ==
This is the Spring 2007 branch release of MediaWiki.
@@ -1371,7 +3156,7 @@
* (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
+ with separated 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
@@ -3444,7 +5229,7 @@
* Fixed a bug in Special:Contributions that caused the namespace selection to
be forgotten between submits
* Special:Watchlist/edit now has namespace subheadings
-* (bug 1714) the "Save page" button now has right margin to seperate it from
+* (bug 1714) the "Save page" button now has right margin to separate it from
"Show preview" and "Show changes"
* Special:Statistics now supports action=raw, useful for bots designed to
harwest e.g. article counts from multiple wikis.
@@ -4501,10 +6286,10 @@
=== Online documentation ===
Documentation for both end-users and site administrators is currently being
-built up on Meta-Wikipedia, and is covered under the GNU Free Documentation
+built up on MediaWiki.org, and is covered under the GNU Free Documentation
License:
- http://meta.wikipedia.org/wiki/Help:Contents
+ http://www.mediawiki.org/
=== Mailing list ===
@@ -4512,10 +6297,10 @@
A MediaWiki-l mailing list has been set up distinct from the Wikipedia
wikitech-l list:
- http://mail.wikipedia.org/mailman/listinfo/mediawiki-l
+ http://lists.wikimedia.org/mailman/listinfo/mediawiki-l
A low-traffic announcements-only list is also available:
- http://mail.wikipedia.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.
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/INSTALL mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/INSTALL
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/INSTALL Mon Jan 8 21:54:59 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/INSTALL Fri Apr 18 10:20:38 2008
@@ -2,101 +2,94 @@
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 prerequisites available.
+Starting with MediaWiki 1.2.0, it's possible to install and configure the wiki
+"in-place", as long as you have the necessary prerequisites available.
Required software:
* Web server with PHP 5.x or higher.
* A MySQL server, 4.0.14 or higher OR a Postgres server, 8.1 or higher
-MediaWiki is developed and tested mainly on Unix/Linux
-platforms, but should work on Windows as well.
+MediaWiki is developed and tested mainly on Unix/Linux platforms, but should
+work on Windows as well.
-If your PHP is configured as a CGI plug-in rather than
-an Apache module you may experience problems, as this
-configuration is not well tested. safe_mode is also not
-tested and unlikely to work.
+If your PHP is configured as a CGI plug-in rather than an Apache module you may
+experience problems, as this configuration is not well tested. safe_mode is also
+not tested and unlikely to work.
If you want math support see the instructions in math/README
Don't forget to check the RELEASE-NOTES file...
-Additional documentation is available online, which may include more
-detailed notes on particular operating systems and workarounds for
-difficult hosting environments:
+Additional documentation is available online, which may include more detailed
+notes on particular operating systems and workarounds for difficult hosting
+environments:
-http://meta.wikimedia.org/wiki/Help:Installation
+http://www.mediawiki.org/wiki/Manual:Installation_guide
-********************** WARNING **************************
+******************* WARNING *******************
-REMEMBER: ALWAYS BACK UP YOUR DATABASE BEFORE ATTEMPTING
-TO INSTALL OR UPGRADE!!!
+REMEMBER: ALWAYS BACK UP YOUR DATABASE BEFORE
+ATTEMPTING TO INSTALL OR UPGRADE!!!
-********************** WARNING **************************
+******************* WARNING *******************
----
In-place web install
----
-Decompress the MediaWiki installation archive either on
-your server, or on your local machine and upload the
-directory tree. Rename it from "mediawiki-1.x.x" to
+Decompress the MediaWiki installation archive either on your server, or on your
+local machine and upload the directory tree. Rename it from "mediawiki-1.x.x" to
something nice, like "wiki", since it'll be in your URL.
- +-----------------------------------------------------------+
- | Hint: If you plan to use a fancy URL-rewriting scheme |
- | to prettify your URLs, you should put the files in a |
- | *different* directory from the virtual path where page |
- | names will appear. |
- | |
- | See: http://meta.wikimedia.org/wiki/Rewrite_rules |
- +-----------------------------------------------------------+
-
-To run the install script, you'll need to temporarily make
-the 'config' subdirectory writable by the web server. The
-simplest way to do this on a Unix/Linux system is to make
-it world-writable:
+ +--------------------------------------------------------------------------+
+ | Hint: If you plan to use a fancy URL-rewriting scheme to prettify your |
+ | URLs, you should put the files in a *different* directory from the |
+ | virtual path where page names will appear. |
+ | |
+ | See: http://www.mediawiki.org/wiki/Manual:Short_URL |
+ +--------------------------------------------------------------------------+
+
+To run the install script, you'll need to temporarily make the 'config'
+subdirectory writable by the web server. The simplest way to do this on a
+Unix/Linux system is to make it world-writable:
chmod a+w config
-Hop into your browser and surf into the wiki directory.
-It'll direct you into the config script. Fill out the form...
-remember you're probably not on an encrypted connection.
+Hop into your browser and surf into the wiki directory. It'll direct you into
+the config script. Fill out the form... remember you're probably not on an
+encrypted connection.
Gaaah! :)
-If all goes well, you should soon be told that it's set up
-your wiki database and written a configuration file. There
-should now be a 'LocalSettings.php' in the config directory;
-move it back up to the main wiki directory, and the wiki
+If all goes well, you should soon be told that it's set up your wiki database
+and written a configuration file. There should now be a 'LocalSettings.php' in
+the config directory; move it back up to the main wiki directory, and the wiki
should now be working.
- +------------------------------------------------------------+
- | Security hint: if you have limited access on your server |
- | and cannot change ownership of files, you might want to |
- | *copy* instead of *move* LocalSettings.php. |
- | |
- | This will make the file owned by your user account |
- | instead of by the web server, which is safer in case |
- | another user's account is compromised. |
- +------------------------------------------------------------+
-
-Once the wiki is set up, you should remove the config
-directory, or at least make it not world-writable (though
-it will refuse to config again if the wiki is set up).
+ +-------------------------------------------------------------------------+
+ | Security hint: if you have limited access on your server and cannot |
+ | change ownership of files, you might want to *copy* instead of *move* |
+ | LocalSettings.php. |
+ | |
+ | This will make the file owned by your user account instead of by |
+ | the web server, which is safer in case another user's account is |
+ | compromised. |
+ +-------------------------------------------------------------------------+
+
+Once the wiki is set up, you should remove the config directory, or at least
+make it not world-writable (though it will refuse to config again if the wiki
+is set up).
----
-Don't forget that this is free software under development!
-Chances are good there's a crucial step that hasn't made it
-into the documentation. You should probably sign up for the
-MediaWiki developers' mailing list; you can ask for help (please
-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.
+Don't forget that this is free software under development! Chances are good
+there's a crucial step that hasn't made it into the documentation. You should
+probably sign up for the MediaWiki developers' mailing list; you can ask for
+help (please 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://lists.wikimedia.org/mailman/listinfo/mediawiki-announce (low traffic)
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/Makefile mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/Makefile
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/Makefile Wed Jul 11 03:55:24 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/Makefile Fri Apr 11 11:56:13 2008
@@ -15,11 +15,14 @@
FAST_TESTS=$(BASE_TEST) $(INCLUDES_TESTS)
ALL_TESTS=$(BASE_TEST) $(INCLUDES_TESTS) $(MAINTENANCE_TESTS)
-test: Test.php
+test: t/Test.php
$(PROVE_BIN) $(ALL_TESTS)
-fast: Test.php
+fast: t/Test.php
$(PROVE_BIN) $(FAST_TESTS)
-verbose: Test.php
+maint:
+ $(PROVE_BIN) $(MAINTENANCE_TESTS)
+
+verbose: t/Test.php
$(PROVE_BIN) -v $(ALL_TESTS) | egrep -v '^ok'
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/README mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/README
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/README Tue May 30 18:06:04 2006
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/README Tue Nov 11 12:38:18 2008
@@ -1,15 +1,15 @@
-2006-04-05
+2008-11-11
-For system requirements, installation and upgrade details, see the files RELEASE-NOTES,
-INSTALL, and UPGRADE.
+For system requirements, installation and upgrade details, see the files
+RELEASE-NOTES, INSTALL, and UPGRADE.
== MediaWiki ==
MediaWiki is the software used for Wikipedia [http://www.wikipedia.org/] and the
other Wikimedia Foundation websites. Compared to other wikis, it has an
excellent range of features and support for high-traffic websites using
-multiple servers (Wikimedia sites peak in the 5000+ requests per second range
-as of November 2005).
+multiple servers (Wikimedia sites peak in the 60000+ requests per second range
+as of November 2008).
While quite usable on smaller sites, you may find you have to "roll your own"
local documentation, and some aspects of configuration may seem overcomplicated
@@ -33,21 +33,24 @@
* Domas Mituzas
* Rob Church
* Jens Frank
-* Several others
-
-The contributors hold the copyright to this work, and it is licensed
-under the terms of the GNU General Public License, version 2 or later[1]
-(see http://www.fsf.org/licenses/gpl.html). Derivative works and later
-versions of the code must be free software licensed under the same
-terms. This includes "extensions" that use MediaWiki functions or
-variables; see http://www.gnu.org/licenses/gpl-faq.html#GPLAndPlugins
-for details.
+* Yuri Astrakhan
+* Aryeh Gregor
+* Aaron Schulz
+* Several others (view CREDITS for a more complete list)
+
+The contributors hold the copyright to this work, and it is licensed under the
+terms of the GNU General Public License, version 2 or later[1] (see
+http://www.fsf.org/licensing/licenses/gpl.html). Derivative works and later
+versions of the code must be free software licensed under the same or a
+compatible license. This includes "extensions" that use MediaWiki functions or
+variables; see http://www.gnu.org/licenses/gpl-faq.html#GPLAndPlugins for
+details.
The Wikimedia Foundation currently has no legal rights to the software.
-[1] Sections of code written exclusively by Lee Crocker or Erik Moeller are
-also released into the public domain, which does not impair the obligations of
-users under the GPL for use of the whole code or other sections thereof.
+[1] Sections of code written exclusively by Lee Crocker or Erik Moeller are also
+released into the public domain, which does not impair the obligations of users
+under the GPL for use of the whole code or other sections thereof.
[2] MediaWiki makes use of the Sajax Toolkit by modernmethod,
http://www.modernmethod.com/sajax/
@@ -61,43 +64,42 @@
Many thanks to the Wikimedia regulars for testing and suggestions.
-The official website for mediawiki is located at:
+The official website for MediaWiki is located at:
http://www.mediawiki.org/
-The code is currently maintained in a Subversion repository
-at svn.wikimedia.org. See http://www.mediawiki.org/wiki/Subversion
-for details.
+The code is currently maintained in a Subversion repository at
+svn.wikimedia.org. See http://www.mediawiki.org/wiki/Subversion for details.
Please report bugs and make feature requests in our Bugzilla system:
- http://bugzilla.wikimedia.org/
+ https://bugzilla.wikimedia.org/
Documentation and discussion on new features may be found at:
- http://www.mediawiki.org/wiki/Help:FAQ
+ http://www.mediawiki.org/wiki/Manual:FAQ
http://www.mediawiki.org/wiki/Documentation
http://www.mediawiki.org/wiki/Development
Extensions are listed at:
- http://meta.wikimedia.org/wiki/Category:MediaWiki_extensions
+ http://www.mediawiki.org/wiki/Category:Extensions
If you are setting up your own wiki based on this software, it is highly
recommended that you subscribe to mediawiki-announce:
- http://mail.wikimedia.org/mailman/listinfo/mediawiki-announce
+ https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce
-The mailing list is very low volume, and is intended primarily for
-announcements of new versions, bug fixes, and security issues.
+The mailing list is very low volume, and is intended primarily for announcements
+of new versions, bug fixes, and security issues.
A higher volume support mailing list can be found at:
- http://mail.wikimedia.org/mailman/listinfo/mediawiki-l
+ https://lists.wikimedia.org/mailman/listinfo/mediawiki-l
Developer discussion takes place at:
- http://mail.wikimedia.org/mailman/listinfo/wikitech-l
+ https://lists.wikimedia.org/mailman/listinfo/wikitech-l
-There is also a development and support channel #mediawiki on
-irc.freenode.net, and an unoffical support forum at www.mwusers.com.
+There is also a development and support channel #mediawiki on irc.freenode.net,
+and an unoffical support forum at www.mwusers.com.
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/RELEASE-NOTES mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/RELEASE-NOTES
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/RELEASE-NOTES Mon Sep 10 17:36:51 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/RELEASE-NOTES Sun Feb 22 05:27:52 2009
@@ -3,11 +3,11 @@
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.11.0 ==
+== MediaWiki 1.14.0 ==
-September 10, 2007
+February 22, 2009
-This is the Fall 2007 snapshot release of MediaWiki.
+This is the first stable release of the 2009 Q1 branch of MediaWiki.
MediaWiki is now using a "continuous integration" development model with
quarterly snapshot releases. The latest development code is always kept
@@ -20,612 +20,616 @@
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
-== Changes since 1.11.0rc1 ==
-
-A possible HTML/XSS injection vector in the API pretty-printing mode has
-been found and fixed.
-
-The vulnerability may be worked around in an unfixed version by simply
-disabling the API interface if it is not in use, by adding this to
-LocalSettings.php:
-
- $wgEnableAPI = false;
-
-(This is the default setting in 1.8.x.)
-
-Not vulnerable versions:
-* 1.11 >= 1.11.0
-* 1.10 >= 1.10.2
-* 1.9 >= 1.9.4
-* 1.8 >= 1.8.5
-
-Vulnerable versions:
-* 1.11 <= 1.11.0rc1
-* 1.10 <= 1.10.1
-* 1.9 <= 1.9.3
-* 1.8 <= 1.8.4 (if $wgEnableAPI has been switched on)
-
-MediaWiki 1.7 and below are not affected as they do not include
-the faulty function, however the BotQuery extension is similarly
-vulnerable unless updated to the latest SVN version.
-
-
-== Configuration changes since 1.10 ==
-
-* $wgThumbUpright - Adjust width of upright images when parameter 'upright' is
- used
-* $wgAddGroups, $wgRemoveGroups - Finer control over who can assign which
- usergroups
-* $wgEnotifImpersonal, $wgEnotifUseJobQ - Bulk mail options for large sites
-* $wgShowHostnames - Expose server host names through the API and HTML comments
-* $wgSaveDeletedFiles has been removed, the feature is now enabled unconditionally
-
-== New features since 1.10 ==
-
-* (bug 8868) Separate "blocked" message for autoblocks
-* Adding expiry of block to block messages
-* Links to redirect pages in categories are wrapped in
-
-* Introduced 'ImageOpenShowImageInlineBefore' hook; see docs/hooks.txt for
- more information
-* (bug 9628) Show warnings about slave lag on Special:Contributions,
- Special:Watchlist
-* (bug 8818) Expose "wpDestFile" as parameter $1 to "uploaddisabledtext"
-* Introducing new image keyword 'upright' and corresponding variable
- $wgThumbUpright. This allows better proportional view of upright images
- related to landscape images on a page without nailing the width of upright
- images to a fix value which makes views for anon unproportional and user
- preferences useless
-* (bug 6072) Introducing 'border' keyword to the [[Image:]] syntax
-* Introducing 'frameless' keyword to [[Image:]] syntax which respects the
- user preferences for image width like 'thumb' but without a frame.
-* (bug 7960) Link to "what links here" for each "what links here" entry
-* Added support for configuration of an arbitrary number of commons-style
- file repositories.
-* Added a Content-Disposition header to thumb.php output
-* Improved thumb.php error handling
-* Display file history on local image description pages of shared images
-* Added $wgArticleRobotPolicies
-* (bug 10076) Additional parameter $7 added to MediaWiki:Blockedtext
- containing, the ip, ip range, or username whose block is affecting the
-* (bug 7691) Show relevant lines from the deletion log when re-creating a
- previously deleted article
-* Added variables 'wgRestrictionEdit' and 'wgRestrictionMove' for JS to header
-* (bug 9898) Allow viewing all namespaces in Special:Newpages
-* (bug 10139) Introduce 'EditSectionLink' and 'EditSectionLinkForOther' hooks;
- see docs/hooks.txt for details
-* (bug 9769) Provide "watch this page" toggle on protection form
-* (bug 9886) Provide clear example "stub link" in Special:Preferences
-* (bug 10055) Populate email address and real name properties of User objects
- passed to the 'AbortNewAccount' hook
-* Show result of Special:Booksources in wiki content language always, it's
- normally better maintained than the generic list from the standard message
- files
-* (bug 7997) Allow users to be blocked from using Special:Emailuser
-* (bug 8989) Blacklist 'mhtml' and 'mht' files from upload
-* (bug 8760) Allow wiki links in "protectexpiry" message
-* (bug 5908) Add "DEFAULTSORTKEY" and "DEFAULTCATEGORYSORT" aliases for
- "DEFAULTSORT" magic word
-* (bug 10181) Support the XCache object caching mechanism
-* (bug 9058) Introduce '--aconf' option for all maintenance scripts, to provide
- a path to the AdminSettings.php file
-* (bug 8781) Remind users to check file permissions for LocalSettings.php
- post-installation
-* Use shared.css for all skins and oldshared.css in place of common.css for
- pre-Monobook skins. As always, modifications should go in-wiki to MediaWiki:
- Common.css and MediaWiki:Monobook.css.
-* (bug 8869) Introduce Special:Uncategorizedtemplates
-* (bug 8734) Different log message when article protection level is changed
-* (bug 8458, 10338) Limit custom signature length to $wgMaxSigChars Unicode
- characters
-* (bug 10096) Added an ability to query interwiki map table
-* On reupload, add a null revision to the image description page
-* Group log output by date
-* Kurdish interface latin/arabic writing system with transliteration
-* Support wiki text in all query page headers
-* Add 'Orphanedpages' as an alias to Special:Lonelypages
-* (bug 9328) Use "revision-info-current" message in place of "revision-info"
- when viewing the current revision of a page, if available
-* (bug 8890) Enable wiki text for "license" message
-* Throw a showstopper exception when a hook function fails to return a value.
- Forgetting to give a 'true' return value is a very common error which tends
- to cause hard-to-track-down interactions between extensions.
-* Use $wgJobClasses to determine the correct Job to instantiate for a particular
- queued task; allows extensions to introduce custom jobs
-* (bug 10326) AJAX-based page watching and unwatching has been cleaned up and
- enabled by default.
-* Added option to install to MyISAM
-* (bug 9250) Remove hardcoded minimum image name length of three characters
-* Fixed DISPLAYTITLE behaviour to reject titles which don't normalise to the
- same title as the current page, and enabled per default
-* Wrap site CSS and JavaScript in a tag, like user JS/CSS
-* (bug 10196) Add classes and dir="ltr" to the s on CSS and JS pages (new
- classes: mw-code, mw-css, mw-js)
-* (bug 6711) Add $wgAddGroups and $wgRemoveGroups to allow finer control over
- usergroup assignment.
-* Introduce 'UserEffectiveGroups' hook; see docs/hooks.txt for more information
-* (bug 10387) Detect and handle '.php5' extension environments at install time
-* Introduce 'ShowRawCssJs' hook; see docs/hooks.txt for more information
-* (bug 10404) Show rights log for the selected user in Special:Userrights
-* New javascript for upload page that will show a warning if a file with the
- "destination filename" already exists.
-* Add 'editsection-brackets' message to allow localization (or removal) of the
- brackets in the "[edit]" link for sections
-* (bug 10437) Move texvc styling to shared.css
-* Introduce "raw editing" mode for the watchlist, to allow bulk additions,
- removals, and convenient exporting of watchlist contents
-* Show "undo" links in page histories
-* Option to jump to specified time period in user contributions
-* Improved feedback on "rollback success" page
-* Show distinct 'namespaceprotected' message to users when namespace protection
- prevents page editing
-* (bug 9936) Per-edit suppression of preview-on-first edit with "preview=no"
-* Allow showing a one-off preview on first edit with "preview=yes"
-* (bug 9151) Remove timed redirects on "Return to X" pages for accessibility.
-* Link to user logs in toolbox when viewing a user page
-* (bug 10508) Allow HTML attributes on
-* (bug 1962) Allow HTML attributes on
-* (bug 10530) Introduce optional "sp-contributions-explain" message for
- additional explanation in Special:Contributions
-* (bug 10520) Preview licences during upload via AJAX (toggle with
- $wgAjaxLicensePreview)
-* New Parser::setTransparentTagHook for parser extension and template
- compatibility
-* Introduced 'ContributionsToolLinks' hook; see docs/hooks.txt for more
- information
-* Add a message if category is empty
-* Add CSS compatibility for Opera 9.5
-* Remove largely untested handheld stylesheet, which was causing more trouble
- than good. Proper handheld support will be added at a future date. For now,
- display should be acceptable either with CSS turned off or when using a so-
- phisticated handheld browser.
-* (bug 3173) Option to offer exported pages as a download, rather than displaying
- inline, as in most browsers
-* Pass the user as an argument to 'isValidPassword' hook callbacks; see
- docs/hooks.txt for more information
-* Introduce 'UserGetRights' hook; see docs/hooks.txt for more information
-* (bug 9595) Pass new Revision to the 'ArticleInsertComplete' and
- 'ArticleSaveComplete' hooks; see docs/hooks.txt for more information
-* (bug 9575) Accept upload description from GET parameters
-* Skip the difference engine cache when 'action=purge' is used while requesting
- a difference page, to allow refreshing the cache in case of errors
-* (bug 10701) Link to Special:Listusers in default Special:Statistics messages
-* Improved file history presentation
-* (bug 10739) Users can now enter comments when reverting files
-* Improved handling of permissions errors
-* (bug 10793) "Mark patrolled" links will now be shown for users with
- patrol permissions on all eligible diff pages
-* (bug 10655) Show standard tool links for blocked users in block log messages
-* Show standard tool links for blocked users in Special:Ipblocklist
-* Miscellaneous aesthetic improvements to Special:Ipblocklist
-* (bug 10826) Added link trail with Cyrillic characters for Mongolian language
-* (bug 10859) Introduce 'UserGetImplicitGroups' hook; see docs/hooks.txt for
- more information
-* (bug 10832) Include user information when viewing a deleted revision
-* (bug 10872) Fall back to sane defaults when generating protection selector
- labels for custom restriction levels
-* Show edit count in user preferences
-* Improved support for audio/video extensions
-* (bug 10937) Distinguish overwritten files in upload log
-* Introduce 'ArticleUpdateBeforeRedirect' hook; see docs/hooks.txt for more
- information
-* Confirmation is now required when deleting old versions of files
-* (bug 7535) Users can now enter comments when deleting old versions of files
-* (bug 11001) Submit Special:Newpages as a GET, rather than a POST request
-* The around links to watched pages in change lists now
- has a class - "mw-watched"
-* (bug 9002) Provide a "view/restore deleted edits" link on Special:Upload
- when a destination filename is provided that corresponds with previous
- deleted files
-* Make the "invalid special page" message clearer
-* Add accesskey 's' and tooltip to 'upload file' button at Special:Upload
-* Introduced 'SkinAfterBottomScripts' hook; see docs/hooks.txt for
- more information
-* (bug 11095) Honour "preview on first edit" preference when preloading
- text for a non-existent page
-* (bug 11022) Use a more accurate page title for Special:Whatlinkshere and
- Special:Recentchangeslinked
-* Add link to user contributions in normal watchlist edit mode
-* (bug 9426) Add 'newsectionheaderdefaultlevel' message to allow
- modification of the heading formatting for new sections when section=new
- argument is supplied
-* (bug 10836) Add 'newsectionsummary' message to allow modification of the
- text that prefixes a new section link in Recent Changes
-
-== Bugfixes since 1.10 ==
-
-* (bug 9712) Use Arabic comma in date/time formats for Arabic and Farsi
-* (bug 9670) Follow redirects when render edit section links to transcluded
- templates.
-* (bug 6204) Fix incorrect unindentation with $wgMaxTocLevel
-* (bug 3431) Suppress "next page" link in Special:Search at end of results
-* Don't show unblock form if the user doesn't have permission to use it
- (cosmetic change, no vulnerabilities existed)
-* Subtitle success message when unblocking a block ID instead of a pseudo link
- like [[User:#123|#123]]
-* Use the standard HTTP fetch functions when retrieving remote wiki pages
- through transwiki, so we can take advantage of cURL goodies if available
-* Disable user JavaScript on Special:Userlogin, Special:Resetpass and
- Special:Preferences, to avoid a compromised script sniffing passwords, etc.
-* (bug 9854, 3770) Clip overflow text in gallery boxes for visual cleanliness
- instead of letting it flow outside the box or trigger ugly scroll bars.
-* Tooltips for print version and permalink
-* Links to the MediaWiki namespace for system messages having their default
- values are no longer shown as nonexistent (e.g., in red)
-* Special:Ipblocklist differentiates between empty list and no search results.
-* (bug 5375) profiling does not respect read-only mode.
-* (bug 7070) monobook/user.gif has antialias artifacts
-* (bug 9123) Safer way when applying $wgLocalTZoffset
-* (bug 9896) Documentation for $wgSquidServers and X-FORWARDED-FOR
-* (bug 9417) Uploading new versions of images when using Postgres no longer
- throws warnings.
-* (bug 9908) Using tsearch2 with Postgres 8.1 no longer gives an error.
-* (bug 1438) Fix for diff table layout on very wide lines.
- Diff style rules have been broken out to common/diff.css,
- and the dupes removed from the default skin files.
- Skins can still override the default rules.
-* (bug 1229) Balance columns in diff display evenly
-* Right-align diff line numbers in RTL language display
-* (bug 9332) Fix instructions in tests/README
-* (bug 9813) Reject usernames containing '#' to avoid silent truncation
- of fragments during the normalisation process
-* (bug 7989) RSS feeds content now use black text when using white background.
-* (bug 9971) Typo in a french language message.
-* (bug 9973) Changed size was shown in advanced recentchanges collapsible items
- with $wgRCShowChangedSized = false.
-* Fix PHP strict standards warning in enhanced recent changes.
-* (bug 5850) Added hexadecimal html entities comments for $digitTransformTable
- entries.
-* (bug 7432) Change language name for Aromanian (roa-rup)
-* (bug 908) Unexistent special pages now generate a red link.
-* (bug 7899) Added \hline and \vline to the list of allowed TeX commands
-* (bug 7993) support mathematical symbol classes
-* (bug 10007) Allow Block IP to work with Postgrs again.
-* Add Google Wireless Transcoder to the Unicode editing blacklist
-* (bug 10083) Fix for Special:Version breakage on PHP 5.2 with some hooks
-* (bug 3624) TeX: \ker, \hom, \arg, \dim treated like \sin & \cos
-* (bug 10132, 10134) Restore back-compatibility Image::imageUrl() function
-* (bug 10113) Fix double-click for view source on protected pages
-* (bug 10117) Special:Wantedpages doesn't handle invalid titles in result
- set [now prints out a warning]
-* (bug 10118) Introduced Special:Mostlinkedtemplates, report which lists
- templates with a high number of inclusion links
-* (bug 10104) Fixed Database::getLag() for PostgreSQL and Oracle
-* (bug 9820) session.save_path check no longer halts installation, but
- warns of possible bad values
-* (bug 9978) Fixed session.save_path validation when using extended
- configuration format, e.g. "5;/tmp"
-* Don't generate a diff link in the patrol log if the page doesn't exist
-* (bug 10067) Translations for former skins removed from message files
-* (bug 9993) Force $wgShowExceptionDetails on during installation
-* (bug 9980) Validate administrator username and password during
- installation
-* (bug 9383) Don't set a default value for BLOB column in rc-deleted
- database patch
-* (bug 10149) Don't show full template list on section-0 edit
-* (bug 9909) Ensure access to binary fields in the math table use encodeBlob()
- and decodeBlob()
-* (bug 6743) Don't link broken image links to the upload form when uploads
- are disabled
-* (bug 9679) Improve documentation for $wgSiteNotice
-* (bug 10215) Show custom editing introduction when editing existing pages
-* (bug 10223) Fix edit link in noarticletext localizations for fr, oc
-* (bug 10247) Fix IP address regex to avoid false positive IPv6 matches
-* (bug 9948) Workaround for diff regression with old Mozilla versions
-* (bug 10265) Fix regression in category image gallery paging
-* (bug 8577) Fix some weird misapplications of time zones.
- {{CURRENT*}} functions now consistently use UTC as intended, while
- {{LOCAL*}} functions return local time per server config or $wgLocaltimezone.
- Signature dates for Japanese and other languages including weekday now show
- the correct day to match the rest of the time in local time.
-* Escape the output of magic variables that return page name or part of it
-* (bug 10309) Initialise parser state properly in extractSections(), fixes
- some cases where section edits broke because tags were improperly stripped
-* Avoid PHP notice errors when doing HTTP proxy purges for an empty list
-* As intended, *skip* the HTTP proxy purges when doing HTCP purges
-* (bug 9696) Fix handling of brace transformations in "pagemovedtext"
-* (bug 10325) Fix regression in form action on Special:Listusers
-* Fixed installation on MyISAM or old InnoDB with charset=utf8, was giving
- overlong key errors.
-* Fixed zero-padding issues with MySQL 5 binary schema
-* (bug 10344) Don't follow a redirect after changing its protection level
-* (bug 10333) Correct date format in Slovenian
-* (bug 10160) Show error message for unknown namespace on Special:Allpages and
- Special:Prefixindex; making forms prettier for RTL wikis.
-* (bug 10334) Replace normal spaces before percent (%) signs with non-breaking
- spaces
-* (bug 10372) namespaceDupes.php no longer ignores namespace aliases
-* (bug 10198) namespaceDupes.php no longer ignores interwiki prefixes
-* namespaceDupes.php should work better for initial-lowercase wikis
-* (bug 10377) "Permanent links" to revisions still work if the page is moved
- and the redirect deleted
-* (bug 7071) Properly handle an 'oldid' passed to view or edit that doesn't
- match the given title. Fixes inconsistencies with talk, history, edit links.
-* (bug 10397) Fix AJAX watch error fallback when we receive a bogus result
-* (bug 10396) Fix AJAX error when $wgScriptPath/index.php is not valid;
- using $wgScript now included in JS info
-* Use native XMLHttpRequest class in preference to ActiveX on IE 7; this
- avoids the "ActiveX "Do you want to allow ActiveX?" prompt when something
- security settings are cranked this way and AJAX-y gets used.
-* Delay AJAX watch initialization until click so IE 6 with ugly security
- settings doesn't prompt you until you use the link.
-* (bug 10401) Provide non-redirecting link to original title in Special:Movepage
-* Fix broken handling of log views for page titles consisting of one
- or more zeros, e.g. "0", "00" etc.
-* Fix read permission check for special pages with subpage parameters, e.g.
- Special:Confirmemail
-* Fix read permission check for unreadable page titles which are numerically
- equivalent to a whitelisted title
-* '?>' closing tag removed from all files to help avoid problems with extraneous
- whitespace (broken XML feeds, etc.)
-* Don't use garbled parser cache output when viewing custom CSS or JavaScript
- pages
-* (bug 10406) Fix Special:Listusers filter form for non-ASCII localizations
-* Fix empty message checks for message names containing &
- This corrects some odd behavior with sidebar items and custom namespaces
- containing ampersands.
-* (bug 10375) Change thousands separator character to for Latin (la)
-* (bug 10477) Fix AJAX watch for Farsi on Firefox: JavaScript encoding tweak
-* (bug 10496) Fix broken DISTINCT option logic in database backend
-* Fix CSS media declaration for "screen, projection"; was causing some
- validation issues
-* (bug 10495) $wgMemcachedDebug set twice in includes/DefaultSettings.php
-* (bug 10316) Prevent inconsistent cached skin settings in gen=js by setting
- the intended skin directly in the URL.
-* (bug 9903) Don't mark redirects in categories as stubs
-* (bug 6965) Cannot include "Template:R" with {{R}} (magic word conflict)
-* Padding parser functions now work with strings like '0' that evaluate to false
-* (bug 10332) Title->userCan( 'edit' ) may return false positive
-* Fix bug with in front of links for wikis where linkPrefixExtension is true
-* (bug 10552) Suppress rollback link in history for single-revision pages
-* (bug 10538) Gracefully handle invalid input on move success page
-* Fix for Esperanto double-x-encoding in move success page
-* (bug 10526) Fix toolbar/insertTags behavior for IE 6/7 and Opera (8+)
- Now matches the selection behavior on Mozilla / Safari.
- Patch by Alex Smotrov.
-* Don't show non-functional toolbar buttons on Opera 7 anymore
-* (bug 9151) Fix relative subpage links with section fragments
-* (bug 10560) Adding a space between category letter heading and "continues"
-* (bug 4650) Keep impossibly large/small counts off Special:Statistics
-* (bug 10608) PHP notice when installing with PostgreSQL
-* (bug 10615) Fix for transwiki import when CURL not available
-* (bug 8054) Return search page for empty search requests with ugly URLs
-* (bug 10572) Force refresh after clearing visitation timestamps on watchlist
-* (bug 10631) Warn when illegal characters are removed from filename at upload
-* Fix several JavaScript bugs under MSIE 5/Macintosh
-* (bug 10591) Use Arabic numerals (0,1,2...) for the Malayam language
-* (bug 10642) Fix shift-click checkbox behavior for Opera 9.0+ and 6.0
-* Work around Safari bug with pages ending in ".gz" or ".tgz"
-* Removed obsolete maintenance/changeuser.sql script; use RenameUser extension
-* (bug 2735) "Preview" shown in title bar for action=submit on special pages
-* Removed "restore" links from the deletion log embedded in Special:Undelete
-* Improved error reporting and robustness for file delete/undelete.
-* Improved speed of file delete by storing the SHA-1 hash in image/oldimage
-* Fixed leading zero in base 36 SHA-1 hash
-* Protection form no longer produces JavaScript errors
-* (bug 10741) File histories show "delete" links for non-sysops
-* (bug 10744) Treat "noarticletext" and "noarticletextanon" as wiki text when
- used on a non-existent page with "action=info"
-* Fix escaping of raw message text when used on a non-existent page with
- "action=info"
-* (bug 10683) Fix inconsistent handling of URL-encoded titles in links
- used in redirects (i.e. they now work)
-* (bug 8878) Changes to $dateFormats in German localization (removing unused,
- nonexistent formats, putting time after date)
-* (bug 10769) Database::update() should return boolean result
-* Fix preference checkbox display for right-to-left languages which caused
- them to be hidden in IE in some cases
-* Fix upload form display in right-to-left languages
-* Fixed regression in blocking of username '0'
-* (bug 9437) Don't overwrite edit form submission handler when setting up
- edit box scroll position preserve/restore behaviour
-* (bug 10805) Fix "undo" link when viewing the diff of the most recent
- change to a page using "diff=0"
-* (bug 10765) img_auth.php will now refuse logged-out requests where
- $wgWhitelistRead is undefined, instead of (incorrectly) honouring them
-* Fixed img_auth.php file name extraction for whitelist checking
-* Tweak spacing of email preference display
-* Table sorting JavaScript prefers textContent over innerText to allow hidden
- sort keys to work on Safari
-* (bug 4530) Fix local name of Kurdish language
-* (bug 10830) Fix local name of Haitian Creole language
-* Fix invalid XHTML in Special:Protectedpages
-* Fix comments in contributions and log pages for right-to-left languages
-* Make installer include_path-independent, so it should work on hosts which
- disable user setting of PHP include_path setting
-* glob() is horribly unreliable and doesn't work on some systems, including
- free.fr shared hosting. No longer using it in Language::getLanguageNames()
-* (bug 10763) Fix multi-insert logic for PostgreSQL
-* Fix invalid XHTML when viewing a deleted revision
-* Fix syntax error in translations of magic words in Romanian language
-* (bug 8737) Fix warnings caused by incorrect use of `/dev/null` when piping
- process error output under Windows
-* (bug 7890) Don't list redirects to special pages in Special:BrokenRedirects
-* (bug 10783) Resizing PNG-24 images with GD no longer causes all alpha
- channel transparency to be lost and transparent pixels to be turned black
-* (bug 9339) General error pages were transforming messages and their parameters
- in the wrong order
-* (bug 9026) Incorrect heading numbering when viewing Special:Statistics with
- "auto-numbered headings" enabled
-* Fixed invalid XHTML in Special:Upload
-* (bug 11013) Make sure dl() is available before attempting to use it to check
- available databases in installer
-* Resizing transparent GIF images with GD now retains transparency by skipping
- resampling
-* (bug 11065) Fix regression in handling of wiki-formatted EXIF metadata
-* Double encoding broke Special:Newpages for some languages
-* Adding a newline before the statistics footer, to prevent parsing problems
-* Preventing the TOC from appearing in Special:Statistics
-* (bug 11082) Fix check for fully-specced table names in Database::tableName
-* (bug 11067) Fix regression in upload conflict thumbnail display
-* (bug 10985) Resolved cached entries on Special:DoubleRedirects were being
- supressed, breaking paging - now strikes out "fixed" results
-* (bug 8393) and need to be preserved (without attributes) for
- entries in the table of contents
-* (bug 11114) Fix regression in read-only mode error display during editing
-* Force non-MySQL databases to use an ORDER BY in SpecialAllpages to ensure
- that the first page_title is truly the first page title.
-* (bug 10836) Change the summary on creating of new section
-* Inclusion of Special:Wantedpages now works again
-
-== API changes since 1.10 ==
-
-Full API documentation is available at http://www.mediawiki.org/wiki/API
-
-* New properties: links, templates, images, langlinks, categories, external
- links
-* Breaking Change: imagelinks renamed into imageusage (il->iu)
-* Bug fix: incorrect generator behavior in some cases
-* JSON format allows an optional callback function to wrap the result.
-* Login module disabled until a more secure solution can be implemented
-* (bug 9938) Querying by revision identifier returns the most recent revision
- for the corresponding page, rather than the requested revision
-* (bug 8772) Filter page revision queries by user
-* (bug 9927) User contributions queries do not accept IP addresses
-* Watchlist feed now reports a proper feed item when the user is not logged in
-* Watchlist feed date bug fixed - automatically shows one last day
-* Watchlist feed now allows to specify number of hours to monitor
-* list=allpages now returns a list instead of a map in JSON format
-* Breaking Change: in json, revisions are now returned as a list, not as a map.
-* Add: prop=info can show page is new flag, current page length, and visit
- counter.
-* Change: Query watchlist now shows flags only when explicitly requested with
- wlparam=flags
-* rc_this_oldid (textid) is no longer accessible from query watchlist
-* action=usercontribs: additional filtering by ucshow=; selection of needed
- fields with ucprop=; the textid (rev_text_id) is no longer being exposed
-* (bug 9970) Breaking Change: backlinks, embeddedin and imageusage now return
- lists in JSON instead of a map, and do not return anything when titles do
- not exist
-* (bug 9121) Introduced indexpageids query parameter to list the page_id
- values of all returned page items
-* (bug 10147) Now interwiki titles are not processed but added to a separate
- "interwiki" section of the output.
-* Added categorymembers list to query for pages in a category.
-* (bug 10260) Show page protection status
-* (bug 10392) Include MediaWiki version details in version output
-* (bug 10411) Site language in meta=siteinfo
-* (bug 10391) action=help doesn't return help if format is fancy markup
-* backlinks, embeddedin and imageusage lists should use (bl|ei|iu)title parameter
- instead of titles. Titles for these lists is obsolete and might stop working soon.
-* Added prop=imageinfo - gets image properties and upload history
-* (bug 10211) Added db server replication lag information in meta=siteinfo
-* Added external url search within wiki pages (list=exturlusage)
-* Added link enumeration (list=alllinks)
-* Added registered users enumeration (list=allusers)
-* Added full text search in titles and content (list=search)
-* (bug 10684) Expanded list=allusers functionality
-* Possible breaking change: prop=revisions no longer includes pageid for rvprop=ids
-* Added rvprop=size to prop=revisions (The size will not be shown if it is NULL in the database)
-* list=allpages now allows to filter by article min/max size and protection status
-* Added site statistics (siprop=statistics for meta=siteinfo)
-* (bug 10902) Unable to fetch user contributions from IP addresses
-* `list=usercontribs` no longer requires that the user exist
-* (bug 10971) `aufrom` parameter doesn't work with spaces
-* Fix username handling issue with `auprefix` parameter
-* Treat underscores as spaces for `aufrom` and `auprefix` parameters
-* Added edit/delete/... token retrieval to prop=info
-* Added meta=userinfo - logged-in user information, group membership, rights
-* (bug 11072) Fix regression in API image history query
-* (bug 11115) Adding SHA1 hash to imageinfo query
-* (bug 10898) API does not return an edit token for non-existent pages
-* (bug 10890) Timestamp support for categorymembers query
-* (bug 10980) Add exclude redirects on backlinks
-* IPv6 titles in User namespace are normalized (run cleanupTitles.php to fix any old stray pages)
-
-== Maintenance script changes since 1.10 ==
-
-* Add support for wgMaxTocLevel option in parserTests
-* (bug 6823) Disable article view counter in maintenance/dumpHTML.php
-* Fix maintenance/importImages.php so it doesn't barf PHP errors when no
- suitable files are found, and make the list of extensions an option (defaults
- to $wgFileExtensions)
-* Add option to maintenance/createAndPromote.php to give the user bureaucrat
- permissions (--bureaucrat)
-* Allow overwriting existing files with a conflicting name using
- maintenance/importImages.php
-* (bug 10266) Use native newlines when rebuilding a messages file.
-
-== Languages updated since 1.10 ==
-
-* Afrikaans (af)
-* Arabic (ar)
-* Bikol (bcl)
-* Bulgarian (bg)
-* Catalan (ca)
-* Danish (da)
-* German (de)
-* Greek (el)
-* Esperanto (eo)
-* Spanish (es)
-* Estonian (et)
-* Extremaduran (ext)
-* Farsi (fa)
-* Finnish (fi)
-* Vöro (fiu-vro)
-* French (fr)
-* Français Cadien (frc) (new)
-* Franco-Provençal/Arpetan (frp)
-* Galician (gl)
-* Hakka (hak)
-* Hebrew (he)
-* Upper Sorbian (hsb)
-* Haitian (ht)
-* Indonesian (id)
-* Icelandic (is)
-* Italian (it)
-* Japanese (ja)
-* Georgian (ka)
-* Kabyle (kab)
-* Kazakh (kk)
-* Korean (ko)
-* Kinaray-a (krj) (new)
-* Kurdish (ku)
-* Latin (la)
-* Lao (lo)
-* Lithuanian (lt)
-* Latviešu (lv)
-* Malayalam (ml)
-* Bahasa Melayu (ms)
-* Burmese (my)
-* Low German (nds)
-* Dutch (nl)
-* Norwegian (no)
-* Occitan (oc)
-* Punjabi (Gurmukhi) (pa)
-* Polish (pl)
-* Piedmontese (pms)
-* Portuguese (pt)
-* Romani (rmy)
-* Romanian (ro)
-* Aromanian (roa-rup)
-* Russian (ru)
-* Sakha (sah)
-* Sango (se) (new)
-* Slovak (sk)
-* Slovenian (sl)
-* Shona (sn)
-* Somali (so)
-* Albanian (sq)
-* Sundanese (su)
-* Swedish (sv)
-* Tamil (ta)
-* Thai (th)
-* Tigrinya (ti)
-* Setswana (tn)
-* Tok Pisin (tpi)
-* Uyghur (ug)
-* Volapük (vo)
-* Winaray (war) (new)
-* Yiddish (yi)
-* Old Chinese / Late Middle Chinese (zh-classical)
-* Chinese (PRC) (zh-cn)
-* Chinese (Taiwan) (zh-tw)
-* Cantonese (zh-yue)
+NOTE: Installation of MediaWiki on SQLite has been temporarily disabled in this
+release due to the discovery of serious problems with the schema. We expect to
+fix this problem for the release of 1.15.0.
+
+== Changes since 1.14.0rc1 ==
+
+* Fixed the performance of the backlinks API module
+* (bug 17420) Send the correct content type from action=raw when the HTML file
+ cache is enabled.
+* (bug 17437) Fixed incorrect link to web-based installer
+* (bug 17527) Fixed missing MySQL-specific options in installer
+
+=== Configuration changes in 1.14 ===
+
+* $wgExemptFromUserRobotsControl is an array of namespaces to be exempt from
+ the effect of the new __INDEX__/__NOINDEX__ magic words. (Default: null, ex-
+ empt all content namespaces.)
+* $wgForwardSearchUrl has been removed entirely. Documented setting since 1.4
+ has been $wgSearchForwardUrl.
+* (bug 15080) $wgOverrideSiteFeed has been added. Setting either
+ $wgSiteFeed['rss'] or 'atom' to a URL will override the default Recent
+ Changes feed that appears on all pages.
+* $wgSQLiteDataDirMode has been introduced as the default directory mode for
+ SQLite data directories on creation. Note that this setting is separate from
+ $wgDirectoryMode, which applies to all normal dirs created by MediaWiki.
+* $wgGroupsAddToSelf and $wgGroupsRemoveFromSelf now work more like
+ $wgAddGroups and $wgRemoveGroups, where the user must belong to a specified
+ group in order to add or remove those groups from themselves.
+ Backwards compatibility is maintained.
+* $wgRestrictDisplayTitle controls if the use of the {{DISPLAYTITLE}} magic
+ word is restricted to titles equivalent to the actual page title. This
+ is true per default, but can be set to false to allow any title.
+* $wgSpamRegex may now be an array of multiple regular expressions.
+* $wgAjaxSearch has been removed; use $wgEnableMWSuggest instead.
+* Editing the MediaWiki namespace is now unconditionally restricted to people
+ with the editinterface right, configuring this in $wgNamespaceProtection
+ is not required.
+* $wgAllowExternalImagesFrom may now be an array of multiple strings.
+* Introduced $wgEnableImageWhitelist to toggle the on-wiki external image
+ whitelist on or off.
+* Added $wgRenderHashAppend to append some string to the parser cache and the
+ sitenotice cache keys.
+* $wgRCChangedSizeThreshold is now a positive integer by default,
+* (bug 16006) $wgEnableWriteAPI is now true by default. Authorized can perform
+ write actions using the API.
+* Added $wgRC2UDPInterwikiPrefix which adds an interwiki prefix
+ ($wgLocalInterwiki) onto the page names in the UDP feed.
+* Added $wgAllowUserSkin to let the wiki's owner disable user selectable skins
+ on the wiki. If it's set to true, then the skin used will *always* be
+ $wgDefaultSkin.
+* Added $wgEnotifUseRealName, which allows UserMailer to send out e-mails based
+ on the user's real name if one is set. Defaults to false (use the username)
+* Removed the 'apiThumbCacheDir' option from $wgForeignFileRepos (only used in
+ ForeignAPIRepo)
+* (bug 44) Image namespace and accompanying talk namespace renamed to File.
+ For backward compatibility purposes, Image still works. External tools may
+ need to be updated.
+* The constants NS_FILE and NS_FILE_TALK can now be used instead of NS_IMAGE and
+ NS_IMAGE_TALK. The old constants are retained as aliases for compatibility,
+ and should still be used in code meant to be compatible with v1.13 or older.
+* MediaWiki can be forced to use private IPs forwarded by a proxy server by
+ using $wgUsePrivateIPs.
+* The 'BeforeWatchlist' hook has been removed due to internal changes in
+ Special:Watchlist. 'SpecialWatchlistQuery' should now be used by extensions
+ to customize the watchlist database query.
+
+
+=== Migrated extensions ===
+The following extensions are migrated into MediaWiki 1.14:
+
+* Special:DeletedContributions to show deleted user contributions (was
+ extension DeletedContributions)
+* Special:Log/newusers recording new users (was extension Newuserlog)
+* Special:LinkSearch to search for external links (was extension LinkSearch)
+* RenderHash
+* NoMoveUserPages
+* UniversalEditButton
+
+=== New features in 1.14 ===
+
+* New URL syntaxes for Special:ListUsers - 'Special:ListUsers/USER' and
+ 'Special:ListUsers/GROUP/USER', in addition to the older syntax
+ 'Special:ListUsers/GROUP' where GROUP is a valid group name.
+* Configurable per-namespace and per-page notices for the edit form,
+ respectively MediaWiki:Editnotice-# where # is the namespace number, and
+ MediaWiki:Editnotice-#-PAGENAME where # is the page's namespace number and
+ PAGENAME is the page name minus the namespace prefix.
+* (bug 8068) New __INDEX__ and __NOINDEX__ magic words allow user control of
+ search engine indexing on a per-article basis.
+* Handheld stylesheet options
+* Added 'DoEditSectionLink' hook as a cleaner unified version of the old
+ 'EditSectionLink' and 'EditSectionLinkForOther' hooks. Note that the
+ 'EditSectionLinkForOther' hook has been removed, but 'EditSectionLink' is
+ run in all cases instead, so extensions using the old hooks should still work
+ if they ran roughly the same code for both hooks (as is almost certain).
+* Signature (~~~~) "cleaning", i.e. template removal, can be disabled with
+ $wgCleanSignatures=false
+* Extensions can use the SkinBuildSidebar hook to modify the content of the
+ sidebar and add custom portlets to it
+* Added 'MakeGlobalVariablesScript' hook for extensions to be able to add vari-
+ ables into into the output of Skin::makeVariablesScript
+* (bug 13846) Added $wgAddGroups and $wgRemoveGroups display on
+ Special:ListGroupRights
+* (bug 14377) Add a date selector to history pages
+* (bug 15007) New 'pagetitle-view-mainpage' message allows the HTML of
+ the main page to be customized
+* Added $wgDisableTitleConversion to disabling the conversion for all pages on
+ the wiki
+* Added 'noconvertlink' toggle that can be set per user preferences, also
+ added 'convertlink=no|yes' on GET requests whether have the link titles
+ being converted or not
+* (bug 14921) Special:Contributions/: add user name to
+ Patch by Emufarmers
+* Unescape more "safe" characters when producing URLs, for added prettiness
+* Introduced a new hook 'SkinAfterContent' that allows extensions to add text
+ after the page content and article metadata. Updated all skins and skin
+ templates to work with that hook.
+* (bug 14929) removeUnusedAccounts.php now supports 'ignore-touched' and
+ 'ignore-groups'. Patch by Louperivois
+* (bug 15127) Work around minor display glitch in Opera.
+* By default, reject file uploads that look like ZIP files, to avoid the
+ so-called GIFAR vulnerability.
+* (bug 15141) Give ability to only list protected pages with the cascading
+ option enabled on Special:ProtectedPages
+* (bug 15157) Special:Watchlist has the same options as Special:Watchlist:
+ Show/Hide logged in users, Show/Hide anonymous, Invert namespace selection
+* Added hook 'UserrightsChangeableGroups' to allow modification of what
+ groups may be added or removed via the Special:UserRights interface.
+* HTML entities like now work (are not escaped) in edit summaries.
+* (bug 13815) In the comment for page moves, use the colon-separator message
+ instead of a hardcoded colon.
+* Allow to accept image names without an Image: prefix
+* Add tooltips to rollback and undo links
+* BMP images are now displayed as PNG
+* (bug 13471) Added NUMBERINGROUP magic word
+* (bug 11884) Now support Flash EXIF attribute
+* Show thumbnails in the file history list, patch by User:Agbad
+* Added support of piped wikilinks using double-width brackets
+* Added an on-wiki external image whitelist. Items in this whitelist are
+ treated as regular expression fragments to match for when possibly
+ displaying an external image inline.
+* (bugs 15405, 15436) Sort more currency types correctly in sortable tables
+* (bug 15422) Sort more different types of numbers in sortable tables
+* (bug 2889) MediaWiki:Print.css applies to the printable version
+* Category counts (e.g. from {{PAGESINCATEGORY:}}) should be more accurate for
+ small categories
+* After logging in, automatically redirect to wherever you logged in from
+* (bug 5619) Break messages used in Special:Statistics down further
+* (bug 11029) Add link to Special:Listusers?group=sysop etc at
+ Special:Statistics
+* (bug 15514) Setting $wgRightsText without $wgRightsUrl now produces a
+ plaintext copyright notice. Patch by Juliano F. Ravasi.
+* (bug 15551) Deletion log excerpt is now shown whenever a user vists a
+ deleted page, even if they are unable to edit it.
+* Added Wantedfiles special pages, allowing users to find image links with no
+ image.
+* (bug 12650) It is now possible to set different expiration times for
+ different restriction types on the protection form.
+* (bug 8440) Allow preventing blocked users from editing their talk pages
+* Improved upload file type detection for OpenDocument formats
+* Added the ability to set the target attribute on external links with
+ $wgExternalLinkTarget
+* api.php now sends "Retry-After" and "X-Database-Lag" HTTP headers if the
+ maxlag check fails, just like index.php does
+* Added "link" parameter to image links, to allow images to link to an
+ arbitrary title or URL. This should replace inaccessible and incomplete
+ solutions such as CSS-based overlays and ImageMap.
+* (bug 368) Don't use caption for alt attribute; allow manual specification
+ using new "alt=" parameter for images
+* (bug 44) The {{ns:}} core parser function now also accepts localized
+ namespace names and aliases; also, its output now uses spaces instead of
+ underscores to match the behavior of the {{NAMESPACE}} magic word
+* Added the ability to display user edit counts in Special:ListUsers. Off by
+ default, enabled with $wgEdititis = true (named after the medical condition
+ marked by unhealthy obsession with edit counts).
+* Added a file cache to the parser to improve page rendering time on pages with
+ several uses of the same image.
+* (bug 1250) Users can still use "show preview" and "show changes" even if the
+ wiki is set to read-only mode.
+* Added a call to the 'UnwatchArticleComplete' hook to the watchlist editor.
+ This should make it so that ALL user-accessible methods of removing a page
+ from a watchlist lead to this hook being called (it was previously only
+ called from within Article.php
+* Maximum execution time for shell processes on linux is now configured with
+ $wgMaxShellTime (180 seconds by default)
+* (bug 1306) 'Email user' link no longer shown on user page when emailing
+ is not available due to lack of confirmed address or disabled preference
+* Special:Wanted templates special page added to display missing templates
+ linked from articles
+* Make search matches bold only, not red as well
+* (bug 10080) Blocks can be modified without unblocking first
+* (bug 15820) Special:BlockIP shows a notice if the user being blocked is
+ already directly blocked
+* (bug 13710) Allow to force "watch this" checkbox via URL using parameter
+ "watchthis"
+* (bug 15125) Add Public Domain to default options when installing. Patch by
+ Nathan Larson.
+* Set a special temporary directory for ImageMagick with $wgImageMagickTempDir
+* (bug 16113) Show/hide for redirects in Special:NewPages
+* (bug 15903) Upload link was added to Nostalgia skin
+* (bug 15761) Add user toggle to omit diff after rollback
+* Added the BitmapHandler_ClientOnly media handler, which allows server-side
+ image scaling to be completely disabled for specific media types, via the
+ $wgMediaHandlers configuration variable.
+* New 'AbortDiffCache' hook can be used to cancel the caching of a diff
+* (bug 15835) Added Content-Style-Type meta tag
+* (bug 11027) Add parameter to MW:Randompage-nopages so that user can see the
+ namespace.
+* Add id="mw-user-domain-section" to tag in Userlogin.php template so that
+ admins with a single domain can hide the domain section using CSS
+* Dropped old Paser_OldPP class. Only new parser with preprocessor is used.
+* Moved password reset form from Special:Preferences to Special:ResetPass
+* Added Special:ChangePassword as a special page alias for Special:ResetPass
+* Added complimentary function for addHandler() called removeHandler() for removing events
+* Improved security of file uploads for IE clients, using a reverse-engineered
+ algorithm very similar to IE's content detection algorithm.
+* Cascading protection no longer requires that both edit and move are restricted
+ to sysop, just edit=sysop is enough
+* (bug 2391) A warning is now shown for invalid ISBN numbers on Special:Booksources.
+* Installer has been updated to reflect the release of the GFDL 1.3. The URL for 1.2
+ has been updated, and the 1.3 URL has been given. 1.2 is still Wikipedia-compatible.
+ RightsCode was changed from 'gfdl' to 'gfdl1_2', so we can now support 1.2 as well
+ as 1.3 (gfdl1_3).
+* (bug 16293) PD URL was changed to the CreativeCommons site on PD (which auto-detects
+ your language) instead of Wikipedia.
+* (bug 16635) The "view and edit watchlist" page (Special:Watchlist/edit) now
+ includes a table of contents
+* File objects returned by wfFindFile() are now cached by default
+* (bug 7492) Rights can now be assigned to specific IP addresses and ranges by
+ using $wgAutopromote (new defines: APCOND_ISIP and APCOND_IPINRANGE)
+* Add a 'change block' link to Special:IPBlockList and Special:Log
+* (bug 16459) Use native getElementsByClassName where possible, for better
+ performance in modern browsers
+* Enable \cancel and \cancelto in texvc (recompile required)
+* Added 'UserCryptPassword' and 'UserComparePasswords' hooks to allow extensions to implement
+ their own password hashing methods.
+* (bug 16760) Add CSS-class to action links of Special:Log
+* (bug 505) Time zones can now be specified by location in user preferences,
+ avoiding the need to manually update for DST. Patch by Brad Jorsch.
+* (bug 2585) HTTP 404 return code is now given for a page view if the page
+ does not exist, allowing spiders and link checkers to detect broken links.
+* Special:Log: Add 'change protection' link for unprotected pages too
+* Special:Log: Add log type specific CSS classes 'mw-logline-$logtype' to
+ 'li' elements
+* (bug 16754) Making arbitrary rows of sortable tables sticky:
+ |- class="unsortable"
+* Show subversion too even if a "normal" version number is available
+* (bug 16121) Add a note that a page move was without creating a redirect in the
+ move log
+* Image moving is now enabled for sysops by default
+* Make "Did you mean" search feature more noticeable
+* (bug 16720) Transcluded Special:NewPages processes "/username="
+
+=== Bug fixes in 1.14 ===
+
+* (bug 14907) DatabasePostgres::fieldType now defined.
+* (bug 14659) Passing the default limit param to Special:Recentchanges no more
+ falls back to the user option
+* (bug 14954) Fix regression in Modern and Simple skins
+* Recursion loop check added to Categoryfinder class
+* Fixed few performance troubles of large job queue processing
+* Not setting various parameters in Foreign Repos now fails more gracefully
+* (bug 2333) Redirects are properly rendered when previewing an edit.
+* (bug 14972) Use localized alias of Special:Search on all search forms
+* (bug 11035) Special:Search should have descriptive
+* Special pages are now not subject to special handling for "self-links"
+* (bug 15053) Syntactically incorrect redirects with another link in them
+ no longer redirect to the second link
+* (bug 15049) Fix for CheckUser extension's log search: usernames containing
+ a "-" were incorrectly turned into bogus IP range searches.
+ Patch by Max Semenik.
+* (bug 15055) Talk page notifications no longer attempt to send mail when
+ user's e-mail address is invalid or unconfirmed
+* (bug 12370) Add throttle on password attempts. Defaults to max 5 attempts in
+ 5 minutes.
+* (bug 15016) 'Templates used on this page' list in view source should be
+ wrapped in a div with class "templatesUsed"
+* (bug 14868) Setting $wgFeedDiffCutoff to 0 now disables generation of the
+ diff entirely, not just the display of it.
+* (bug 6387) Introduced new setting $wgCategoryPrefixedDefaultSortkey which
+ allows having the unprefixed page title as the default category sortkey
+* (bug 15079) Add class="ns-talk" / "ns-subject" to . Also added
+ ns-special to special pages.
+* (bug 15052) Skins should add their name as a class in
+* (bug 14165, bug 14294) Wikimedia specific configuration in convertGrammar()
+ for several languages was removed. The settings have been put in extension
+ WikimediaMessages. Patch for Czech by Danny B.
+* (bug 15101) Displaying only bots edits in Special:Recentchanges now works
+ again
+* (bug 13770) Fixed incorrect detection of PHP's DOM module
+* (bug 14790) Export of category pages when using Category: prefix now actually
+ gives results
+* Avoid recursive crazy expansions in section edit comments for pages which
+ contain '/*' in the title
+* Fix excessive memory usage when parsing pages with lots of links
+* $wgSpamRegex now matches the edit summary and page move descriptions in
+ addition to body text.
+* Navigation links to images available from a shared repository (like Commons)
+ from their local talk pages no longer appear as redlinks
+* Action=purge on ForeignApiFiles now works (purges their thumbnails and
+ description pages).
+* (bug 15303) Title conversion for templates wasn't working in some cases.
+* (bug 15264) Underscores in Special:Search/Foo_bar parameters were taken
+ literally; now converting them to spaces per expectation.
+* (bug 15342) "Invert" checkbox now works correctly when selecting main
+ namespace in Special:Watchlist
+* (bug 15172) 'Go' button of Special:Recentchanges now on the same line as the
+ last input element (like Special:Watchlist too)
+* (bug 15351) Fix fatal error for invalid section fragments in autocomments
+* Fixed intermittent deadlock errors involving objectcache table queries.
+ Use a separate database connection for the objectcache table to avoid
+ long-lasting locks on that table.
+* Respect file restrictions in the file history list
+* (bug 15399) Odd/even classes on sortable tables' rows could be slow for large
+ tables, and have been disabled by default.
+* (bug 15482) Special:Recentchangeslinked has no longer two submit buttons
+* (bug 15292) New message notification for unregistred users now works again
+* (bug 14398) mwsuggest.js: Let width of container be configurable
+* (bug 15543) Only include user touched timestamp to generated CSS
+* (bug 15497) Removed encoding attribute from tag
+* (bug 12284) Special:Preferences now sets a returnto parameter on the link to
+ Special:UserLogin. Patch by Marooned.
+* Fixed the HTTP accept language string detection length in
+ LanguageConverter.php, instead of the fixed length language codes.
+* Special:RecentChangesLinked no longer shows outgoing links for nonexistent
+ pages even if there are broken link records with source article id 0 in the
+ database
+* (bug 15598) Special:Newpages default limit uses user preference for
+ recentchanges limit instead of hardcoded 50.
+* (bug 15617) $wgFeedClassesOutputPage::getHeadLinks() respects $wgFeedClasses,
+ instead of hardcoding rss and atom. Patch by Juliano F. Ravasi.
+* (bug 14638) Special:Blockip now provides a link to the block log if the user
+ has been blocked more than 10 times. Patch by Matt Johnston.
+* (bug 12678) Skins don't show Upload link if the user isn't allowed to upload.
+* Fixed incorrect usage of DB_LAST in Special:Export. Deprecated DB_LAST.
+* (bug 15642) Blocked sysops can no longer block other users
+* Http::request() now respects $wgHTTPtimeout when not using cURL
+* (bug 15158) Userinvalidcssjstitle not shown on preview
+* (bug 15196) Free external links should be numbered in a localised manner
+* (bug 15388) Title of Special:PrefixIndex
+* Links with no title but a curid parameter now use the curid to pick a page
+* (bug 10323) Special:Undelete should have "inverse selection" button
+* (bug 15831) Modern skin RTL support is bugous
+* (bug 15869) Nostalgia skin does not show page title in printable mode
+* (bug 15795) Special:Userrights is now listed on Special:SpecialPages when the
+ user can only change his rights
+* (bug 15846) Categories "leak" from older revisions in certain circumstances
+* (bug 15928) Special pages dropdown should be inline in non-MonoBook skins
+* (bug 14178) Some uses of UserLoadFromSession hook cause segfault
+* (bug 15925) Postitive bytes added on recentchanges and watchlists are now
+ bolded if above the threshold, previously it only worked for negatives
+* Specify apple-touch-icon before favicon in HTML head section to make the
+ Konqueror browser correctly use the latter
+* (bug 15717) Set $separatorTransformTable for language 'eu'
+* (bug 15605) Enabled $datePreferences for language 'hr'. Added standard date
+ preferences.
+* (bug 13701) {{NUMBEROFVIEWS}} magic word to show number of total views.
+* (bug 5101) Image from Commons doesn't show up when searched in Wikipedia
+ search box
+* (bug 14609) User's namespaces to be searched default not updated after adding
+ new namespace
+* Purge form uses valid XHTML
+* (bug 12764) Special:LonelyPages shows transcluded pages
+* (bug 16073) Enhanced RecentChanges uses onclick handler with better fallback
+ if JavaScript is disabled
+* (bug 4253) Recentchanges IRC messages no longer include title in diff URLs
+* Allow '0' to be an accesskey.
+* (bug 8063) Use language-dependent sorting in client-side sortable tables
+* (bug 16160) Suggestions box should be resized from left for RTL wikis
+* (bug 11533) Fixed insane slowdown when in read-only mode for long periods
+ of time with CACHE_NONE (default objectcache table configuration).
+* Trying to set two different default category sort keys for one page now
+ produces a warning
+* (bug 16143) Fix redirect loop on special pages starting with lower case
+ letters
+* (bug 15737) Fix notices while expanding using PPCustomFrame
+* (bug 15544) Non-index entry points cause the "Wiki not set up" message to
+ have corrupt URLs
+* (bug 5101) Image from Commons doesn't show up when searched in Wikipedia
+ search box
+* (bug 4362) [[MediaWiki:History copyright]] no more used with most recent
+ revision when passing oldid parameter in the url
+* (bug 16265) When caching thumbs with the ForeignApiRepo, we now use the same
+ filename as the remote site.
+* (bug 8345) Don't autosummarize where a redirect was left unchanged
+* Made thumb caching in ForeignApiFile objects integrated with normal thumb
+ path naming (/thumbs/hash/file), retired 'apiThumbCacheDir' as a result.
+* (bug 5530) Consistency between character encoding in {{PAGENAMEE}},
+ {{SUBPAGENAMEE}} and {{FULLPAGENAMEE}}
+* Safer handling of non-MediaWiki exceptions -- now obeys our settings for
+ formatting and path exposure.
+* Less verbose errors from profileinfo.php when not configured
+* Blacklist redirects via Special:Filepath, hard to use.
+* Improved input validation on Special:Import form
+* Add a .htaccess to deleted images directory for additional protection
+ against exposure of deleted files with known SHA-1 hashes on default
+ installations.
+* Improved scripting safety heuristics for IE 5/6 content-type detection.
+* Improved scripting safety heuristics on SVG uploads.
+* (bug 11728) Unify layout of enhanced watchlist/recent changes
+* (bug 8702) Properly update stats when running nukePage maintenance script
+* (bug 7726) Searches for words less than 4 characters now work without
+ requiring customization of MySQL server settings
+* Honour unchecked "Leave a redirect behind" for moved subpages
+* (bug 16440) Broken 0-byte math renderings are now deleted and re-rendered
+ when page is re-parsed.
+* (bug 6100) Unicode BiDi embedding/override characters (U+202A - U+202E) are
+ now automatically removed from titles; these characters can accidentally end
+ up in copy-and-pasted titles, and, by overriding normal bidirectional text
+ handling, can lead to annoying behavior such as text rendering backwards
+* Fixed minor bug where the memcached value for how many accounts an IP had
+ created that day would be increased even if $wgAccountCreationThrottle was
+ hit. This meant if an IP hit the throttle and then the throttle was raised
+ later that day, the IP still couldn't create another account, because it
+ had marked them as having created another account, when their last account
+ creation had actually failed.
+* (bug 12647) Allow autogenerated edit summary messages to be blanked with '-'
+* (bug 16026) 'Revision-info' and 'revision-info-current' both accept wiki
+ markup now.
+* (bug 16529) Fix for search suggestions with some third-party JS libraries
+* (bug 13342) importScript() generates more consistent URI encoding
+* (bug 16577) When a blocked user tries to rollback a page, the block message
+ is now only displayed once
+* (bug 14268) SVG image sizes now extracted with proper XML parser
+* (bug 14365) RepoGroup::findFiles() no longer crashes if passed an invalid
+ title via the API
+* (bug 4253, bug 16586) Revision ID is now given instead of title in URLs for
+ new pages in the recent changes IRC feed
+* Ugly tooltips in Special:Statistics were phased out in favor of more direct
+ information. Went ahead and rewrote SpecialStatistics to subclass SpecialPage
+* (bug 5506) Links to files on foreign repositories are now shown consistently
+ as bluelinks e.g. in logs and edit summaries
+* (bug 16623) Add missing tag in Special:LockDB
+* (bug 15849) Special:Movepage now throws a more specific error when trying to
+ move a title to an interwiki target
+* (bug 16638) 8-bit URL fallback encoding now set on additional languages using
+ Arabic script (Persian, Urdu, Sindhi, Punjabi)
+* (bug 16656) cleanupTitles and friends should now work in load-balanced
+ DB environments when $wgDBserver isn't set.
+* (bug 3691) Aspect ratio from viewBox attribute is now preserved for SVG
+ images which do not specify width and height attributes.
+* (bug 15027) Internet domain names and IP addresses can now be indexed and
+ searched sensibly with the default MySQL search backend.
+* (bug 11733) Fixed parameter validation in importTextFile.php
+* (bug 16712) Special:NewFiles updated to use "newer"/"older" paging messages
+ for clarity over "previous/next"
+* (bug 16612) Fixed "noprint" class for Modern skin print style
+* Section anchors now have an "id" attribute as well as a "name" attribute,
+ even when Tidy is not used
+* (bug 16026) revision-info, revision-info-current, cannotdelete,
+ redirectedfrom, historywarning and difference messages now use Wiki text
+ rather than raw HTML markup
+* (bug 13835) Fix rendering of {{filepath:Wiki.png|nowiki}}
+* (bug 16772) Special:Upload now correctly rejects files with spaces in the
+ file extension (e.g. Foo. jpg).
+* Image moving over an existing file no longer throws a database error
+* (bug 16786) Restored "redundant" links recently removed from Classic sidebar
+* (bug 16850) $wgActionPaths can have query strings now, previously, this broke
+ local URLs
+* (bug 16376) Mention in deleteBatch.php and moveBatch.php maintenance scripts
+ that STDIN can be used for page list
+* (bug 16560) Special:Random returns a page from ContentNamespaces, and no
+ longer from NS_MAIN
+* (bug 16123) Fixed Special:Import on SQLite.
+* (bug 16937) Show appropriate error message for attempted installs on
+ PostgreSQL 7.3 or earlier.
+* Disabled SQLite support in the installer.
+* Fixed XSS vulnerabilities in the web-based installer.
+* Added a meta robots tag to the installer to prevent indexing of potentially
+ sensitive configuration data.
+* (bug 16483) Prevented a filesort in ApiQueryBacklinks caused by missing parentheses.
+ Building query properly now using makeList()
+
+=== API changes in 1.14 ===
+
+* Registration time of users registered before the DB field was created is now
+ shown as empty instead of the current time.
+* API search now falls back to fulltext search by default when using Lucene
+ or other engine which doesn't support a separate title search function.
+ This means you can use API search on Wikipedia without explicitly adding
+ &srwhat=text to the query.
+* Added iiprop=bitdepth to imageinfo and aiprop=bitdepth to allimages
+* (bug 14713) API-specific permissions (such as 'writeapi' and 'apihighlimits'
+ are now listed on action=help
+* (bug 15044) Added requestid parameter to api.php to facilitate distinguishing
+ between requests
+* (bug 15048) Added limit field for multivalue parameters to action=paraminfo
+ output.
+* When the limit on multivalue parameters is exceeded, a warning is issued
+* list=search doesn't list missing pages any more
+* (bug 15178) Added clshow to prop=categories to allow filtering for hidden/
+ non-hidden categories
+* (bug 15228) Combining revids= and redirects now throws a warning instead of
+ an error, and still resolves redirects generated by the generator.
+* list={backlinks,embeddedin,imageusage} now return arrays with keys 0, 1, 2,
+ etc. (AKA lists) instead of arrays with pageIDs as keys (AKA hash tables)
+ for consistency with other list modules.
+* Added action=watch
+* (bug 15275) apprefix and related parameters ignore spaces at the end
+* action=edit no longer throws unknown error 228 when trying to create an
+ empty section with section=new
+* Database replication lag doesn't cause all action=edit requests to return the
+ nochange flag any more
+* (bug 15392) ApiFormatBase::formatHTML now uses $wgUrlProtocols.
+* (bug 15444) action=edit returns "Unknown error: ``AS_END''" where it should
+ return just "Unknown error"
+* (bug 15448) YAML output returns empty values instead of 0
+* (bug 15445) Added action=patrol
+* (bug 15466) Added action=purge
+* (bug 15486) action=block ignores autoblock parameter
+* (bug 15492) added rcprop=loginfo to list=recentchanges
+* (bug 15527) action=rollback can now revert anonymous editors
+* (bug 15535) prop=info&inprop=protection doesn't list pre-1.10 protections
+ if the page is also protected otherwise (1.10+ style or cascading)
+* list=random now has rnredirect parameter, to get random redirects.
+* Added APIAfterExecute, APIQueryAfterExecute and APIQueryGeneratorAfterExecute
+ hooks which allow for extending core modules in a cleaner way
+* action=protect checks for invalid protection types and levels
+* (bug 15673) Added indentation to format=wddxfm output and improved built-in
+ WDDX formatter to resemble PHP's more
+* (bug 15706) Empty values for apprtype and apprlevel are now silently ignored
+ rather than causing an exception
+* Added uiprop=preferencestoken to meta=userinfo
+* (bug 15609) Add inprop=url and inprop=readable to prop=info
+* Add ApiDisabled and ApiQueryDisabled classes so individual modules can
+ be disabled in LocalSettings.php
+* (bug 15653) Add prop=duplicatefiles
+* (bug 15768) Add list=watchlistraw
+* (bug 15647) action=edit with basetimestamp fails if the page has been deleted
+ and undeleted since the last edit
+* (bug 15785) Allow for different expiry times for different protections in
+ action=protect
+* Added allowsduplicates attribute to action=paraminfo output
+* (bug 15767) apfilterlanglinks returns duplicate results
+* (bug 15845) Added pageid/fromid parameter to action=delete/move, making
+ manipulation of legacy pages with invalid titles possible
+* (bug 15881) Empty or invalid parameters cause database errors
+* The maxage and smaxage parameters are now properly validated
+* (bug 15945) list=recentchanges doesn't check $wgUseRCPatrol, $wgUseNPPatrol
+ and patrolmarks right
+* (bug 15985) acfrom and aifrom parameters didn't work when sorting in
+ descending order.
+* (bug 15995) Add cmstartsortkey and cmendsortkey parameters to
+ list=categorymembers
+* (bug 16017) list=categorymembers sets invalid continue parameters for
+ sortkeys containing pipes
+* (bug 16018) Added uccontinue parameter to list=usercontribs so paging
+ works properly when multiple users are queried or a userprefix is used
+* (bug 16047) Added activeusers attribute to meta=siteinfo&siprop=statistics
+ output
+* Added redirect resolution to action=parse
+* (bug 16074) rvprop=content combined with a generator with a high limit causes
+ an error
+* (bug 16105) Image metadata attributes containing spaces result in invalid XML
+* (bug 16126) Added siprop=magicwords to meta=siteinfo
+* (bug 16159) Added wlshow=patrolled|!patrolled to list=watchlist
+* (bug 16225) Titles like Talk:Talk:Foo broke apfrom and friends
+* meta=siteinfo&siprop=interwikimap no longer throws an exception for empty
+ sifilter parameter.
+* (bug 12760) meta=userinfo&uiprop=ratelimits doesn't list group-specific rate
+ limits
+* (bug 16398) meta=userinfo&uiprop=rights lists some rights twice in some cases
+* (bug 16408) Added rvgeneratexml to prop=revisions
+* (bug 16421) Made list=logevents's leuser accept user names with underscores
+ instead of spaces
+* (bug 16516) Made rvsection=T-2 work
+* (bug 16526) Added usprop=emailable to list=users
+* (bug 16548) list=search threw errors with an invalid error code
+* (bug 16515) Added pst and onlypst parameters to action=parse
+* (bug 16541) Added block expiry timestamp to list=logevents output
+* (bug 16613) action=protect doesn't tell when &cascade was set but cascading
+ protection wasn't allowed
+* (bug 16626) action=delete now correctly handles empty "reason" param
+* (bug 15579) clshow considers all categories !hidden
+* (bug 16647) list=allcategories, prop=categories don't return "hidden"
+ property for hidden categories
+* New siprop parameter of 'extensions' to list all installed extensions
+* (bug 16672) Include canonical namespace name in
+ meta=siteinfo&siprop=namespaces.
+* (bug 16726) siprop=namespacealiases should also list localized aliases
+* (bug 16730) Added apprfiltercascade parameter to list=allpages to filter
+ cascade-protected pages
+* (bug 16798) JSON encoding errors for some characters outside the BMP
+* (bug 16629) prop=info&inprop=protection lists empty legacy protections
+ incorrectly
+* (bug 15261, 16262) API no longer outputs invalid UTF-8
+* Fix broken list=alllinks paging and make alunique actually work
+
+=== Languages updated in 1.14 ===
+
+MediaWiki supports over 300 languages. Many localisations are updated
+regularly. Below only new and removed languages are listed.
+
+* Bakhtiari (bqi) (new)
+* Fiji Hindi (Devanagari script) (hif-deva) (new)
+* Krio (kri) (new)
+* Lezghian (lez) (new)
+* Laz (lzz) (new)
+* Eastern Mari (mhr) (new)
+* Niuean (niu) (new)
+* Oromo (om) (new)
+* Plautdietsch (pdt) (new)
+* Western Punjabi (pnb) (new)
+* Tarantino (roa-tara) (new)
+* Serbo-Croatian (sh) (new)
+* Tulu (tcy) (new)
== Compatibility ==
-MediaWiki 1.11 requires PHP 5 (5.1 recommended). PHP 4 is no longer supported.
+MediaWiki 1.14 requires PHP 5 (5.2 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
@@ -637,9 +641,13 @@
== Upgrading ==
-1.11 has several database changes since 1.10, and will not work without schema
+1.14 has several database changes since 1.13, and will not work without schema
updates.
+If upgrading from before 1.11, and you are using a wiki as a commons reposito-
+ry, make sure that it is updated as well. Otherwise, errors may arise due to
+database schema changes.
+
If upgrading from before 1.7, you may want to run refreshLinks.php to ensure
new database fields are filled with data.
@@ -649,6 +657,7 @@
See the file UPGRADE for more detailed upgrade instructions.
+
=== Caveats ===
Some output, particularly involving user-supplied inline HTML, may not
@@ -657,8 +666,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.10.x and older releases, see HISTORY.
+For notes on 1.13.x and older releases, see HISTORY.
=== Online documentation ===
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/UPGRADE mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/UPGRADE
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/UPGRADE Thu Aug 23 14:56:40 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/UPGRADE Tue Sep 23 07:13:10 2008
@@ -4,14 +4,14 @@
* the documentation at http://www.mediawiki.org
* the mediawiki-l mailing list archive at
http://lists.wikimedia.org/pipermail/mediawiki-l/
-* the bug tracker at http://bugzilla.wikimedia.org
+* the bug tracker at https://bugzilla.wikimedia.org
for information and workarounds to common issues.
== Overview ==
Comprehensive documentation on upgrading to the latest version of the software
-is available at http://www.mediawiki.org/wiki/Manual:Upgrading_MediaWiki.
+is available at http://www.mediawiki.org/wiki/Manual:Upgrading.
=== Consult the release notes ===
@@ -66,6 +66,11 @@
with $wgEnableUploads in later versions. When upgrading, consult the release
notes to check for configuration changes which would alter the expected
behaviour of MediaWiki.
+
+=== Check installed extensions ===
+In MediaWiki 1.14 some extensions are migrated into the core. Please see the
+RELEASE-NOTES section "Migrated extensions" and disable these extensions in your
+localSettings.php
=== Test ===
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/api.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/api.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/api.php Mon Jul 30 04:09:15 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/api.php Fri Nov 14 00:51:39 2008
@@ -37,11 +37,34 @@
wfProfileIn('api.php');
+// URL safety checks
+//
+// See RawPage.php for details; summary is that MSIE can override the
+// Content-Type if it sees a recognized extension on the URL, such as
+// might be appended via PATH_INFO after 'api.php'.
+//
+// Some data formats can end up containing unfiltered user-provided data
+// which will end up triggering HTML detection and execution, hence
+// XSS injection and all that entails.
+//
+// Ensure that all access is through the canonical entry point...
+//
+if( isset( $_SERVER['SCRIPT_URL'] ) ) {
+ $url = $_SERVER['SCRIPT_URL'];
+} else {
+ $url = $_SERVER['PHP_SELF'];
+}
+if( strcmp( "$wgScriptPath/api$wgScriptExtension", $url ) ) {
+ wfHttpError( 403, 'Forbidden',
+ 'API must be accessed through the primary script entry point.' );
+ return;
+}
+
// Verify that the API has not been disabled
if (!$wgEnableAPI) {
echo 'MediaWiki API is not enabled for this site. Add the following line to your LocalSettings.php';
echo '$wgEnableAPI=true; ';
- die(-1);
+ die(1);
}
/* Construct an ApiMain with the arguments passed via the URL. What we get back
@@ -53,7 +76,13 @@
// Process data & print results
$processor->execute();
+// Execute any deferred updates
+wfDoUpdates();
+
// Log what the user did, for book-keeping purposes.
wfProfileOut('api.php');
wfLogProfilingData();
+
+// Shut down the database
+wfGetLBFactory()->shutdown();
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/api.php5 mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/api.php5
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/api.php5 Wed Jul 11 03:55:24 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/api.php5 Sun Feb 3 23:49:10 2008
@@ -1 +1 @@
-
+
-
-
+
+
-
- MediaWiki Installation
+
+
+ MediaWiki Installation
+
+
+HTML validation error
+
+EOT;
+
+ $error = strtok( $tidy->errorBuffer, "\n" );
+ $badLines = array();
+ while ( $error !== false ) {
+ if ( preg_match( '/^line (\d+)/', $error, $m ) ) {
+ $lineNum = intval( $m[1] );
+ $badLines[$lineNum] = true;
+ $out .= "" . htmlspecialchars( $error ) . " \n";
+ }
+ $error = strtok( "\n" );
+ }
+
+ $out .= ' ' . htmlspecialchars( $tidy->errorBuffer ) . ' ';
+ $out .= '';
+ $line = strtok( $s, "\n" );
+ $i = 1;
+ while ( $line !== false ) {
+ if ( isset( $badLines[$i] ) ) {
+ $out .= "";
+ } else {
+ $out .= ' ';
+ }
+ $out .= htmlspecialchars( $line ) . ' ';
+ $line = strtok( "\n" );
+ $i++;
+ }
+ $out .= ' ';
+ return $out;
+}
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/OutputPage.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/OutputPage.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/OutputPage.php Fri Aug 31 17:33:44 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/OutputPage.php Wed Dec 31 12:56:04 2008
@@ -1,34 +1,49 @@
addStyle()
+ */
+ var $styles = array();
+
+ private $mIndexPolicy = 'index';
+ private $mFollowPolicy = 'follow';
/**
* Constructor
@@ -37,33 +52,18 @@
function __construct() {
global $wgAllowUserJs;
$this->mAllowUserJs = $wgAllowUserJs;
- $this->mMetatags = $this->mKeywords = $this->mLinktags = array();
- $this->mHTMLtitle = $this->mPagetitle = $this->mBodytext =
- $this->mRedirect = $this->mLastModified =
- $this->mSubtitle = $this->mDebugtext = $this->mRobotpolicy =
- $this->mOnloadHandler = $this->mPageLinkTitle = '';
- $this->mIsArticleRelated = $this->mIsarticle = $this->mPrintable = true;
- $this->mSuppressQuickbar = $this->mPrintable = false;
- $this->mLanguageLinks = array();
- $this->mCategoryLinks = array();
- $this->mDoNothing = false;
- $this->mContainsOldMagic = $this->mContainsNewMagic = 0;
- $this->mParserOptions = null;
- $this->mSquidMaxage = 0;
- $this->mScripts = '';
- $this->mHeadItems = array();
- $this->mETag = false;
- $this->mRevisionId = null;
- $this->mNewSectionLink = false;
- $this->mTemplateIds = array();
}
-
+
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;
}
+ public function getRedirect() {
+ return $this->mRedirect;
+ }
+
/**
* Set the HTTP status code to send with the output.
*
@@ -72,19 +72,40 @@
*/
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 ) ); }
+ /**
+ * Add a new tag
+ * To add an http-equiv meta tag, precede the name with "http:"
+ *
+ * @param $name tag name
+ * @param $val tag value
+ */
+ function addMeta( $name, $val ) {
+ array_push( $this->mMetatags, array( $name, $val ) );
+ }
+
function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
function addScript( $script ) { $this->mScripts .= "\t\t".$script; }
- function addStyle( $style ) {
- global $wgStylePath, $wgStyleVersion;
- $this->addLink(
- array(
- 'rel' => 'stylesheet',
- 'href' => $wgStylePath . '/' . $style . '?' . $wgStyleVersion ) );
+
+ function addExtensionStyle( $url ) {
+ $linkarr = array( 'rel' => 'stylesheet', 'href' => $url, 'type' => 'text/css' );
+ array_push( $this->mExtStyles, $linkarr );
}
/**
+ * Add a JavaScript file out of skins/common, or a given relative path.
+ * @param string $file filename in skins/common or complete on-server path (/foo/bar.js)
+ */
+ function addScriptFile( $file ) {
+ global $wgStylePath, $wgStyleVersion, $wgJsMimeType;
+ if( substr( $file, 0, 1 ) == '/' ) {
+ $path = $file;
+ } else {
+ $path = "{$wgStylePath}/common/{$file}";
+ }
+ $this->addScript( "\n" );
+ }
+
+ /**
* Add a self-contained script tag with the given contents
* @param string $script JavaScript text, no ";
}
- function getScript() {
- return $this->mScripts . $this->getHeadItems();
+ function getScript() {
+ return $this->mScripts . $this->getHeadItems();
}
function getHeadItems() {
@@ -121,6 +142,11 @@
# $linkarr should be an associative array of attributes. We'll escape on output.
array_push( $this->mLinktags, $linkarr );
}
+
+ # Get all links added by extensions
+ function getExtStyle() {
+ return $this->mExtStyles;
+ }
function addMetadataLink( $linkarr ) {
# note: buggy CC software only reads first "meta" link
@@ -135,63 +161,87 @@
* possible. If sucessful, the OutputPage is disabled so that
* any future call to OutputPage->output() have no effect.
*
+ * Side effect: sets mLastModified for Last-Modified header
+ *
* @return bool True iff cache-ok headers was sent.
*/
function checkLastModified ( $timestamp ) {
global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
- $fname = 'OutputPage::checkLastModified';
-
+
if ( !$timestamp || $timestamp == '19700101000000' ) {
- wfDebug( "$fname: CACHE DISABLED, NO TIMESTAMP\n" );
- return;
+ wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
+ return false;
}
if( !$wgCachePages ) {
- wfDebug( "$fname: CACHE DISABLED\n", false );
- return;
+ wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
+ return false;
}
if( $wgUser->getOption( 'nocache' ) ) {
- wfDebug( "$fname: USER DISABLED CACHE\n", false );
- return;
+ wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
+ return false;
}
- $timestamp=wfTimestamp(TS_MW,$timestamp);
- $lastmod = wfTimestamp( TS_RFC2822, max( $timestamp, $wgUser->mTouched, $wgCacheEpoch ) );
+ $timestamp = wfTimestamp( TS_MW, $timestamp );
+ $modifiedTimes = array(
+ 'page' => $timestamp,
+ 'user' => $wgUser->getTouched(),
+ 'epoch' => $wgCacheEpoch
+ );
+ wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
- if( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
- # IE sends sizes after the date like this:
- # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
- # this breaks strtotime().
- $modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
-
- wfSuppressWarnings(); // E_STRICT system time bitching
- $modsinceTime = strtotime( $modsince );
- wfRestoreWarnings();
-
- $ismodsince = wfTimestamp( TS_MW, $modsinceTime ? $modsinceTime : 1 );
- wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", false );
- wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", false );
- if( ($ismodsince >= $timestamp ) && $wgUser->validateCache( $ismodsince ) && $ismodsince >= $wgCacheEpoch ) {
- # Make sure you're in a place you can leave when you call us!
- $wgRequest->response()->header( "HTTP/1.0 304 Not Modified" );
- $this->mLastModified = $lastmod;
- $this->sendCacheControl();
- wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
- $this->disable();
-
- // 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 );
- $this->mLastModified = $lastmod;
+ $maxModified = max( $modifiedTimes );
+ $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
+
+ if( empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
+ wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false );
+ return false;
+ }
+
+ # Make debug info
+ $info = '';
+ foreach ( $modifiedTimes as $name => $value ) {
+ if ( $info !== '' ) {
+ $info .= ', ';
}
- } else {
- wfDebug( "$fname: client did not send If-Modified-Since header\n", false );
- $this->mLastModified = $lastmod;
+ $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
+ }
+
+ # IE sends sizes after the date like this:
+ # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
+ # this breaks strtotime().
+ $clientHeader = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
+
+ wfSuppressWarnings(); // E_STRICT system time bitching
+ $clientHeaderTime = strtotime( $clientHeader );
+ wfRestoreWarnings();
+ if ( !$clientHeaderTime ) {
+ wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
+ return false;
}
+ $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
+
+ wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
+ wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
+ wfDebug( __METHOD__ . ": effective Last-Modified: " .
+ wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
+ if( $clientHeaderTime < $maxModified ) {
+ wfDebug( __METHOD__ . ": STALE, $info\n", false );
+ return false;
+ }
+
+ # Not modified
+ # Give a 304 response code and disable body output
+ wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
+ $wgRequest->response()->header( "HTTP/1.1 304 Not Modified" );
+ $this->sendCacheControl();
+ $this->disable();
+
+ // 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;
}
function setPageTitleActionText( $text ) {
@@ -204,7 +254,61 @@
}
}
- public function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; }
+ /**
+ * Set the robot policy for the page:
+ *
+ * @param $policy string The literal string to output as the contents of
+ * the meta tag. Will be parsed according to the spec and output in
+ * standardized form.
+ * @return null
+ */
+ public function setRobotPolicy( $policy ) {
+ $policy = explode( ',', $policy );
+ $policy = array_map( 'trim', $policy );
+
+ # The default policy is follow, so if nothing is said explicitly, we
+ # do that.
+ if( in_array( 'nofollow', $policy ) ) {
+ $this->mFollowPolicy = 'nofollow';
+ } else {
+ $this->mFollowPolicy = 'follow';
+ }
+
+ if( in_array( 'noindex', $policy ) ) {
+ $this->mIndexPolicy = 'noindex';
+ } else {
+ $this->mIndexPolicy = 'index';
+ }
+ }
+
+ /**
+ * Set the index policy for the page, but leave the follow policy un-
+ * touched.
+ *
+ * @param $policy string Either 'index' or 'noindex'.
+ * @return null
+ */
+ public function setIndexPolicy( $policy ) {
+ $policy = trim( $policy );
+ if( in_array( $policy, array( 'index', 'noindex' ) ) ) {
+ $this->mIndexPolicy = $policy;
+ }
+ }
+
+ /**
+ * Set the follow policy for the page, but leave the index policy un-
+ * touched.
+ *
+ * @param $policy string Either 'follow' or 'nofollow'.
+ * @return null
+ */
+ public function setFollowPolicy( $policy ) {
+ $policy = trim( $policy );
+ if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
+ $this->mFollowPolicy = $policy;
+ }
+ }
+
public function setHTMLTitle( $name ) {$this->mHTMLtitle = $name; }
public function setPageTitle( $name ) {
global $action, $wgContLang;
@@ -222,12 +326,15 @@
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 appendSubtitle( $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 setFeedAppendQuery( $val ) { $this->mFeedLinksAppendQuery = $val; }
+ public function getFeedAppendQuery() { return $this->mFeedLinksAppendQuery; }
public function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; }
public function getOnloadHandler() { return $this->mOnloadHandler; }
public function disable() { $this->mDoNothing = true; }
@@ -262,23 +369,49 @@
/**
* Add an array of categories, with names in the keys
*/
- public function addCategoryLinks($categories) {
+ public function addCategoryLinks( $categories ) {
global $wgUser, $wgContLang;
- if ( !is_array( $categories ) ) {
+ if ( !is_array( $categories ) || count( $categories ) == 0 ) {
return;
}
- # Add the links to the link cache in a batch
+
+ # Add the links to a LinkBatch
$arr = array( NS_CATEGORY => $categories );
$lb = new LinkBatch;
$lb->setArray( $arr );
- $lb->execute();
- $sk = $wgUser->getSkin();
- foreach ( $categories as $category => $unused ) {
- $title = Title::makeTitleSafe( NS_CATEGORY, $category );
- $text = $wgContLang->convertHtml( $title->getText() );
- $this->mCategoryLinks[] = $sk->makeLinkObj( $title, $text );
+ # Fetch existence plus the hiddencat property
+ $dbr = wfGetDB( DB_SLAVE );
+ $pageTable = $dbr->tableName( 'page' );
+ $where = $lb->constructSet( 'page', $dbr );
+ $propsTable = $dbr->tableName( 'page_props' );
+ $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect, pp_value
+ FROM $pageTable LEFT JOIN $propsTable ON pp_propname='hiddencat' AND pp_page=page_id WHERE $where";
+ $res = $dbr->query( $sql, __METHOD__ );
+
+ # Add the results to the link cache
+ $lb->addResultToCache( LinkCache::singleton(), $res );
+
+ # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
+ $categories = array_combine( array_keys( $categories ),
+ array_fill( 0, count( $categories ), 'normal' ) );
+
+ # Mark hidden categories
+ foreach ( $res as $row ) {
+ if ( isset( $row->pp_value ) ) {
+ $categories[$row->page_title] = 'hidden';
+ }
+ }
+
+ # Add the remaining categories to the skin
+ if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
+ $sk = $wgUser->getSkin();
+ foreach ( $categories as $category => $type ) {
+ $title = Title::makeTitleSafe( NS_CATEGORY, $category );
+ $text = $wgContLang->convertHtml( $title->getText() );
+ $this->mCategoryLinks[$type][] = $sk->makeLinkObj( $title, $text );
+ }
}
}
@@ -293,6 +426,7 @@
public function disallowUserJs() { $this->mAllowUserJs = false; }
public function isUserJsAllowed() { return $this->mAllowUserJs; }
+ public function prependHTML( $text ) { $this->mBodytext = $text . $this->mBodytext; }
public function addHTML( $text ) { $this->mBodytext .= $text; }
public function clearHTML() { $this->mBodytext = ''; }
public function getHTML() { return $this->mBodytext; }
@@ -300,6 +434,7 @@
/* @deprecated */
public function setParserOptions( $options ) {
+ wfDeprecated( __METHOD__ );
return $this->parserOptions( $options );
}
@@ -320,6 +455,10 @@
$val = is_null( $revid ) ? null : intval( $revid );
return wfSetVar( $this->mRevisionId, $val );
}
+
+ public function getRevisionId() {
+ return $this->mRevisionId;
+ }
/**
* Convert wikitext to HTML and add it to the buffer
@@ -345,20 +484,21 @@
public function addWikiTextTitle($text, &$title, $linestart, $tidy = false) {
global $wgParser;
- $fname = 'OutputPage:addWikiTextTitle';
- wfProfileIn($fname);
+ wfProfileIn( __METHOD__ );
- wfIncrStats('pcache_not_possible');
+ wfIncrStats( 'pcache_not_possible' );
$popts = $this->parserOptions();
- $popts->setTidy($tidy);
+ $oldTidy = $popts->setTidy( $tidy );
$parserOutput = $wgParser->parse( $text, $title, $popts,
$linestart, true, $this->mRevisionId );
+ $popts->setTidy( $oldTidy );
+
$this->addParserOutput( $parserOutput );
- wfProfileOut($fname);
+ wfProfileOut( __METHOD__ );
}
/**
@@ -366,23 +506,43 @@
* @param ParserOutput object &$parserOutput
*/
public function addParserOutputNoText( &$parserOutput ) {
+ global $wgTitle, $wgExemptFromUserRobotsControl, $wgContentNamespaces;
+
$this->mLanguageLinks += $parserOutput->getLanguageLinks();
$this->addCategoryLinks( $parserOutput->getCategories() );
$this->mNewSectionLink = $parserOutput->getNewSection();
+
+ if( is_null( $wgExemptFromUserRobotsControl ) ) {
+ $bannedNamespaces = $wgContentNamespaces;
+ } else {
+ $bannedNamespaces = $wgExemptFromUserRobotsControl;
+ }
+ if( !in_array( $wgTitle->getNamespace(), $bannedNamespaces ) ) {
+ # FIXME (bug 14900): This overrides $wgArticleRobotPolicies, and it
+ # shouldn't
+ $this->setIndexPolicy( $parserOutput->getIndexPolicy() );
+ }
+
$this->addKeywords( $parserOutput );
+ $this->mParseWarnings = $parserOutput->getWarnings();
if ( $parserOutput->getCacheTime() == -1 ) {
$this->enableClientCache( false );
}
$this->mNoGallery = $parserOutput->getNoGallery();
$this->mHeadItems = array_merge( $this->mHeadItems, (array)$parserOutput->mHeadItems );
// Versioning...
- $this->mTemplateIds += (array)$parserOutput->mTemplateIds;
-
- # Display title
+ foreach ( (array)$parserOutput->mTemplateIds as $ns => $dbks ) {
+ if ( isset( $this->mTemplateIds[$ns] ) ) {
+ $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
+ } else {
+ $this->mTemplateIds[$ns] = $dbks;
+ }
+ }
+ // Display title
if( ( $dt = $parserOutput->getDisplayTitle() ) !== false )
$this->setPageTitle( $dt );
- # Hooks registered in the object
+ // Hooks registered in the object
global $wgParserOutputHooks;
foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
list( $hookName, $data ) = $hookInfo;
@@ -417,13 +577,15 @@
public function addPrimaryWikiText( $text, $article, $cache = true ) {
global $wgParser, $wgUser;
+ wfDeprecated( __METHOD__ );
+
$popts = $this->parserOptions();
$popts->setTidy(true);
$parserOutput = $wgParser->parse( $text, $article->mTitle,
$popts, true, true, $this->mRevisionId );
$popts->setTidy(false);
if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) {
- $parserCache =& ParserCache::singleton();
+ $parserCache = ParserCache::singleton();
$parserCache->save( $parserOutput, $article, $wgUser );
}
@@ -435,6 +597,7 @@
*/
public function addSecondaryWikiText( $text, $linestart = true ) {
global $wgTitle;
+ wfDeprecated( __METHOD__ );
$this->addWikiTextTitleTidy($text, $wgTitle, $linestart);
}
@@ -468,6 +631,9 @@
*/
public function parse( $text, $linestart = true, $interface = false ) {
global $wgParser, $wgTitle;
+ if( is_null( $wgTitle ) ) {
+ throw new MWException( 'Empty $wgTitle in ' . __METHOD__ );
+ }
$popts = $this->parserOptions();
if ( $interface) { $popts->setInterfaceMessage(true); }
$parserOutput = $wgParser->parse( $text, $wgTitle, $popts,
@@ -483,7 +649,7 @@
* @return bool True if successful, else false.
*/
public function tryParserCache( &$article, $user ) {
- $parserCache =& ParserCache::singleton();
+ $parserCache = ParserCache::singleton();
$parserOutput = $parserCache->get( $article, $user );
if ( $parserOutput !== false ) {
$this->addParserOutput( $parserOutput );
@@ -508,61 +674,122 @@
return wfSetVar( $this->mEnableClientCache, $state );
}
- function uncacheableBecauseRequestvars() {
+ function getCacheVaryCookies() {
+ global $wgCookiePrefix, $wgCacheVaryCookies;
+ static $cookies;
+ if ( $cookies === null ) {
+ $cookies = array_merge(
+ array(
+ "{$wgCookiePrefix}Token",
+ "{$wgCookiePrefix}LoggedOut",
+ session_name()
+ ),
+ $wgCacheVaryCookies
+ );
+ wfRunHooks('GetCacheVaryCookies', array( $this, &$cookies ) );
+ }
+ return $cookies;
+ }
+
+ function uncacheableBecauseRequestVars() {
global $wgRequest;
return $wgRequest->getText('useskin', false) === false
&& $wgRequest->getText('uselang', false) === false;
}
+ /**
+ * Check if the request has a cache-varying cookie header
+ * If it does, it's very important that we don't allow public caching
+ */
+ function haveCacheVaryCookies() {
+ global $wgRequest;
+ $cookieHeader = $wgRequest->getHeader( 'cookie' );
+ if ( $cookieHeader === false ) {
+ return false;
+ }
+ $cvCookies = $this->getCacheVaryCookies();
+ foreach ( $cvCookies as $cookieName ) {
+ # Check for a simple string match, like the way squid does it
+ if ( strpos( $cookieHeader, $cookieName ) ) {
+ wfDebug( __METHOD__.": found $cookieName\n" );
+ return true;
+ }
+ }
+ wfDebug( __METHOD__.": no cache-varying cookies found\n" );
+ return false;
+ }
+
+ /** Get a complete X-Vary-Options header */
+ public function getXVO() {
+ $cvCookies = $this->getCacheVaryCookies();
+ $xvo = 'X-Vary-Options: Accept-Encoding;list-contains=gzip,Cookie;';
+ $first = true;
+ foreach ( $cvCookies as $cookieName ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $xvo .= ';';
+ }
+ $xvo .= 'string-contains=' . $cookieName;
+ }
+ return $xvo;
+ }
+
public function sendCacheControl() {
global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest;
- $fname = 'OutputPage::sendCacheControl';
+ $response = $wgRequest->response();
if ($wgUseETag && $this->mETag)
- $wgRequest->response()->header("ETag: $this->mETag");
+ $response->header("ETag: $this->mETag");
# don't serve compressed data to clients who can't handle it
# maintain different caches for logged-in users and non-logged in ones
- $wgRequest->response()->header( 'Vary: Accept-Encoding, Cookie' );
- if( !$this->uncacheableBecauseRequestvars() && $this->mEnableClientCache ) {
+ $response->header( 'Vary: Accept-Encoding, Cookie' );
+
+ # Add an X-Vary-Options header for Squid with Wikimedia patches
+ $response->header( $this->getXVO() );
+
+ if( !$this->uncacheableBecauseRequestVars() && $this->mEnableClientCache ) {
if( $wgUseSquid && session_id() == '' &&
- ! $this->isPrintable() && $this->mSquidMaxage != 0 )
+ ! $this->isPrintable() && $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() )
{
if ( $wgUseESI ) {
# We'll purge the proxy cache explicitly, but require end user agents
# to revalidate against the proxy on each visit.
# Surrogate-Control controls our Squid, Cache-Control downstream caches
- wfDebug( "$fname: proxy caching with ESI; {$this->mLastModified} **\n", false );
+ wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false );
# start with a shorter timeout for initial testing
# header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
- $wgRequest->response()->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
- $wgRequest->response()->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
+ $response->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
+ $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
} else {
# We'll purge the proxy cache for anons explicitly, but require end user agents
# to revalidate against the proxy on each visit.
# IMPORTANT! The Squid needs to replace the Cache-Control header with
# Cache-Control: s-maxage=0, must-revalidate, max-age=0
- wfDebug( "$fname: local proxy caching; {$this->mLastModified} **\n", false );
+ wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false );
# start with a shorter timeout for initial testing
# header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
- $wgRequest->response()->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
+ $response->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
}
} else {
# We do want clients to cache if they can, but they *must* check for updates
# on revisiting the page.
- wfDebug( "$fname: private caching; {$this->mLastModified} **\n", false );
- $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
- $wgRequest->response()->header( "Cache-Control: private, must-revalidate, max-age=0" );
+ wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false );
+ $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
+ $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
+ }
+ if($this->mLastModified) {
+ $response->header( "Last-Modified: {$this->mLastModified}" );
}
- if($this->mLastModified) $wgRequest->response()->header( "Last-modified: {$this->mLastModified}" );
} else {
- wfDebug( "$fname: no caching **\n", false );
+ wfDebug( __METHOD__ . ": no caching **\n", false );
# In general, the absence of a last modified header should be enough to prevent
# the client from using its cache. We send a few other things just to make sure.
- $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
- $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
- $wgRequest->response()->header( 'Pragma: no-cache' );
+ $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
+ $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
+ $response->header( 'Pragma: no-cache' );
}
}
@@ -573,37 +800,19 @@
public function output() {
global $wgUser, $wgOutputEncoding, $wgRequest;
global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType;
- global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgAjaxWatch;
- global $wgServer, $wgStyleVersion;
+ global $wgJsMimeType, $wgUseAjax, $wgAjaxWatch;
+ global $wgEnableMWSuggest, $wgUniversalEditButton;
+ global $wgArticle, $wgTitle;
if( $this->mDoNothing ){
return;
}
- $fname = 'OutputPage::output';
- wfProfileIn( $fname );
- $sk = $wgUser->getSkin();
-
- if ( $wgUseAjax ) {
- $this->addScript( "\n" );
-
- wfRunHooks( 'AjaxAddScript', array( &$this ) );
-
- if( $wgAjaxSearch ) {
- $this->addScript( "\n" );
- $this->addScript( "\n" );
- }
- if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
- $this->addScript( "\n" );
- }
- }
+ wfProfileIn( __METHOD__ );
if ( '' != $this->mRedirect ) {
- if( substr( $this->mRedirect, 0, 4 ) != 'http' ) {
- # Standards require redirect URLs to be absolute
- global $wgServer;
- $this->mRedirect = $wgServer . $this->mRedirect;
- }
+ # Standards require redirect URLs to be absolute
+ $this->mRedirect = wfExpandUrl( $this->mRedirect );
if( $this->mRedirectCode == '301') {
if( !$wgDebugRedirects ) {
$wgRequest->response()->header("HTTP/1.1 {$this->mRedirectCode} Moved Permanently");
@@ -622,7 +831,7 @@
} else {
$wgRequest->response()->header( 'Location: '.$this->mRedirect );
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return;
}
elseif ( $this->mStatusCode )
@@ -680,18 +889,58 @@
$wgRequest->response()->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $statusMessage[$this->mStatusCode] );
}
+ $sk = $wgUser->getSkin();
+
+ if ( $wgUseAjax ) {
+ $this->addScriptFile( 'ajax.js' );
+
+ wfRunHooks( 'AjaxAddScript', array( &$this ) );
+
+ if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
+ $this->addScriptFile( 'ajaxwatch.js' );
+ }
+
+ if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ){
+ $this->addScriptFile( 'mwsuggest.js' );
+ }
+ }
+
+ if( $wgUser->getBoolOption( 'editsectiononrightclick' ) ) {
+ $this->addScriptFile( 'rightclickedit.js' );
+ }
+
+ if( $wgUniversalEditButton ) {
+ if( isset( $wgArticle ) && isset( $wgTitle ) && $wgTitle->quickUserCan( 'edit' )
+ && ( $wgTitle->exists() || $wgTitle->quickUserCan( 'create' ) ) ) {
+ // Original UniversalEditButton
+ $this->addLink( array(
+ 'rel' => 'alternate',
+ 'type' => 'application/x-wiki',
+ 'title' => wfMsg( 'edit' ),
+ 'href' => $wgTitle->getFullURL( 'action=edit' )
+ ) );
+ // Alternate edit link
+ $this->addLink( array(
+ 'rel' => 'edit',
+ 'title' => wfMsg( 'edit' ),
+ 'href' => $wgTitle->getFullURL( 'action=edit' )
+ ) );
+ }
+ }
+
# Buffer output; final headers may depend on later processing
ob_start();
- # Disable temporary placeholders, so that the skin produces HTML
- $sk->postParseLinkColour( false );
-
$wgRequest->response()->header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
$wgRequest->response()->header( 'Content-language: '.$wgContLanguageCode );
if ($this->mArticleBodyOnly) {
$this->out($this->mBodytext);
} else {
+ // Hook that allows last minute changes to the output page, e.g.
+ // adding of CSS or Javascript by extensions.
+ wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
+
wfProfileIn( 'Output-skin' );
$sk->outputPage( $this );
wfProfileOut( 'Output-skin' );
@@ -699,7 +948,7 @@
$this->sendCacheControl();
ob_end_flush();
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
}
/**
@@ -739,6 +988,7 @@
* @deprecated
*/
public function reportTime() {
+ wfDeprecated( __METHOD__ );
$time = wfReportTime();
return $time;
}
@@ -753,11 +1003,14 @@
global $wgUser, $wgContLang, $wgTitle, $wgLang;
$this->setPageTitle( wfMsg( 'blockedtitle' ) );
- $this->setRobotpolicy( 'noindex,nofollow' );
+ $this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
$name = User::whoIs( $wgUser->blockedBy() );
$reason = $wgUser->blockedFor();
+ if( $reason == '' ) {
+ $reason = wfMsg( 'blockednoreason' );
+ }
$blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $wgUser->mBlock->mTimestamp ), true );
$ip = wfGetIP();
@@ -793,12 +1046,12 @@
* This could be a username, an ip range, or a single ip. */
$intended = $wgUser->mBlock->mAddress;
- $this->addWikiText( wfMsg( $msg, $link, $reason, $ip, $name, $blockid, $blockExpiry, $intended, $blockTimestamp ) );
+ $this->addWikiMsg( $msg, $link, $reason, $ip, $name, $blockid, $blockExpiry, $intended, $blockTimestamp );
# Don't auto-return to special pages
if( $return ) {
- $return = $wgTitle->getNamespace() > -1 ? $wgTitle->getPrefixedText() : NULL;
- $this->returnToMain( false, $return );
+ $return = $wgTitle->getNamespace() > -1 ? $wgTitle : NULL;
+ $this->returnToMain( null, $return );
}
}
@@ -811,22 +1064,22 @@
*/
public function showErrorPage( $title, $msg, $params = array() ) {
global $wgTitle;
-
- $this->mDebugtext .= 'Original title: ' .
- $wgTitle->getPrefixedText() . "\n";
+ if ( isset($wgTitle) ) {
+ $this->mDebugtext .= 'Original title: ' . $wgTitle->getPrefixedText() . "\n";
+ }
$this->setPageTitle( wfMsg( $title ) );
$this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
- $this->setRobotpolicy( 'noindex,nofollow' );
+ $this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
$this->enableClientCache( false );
$this->mRedirect = '';
$this->mBodytext = '';
-
+
array_unshift( $params, 'parse' );
array_unshift( $params, $msg );
- $this->addHtml( call_user_func_array( 'wfMsgExt', $params ) );
-
- $this->returnToMain( false );
+ $this->addHTML( call_user_func_array( 'wfMsgExt', $params ) );
+
+ $this->returnToMain();
}
/**
@@ -834,27 +1087,28 @@
*
* @param array $errors Error message keys
*/
- public function showPermissionsErrorPage( $errors )
+ public function showPermissionsErrorPage( $errors, $action = null )
{
global $wgTitle;
$this->mDebugtext .= 'Original title: ' .
- $wgTitle->getPrefixedText() . "\n";
+ $wgTitle->getPrefixedText() . "\n";
$this->setPageTitle( wfMsg( 'permissionserrors' ) );
$this->setHTMLTitle( wfMsg( 'permissionserrors' ) );
- $this->setRobotpolicy( 'noindex,nofollow' );
+ $this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
$this->enableClientCache( false );
$this->mRedirect = '';
$this->mBodytext = '';
- $this->addWikiText( $this->formatPermissionsErrorMessage( $errors ) );
+ $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
}
/** @deprecated */
public function errorpage( $title, $msg ) {
+ wfDeprecated( __METHOD__ );
throw new ErrorPageError( $title, $msg );
}
-
+
/**
* Display an error page indicating that a given version of MediaWiki is
* required to use it
@@ -864,11 +1118,11 @@
public function versionRequired( $version ) {
$this->setPageTitle( wfMsg( 'versionrequired', $version ) );
$this->setHTMLTitle( wfMsg( 'versionrequired', $version ) );
- $this->setRobotpolicy( 'noindex,nofollow' );
+ $this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
$this->mBodytext = '';
- $this->addWikiText( wfMsg( 'versionrequiredtext', $version ) );
+ $this->addWikiMsg( 'versionrequiredtext', $version );
$this->returnToMain();
}
@@ -878,40 +1132,24 @@
* @param string $permission key required
*/
public function permissionRequired( $permission ) {
- global $wgGroupPermissions, $wgUser;
+ global $wgUser;
$this->setPageTitle( wfMsg( 'badaccess' ) );
$this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
- $this->setRobotpolicy( 'noindex,nofollow' );
+ $this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
$this->mBodytext = '';
- $groups = array();
- foreach( $wgGroupPermissions as $key => $value ) {
- if( isset( $value[$permission] ) && $value[$permission] == true ) {
- $groupName = User::getGroupName( $key );
- $groupPage = User::getGroupPage( $key );
- if( $groupPage ) {
- $skin = $wgUser->getSkin();
- $groups[] = $skin->makeLinkObj( $groupPage, $groupName );
- } else {
- $groups[] = $groupName;
- }
- }
- }
- $n = count( $groups );
- $groups = implode( ', ', $groups );
- switch( $n ) {
- case 0:
- case 1:
- case 2:
- $message = wfMsgHtml( "badaccess-group$n", $groups );
- break;
- default:
- $message = wfMsgHtml( 'badaccess-groups', $groups );
+ $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
+ User::getGroupsWithPermission( $permission ) );
+ if( $groups ) {
+ $this->addWikiMsg( 'badaccess-groups',
+ implode( ', ', $groups ),
+ count( $groups) );
+ } else {
+ $this->addWikiMsg( 'badaccess-group0' );
}
- $this->addHtml( $message );
- $this->returnToMain( false );
+ $this->returnToMain();
}
/**
@@ -942,22 +1180,22 @@
}
$skin = $wgUser->getSkin();
-
+
$this->setPageTitle( wfMsg( 'loginreqtitle' ) );
$this->setHtmlTitle( wfMsg( 'errorpagetitle' ) );
$this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleFlag( false );
-
+
$loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
$loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $wgTitle->getPrefixedUrl() );
- $this->addHtml( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
- $this->addHtml( "\n" );
-
+ $this->addHTML( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
+ $this->addHTML( "\n" );
+
# 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::newMainPage();
if( $mainPage->userCanRead() )
- $this->returnToMain( true, $mainPage );
+ $this->returnToMain( null, $mainPage );
}
/** @deprecated */
@@ -967,133 +1205,151 @@
/**
* @param array $errors An array of arrays returned by Title::getUserPermissionsErrors
- * @return string The error-messages, formatted into a list.
+ * @return string The wikitext error-messages, formatted into a list.
*/
- public function formatPermissionsErrorMessage( $errors ) {
- $text = '';
-
- if (sizeof( $errors ) > 1) {
+ public function formatPermissionsErrorMessage( $errors, $action = null ) {
+ if ($action == null) {
+ $text = wfMsgNoTrans( 'permissionserrorstext', count($errors)). "\n\n";
+ } else {
+ global $wgLang;
+ $action_desc = wfMsg( "action-$action" );
+ $text = wfMsgNoTrans( 'permissionserrorstext-withaction', count($errors), $action_desc ) . "\n\n";
+ }
- $text .= wfMsgExt( 'permissionserrorstext', array( 'parse' ), count( $errors ) ) . "\n";
+ if (count( $errors ) > 1) {
$text .= '' . "\n";
foreach( $errors as $error )
{
$text .= '';
- $text .= call_user_func_array( 'wfMsg', $error );
+ $text .= call_user_func_array( 'wfMsgNoTrans', $error );
$text .= " \n";
}
$text .= ' ';
} else {
- $text .= call_user_func_array( 'wfMsg', $errors[0]);
+ $text .= '' . call_user_func_array( 'wfMsgNoTrans', reset( $errors ) ) . '
';
}
return $text;
}
/**
- * @todo document
- * @param bool $protected Is the reason the page can't be reached because it's protected?
- * @param mixed $source
- * @param bool $protected, page is protected?
- * @param array $reason, array of arrays( msg, args )
+ * Display a page stating that the Wiki is in read-only mode,
+ * and optionally show the source of the page that the user
+ * was trying to edit. Should only be called (for this
+ * purpose) after wfReadOnly() has returned true.
+ *
+ * For historical reasons, this function is _also_ used to
+ * show the error message when a user tries to edit a page
+ * they are not allowed to edit. (Unless it's because they're
+ * blocked, then we show blockedPage() instead.) In this
+ * case, the second parameter should be set to true and a list
+ * of reasons supplied as the third parameter.
+ *
+ * @todo Needs to be split into multiple functions.
+ *
+ * @param string $source Source code to show (or null).
+ * @param bool $protected Is this a permissions error?
+ * @param array $reasons List of reasons for this error, as returned by Title::getUserPermissionsErrors().
*/
- public function readOnlyPage( $source = null, $protected = false, $reasons = array() ) {
- global $wgUser, $wgReadOnlyFile, $wgReadOnly, $wgTitle;
+ public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
+ global $wgUser, $wgTitle;
$skin = $wgUser->getSkin();
- $this->setRobotpolicy( 'noindex,nofollow' );
+ $this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
-
- if ( !empty($reasons) ) {
- $this->setPageTitle( wfMsg( 'viewsource' ) );
- $this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) );
- $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons ) );
- } else if( $protected ) {
- $this->setPageTitle( wfMsg( 'viewsource' ) );
- $this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) );
- list( $cascadeSources, /* $restrictions */ ) = $wgTitle->getCascadeProtectionSources();
-
- // Show an appropriate explanation depending upon the reason
- // for the protection...all of these should be moved to the
- // callers
- if( $wgTitle->getNamespace() == NS_MEDIAWIKI ) {
- // User isn't allowed to edit the interface
- $this->addWikiText( wfMsg( 'protectedinterface' ) );
- } elseif( $cascadeSources && ( $count = count( $cascadeSources ) ) > 0 ) {
- // Cascading protection
- $titles = '';
- foreach( $cascadeSources as $title )
- $titles .= "* [[:" . $title->getPrefixedText() . "]]\n";
- $this->addWikiText( wfMsgExt( 'cascadeprotected', 'parsemag', $count ) . "\n{$titles}" );
- } elseif( !$wgTitle->isProtected( 'edit' ) && $wgTitle->isNamespaceProtected() ) {
- // Namespace protection
- $ns = $wgTitle->getNamespace() == NS_MAIN
- ? wfMsg( 'nstab-main' )
- : $wgTitle->getNsText();
- $this->addWikiText( wfMsg( 'namespaceprotected', $ns ) );
+ // If no reason is given, just supply a default "I can't let you do
+ // that, Dave" message. Should only occur if called by legacy code.
+ if ( $protected && empty($reasons) ) {
+ $reasons[] = array( 'badaccess-group0' );
+ }
+
+ if ( !empty($reasons) ) {
+ // Permissions error
+ if( $source ) {
+ $this->setPageTitle( wfMsg( 'viewsource' ) );
+ $this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) );
} else {
- // Standard protection
- $this->addWikiText( wfMsg( 'protectedpagetext' ) );
+ $this->setPageTitle( wfMsg( 'badaccess' ) );
}
+ $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
} else {
+ // Wiki is read only
$this->setPageTitle( wfMsg( 'readonly' ) );
- if ( $wgReadOnly ) {
- $reason = $wgReadOnly;
- } else {
- $reason = file_get_contents( $wgReadOnlyFile );
- }
- $this->addWikiText( wfMsg( 'readonlytext', $reason ) );
+ $reason = wfReadOnlyReason();
+ $this->wrapWikiMsg( '$1
', array( 'readonlytext', $reason ) );
}
+ // Show source, if supplied
if( is_string( $source ) ) {
- $this->addWikiText( wfMsg( 'viewsourcetext' ) );
- $rows = $wgUser->getIntOption( 'rows' );
- $cols = $wgUser->getIntOption( 'cols' );
- $text = "\n";
+ $this->addWikiMsg( 'viewsourcetext' );
+ $text = Xml::openElement( 'textarea',
+ array( 'id' => 'wpTextbox1',
+ 'name' => 'wpTextbox1',
+ 'cols' => $wgUser->getOption( 'cols' ),
+ 'rows' => $wgUser->getOption( 'rows' ),
+ 'readonly' => 'readonly' ) );
+ $text .= htmlspecialchars( $source );
+ $text .= Xml::closeElement( 'textarea' );
$this->addHTML( $text );
+
+ // Show templates used by this article
+ $skin = $wgUser->getSkin();
+ $article = new Article( $wgTitle );
+ $this->addHTML( "
+{$skin->formatTemplates( $article->getUsedTemplates() )}
+
+" );
}
- $article = new Article( $wgTitle );
- $this->addHTML( $skin->formatTemplates( $article->getUsedTemplates() ) );
- $this->returnToMain( false );
+ # If the title doesn't exist, it's fairly pointless to print a return
+ # link to it. After all, you just tried editing it and couldn't, so
+ # what's there to do there?
+ if( $wgTitle->exists() ) {
+ $this->returnToMain( null, $wgTitle );
+ }
}
/** @deprecated */
public function fatalError( $message ) {
- throw new FatalError( $message );
+ wfDeprecated( __METHOD__ );
+ throw new FatalError( $message );
}
-
+
/** @deprecated */
public function unexpectedValueError( $name, $val ) {
+ wfDeprecated( __METHOD__ );
throw new FatalError( wfMsg( 'unexpected', $name, $val ) );
}
/** @deprecated */
public function fileCopyError( $old, $new ) {
+ wfDeprecated( __METHOD__ );
throw new FatalError( wfMsg( 'filecopyerror', $old, $new ) );
}
/** @deprecated */
public function fileRenameError( $old, $new ) {
+ wfDeprecated( __METHOD__ );
throw new FatalError( wfMsg( 'filerenameerror', $old, $new ) );
}
/** @deprecated */
public function fileDeleteError( $name ) {
+ wfDeprecated( __METHOD__ );
throw new FatalError( wfMsg( 'filedeleteerror', $name ) );
}
/** @deprecated */
public function fileNotFoundError( $name ) {
+ wfDeprecated( __METHOD__ );
throw new FatalError( wfMsg( 'filenotfound', $name ) );
}
public function showFatalError( $message ) {
$this->setPageTitle( wfMsg( "internalerror" ) );
- $this->setRobotpolicy( "noindex,nofollow" );
+ $this->setRobotPolicy( "noindex,nofollow" );
$this->setArticleRelated( false );
$this->enableClientCache( false );
$this->mRedirect = '';
@@ -1127,8 +1383,9 @@
*/
public function addReturnTo( $title ) {
global $wgUser;
+ $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullUrl() ) );
$link = wfMsg( 'returnto', $wgUser->getSkin()->makeLinkObj( $title ) );
- $this->addHtml( "{$link}
\n" );
+ $this->addHTML( "{$link}
\n" );
}
/**
@@ -1140,11 +1397,11 @@
*/
public function returnToMain( $unused = null, $returnto = NULL ) {
global $wgRequest;
-
+
if ( $returnto == NULL ) {
$returnto = $wgRequest->getText( 'returnto' );
}
-
+
if ( '' === $returnto ) {
$returnto = Title::newMainPage();
}
@@ -1188,15 +1445,19 @@
/**
* @return string The doctype, opening , and head element.
*/
- public function headElement() {
+ public function headElement( Skin $sk ) {
global $wgDocType, $wgDTD, $wgContLanguageCode, $wgOutputEncoding, $wgMimeType;
global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces;
global $wgUser, $wgContLang, $wgUseTrackbacks, $wgTitle, $wgStyleVersion;
+ $this->addMeta( "http:Content-type", "$wgMimeType; charset={$wgOutputEncoding}" );
+ $this->addStyle( 'common/wikiprintable.css', 'print' );
+ $sk->setupUserCss( $this );
+
+ $ret = '';
+
if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) {
- $ret = "\n";
- } else {
- $ret = '';
+ $ret .= "\n";
}
$ret .= "\n";
@@ -1211,24 +1472,17 @@
$ret .= "xmlns:{$tag}=\"{$ns}\" ";
}
$ret .= "xml:lang=\"$wgContLanguageCode\" lang=\"$wgContLanguageCode\" $rtl>\n";
- $ret .= "\n" . htmlspecialchars( $this->getHTMLTitle() ) . " \n";
- array_push( $this->mMetatags, array( "http:Content-type", "$wgMimeType; charset={$wgOutputEncoding}" ) );
-
- $ret .= $this->getHeadLinks();
- global $wgStylePath;
- if( $this->isPrintable() ) {
- $media = '';
- } else {
- $media = "media='print'";
+ $ret .= "\n" . htmlspecialchars( $this->getHTMLTitle() ) . " \n\t\t";
+ $ret .= implode( "\t\t", array(
+ $this->getHeadLinks(),
+ $this->buildCssLinks(),
+ $sk->getHeadScripts( $this->mAllowUserJs ),
+ $this->mScripts,
+ $this->getHeadItems(),
+ ));
+ if( $sk->usercss ){
+ $ret .= "";
}
- $printsheet = htmlspecialchars( "$wgStylePath/common/wikiprintable.css?$wgStyleVersion" );
- $ret .= " \n";
-
- $sk = $wgUser->getSkin();
- $ret .= $sk->getHeadScripts( $this->mAllowUserJs );
- $ret .= $this->mScripts;
- $ret .= $sk->getUserStyles();
- $ret .= $this->getHeadItems();
if ($wgUseTrackbacks && $this->isArticleRelated())
$ret .= $wgTitle->trackbackRDF();
@@ -1236,13 +1490,39 @@
$ret .= "\n";
return $ret;
}
+
+ protected function addDefaultMeta() {
+ global $wgVersion;
+ $this->addMeta( 'http:Content-Style-Type', 'text/css' ); //bug 15835
+ $this->addMeta( 'generator', "MediaWiki $wgVersion" );
+
+ $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
+ if( $p !== 'index,follow' ) {
+ // http://www.robotstxt.org/wc/meta-user.html
+ // Only show if it's different from the default robots policy
+ $this->addMeta( 'robots', $p );
+ }
+
+ if ( count( $this->mKeywords ) > 0 ) {
+ $strip = array(
+ "/<.*?>/" => '',
+ "/_/" => ' '
+ );
+ $this->addMeta( 'keywords', preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ) ) );
+ }
+ }
/**
* @return string HTML tag links to be put in the header.
*/
public function getHeadLinks() {
- global $wgRequest;
- $ret = '';
+ global $wgRequest, $wgFeed;
+
+ // Ideally this should happen earlier, somewhere. :P
+ $this->addDefaultMeta();
+
+ $tags = array();
+
foreach ( $this->mMetatags as $tag ) {
if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
$a = 'http-equiv';
@@ -1250,55 +1530,225 @@
} else {
$a = 'name';
}
- $ret .= " \n";
+ $tags[] = Xml::element( 'meta',
+ array(
+ $a => $tag[0],
+ 'content' => $tag[1] ) );
+ }
+ foreach ( $this->mLinktags as $tag ) {
+ $tags[] = Xml::element( 'link', $tag );
}
- $p = $this->mRobotpolicy;
- if( $p !== '' && $p != 'index,follow' ) {
- // http://www.robotstxt.org/wc/meta-user.html
- // Only show if it's different from the default robots policy
- $ret .= " \n";
+ if( $wgFeed ) {
+ global $wgTitle;
+ foreach( $this->getSyndicationLinks() as $format => $link ) {
+ # Use the page name for the title (accessed through $wgTitle since
+ # there's no other way). In principle, this could lead to issues
+ # with having the same name for different feeds corresponding to
+ # the same page, but we can't avoid that at this low a level.
+
+ $tags[] = $this->feedLink(
+ $format,
+ $link,
+ wfMsg( "page-{$format}-feed", $wgTitle->getPrefixedText() ) ); # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
+ }
+
+ # Recent changes feed should appear on every page (except recentchanges,
+ # that would be redundant). Put it after the per-page feed to avoid
+ # changing existing behavior. It's still available, probably via a
+ # menu in your browser. Some sites might have a different feed they'd
+ # like to promote instead of the RC feed (maybe like a "Recent New Articles"
+ # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
+ # If so, use it instead.
+
+ global $wgOverrideSiteFeed, $wgSitename, $wgFeedClasses;
+ $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
+
+ if ( $wgOverrideSiteFeed ) {
+ foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
+ $tags[] = $this->feedLink (
+ $type,
+ htmlspecialchars( $feedUrl ),
+ wfMsg( "site-{$type}-feed", $wgSitename ) );
+ }
+ }
+ else if ( $wgTitle->getPrefixedText() != $rctitle->getPrefixedText() ) {
+ foreach( $wgFeedClasses as $format => $class ) {
+ $tags[] = $this->feedLink(
+ $format,
+ $rctitle->getFullURL( "feed={$format}" ),
+ wfMsg( "site-{$format}-feed", $wgSitename ) ); # For grep: 'site-rss-feed', 'site-atom-feed'.
+ }
+ }
}
- if ( count( $this->mKeywords ) > 0 ) {
- $strip = array(
- "/<.*?>/" => '',
- "/_/" => ' '
- );
- $ret .= "\t\t mKeywords ))) . "\" />\n";
+ return implode( "\n\t\t", $tags ) . "\n";
+ }
+
+ /**
+ * Return URLs for each supported syndication format for this page.
+ * @return array associating format keys with URLs
+ */
+ public function getSyndicationLinks() {
+ global $wgTitle, $wgFeedClasses;
+ $links = array();
+
+ if( $this->isSyndicated() ) {
+ if( is_string( $this->getFeedAppendQuery() ) ) {
+ $appendQuery = "&" . $this->getFeedAppendQuery();
+ } else {
+ $appendQuery = "";
+ }
+
+ foreach( $wgFeedClasses as $format => $class ) {
+ $links[$format] = $wgTitle->getLocalUrl( "feed=$format{$appendQuery}" );
+ }
}
- foreach ( $this->mLinktags as $tag ) {
- $ret .= "\t\t $val ) {
- $ret .= " $attr=\"" . htmlspecialchars( $val ) . "\"";
+ return $links;
+ }
+
+ /**
+ * Generate a for an RSS feed.
+ */
+ private function feedLink( $type, $url, $text ) {
+ return Xml::element( 'link', array(
+ 'rel' => 'alternate',
+ 'type' => "application/$type+xml",
+ 'title' => $text,
+ 'href' => $url ) );
+ }
+
+ /**
+ * Add a local or specified stylesheet, with the given media options.
+ * Meant primarily for internal use...
+ *
+ * @param $media -- to specify a media type, 'screen', 'printable', 'handheld' or any.
+ * @param $conditional -- for IE conditional comments, specifying an IE version
+ * @param $dir -- set to 'rtl' or 'ltr' for direction-specific sheets
+ */
+ public function addStyle( $style, $media='', $condition='', $dir='' ) {
+ $options = array();
+ if( $media )
+ $options['media'] = $media;
+ if( $condition )
+ $options['condition'] = $condition;
+ if( $dir )
+ $options['dir'] = $dir;
+ $this->styles[$style] = $options;
+ }
+
+ /**
+ * Build a set of s for the stylesheets specified in the $this->styles array.
+ * These will be applied to various media & IE conditionals.
+ */
+ public function buildCssLinks() {
+ $links = array();
+ foreach( $this->styles as $file => $options ) {
+ $link = $this->styleLink( $file, $options );
+ if( $link )
+ $links[] = $link;
+ }
+
+ return implode( "\n\t\t", $links );
+ }
+
+ protected function styleLink( $style, $options ) {
+ global $wgRequest;
+
+ if( isset( $options['dir'] ) ) {
+ global $wgContLang;
+ $siteDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
+ if( $siteDir != $options['dir'] )
+ return '';
+ }
+
+ if( isset( $options['media'] ) ) {
+ $media = $this->transformCssMedia( $options['media'] );
+ if( is_null( $media ) ) {
+ return '';
}
- $ret .= " />\n";
+ } else {
+ $media = '';
}
- if( $this->isSyndicated() ) {
- # FIXME: centralize the mime-type and name information in Feed.php
- $link = $wgRequest->escapeAppendQuery( 'feed=rss' );
- $ret .= " \n";
- $link = $wgRequest->escapeAppendQuery( 'feed=atom' );
- $ret .= " \n";
+
+ if( substr( $style, 0, 1 ) == '/' ||
+ substr( $style, 0, 5 ) == 'http:' ||
+ substr( $style, 0, 6 ) == 'https:' ) {
+ $url = $style;
+ } else {
+ global $wgStylePath, $wgStyleVersion;
+ $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
}
- return $ret;
+ $attribs = array(
+ 'rel' => 'stylesheet',
+ 'href' => $url,
+ 'type' => 'text/css' );
+ if( $media ) {
+ $attribs['media'] = $media;
+ }
+
+ $link = Xml::element( 'link', $attribs );
+
+ if( isset( $options['condition'] ) ) {
+ $condition = htmlspecialchars( $options['condition'] );
+ $link = "";
+ }
+ return $link;
+ }
+
+ function transformCssMedia( $media ) {
+ global $wgRequest, $wgHandheldForIPhone;
+
+ // Switch in on-screen display for media testing
+ $switches = array(
+ 'printable' => 'print',
+ 'handheld' => 'handheld',
+ );
+ foreach( $switches as $switch => $targetMedia ) {
+ if( $wgRequest->getBool( $switch ) ) {
+ if( $media == $targetMedia ) {
+ $media = '';
+ } elseif( $media == 'screen' ) {
+ return null;
+ }
+ }
+ }
+
+ // Expand longer media queries as iPhone doesn't grok 'handheld'
+ if( $wgHandheldForIPhone ) {
+ $mediaAliases = array(
+ 'screen' => 'screen and (min-device-width: 481px)',
+ 'handheld' => 'handheld, only screen and (max-device-width: 480px)',
+ );
+
+ if( isset( $mediaAliases[$media] ) ) {
+ $media = $mediaAliases[$media];
+ }
+ }
+
+ return $media;
}
/**
* Turn off regular page output and return an error reponse
* for when rate limiting has triggered.
- * @todo i18n
*/
public function rateLimited() {
- global $wgOut;
- $wgOut->disable();
- wfHttpError( 500, 'Internal Server Error',
- 'Sorry, the server has encountered an internal error. ' .
- 'Please wait a moment and hit "refresh" to submit the request again.' );
+ global $wgTitle;
+
+ $this->setPageTitle(wfMsg('actionthrottled'));
+ $this->setRobotPolicy( 'noindex,follow' );
+ $this->setArticleRelated( false );
+ $this->enableClientCache( false );
+ $this->mRedirect = '';
+ $this->clearHTML();
+ $this->setStatusCode(503);
+ $this->addWikiMsg( 'actionthrottledtext' );
+
+ $this->returnToMain( null, $wgTitle );
}
-
+
/**
* Show an "add new section" link?
*
@@ -1307,7 +1757,7 @@
public function showNewSectionLink() {
return $this->mNewSectionLink;
}
-
+
/**
* Show a warning about slave lag
*
@@ -1324,8 +1774,77 @@
? 'lag-warn-normal'
: 'lag-warn-high';
$warning = wfMsgExt( $message, 'parse', $lag );
- $this->addHtml( "\n{$warning}\n
\n" );
+ $this->addHTML( "\n{$warning}\n
\n" );
}
}
-
+
+ /**
+ * Add a wikitext-formatted message to the output.
+ * This is equivalent to:
+ *
+ * $wgOut->addWikiText( wfMsgNoTrans( ... ) )
+ */
+ public function addWikiMsg( /*...*/ ) {
+ $args = func_get_args();
+ $name = array_shift( $args );
+ $this->addWikiMsgArray( $name, $args );
+ }
+
+ /**
+ * Add a wikitext-formatted message to the output.
+ * Like addWikiMsg() except the parameters are taken as an array
+ * instead of a variable argument list.
+ *
+ * $options is passed through to wfMsgExt(), see that function for details.
+ */
+ public function addWikiMsgArray( $name, $args, $options = array() ) {
+ $options[] = 'parse';
+ $text = wfMsgExt( $name, $options, $args );
+ $this->addHTML( $text );
+ }
+
+ /**
+ * This function takes a number of message/argument specifications, wraps them in
+ * some overall structure, and then parses the result and adds it to the output.
+ *
+ * In the $wrap, $1 is replaced with the first message, $2 with the second, and so
+ * on. The subsequent arguments may either be strings, in which case they are the
+ * message names, or an arrays, in which case the first element is the message name,
+ * and subsequent elements are the parameters to that message.
+ *
+ * The special named parameter 'options' in a message specification array is passed
+ * through to the $options parameter of wfMsgExt().
+ *
+ * Don't use this for messages that are not in users interface language.
+ *
+ * For example:
+ *
+ * $wgOut->wrapWikiMsg( '$1
', 'some-error' );
+ *
+ * Is equivalent to:
+ *
+ * $wgOut->addWikiText( '' . wfMsgNoTrans( 'some-error' ) . '
' );
+ */
+ public function wrapWikiMsg( $wrap /*, ...*/ ) {
+ $msgSpecs = func_get_args();
+ array_shift( $msgSpecs );
+ $msgSpecs = array_values( $msgSpecs );
+ $s = $wrap;
+ foreach ( $msgSpecs as $n => $spec ) {
+ $options = array();
+ if ( is_array( $spec ) ) {
+ $args = $spec;
+ $name = array_shift( $args );
+ if ( isset( $args['options'] ) ) {
+ $options = $args['options'];
+ unset( $args['options'] );
+ }
+ } else {
+ $args = array();
+ $name = $spec;
+ }
+ $s = str_replace( '$' . ($n+1), wfMsgExt( $name, $options, $args ), $s );
+ }
+ $this->addHTML( $this->parse( $s, /*linestart*/true, /*uilang*/true ) );
+ }
}
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/PageHistory.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/PageHistory.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/PageHistory.php Mon Aug 20 03:42:32 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/PageHistory.php Wed Dec 31 16:16:36 2008
@@ -3,6 +3,7 @@
* Page history
*
* Split off from Article.php and Skin.php, 2003-12-22
+ * @file
*/
/**
@@ -17,11 +18,10 @@
class PageHistory {
const DIR_PREV = 0;
const DIR_NEXT = 1;
-
+
var $mArticle, $mTitle, $mSkin;
var $lastdate;
var $linesonpage;
- var $mNotificationTimestamp;
var $mLatestId = null;
/**
@@ -30,13 +30,33 @@
* @param Article $article
* @returns nothing
*/
- function __construct($article) {
+ function __construct( $article ) {
global $wgUser;
-
$this->mArticle =& $article;
$this->mTitle =& $article->mTitle;
- $this->mNotificationTimestamp = NULL;
$this->mSkin = $wgUser->getSkin();
+ $this->preCacheMessages();
+ }
+
+ function getArticle() {
+ return $this->mArticle;
+ }
+
+ function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
+ * As we use the same small set of messages in various methods and that
+ * they are called often, we call them once and save them in $this->message
+ */
+ function preCacheMessages() {
+ // Precache various messages
+ if( !isset( $this->message ) ) {
+ foreach( explode(' ', 'cur last rev-delundel' ) as $msg ) {
+ $this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
+ }
+ }
}
/**
@@ -45,38 +65,36 @@
* @returns nothing
*/
function history() {
- global $wgOut, $wgRequest, $wgTitle;
+ global $wgOut, $wgRequest, $wgTitle, $wgScript;
/*
* Allow client caching.
*/
+ if( $wgOut->checkLastModified( $this->mArticle->getTouched() ) )
+ return; // Client cache fresh and headers sent, nothing more to do.
- if( $wgOut->checkLastModified( $this->mArticle->getTimestamp() ) )
- /* Client cache fresh and headers sent, nothing more to do. */
- return;
-
- $fname = 'PageHistory::history';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
/*
* Setup page variables.
*/
- $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
+ $wgOut->setPageTitle( wfMsg( 'history-title', $this->mTitle->getPrefixedText() ) );
$wgOut->setPageTitleActionText( wfMsg( 'history_short' ) );
$wgOut->setArticleFlag( false );
$wgOut->setArticleRelated( true );
- $wgOut->setRobotpolicy( 'noindex,nofollow' );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setSyndicated( true );
+ $wgOut->setFeedAppendQuery( 'action=history' );
+ $wgOut->addScriptFile( 'history.js' );
$logPage = SpecialPage::getTitleFor( 'Log' );
- $logLink = $this->mSkin->makeKnownLinkObj( $logPage, wfMsgHtml( 'viewpagelogs' ), 'page=' . $this->mTitle->getPrefixedUrl() );
-
- $subtitle = wfMsgHtml( 'revhistory' ) . ' ' . $logLink;
- $wgOut->setSubtitle( $subtitle );
+ $logLink = $this->mSkin->makeKnownLinkObj( $logPage, wfMsgHtml( 'viewpagelogs' ),
+ 'page=' . $this->mTitle->getPrefixedUrl() );
+ $wgOut->setSubtitle( $logLink );
$feedType = $wgRequest->getVal( 'feed' );
if( $feedType ) {
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $this->feed( $feedType );
}
@@ -84,85 +102,167 @@
* Fail if article doesn't exist.
*/
if( !$this->mTitle->exists() ) {
- $wgOut->addWikiText( wfMsg( 'nohistory' ) );
- wfProfileOut( $fname );
+ $wgOut->addWikiMsg( 'nohistory' );
+ wfProfileOut( __METHOD__ );
return;
}
-
- /*
- * "go=first" means to jump to the last (earliest) history page.
- * This is deprecated, it no longer appears in the user interface
+ /**
+ * Add date selector to quickly get to a certain time
*/
- if ( $wgRequest->getText("go") == 'first' ) {
- $limit = $wgRequest->getInt( 'limit', 50 );
- $wgOut->redirect( $wgTitle->getLocalURL( "action=history&limit={$limit}&dir=prev" ) );
- return;
- }
-
+ $year = $wgRequest->getInt( 'year' );
+ $month = $wgRequest->getInt( 'month' );
+
+ $action = htmlspecialchars( $wgScript );
+ $wgOut->addHTML(
+ "'
+ );
+
wfRunHooks( 'PageHistoryBeforeList', array( &$this->mArticle ) );
- /**
+ /**
* Do the list
*/
- $pager = new PageHistoryPager( $this );
+ $pager = new PageHistoryPager( $this, $year, $month );
$this->linesonpage = $pager->getNumRows();
$wgOut->addHTML(
- $pager->getNavigationBar() .
- $this->beginHistoryList() .
+ $pager->getNavigationBar() .
+ $this->beginHistoryList() .
$pager->getBody() .
$this->endHistoryList() .
$pager->getNavigationBar()
);
- wfProfileOut( $fname );
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * @return string Formatted HTML
+ * @param int $year
+ * @param int $month
+ */
+ private function getDateMenu( $year, $month ) {
+ # Offset overrides year/month selection
+ if( $month && $month !== -1 ) {
+ $encMonth = intval( $month );
+ } else {
+ $encMonth = '';
+ }
+ if( $year ) {
+ $encYear = intval( $year );
+ } else if( $encMonth ) {
+ $thisMonth = intval( gmdate( 'n' ) );
+ $thisYear = intval( gmdate( 'Y' ) );
+ if( intval($encMonth) > $thisMonth ) {
+ $thisYear--;
+ }
+ $encYear = $thisYear;
+ } else {
+ $encYear = '';
+ }
+ return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
+ Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) .
+ ' '.
+ Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
+ Xml::monthSelector( $encMonth, -1 );
}
- /** @todo document */
+ /**
+ * Creates begin of history list with a submit button
+ *
+ * @return string HTML output
+ */
function beginHistoryList() {
- global $wgTitle;
+ global $wgTitle, $wgScript, $wgEnableHtmlDiff;
$this->lastdate = '';
$s = wfMsgExt( 'histlegend', array( 'parse') );
- $s .= '';
return $s;
}
- /** @todo document */
- function submitButton( $bits = array() ) {
- return ( $this->linesonpage > 0 )
- ? wfElement( 'input', array_merge( $bits,
- array(
- 'class' => 'historysubmit',
- 'type' => 'submit',
- 'accesskey' => wfMsg( 'accesskey-compareselectedversions' ),
- 'title' => wfMsg( 'tooltip-compareselectedversions' ).' ['.wfMsg( 'accesskey-compareselectedversions' ).']',
- 'value' => wfMsg( 'compareselectedversions' ),
- ) ) )
- : '';
+ /**
+ * Creates a submit button
+ *
+ * @param array $attributes attributes
+ * @return string HTML output for the submit button
+ */
+ function submitButton($message, $attributes = array() ) {
+ # Disable submit button if history has 1 revision only
+ if( $this->linesonpage > 1 ) {
+ return Xml::submitButton( $message , $attributes );
+ } else {
+ return '';
+ }
}
/**
@@ -170,8 +270,8 @@
*
* @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 Row $row The database row corresponding to the previous line.
+ * @param mixed $next The database row corresponding to the next line.
* @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.
@@ -183,104 +283,98 @@
$rev = new Revision( $row );
$rev->setTitle( $this->mTitle );
- $s = '';
$curlink = $this->curLink( $rev, $latest );
$lastlink = $this->lastLink( $rev, $next, $counter );
$arbitrary = $this->diffButtons( $rev, $firstInList, $counter );
$link = $this->revLink( $rev );
-
- $user = $this->mSkin->userLink( $rev->getUser(), $rev->getUserText() )
- . $this->mSkin->userToolLinks( $rev->getUser(), $rev->getUserText() );
-
- $s .= "($curlink) ($lastlink) $arbitrary";
-
+
+ $s = "($curlink) ($lastlink) $arbitrary";
+
if( $wgUser->isAllowed( 'deleterevision' ) ) {
$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
if( $firstInList ) {
// We don't currently handle well changing the top revision's settings
- $del = wfMsgHtml( 'rev-delundel' );
+ $del = $this->message['rev-delundel'];
} else if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- // If revision was hidden from sysops
- $del = wfMsgHtml( 'rev-delundel' );
+ // If revision was hidden from sysops
+ $del = $this->message['rev-delundel'];
} else {
$del = $this->mSkin->makeKnownLinkObj( $revdel,
- wfMsg( 'rev-delundel' ),
+ $this->message['rev-delundel'],
'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) .
'&oldid=' . urlencode( $rev->getId() ) );
+ // Bolden oversighted content
+ if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $del = "$del ";
}
- $s .= " ($del ) ";
+ $s .= " ($del ) ";
}
-
+
$s .= " $link";
- #getUser is safe, but this avoids making the invalid untargeted contribs links
- if( $row->rev_deleted & Revision::DELETED_USER ) {
- $user = '' . wfMsg('rev-deleted-user') . ' ';
- }
- $s .= " $user ";
+ $s .= " " . $this->mSkin->revUserTools( $rev, true ) . " ";
if( $row->rev_minor_edit ) {
- $s .= ' ' . wfElement( 'span', array( 'class' => 'minor' ), wfMsg( 'minoreditletter') );
+ $s .= ' ' . Xml::element( 'span', array( 'class' => 'minor' ), wfMsg( 'minoreditletter') );
}
- if (!is_null($size = $rev->getSize())) {
- if ($size == 0)
- $stxt = wfMsgHtml('historyempty');
- else
- $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) );
- $s .= " $stxt ";
+ if( !is_null( $size = $rev->getSize() ) && $rev->userCan( Revision::DELETED_TEXT ) ) {
+ $s .= ' ' . $this->mSkin->formatRevisionSize( $size );
}
- #getComment is safe, but this is better formatted
- if( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
- $s .= " ";
- } else {
- $s .= $this->mSkin->revComment( $rev );
- }
-
- if ($notificationtimestamp && ($row->rev_timestamp >= $notificationtimestamp)) {
+ $s .= $this->mSkin->revComment( $rev, false, true );
+
+ if( $notificationtimestamp && ($row->rev_timestamp >= $notificationtimestamp) ) {
$s .= ' ' . wfMsgHtml( 'updatedmarker' ) . ' ';
}
- #add blurb about text having been deleted
- if( $row->rev_deleted & Revision::DELETED_TEXT ) {
- $s .= ' ' . wfMsgHtml( 'deletedrev' );
+ if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $s .= ' ' . wfMsgHtml( 'deletedrev' ) . ' ';
}
-
+
$tools = array();
-
- if ( !is_null( $next ) && is_object( $next ) ) {
- if( $wgUser->isAllowed( 'rollback' ) && $latest ) {
- $tools[] = ''
- . $this->mSkin->buildRollbackLink( $rev )
- . ' ';
+
+ if( !is_null( $next ) && is_object( $next ) ) {
+ if( $latest && $this->mTitle->userCan( 'rollback' ) && $this->mTitle->userCan( 'edit' ) ) {
+ $tools[] = ''.$this->mSkin->buildRollbackLink( $rev ).' ';
}
- $undolink = $this->mSkin->makeKnownLinkObj(
- $this->mTitle,
- wfMsgHtml( 'editundo' ),
- 'action=edit&undoafter=' . $next->rev_id . '&undo=' . $rev->getId()
- );
- $tools[] = "{$undolink} ";
+ if( $this->mTitle->quickUserCan( 'edit' ) && !$rev->isDeleted( Revision::DELETED_TEXT ) &&
+ !$next->rev_deleted & Revision::DELETED_TEXT )
+ {
+ # Create undo tooltip for the first (=latest) line only
+ $undoTooltip = $latest
+ ? array( 'title' => wfMsg( 'tooltip-undo' ) )
+ : array();
+ $undolink = $this->mSkin->link(
+ $this->mTitle,
+ wfMsgHtml( 'editundo' ),
+ $undoTooltip,
+ array( 'action' => 'edit', 'undoafter' => $next->rev_id, 'undo' => $rev->getId() ),
+ array( 'known', 'noclasses' )
+ );
+ $tools[] = "{$undolink} ";
+ }
}
-
+
if( $tools ) {
$s .= ' (' . implode( ' | ', $tools ) . ')';
}
-
- wfRunHooks( 'PageHistoryLineEnding', array( &$row , &$s ) );
-
- $s .= " \n";
- return $s;
+ wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row , &$s ) );
+
+ return "$s \n";
}
-
- /** @todo document */
+
+ /**
+ * Create a link to view this revision of the page
+ * @param Revision $rev
+ * @returns string
+ */
function revLink( $rev ) {
global $wgLang;
$date = $wgLang->timeanddate( wfTimestamp(TS_MW, $rev->getTimestamp()), true );
if( $rev->userCan( Revision::DELETED_TEXT ) ) {
$link = $this->mSkin->makeKnownLinkObj(
- $this->mTitle, $date, "oldid=" . $rev->getId() );
+ $this->mTitle, $date, "oldid=" . $rev->getId() );
} else {
$link = $date;
}
@@ -290,53 +384,61 @@
return $link;
}
- /** @todo document */
+ /**
+ * Create a diff-to-current link for this revision for this page
+ * @param Revision $rev
+ * @param Bool $latest, this is the latest revision of the page?
+ * @returns string
+ */
function curLink( $rev, $latest ) {
- $cur = wfMsgExt( 'cur', array( 'escape') );
+ $cur = $this->message['cur'];
if( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
return $cur;
} else {
- return $this->mSkin->makeKnownLinkObj(
- $this->mTitle, $cur,
- 'diff=' . $this->getLatestID() .
- "&oldid=" . $rev->getId() );
+ return $this->mSkin->makeKnownLinkObj( $this->mTitle, $cur,
+ 'diff=' . $this->mTitle->getLatestRevID() . "&oldid=" . $rev->getId() );
}
}
- /** @todo document */
- function lastLink( $rev, $next, $counter ) {
- $last = wfMsgExt( 'last', array( 'escape' ) );
- if ( is_null( $next ) ) {
+ /**
+ * Create a diff-to-previous link for this revision for this page.
+ * @param Revision $prevRev, the previous revision
+ * @param mixed $next, the newer revision
+ * @param int $counter, what row on the history list this is
+ * @returns string
+ */
+ function lastLink( $prevRev, $next, $counter ) {
+ $last = $this->message['last'];
+ # $next may either be a Row, null, or "unkown"
+ $nextRev = is_object($next) ? new Revision( $next ) : $next;
+ if( is_null($next) ) {
# Probably no next row
return $last;
- } elseif ( $next === 'unknown' ) {
+ } elseif( $next === 'unknown' ) {
# Next row probably exists but is unknown, use an oldid=prev link
- return $this->mSkin->makeKnownLinkObj(
- $this->mTitle,
- $last,
- "diff=" . $rev->getId() . "&oldid=prev" );
- } elseif( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ return $this->mSkin->makeKnownLinkObj( $this->mTitle, $last,
+ "diff=" . $prevRev->getId() . "&oldid=prev" );
+ } elseif( !$prevRev->userCan(Revision::DELETED_TEXT) || !$nextRev->userCan(Revision::DELETED_TEXT) ) {
return $last;
} else {
- return $this->mSkin->makeKnownLinkObj(
- $this->mTitle,
- $last,
- "diff=" . $rev->getId() . "&oldid={$next->rev_id}"
- /*,
- '',
- '',
- "tabindex={$counter}"*/ );
+ return $this->mSkin->makeKnownLinkObj( $this->mTitle, $last,
+ "diff=" . $prevRev->getId() . "&oldid={$next->rev_id}" );
}
}
- /** @todo document */
+ /**
+ * Create radio buttons for page history
+ *
+ * @param object $rev Revision
+ * @param bool $firstInList Is this version the first one?
+ * @param int $counter A counter of what row number we're at, counted from the top row = 1.
+ * @return string HTML output for the radio buttons
+ */
function diffButtons( $rev, $firstInList, $counter ) {
if( $this->linesonpage > 1) {
$radio = array(
'type' => 'radio',
'value' => $rev->getId(),
-# do we really need to flood this on every item?
-# 'title' => wfMsgHtml( 'selectolderversionfordiff' )
);
if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
@@ -344,10 +446,10 @@
}
/** @todo: move title texts to javascript */
- if ( $firstInList ) {
- $first = wfElement( 'input', array_merge(
- $radio,
- array(
+ if( $firstInList ) {
+ $first = Xml::element( 'input', array_merge(
+ $radio,
+ array(
'style' => 'visibility:hidden',
'name' => 'oldid' ) ) );
$checkmark = array( 'checked' => 'checked' );
@@ -357,124 +459,75 @@
} else {
$checkmark = array();
}
- $first = wfElement( 'input', array_merge(
- $radio,
- $checkmark,
- array( 'name' => 'oldid' ) ) );
- $checkmark = array();
- }
- $second = wfElement( 'input', array_merge(
+ $first = Xml::element( 'input', array_merge(
$radio,
$checkmark,
- array( 'name' => 'diff' ) ) );
+ array( 'name' => 'oldid' ) ) );
+ $checkmark = array();
+ }
+ $second = Xml::element( 'input', array_merge(
+ $radio,
+ $checkmark,
+ array( 'name' => 'diff' ) ) );
return $first . $second;
} else {
return '';
}
}
- /** @todo document */
- function getLatestId() {
- if( is_null( $this->mLatestId ) ) {
- $id = $this->mTitle->getArticleID();
- $db = wfGetDB(DB_SLAVE);
- $this->mLatestId = $db->selectField( 'page',
- "page_latest",
- array( 'page_id' => $id ),
- 'PageHistory::getLatestID' );
- }
- return $this->mLatestId;
- }
-
/**
* Fetch an array of revisions, specified by a given limit, offset and
- * direction. This is now only used by the feeds. It was previously
+ * direction. This is now only used by the feeds. It was previously
* used by the main UI but that's now handled by the pager.
*/
function fetchRevisions($limit, $offset, $direction) {
- $fname = 'PageHistory::fetchRevisions';
-
$dbr = wfGetDB( DB_SLAVE );
- if ($direction == PageHistory::DIR_PREV)
+ if( $direction == PageHistory::DIR_PREV )
list($dirs, $oper) = array("ASC", ">=");
else /* $direction == PageHistory::DIR_NEXT */
list($dirs, $oper) = array("DESC", "<=");
- if ($offset)
+ if( $offset )
$offsets = array("rev_timestamp $oper '$offset'");
else
$offsets = array();
$page_id = $this->mTitle->getArticleID();
- $res = $dbr->select(
- 'revision',
+ return $dbr->select( 'revision',
Revision::selectFields(),
array_merge(array("rev_page=$page_id"), $offsets),
- $fname,
- array('ORDER BY' => "rev_timestamp $dirs",
+ __METHOD__,
+ array( 'ORDER BY' => "rev_timestamp $dirs",
'USE INDEX' => 'page_timestamp', 'LIMIT' => $limit)
- );
-
- $result = array();
- while (($obj = $dbr->fetchObject($res)) != NULL)
- $result[] = $obj;
-
- return $result;
+ );
}
- /** @todo document */
- function getNotificationTimestamp() {
- global $wgUser, $wgShowUpdatedMarker;
- $fname = 'PageHistory::getNotficationTimestamp';
-
- if ($this->mNotificationTimestamp !== NULL)
- return $this->mNotificationTimestamp;
-
- if ($wgUser->isAnon() || !$wgShowUpdatedMarker)
- return $this->mNotificationTimestamp = false;
-
- $dbr = wfGetDB(DB_SLAVE);
-
- $this->mNotificationTimestamp = $dbr->selectField(
- 'watchlist',
- 'wl_notificationtimestamp',
- array( 'wl_namespace' => $this->mTitle->getNamespace(),
- 'wl_title' => $this->mTitle->getDBkey(),
- 'wl_user' => $wgUser->getID()
- ),
- $fname);
-
- // Don't use the special value reserved for telling whether the field is filled
- if ( is_null( $this->mNotificationTimestamp ) ) {
- $this->mNotificationTimestamp = false;
- }
-
- return $this->mNotificationTimestamp;
- }
-
/**
* Output a subscription feed listing recent edits to this page.
* @param string $type
*/
function feed( $type ) {
- require_once 'SpecialRecentchanges.php';
-
- global $wgFeedClasses;
- if( !isset( $wgFeedClasses[$type] ) ) {
- global $wgOut;
- $wgOut->addWikiText( wfMsg( 'feed-invalid' ) );
+ global $wgFeedClasses, $wgRequest, $wgFeedLimit;
+ if( !FeedUtils::checkFeedOutput($type) ) {
return;
}
-
+
$feed = new $wgFeedClasses[$type](
- $this->mTitle->getPrefixedText() . ' - ' .
- wfMsgForContent( 'history-feed-title' ),
- wfMsgForContent( 'history-feed-description' ),
- $this->mTitle->getFullUrl( 'action=history' ) );
+ $this->mTitle->getPrefixedText() . ' - ' .
+ wfMsgForContent( 'history-feed-title' ),
+ wfMsgForContent( 'history-feed-description' ),
+ $this->mTitle->getFullUrl( 'action=history' ) );
+
+ // Get a limit on number of feed entries. Provide a sane default
+ // of 10 if none is defined (but limit to $wgFeedLimit max)
+ $limit = $wgRequest->getInt( 'limit', 10 );
+ if( $limit > $wgFeedLimit || $limit < 1 ) {
+ $limit = 10;
+ }
+ $items = $this->fetchRevisions($limit, 0, PageHistory::DIR_NEXT);
- $items = $this->fetchRevisions(10, 0, PageHistory::DIR_NEXT);
$feed->outHeader();
if( $items ) {
foreach( $items as $row ) {
@@ -485,7 +538,7 @@
}
$feed->outFooter();
}
-
+
function feedEmpty() {
global $wgOut;
return new FeedItem(
@@ -493,10 +546,10 @@
$wgOut->parse( wfMsgForContent( 'history-feed-empty' ) ),
$this->mTitle->getFullUrl(),
wfTimestamp( TS_MW ),
- '',
+ '',
$this->mTitle->getTalkPage()->getFullUrl() );
}
-
+
/**
* Generate a FeedItem object from a given revision table row
* Borrows Recent Changes' feed generation functions for formatting;
@@ -508,19 +561,19 @@
function feedItem( $row ) {
$rev = new Revision( $row );
$rev->setTitle( $this->mTitle );
- $text = rcFormatDiffRow( $this->mTitle,
- $this->mTitle->getPreviousRevisionID( $rev->getId() ),
- $rev->getId(),
- $rev->getTimestamp(),
- $rev->getComment() );
-
+ $text = FeedUtils::formatDiffRow( $this->mTitle,
+ $this->mTitle->getPreviousRevisionID( $rev->getId() ),
+ $rev->getId(),
+ $rev->getTimestamp(),
+ $rev->getComment() );
+
if( $rev->getComment() == '' ) {
global $wgContLang;
$title = wfMsgForContent( 'history-feed-item-nocomment',
- $rev->getUserText(),
- $wgContLang->timeanddate( $rev->getTimestamp() ) );
+ $rev->getUserText(),
+ $wgContLang->timeanddate( $rev->getTimestamp() ) );
} else {
- $title = $rev->getUserText() . ": " . $this->stripComment( $rev->getComment() );
+ $title = $rev->getUserText() . ": " . FeedItem::stripComment( $rev->getComment() );
}
return new FeedItem(
@@ -531,34 +584,31 @@
$rev->getUserText(),
$this->mTitle->getTalkPage()->getFullUrl() );
}
-
- /**
- * Quickie hack... strip out wikilinks to more legible form from the comment.
- */
- function stripComment( $text ) {
- return preg_replace( '/\[\[([^]]*\|)?([^]]+)\]\]/', '\2', $text );
- }
}
/**
- * @addtogroup Pager
+ * @ingroup Pager
*/
class PageHistoryPager extends ReverseChronologicalPager {
- public $mLastRow = false, $mPageHistory;
-
- function __construct( $pageHistory ) {
+ public $mLastRow = false, $mPageHistory, $mTitle;
+
+ function __construct( $pageHistory, $year='', $month='' ) {
parent::__construct();
$this->mPageHistory = $pageHistory;
+ $this->mTitle =& $this->mPageHistory->mTitle;
+ $this->getDateCond( $year, $month );
}
function getQueryInfo() {
- return array(
- 'tables' => 'revision',
- 'fields' => Revision::selectFields(),
- 'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ),
- 'options' => array( 'USE INDEX' => 'page_timestamp' )
+ $queryInfo = array(
+ 'tables' => array('revision'),
+ 'fields' => Revision::selectFields(),
+ 'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ),
+ 'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') )
);
+ wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
+ return $queryInfo;
}
function getIndexField() {
@@ -566,18 +616,18 @@
}
function formatRow( $row ) {
- if ( $this->mLastRow ) {
- $latest = $this->mCounter == 1 && $this->mOffset == '';
+ if( $this->mLastRow ) {
+ $latest = $this->mCounter == 1 && $this->mIsFirst;
$firstInList = $this->mCounter == 1;
- $s = $this->mPageHistory->historyLine( $this->mLastRow, $row, $this->mCounter++,
- $this->mPageHistory->getNotificationTimestamp(), $latest, $firstInList );
+ $s = $this->mPageHistory->historyLine( $this->mLastRow, $row, $this->mCounter++,
+ $this->mTitle->getNotificationTimestamp(), $latest, $firstInList );
} else {
$s = '';
}
$this->mLastRow = $row;
return $s;
}
-
+
function getStartBody() {
$this->mLastRow = false;
$this->mCounter = 1;
@@ -585,12 +635,12 @@
}
function getEndBody() {
- if ( $this->mLastRow ) {
- $latest = $this->mCounter == 1 && $this->mOffset == 0;
+ if( $this->mLastRow ) {
+ $latest = $this->mCounter == 1 && $this->mIsFirst;
$firstInList = $this->mCounter == 1;
- if ( $this->mIsBackwards ) {
+ if( $this->mIsBackwards ) {
# Next row is unknown, but for UI reasons, probably exists if an offset has been specified
- if ( $this->mOffset == '' ) {
+ if( $this->mOffset == '' ) {
$next = null;
} else {
$next = 'unknown';
@@ -599,14 +649,11 @@
# The next row is the past-the-end row
$next = $this->mPastTheEndRow;
}
- $s = $this->mPageHistory->historyLine( $this->mLastRow, $next, $this->mCounter++,
- $this->mPageHistory->getNotificationTimestamp(), $latest, $firstInList );
+ $s = $this->mPageHistory->historyLine( $this->mLastRow, $next, $this->mCounter++,
+ $this->mTitle->getNotificationTimestamp(), $latest, $firstInList );
} else {
$s = '';
}
return $s;
}
}
-
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/PageQueryPage.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/PageQueryPage.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/PageQueryPage.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/PageQueryPage.php Mon Oct 6 00:36:39 2008
@@ -3,24 +3,23 @@
/**
* Variant of QueryPage which formats the result as a simple link to the page
*
- * @package MediaWiki
- * @addtogroup SpecialPage
+ * @ingroup SpecialPage
*/
class PageQueryPage extends QueryPage {
/**
* Format the result as a simple link to the page
*
- * @param Skin $skin
- * @param object $row Result row
+ * @param $skin Skin
+ * @param $row Object: result row
* @return string
*/
public function formatResult( $skin, $row ) {
global $wgContLang;
$title = Title::makeTitleSafe( $row->namespace, $row->title );
- return $skin->makeKnownLinkObj( $title,
- htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) );
+ $text = $row->title;
+ if ($title instanceof Title)
+ $text = $wgContLang->convert( $title->getPrefixedText() );
+ return $skin->link( $title, htmlspecialchars($text), array(), array(), array('known', 'noclasses') );
}
}
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Pager.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Pager.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Pager.php Wed Jul 18 05:52:17 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Pager.php Wed Oct 29 15:21:21 2008
@@ -1,8 +1,14 @@
mRequest = $wgRequest;
-
+
# NB: the offset is quoted, not validated. It is treated as an
# arbitrary string to support the widest variety of index types. Be
# careful outputting it into HTML!
$this->mOffset = $this->mRequest->getText( 'offset' );
-
+
# Use consistent behavior for the limit options
$this->mDefaultLimit = intval( $wgUser->getOption( 'rclimit' ) );
list( $this->mLimit, /* $offset */ ) = $this->mRequest->getLimitOffset();
-
+
$this->mIsBackwards = ( $this->mRequest->getVal( 'dir' ) == 'prev' );
- $this->mIndexField = $this->getIndexField();
$this->mDb = wfGetDB( DB_SLAVE );
+
+ $index = $this->getIndexField();
+ $order = $this->mRequest->getVal( 'order' );
+ if( is_array( $index ) && isset( $index[$order] ) ) {
+ $this->mOrderType = $order;
+ $this->mIndexField = $index[$order];
+ } elseif( is_array( $index ) ) {
+ # First element is the default
+ reset( $index );
+ list( $this->mOrderType, $this->mIndexField ) = each( $index );
+ } else {
+ # $index is not an array
+ $this->mOrderType = null;
+ $this->mIndexField = $index;
+ }
+
+ if( !isset( $this->mDefaultDirection ) ) {
+ $dir = $this->getDefaultDirections();
+ $this->mDefaultDirection = is_array( $dir )
+ ? $dir[$this->mOrderType]
+ : $dir;
+ }
}
/**
- * Do the query, using information from the object context. This function
- * has been kept minimal to make it overridable if necessary, to allow for
+ * Do the query, using information from the object context. This function
+ * has been kept minimal to make it overridable if necessary, to allow for
* result sets formed from multiple DB queries.
*/
function doQuery() {
@@ -107,15 +148,35 @@
$this->mResult = $this->reallyDoQuery( $this->mOffset, $queryLimit, $descending );
$this->extractResultInfo( $this->mOffset, $queryLimit, $this->mResult );
$this->mQueryDone = true;
-
+
$this->preprocessResults( $this->mResult );
$this->mResult->rewind(); // Paranoia
wfProfileOut( $fname );
}
+
+ /**
+ * Return the result wrapper.
+ */
+ function getResult() {
+ return $this->mResult;
+ }
+
+ /**
+ * Set the offset from an other source than $wgRequest
+ */
+ function setOffset( $offset ) {
+ $this->mOffset = $offset;
+ }
+ /**
+ * Set the limit from an other source than $wgRequest
+ */
+ function setLimit( $limit ) {
+ $this->mLimit = $limit;
+ }
/**
- * Extract some useful data from the result object for use by
+ * Extract some useful data from the result object for use by
* the navigation bar, put it into $this
*/
function extractResultInfo( $offset, $limit, ResultWrapper $res ) {
@@ -180,6 +241,7 @@
$fields = $info['fields'];
$conds = isset( $info['conds'] ) ? $info['conds'] : array();
$options = isset( $info['options'] ) ? $info['options'] : array();
+ $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array();
if ( $descending ) {
$options['ORDER BY'] = $this->mIndexField;
$operator = '>';
@@ -191,7 +253,7 @@
$conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
}
$options['LIMIT'] = intval( $limit );
- $res = $this->mDb->select( $tables, $fields, $conds, $fname, $options );
+ $res = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
return new ResultWrapper( $this->mDb, $res );
}
@@ -203,7 +265,7 @@
protected function preprocessResults( $result ) {}
/**
- * Get the formatted result list. Calls getStartBody(), formatRow() and
+ * Get the formatted result list. Calls getStartBody(), formatRow() and
* getEndBody(), concatenates the results and returns them.
*/
function getBody() {
@@ -238,17 +300,28 @@
/**
* Make a self-link
*/
- function makeLink($text, $query = NULL) {
+ function makeLink($text, $query = null, $type=null) {
if ( $query === null ) {
return $text;
+ }
+ if( $type == 'prev' || $type == 'next' ) {
+ $attrs = "rel=\"$type\"";
+ } elseif( $type == 'first' ) {
+ $attrs = "rel=\"start\"";
} else {
- return $this->getSkin()->makeKnownLinkObj( $this->getTitle(), $text,
- wfArrayToCGI( $query, $this->getDefaultQuery() ) );
+ # HTML 4 has no rel="end" . . .
+ $attrs = '';
+ }
+
+ if( $type ) {
+ $attrs .= " class=\"mw-{$type}link\"" ;
}
+ return $this->getSkin()->makeKnownLinkObj( $this->getTitle(), $text,
+ wfArrayToCGI( $query, $this->getDefaultQuery() ), '', '', $attrs );
}
/**
- * Hook into getBody(), allows text to be inserted at the start. This
+ * Hook into getBody(), allows text to be inserted at the start. This
* will be called even if there are no rows in the result set.
*/
function getStartBody() {
@@ -263,15 +336,15 @@
}
/**
- * Hook into getBody(), for the bit between the start and the
+ * Hook into getBody(), for the bit between the start and the
* end when there are no rows
*/
function getEmptyBody() {
return '';
}
-
+
/**
- * Title used for self-links. Override this if you want to be able to
+ * Title used for self-links. Override this if you want to be able to
* use a title other than $wgTitle
*/
function getTitle() {
@@ -290,8 +363,8 @@
}
/**
- * Get an array of query parameters that should be put into self-links.
- * By default, all parameters passed in the URL are used, except for a
+ * Get an array of query parameters that should be put into self-links.
+ * By default, all parameters passed in the URL are used, except for a
* short blacklist.
*/
function getDefaultQuery() {
@@ -301,6 +374,9 @@
unset( $this->mDefaultQuery['dir'] );
unset( $this->mDefaultQuery['offset'] );
unset( $this->mDefaultQuery['limit'] );
+ unset( $this->mDefaultQuery['order'] );
+ unset( $this->mDefaultQuery['month'] );
+ unset( $this->mDefaultQuery['year'] );
}
return $this->mDefaultQuery;
}
@@ -316,16 +392,16 @@
}
/**
- * Get a query array for the prev, next, first and last links.
+ * Get a URL query array for the prev, next, first and last links.
*/
function getPagingQueries() {
if ( !$this->mQueryDone ) {
$this->doQuery();
}
-
+
# Don't announce the limit everywhere if it's the default
$urlLimit = $this->mLimit == $this->mDefaultLimit ? '' : $this->mLimit;
-
+
if ( $this->mIsFirst ) {
$prev = false;
$first = false;
@@ -354,7 +430,7 @@
$links = array();
foreach ( $queries as $type => $query ) {
if ( $query !== false ) {
- $links[$type] = $this->makeLink( $linkTexts[$type], $queries[$type] );
+ $links[$type] = $this->makeLink( $linkTexts[$type], $queries[$type], $type );
} elseif ( isset( $disabledTexts[$type] ) ) {
$links[$type] = $disabledTexts[$type];
} else {
@@ -374,21 +450,21 @@
}
foreach ( $this->mLimitsShown as $limit ) {
$links[] = $this->makeLink( $wgLang->formatNum( $limit ),
- array( 'offset' => $offset, 'limit' => $limit ) );
+ array( 'offset' => $offset, 'limit' => $limit ), 'num' );
}
return $links;
}
/**
- * Abstract formatting function. This should return an HTML string
+ * Abstract formatting function. This should return an HTML string
* representing the result row $row. Rows will be concatenated and
* returned by getBody()
*/
abstract function formatRow( $row );
/**
- * This function should be overridden to provide all parameters
- * needed for the main paged query. It returns an associative
+ * This function should be overridden to provide all parameters
+ * needed for the main paged query. It returns an associative
* array with the following elements:
* tables => Table(s) for passing to Database::select()
* fields => Field(s) for passing to Database::select(), may be *
@@ -398,54 +474,123 @@
abstract function getQueryInfo();
/**
- * This function should be overridden to return the name of the
- * index field.
+ * This function should be overridden to return the name of the index fi-
+ * eld. If the pager supports multiple orders, it may return an array of
+ * 'querykey' => 'indexfield' pairs, so that a request with &count=querykey
+ * will use indexfield to sort. In this case, the first returned key is
+ * the default.
+ *
+ * Needless to say, it's really not a good idea to use a non-unique index
+ * for this! That won't page right.
*/
abstract function getIndexField();
+
+ /**
+ * Return the default sorting direction: false for ascending, true for de-
+ * scending. You can also have an associative array of ordertype => dir,
+ * if multiple order types are supported. In this case getIndexField()
+ * must return an array, and the keys of that must exactly match the keys
+ * of this.
+ *
+ * For backward compatibility, this method's return value will be ignored
+ * if $this->mDefaultDirection is already set when the constructor is
+ * called, for instance if it's statically initialized. In that case the
+ * value of that variable (which must be a boolean) will be used.
+ *
+ * Note that despite its name, this does not return the value of the
+ * $this->mDefaultDirection member variable. That's the default for this
+ * particular instantiation, which is a single value. This is the set of
+ * all defaults for the class.
+ */
+ protected function getDefaultDirections() { return false; }
}
/**
* IndexPager with an alphabetic list and a formatted navigation bar
- * @addtogroup Pager
+ * @ingroup Pager
*/
abstract class AlphabeticPager extends IndexPager {
- public $mDefaultDirection = false;
-
- function __construct() {
- parent::__construct();
- }
-
- /**
- * Shamelessly stolen bits from ReverseChronologicalPager, d
- * didn't want to do class magic as may be still revamped
+ /**
+ * Shamelessly stolen bits from ReverseChronologicalPager,
+ * didn't want to do class magic as may be still revamped
*/
function getNavigationBar() {
global $wgLang;
-
+
+ if( isset( $this->mNavigationBar ) ) {
+ return $this->mNavigationBar;
+ }
+
+ $opts = array( 'parsemag', 'escapenoentities' );
$linkTexts = array(
- 'prev' => wfMsgHtml( "prevn", $this->mLimit ),
- 'next' => wfMsgHtml( 'nextn', $this->mLimit ),
- 'first' => wfMsgHtml('page_first'), /* Introduced the message */
- 'last' => wfMsgHtml( 'page_last' ) /* Introduced the message */
+ 'prev' => wfMsgExt( 'prevn', $opts, $wgLang->formatNum( $this->mLimit ) ),
+ 'next' => wfMsgExt( 'nextn', $opts, $wgLang->formatNum($this->mLimit ) ),
+ 'first' => wfMsgExt( 'page_first', $opts ),
+ 'last' => wfMsgExt( 'page_last', $opts )
);
-
+
$pagingLinks = $this->getPagingLinks( $linkTexts );
$limitLinks = $this->getLimitLinks();
$limits = implode( ' | ', $limitLinks );
-
- $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits);
+
+ $this->mNavigationBar =
+ "({$pagingLinks['first']} | {$pagingLinks['last']}) " .
+ wfMsgHtml( 'viewprevnext', $pagingLinks['prev'],
+ $pagingLinks['next'], $limits );
+
+ if( !is_array( $this->getIndexField() ) ) {
+ # Early return to avoid undue nesting
+ return $this->mNavigationBar;
+ }
+
+ $extra = '';
+ $first = true;
+ $msgs = $this->getOrderTypeMessages();
+ foreach( array_keys( $msgs ) as $order ) {
+ if( $first ) {
+ $first = false;
+ } else {
+ $extra .= ' | ';
+ }
+
+ if( $order == $this->mOrderType ) {
+ $extra .= wfMsgHTML( $msgs[$order] );
+ } else {
+ $extra .= $this->makeLink(
+ wfMsgHTML( $msgs[$order] ),
+ array( 'order' => $order )
+ );
+ }
+ }
+
+ if( $extra !== '' ) {
+ $this->mNavigationBar .= " ($extra)";
+ }
+
return $this->mNavigationBar;
-
+ }
+
+ /**
+ * If this supports multiple order type messages, give the message key for
+ * enabling each one in getNavigationBar. The return type is an associa-
+ * tive array whose keys must exactly match the keys of the array returned
+ * by getIndexField(), and whose values are message keys.
+ * @return array
+ */
+ protected function getOrderTypeMessages() {
+ return null;
}
}
/**
* IndexPager with a formatted navigation bar
- * @addtogroup Pager
+ * @ingroup Pager
*/
abstract class ReverseChronologicalPager extends IndexPager {
public $mDefaultDirection = true;
+ public $mYear;
+ public $mMonth;
function __construct() {
parent::__construct();
@@ -457,26 +602,74 @@
if ( isset( $this->mNavigationBar ) ) {
return $this->mNavigationBar;
}
+ $nicenumber = $wgLang->formatNum( $this->mLimit );
$linkTexts = array(
- 'prev' => wfMsgHtml( "prevn", $this->mLimit ),
- 'next' => wfMsgHtml( 'nextn', $this->mLimit ),
- 'first' => wfMsgHtml('histlast'),
+ 'prev' => wfMsgExt( 'pager-newer-n', array( 'parsemag' ), $nicenumber ),
+ 'next' => wfMsgExt( 'pager-older-n', array( 'parsemag' ), $nicenumber ),
+ 'first' => wfMsgHtml( 'histlast' ),
'last' => wfMsgHtml( 'histfirst' )
);
$pagingLinks = $this->getPagingLinks( $linkTexts );
$limitLinks = $this->getLimitLinks();
$limits = implode( ' | ', $limitLinks );
-
- $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " .
+
+ $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " .
wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits);
return $this->mNavigationBar;
}
+
+ function getDateCond( $year, $month ) {
+ $year = intval($year);
+ $month = intval($month);
+ // Basic validity checks
+ $this->mYear = $year > 0 ? $year : false;
+ $this->mMonth = ($month > 0 && $month < 13) ? $month : false;
+ // Given an optional year and month, we need to generate a timestamp
+ // to use as "WHERE rev_timestamp <= result"
+ // Examples: year = 2006 equals < 20070101 (+000000)
+ // year=2005, month=1 equals < 20050201
+ // year=2005, month=12 equals < 20060101
+ if ( !$this->mYear && !$this->mMonth ) {
+ return;
+ }
+ if ( $this->mYear ) {
+ $year = $this->mYear;
+ } else {
+ // If no year given, assume the current one
+ $year = gmdate( 'Y' );
+ // If this month hasn't happened yet this year, go back to last year's month
+ if( $this->mMonth > gmdate( 'n' ) ) {
+ $year--;
+ }
+ }
+ if ( $this->mMonth ) {
+ $month = $this->mMonth + 1;
+ // For December, we want January 1 of the next year
+ if ($month > 12) {
+ $month = 1;
+ $year++;
+ }
+ } else {
+ // No month implies we want up to the end of the year in question
+ $month = 1;
+ $year++;
+ }
+ // Y2K38 bug
+ if ( $year > 2032 ) {
+ $year = 2032;
+ }
+ $ymd = (int)sprintf( "%04d%02d01", $year, $month );
+ if ( $ymd > 20320101 ) {
+ $ymd = 20320101;
+ }
+ $this->mOffset = $this->mDb->timestamp( "${ymd}000000" );
+ }
}
/**
* Table-based display with a user-selectable sort order
- * @addtogroup Pager
+ * @ingroup Pager
*/
abstract class TablePager extends IndexPager {
var $mSort;
@@ -501,7 +694,7 @@
global $wgStylePath;
$tableClass = htmlspecialchars( $this->getTableClass() );
$sortClass = htmlspecialchars( $this->getSortHeaderClass() );
-
+
$s = "\n";
$fields = $this->getFieldNames();
@@ -528,7 +721,7 @@
$alt = htmlspecialchars( wfMsg( 'ascending_abbrev' ) );
}
$image = htmlspecialchars( "$wgStylePath/common/images/$image" );
- $link = $this->makeLink(
+ $link = $this->makeLink(
" " .
htmlspecialchars( $name ), $query );
$s .= "$link \n";
@@ -540,7 +733,7 @@
}
}
$s .= " \n";
- return $s;
+ return $s;
}
function getEndBody() {
@@ -646,8 +839,8 @@
}
/**
- * Get elements for use in a method="get" form.
- * Resubmits all defined elements of the $_GET array, except for a
+ * Get elements for use in a method="get" form.
+ * Resubmits all defined elements of the $_GET array, except for a
* blacklist, passed in the $blacklist parameter.
*/
function getHiddenFields( $blacklist = array() ) {
@@ -673,10 +866,10 @@
$url = $this->getTitle()->escapeLocalURL();
$msgSubmit = wfMsgHtml( 'table_pager_limit_submit' );
return
- "\n";
}
@@ -690,7 +883,7 @@
/**
* Format a table cell. The return value should be HTML, but use an empty
- * string not for empty cells. Do not include the and .
+ * string not for empty cells. Do not include the and .
*
* The current result row is available as $this->mCurrentRow, in case you
* need more context.
@@ -712,4 +905,3 @@
*/
abstract function getFieldNames();
}
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/PatrolLog.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/PatrolLog.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/PatrolLog.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/PatrolLog.php Mon Apr 14 03:45:50 2008
@@ -31,7 +31,7 @@
return false;
}
}
-
+
/**
* Generate the log action text corresponding to a patrol log item
*
@@ -67,7 +67,7 @@
return '';
}
}
-
+
/**
* Prepare log parameters for a patrolled change
*
@@ -82,6 +82,4 @@
(int)$auto
);
}
-
}
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Profiler.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Profiler.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Profiler.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Profiler.php Mon Oct 6 03:30:38 2008
@@ -1,31 +1,47 @@
profileIn($functionname);
+ $wgProfiler->profileIn( $functionname );
}
/**
+ * Stop profiling of a function
* @param $functioname name of the function we have profiled
*/
-function wfProfileOut($functionname = 'missing') {
+function wfProfileOut( $functionname = 'missing' ) {
global $wgProfiler;
- $wgProfiler->profileOut($functionname);
+ $wgProfiler->profileOut( $functionname );
}
-function wfGetProfilingOutput($start, $elapsed) {
+/**
+ * Returns a profiling output to be stored in debug file
+ *
+ * @param float $start
+ * @param float $elapsed time elapsed since the beginning of the request
+ */
+function wfGetProfilingOutput( $start, $elapsed ) {
global $wgProfiler;
- return $wgProfiler->getOutput($start, $elapsed);
+ return $wgProfiler->getOutput( $start, $elapsed );
}
+/**
+ * Close opened profiling sections
+ */
function wfProfileClose() {
global $wgProfiler;
$wgProfiler->close();
@@ -39,8 +55,8 @@
}
/**
+ * @ingroup Profiler
* @todo document
- * @addtogroup Profiler
*/
class Profiler {
var $mStack = array (), $mWorkStack = array (), $mCollated = array ();
@@ -57,38 +73,48 @@
}
}
- function profileIn($functionname) {
+ /**
+ * Called by wfProfieIn()
+ * @param $functionname string
+ */
+ function profileIn( $functionname ) {
global $wgDebugFunctionEntry;
- if ($wgDebugFunctionEntry && function_exists('wfDebug')) {
- wfDebug(str_repeat(' ', count($this->mWorkStack)).'Entering '.$functionname."\n");
+
+ if( $wgDebugFunctionEntry ){
+ $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
}
- $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage());
+
+ $this->mWorkStack[] = array( $functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage() );
}
+ /**
+ * Called by wfProfieOut()
+ * @param $functionname string
+ */
function profileOut($functionname) {
+ global $wgDebugFunctionEntry;
+
$memory = memory_get_usage();
$time = $this->getTime();
- global $wgDebugFunctionEntry;
-
- if ($wgDebugFunctionEntry && function_exists('wfDebug')) {
- wfDebug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
+ if( $wgDebugFunctionEntry ){
+ $this->debug( str_repeat( ' ', count( $this->mWorkStack ) - 1 ) . 'Exiting ' . $functionname . "\n" );
}
$bit = array_pop($this->mWorkStack);
if (!$bit) {
- wfDebug("Profiling error, !\$bit: $functionname\n");
+ $this->debug("Profiling error, !\$bit: $functionname\n");
} else {
- //if ($wgDebugProfiling) {
- if ($functionname == 'close') {
+ //if( $wgDebugProfiling ){
+ if( $functionname == 'close' ){
$message = "Profile section ended by close(): {$bit[0]}";
- wfDebug( "$message\n" );
+ $this->debug( "$message\n" );
$this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
}
- elseif ($bit[0] != $functionname) {
+ elseif( $bit[0] != $functionname ){
$message = "Profiling error: in({$bit[0]}), out($functionname)";
- wfDebug( "$message\n" );
+ $this->debug( "$message\n" );
$this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
}
//}
@@ -98,70 +124,86 @@
}
}
+ /**
+ * called by wfProfileClose()
+ */
function close() {
- while (count($this->mWorkStack)) {
- $this->profileOut('close');
+ while( count( $this->mWorkStack ) ){
+ $this->profileOut( 'close' );
}
}
+ /**
+ * called by wfGetProfilingOutput()
+ */
function getOutput() {
- global $wgDebugFunctionEntry;
+ global $wgDebugFunctionEntry, $wgProfileCallTree;
$wgDebugFunctionEntry = false;
- if (!count($this->mStack) && !count($this->mCollated)) {
+ if( !count( $this->mStack ) && !count( $this->mCollated ) ){
return "No profiling output\n";
}
$this->close();
- global $wgProfileCallTree;
- if ($wgProfileCallTree) {
+ if( $wgProfileCallTree ){
return $this->getCallTree();
} else {
return $this->getFunctionReport();
}
}
- function getCallTree($start = 0) {
- return implode('', array_map(array (& $this, 'getCallTreeLine'), $this->remapCallTree($this->mStack)));
+ /**
+ * returns a tree of function call instead of a list of functions
+ */
+ function getCallTree() {
+ return implode( '', array_map( array( &$this, 'getCallTreeLine' ), $this->remapCallTree( $this->mStack ) ) );
}
- function remapCallTree($stack) {
- if (count($stack) < 2) {
+ /**
+ * Recursive function the format the current profiling array into a tree
+ *
+ * @param $stack profiling array
+ */
+ function remapCallTree( $stack ) {
+ if( count( $stack ) < 2 ){
return $stack;
}
$outputs = array ();
- for ($max = count($stack) - 1; $max > 0;) {
+ for( $max = count( $stack ) - 1; $max > 0; ){
/* Find all items under this entry */
$level = $stack[$max][1];
$working = array ();
- for ($i = $max -1; $i >= 0; $i --) {
- if ($stack[$i][1] > $level) {
+ for( $i = $max -1; $i >= 0; $i-- ){
+ if( $stack[$i][1] > $level ){
$working[] = $stack[$i];
} else {
break;
}
}
- $working = $this->remapCallTree(array_reverse($working));
- $output = array ();
- foreach ($working as $item) {
- array_push($output, $item);
+ $working = $this->remapCallTree( array_reverse( $working ) );
+ $output = array();
+ foreach( $working as $item ){
+ array_push( $output, $item );
}
- array_unshift($output, $stack[$max]);
+ array_unshift( $output, $stack[$max] );
$max = $i;
- array_unshift($outputs, $output);
+ array_unshift( $outputs, $output );
}
- $final = array ();
- foreach ($outputs as $output) {
- foreach ($output as $item) {
+ $final = array();
+ foreach( $outputs as $output ){
+ foreach( $output as $item ){
$final[] = $item;
}
}
return $final;
}
+ /**
+ * Callback to get a formatted line for the call tree
+ */
function getCallTreeLine($entry) {
- list ($fname, $level, $start, /* $x */, $end) = $entry;
+ list( $fname, $level, $start, /* $x */, $end) = $entry;
$delta = $end - $start;
$space = str_repeat(' ', $level);
@@ -182,66 +224,72 @@
return $ru['ru_utime.tv_sec'].' '.$ru['ru_utime.tv_usec'] / 1e6;
}
+ /**
+ * Returns a list of profiled functions.
+ * Also log it into the database if $wgProfileToDatabase is set to true.
+ */
function getFunctionReport() {
+ global $wgProfileToDatabase;
+
$width = 140;
$nameWidth = $width - 65;
$format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
$titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
$prof = "\nProfiling data\n";
- $prof .= sprintf($titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem');
+ $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
$this->mCollated = array ();
$this->mCalls = array ();
$this->mMemory = array ();
# Estimate profiling overhead
$profileCount = count($this->mStack);
- wfProfileIn('-overhead-total');
- for ($i = 0; $i < $profileCount; $i ++) {
- wfProfileIn('-overhead-internal');
- wfProfileOut('-overhead-internal');
+ wfProfileIn( '-overhead-total' );
+ for( $i = 0; $i < $profileCount; $i ++ ){
+ wfProfileIn( '-overhead-internal' );
+ wfProfileOut( '-overhead-internal' );
}
- wfProfileOut('-overhead-total');
+ wfProfileOut( '-overhead-total' );
# First, subtract the overhead!
- foreach ($this->mStack as $entry) {
+ foreach( $this->mStack as $entry ){
$fname = $entry[0];
$start = $entry[2];
$end = $entry[4];
$elapsed = $end - $start;
$memory = $entry[5] - $entry[3];
- if ($fname == '-overhead-total') {
+ if( $fname == '-overhead-total' ){
$overheadTotal[] = $elapsed;
$overheadMemory[] = $memory;
}
- elseif ($fname == '-overhead-internal') {
+ elseif( $fname == '-overhead-internal' ){
$overheadInternal[] = $elapsed;
}
}
- $overheadTotal = array_sum($overheadTotal) / count($overheadInternal);
- $overheadMemory = array_sum($overheadMemory) / count($overheadInternal);
- $overheadInternal = array_sum($overheadInternal) / count($overheadInternal);
+ $overheadTotal = array_sum( $overheadTotal ) / count( $overheadInternal );
+ $overheadMemory = array_sum( $overheadMemory ) / count( $overheadInternal );
+ $overheadInternal = array_sum( $overheadInternal ) / count( $overheadInternal );
# Collate
- foreach ($this->mStack as $index => $entry) {
+ foreach( $this->mStack as $index => $entry ){
$fname = $entry[0];
$start = $entry[2];
$end = $entry[4];
$elapsed = $end - $start;
$memory = $entry[5] - $entry[3];
- $subcalls = $this->calltreeCount($this->mStack, $index);
+ $subcalls = $this->calltreeCount( $this->mStack, $index );
- if (!preg_match('/^-overhead/', $fname)) {
+ if( !preg_match( '/^-overhead/', $fname ) ){
# Adjust for profiling overhead (except special values with elapsed=0
- if ( $elapsed ) {
+ if( $elapsed ) {
$elapsed -= $overheadInternal;
$elapsed -= ($subcalls * $overheadTotal);
$memory -= ($subcalls * $overheadMemory);
}
}
- if (!array_key_exists($fname, $this->mCollated)) {
+ if( !array_key_exists( $fname, $this->mCollated ) ){
$this->mCollated[$fname] = 0;
$this->mCalls[$fname] = 0;
$this->mMemory[$fname] = 0;
@@ -258,20 +306,19 @@
$this->mOverhead[$fname] += $subcalls;
}
- $total = @ $this->mCollated['-total'];
+ $total = @$this->mCollated['-total'];
$this->mCalls['-overhead-total'] = $profileCount;
# Output
- arsort($this->mCollated, SORT_NUMERIC);
- foreach ($this->mCollated as $fname => $elapsed) {
+ arsort( $this->mCollated, SORT_NUMERIC );
+ foreach( $this->mCollated as $fname => $elapsed ){
$calls = $this->mCalls[$fname];
$percent = $total ? 100. * $elapsed / $total : 0;
$memory = $this->mMemory[$fname];
$prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]);
- global $wgProfileToDatabase;
- if ($wgProfileToDatabase) {
- Profiler :: logToDB($fname, (float) ($elapsed * 1000), $calls);
+ if( $wgProfileToDatabase ){
+ self::logToDB($fname, (float) ($elapsed * 1000), $calls, (float) ($memory) );
}
}
$prof .= "\nTotal: $total\n\n";
@@ -298,39 +345,53 @@
}
/**
- * @static
+ * Log a function into the database.
+ *
+ * @param $name string: function name
+ * @param $timeSum float
+ * @param $eventCount int: number of times that function was called
*/
- function logToDB($name, $timeSum, $eventCount) {
+ static function logToDB( $name, $timeSum, $eventCount, $memorySum ){
# Do not log anything if database is readonly (bug 5375)
if( wfReadOnly() ) { return; }
- # Warning: $wguname is a live patch, it should be moved to Setup.php
- global $wguname, $wgProfilePerHost;
+ global $wgProfilePerHost;
- $fname = 'Profiler::logToDB';
- $dbw = wfGetDB(DB_MASTER);
- if (!is_object($dbw))
+ $dbw = wfGetDB( DB_MASTER );
+ if( !is_object( $dbw ) )
return false;
$errorState = $dbw->ignoreErrors( true );
- $profiling = $dbw->tableName('profiling');
$name = substr($name, 0, 255);
- $encname = $dbw->strencode($name);
-
- if ($wgProfilePerHost) {
- $pfhost = $wguname['nodename'];
+
+ if( $wgProfilePerHost ){
+ $pfhost = wfHostname();
} else {
$pfhost = '';
}
-
- $sql = "UPDATE $profiling "."SET pf_count=pf_count+{$eventCount}, "."pf_time=pf_time + {$timeSum} ".
- "WHERE pf_name='{$encname}' AND pf_server='{$pfhost}'";
- $dbw->query($sql);
+
+ // Kludge
+ $timeSum = ($timeSum >= 0) ? $timeSum : 0;
+ $memorySum = ($memorySum >= 0) ? $memorySum : 0;
+
+ $dbw->update( 'profiling',
+ array(
+ "pf_count=pf_count+{$eventCount}",
+ "pf_time=pf_time+{$timeSum}",
+ "pf_memory=pf_memory+{$memorySum}",
+ ),
+ array(
+ 'pf_name' => $name,
+ 'pf_server' => $pfhost,
+ ),
+ __METHOD__ );
+
$rc = $dbw->affectedRows();
if ($rc == 0) {
$dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount,
- 'pf_time' => $timeSum, 'pf_server' => $pfhost ), $fname, array ('IGNORE'));
+ 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
+ __METHOD__, array ('IGNORE'));
}
// When we upgrade to mysql 4.1, the insert+update
// can be merged into just a insert with this construct added:
@@ -344,10 +405,14 @@
* Get the function name of the current profiling section
*/
function getCurrentSection() {
- $elt = end($this->mWorkStack);
+ $elt = end( $this->mWorkStack );
return $elt[0];
}
-
+
+ /**
+ * Get function caller
+ * @param $level int
+ */
static function getCaller( $level ) {
$backtrace = wfDebugBacktrace();
if ( isset( $backtrace[$level] ) ) {
@@ -362,6 +427,13 @@
return $caller;
}
+ /**
+ * Add an entry in the debug log file
+ * @param $s string to output
+ */
+ function debug( $s ) {
+ if( function_exists( 'wfDebug' ) ) {
+ wfDebug( $s );
+ }
+ }
}
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/ProfilerSimple.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/ProfilerSimple.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/ProfilerSimple.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/ProfilerSimple.php Tue May 20 13:13:28 2008
@@ -1,18 +1,22 @@
mWorkStack[] = array( '-total', 0, $wgRequestTime,$this->getCpuTime($wgRUstart));
@@ -72,10 +76,14 @@
$message = "Profile section ended by close(): {$ofname}";
$functionname = $ofname;
$this->debug( "$message\n" );
+ $this->mCollated[$message] = array(
+ 'real' => 0.0, 'count' => 1);
}
elseif ($ofname != $functionname) {
$message = "Profiling error: in({$ofname}), out($functionname)";
$this->debug( "$message\n" );
+ $this->mCollated[$message] = array(
+ 'real' => 0.0, 'count' => 1);
}
$entry =& $this->mCollated[$functionname];
$elapsedcpu = $this->getCpuTime() - $octime;
@@ -101,7 +109,7 @@
if ( function_exists( 'getrusage' ) ) {
if ( $ru == null )
$ru = getrusage();
- return ($ru['ru_utime.tv_sec'] + $ru['ru_stime.tv_sec'] + ($ru['ru_utime.tv_usec'] +
+ return ($ru['ru_utime.tv_sec'] + $ru['ru_stime.tv_sec'] + ($ru['ru_utime.tv_usec'] +
$ru['ru_stime.tv_usec']) * 1e-6);
} else {
return 0;
@@ -115,11 +123,4 @@
list($a,$b)=explode(" ",$time);
return (float)($a+$b);
}
-
- function debug( $s ) {
- if (function_exists( 'wfDebug' ) ) {
- wfDebug( $s );
- }
- }
}
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/ProfilerSimpleUDP.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/ProfilerSimpleUDP.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/ProfilerSimpleUDP.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/ProfilerSimpleUDP.php Tue May 20 13:13:28 2008
@@ -1,17 +1,19 @@
mCollated['-total']['real'] < $this->mMinimumTime ) {
# Less than minimum, ignore
@@ -37,4 +39,3 @@
socket_sendto($sock,$packet,$plength,0x100,$wgUDPProfilerHost,$wgUDPProfilerPort);
}
}
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/ProfilerStub.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/ProfilerStub.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/ProfilerStub.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/ProfilerStub.php Tue May 20 13:13:28 2008
@@ -1,26 +1,48 @@
mArticle =& $article;
- $this->mTitle =& $article->mTitle;
+ $this->mArticle = $article;
+ $this->mTitle = $article->mTitle;
+ $this->mApplicableTypes = $this->mTitle->exists() ? $wgRestrictionTypes : array('create');
- if( $this->mTitle ) {
- $this->mTitle->loadRestrictions();
+ $this->mCascade = $this->mTitle->areRestrictionsCascading();
- foreach( $wgRestrictionTypes as $action ) {
- // Fixme: this form currently requires individual selections,
- // but the db allows multiples separated by commas.
- $this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
+ // The form will be available in read-only to show levels.
+ $this->mPermErrors = $this->mTitle->getUserPermissionsErrors('protect',$wgUser);
+ $this->disabled = wfReadOnly() || $this->mPermErrors != array();
+ $this->disabledAttrib = $this->disabled
+ ? array( 'disabled' => 'disabled' )
+ : array();
+
+ $this->mReason = $wgRequest->getText( 'mwProtect-reason' );
+ $this->mReasonSelection = $wgRequest->getText( 'wpProtectReasonSelection' );
+ $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade', $this->mCascade );
+
+ foreach( $this->mApplicableTypes as $action ) {
+ // Fixme: this form currently requires individual selections,
+ // but the db allows multiples separated by commas.
+ $this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
+
+ if ( !$this->mRestrictions[$action] ) {
+ // No existing expiry
+ $existingExpiry = '';
+ } else {
+ $existingExpiry = $this->mTitle->getRestrictionExpiry( $action );
}
+ $this->mExistingExpiry[$action] = $existingExpiry;
- $this->mCascade = $this->mTitle->areRestrictionsCascading();
+ $requestExpiry = $wgRequest->getText( "mwProtect-expiry-$action" );
+ $requestExpirySelection = $wgRequest->getVal( "wpProtectExpirySelection-$action" );
- if ( $this->mTitle->mRestrictionsExpiry == 'infinity' ) {
- $this->mExpiry = 'infinite';
- } else if ( strlen($this->mTitle->mRestrictionsExpiry) == 0 ) {
- $this->mExpiry = '';
+ if ( $requestExpiry ) {
+ // Custom expiry takes precedence
+ $this->mExpiry[$action] = $requestExpiry;
+ $this->mExpirySelection[$action] = 'othertime';
+ } elseif ( $requestExpirySelection ) {
+ // Expiry selected from list
+ $this->mExpiry[$action] = '';
+ $this->mExpirySelection[$action] = $requestExpirySelection;
+ } elseif ( $existingExpiry == 'infinity' ) {
+ // Existing expiry is infinite, use "infinite" in drop-down
+ $this->mExpiry[$action] = '';
+ $this->mExpirySelection[$action] = 'infinite';
+ } elseif ( $existingExpiry ) {
+ // Use existing expiry in its own list item
+ $this->mExpiry[$action] = '';
+ $this->mExpirySelection[$action] = $existingExpiry;
} else {
- $this->mExpiry = wfTimestamp( TS_RFC2822, $this->mTitle->mRestrictionsExpiry );
+ // Final default: infinite
+ $this->mExpiry[$action] = '';
+ $this->mExpirySelection[$action] = 'infinite';
+ }
+
+ $val = $wgRequest->getVal( "mwProtect-level-$action" );
+ if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
+ // Prevent users from setting levels that they cannot later unset
+ if( $val == 'sysop' ) {
+ // Special case, rewrite sysop to either protect and editprotected
+ if( !$wgUser->isAllowed('protect') && !$wgUser->isAllowed('editprotected') )
+ continue;
+ } else {
+ if( !$wgUser->isAllowed($val) )
+ continue;
+ }
+ $this->mRestrictions[$action] = $val;
}
}
+ }
- // The form will be available in read-only to show levels.
- $this->disabled = !$wgUser->isAllowed( 'protect' ) || wfReadOnly() || $wgUser->isBlocked();
- $this->disabledAttrib = $this->disabled
- ? array( 'disabled' => 'disabled' )
- : array();
+ /**
+ * Get the expiry time for a given action, by combining the relevant inputs.
+ * Returns a 14-char timestamp or "infinity", or false if the input was invalid
+ */
+ function getExpiry( $action ) {
+ if ( $this->mExpirySelection[$action] == 'existing' ) {
+ return $this->mExistingExpiry[$action];
+ } elseif ( $this->mExpirySelection[$action] == 'othertime' ) {
+ $value = $this->mExpiry[$action];
+ } else {
+ $value = $this->mExpirySelection[$action];
+ }
+ if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) {
+ $time = Block::infinity();
+ } else {
+ $unix = strtotime( $value );
- if( $wgRequest->wasPosted() ) {
- $this->mReason = $wgRequest->getText( 'mwProtect-reason' );
- $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
- $this->mExpiry = $wgRequest->getText( 'mwProtect-expiry' );
-
- foreach( $wgRestrictionTypes as $action ) {
- $val = $wgRequest->getVal( "mwProtect-level-$action" );
- if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
- $this->mRestrictions[$action] = $val;
- }
+ if ( !$unix || $unix === -1 ) {
+ return false;
}
+
+ // Fixme: non-qualified absolute times are not in users specified timezone
+ // and there isn't notice about it in the ui
+ $time = wfTimestamp( TS_MW, $unix );
}
+ return $time;
}
-
+
function execute() {
global $wgRequest, $wgOut;
if( $wgRequest->wasPosted() ) {
if( $this->save() ) {
- $article = new Article( $this->mTitle );
- $q = $article->isRedirect() ? 'redirect=no' : '';
+ $q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
$wgOut->redirect( $this->mTitle->getFullUrl( $q ) );
}
} else {
@@ -91,10 +170,9 @@
function show( $err = null ) {
global $wgOut, $wgUser;
- $wgOut->setRobotpolicy( 'noindex,nofollow' );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
if( is_null( $this->mTitle ) ||
- !$this->mTitle->exists() ||
$this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
$wgOut->showFatalError( wfMsg( 'badarticleerror' ) );
return;
@@ -114,33 +192,25 @@
$titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
}
- $notice = wfMsgExt( 'protect-cascadeon', array('parsemag'), count($cascadeSources) ) . "\r\n$titles";
-
- $wgOut->addWikiText( $notice );
+ $wgOut->wrapWikiMsg( "$1\n$titles", array( 'protect-cascadeon', count($cascadeSources) ) );
}
- $wgOut->setPageTitle( wfMsg( 'confirmprotect' ) );
- $wgOut->setSubtitle( wfMsg( 'protectsub', $this->mTitle->getPrefixedText() ) );
+ $sk = $wgUser->getSkin();
+ $titleLink = $sk->makeLinkObj( $this->mTitle );
+ $wgOut->setPageTitle( wfMsg( 'protect-title', $this->mTitle->getPrefixedText() ) );
+ $wgOut->setSubtitle( wfMsg( 'protect-backlink', $titleLink ) );
# Show an appropriate message if the user isn't allowed or able to change
# the protection settings at this time
if( $this->disabled ) {
- if( $wgUser->isAllowed( 'protect' ) ) {
- if( $wgUser->isBlocked() ) {
- # Blocked
- $message = 'protect-locked-blocked';
- } else {
- # Database lock
- $message = 'protect-locked-dblock';
- }
- } else {
- # Permission error
- $message = 'protect-locked-access';
+ if( wfReadOnly() ) {
+ $wgOut->readOnlyPage();
+ } elseif( $this->mPermErrors ) {
+ $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $this->mPermErrors ) );
}
} else {
- $message = 'protect-text';
+ $wgOut->addWikiMsg( 'protect-text', $this->mTitle->getPrefixedText() );
}
- $wgOut->addWikiText( wfMsg( $message, wfEscapeWikiText( $this->mTitle->getPrefixedText() ) ) );
$wgOut->addHTML( $this->buildForm() );
@@ -149,138 +219,276 @@
function save() {
global $wgRequest, $wgUser, $wgOut;
-
- if( $this->disabled ) {
+ # Permission check!
+ if ( $this->disabled ) {
$this->show();
return false;
}
$token = $wgRequest->getVal( 'wpEditToken' );
- if( !$wgUser->matchEditToken( $token ) ) {
+ if ( !$wgUser->matchEditToken( $token ) ) {
$this->show( wfMsg( 'sessionfailure' ) );
return false;
}
-
- if ( strlen( $this->mExpiry ) == 0 ) {
- $this->mExpiry = 'infinite';
- }
-
- if ( $this->mExpiry == 'infinite' || $this->mExpiry == 'indefinite' ) {
- $expiry = Block::infinity();
- } else {
- # Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1
- $expiry = strtotime( $this->mExpiry );
-
- if ( $expiry < 0 || $expiry === false ) {
+
+ # Create reason string. Use list and/or custom string.
+ $reasonstr = $this->mReasonSelection;
+ if ( $reasonstr != 'other' && $this->mReason != '' ) {
+ // Entry from drop down menu + additional comment
+ $reasonstr .= ': ' . $this->mReason;
+ } elseif ( $reasonstr == 'other' ) {
+ $reasonstr = $this->mReason;
+ }
+ $expiry = array();
+ foreach( $this->mApplicableTypes as $action ) {
+ $expiry[$action] = $this->getExpiry( $action );
+ if( empty($this->mRestrictions[$action]) )
+ continue; // unprotected
+ if ( !$expiry[$action] ) {
$this->show( wfMsg( 'protect_expiry_invalid' ) );
return false;
}
-
- $expiry = wfTimestamp( TS_MW, $expiry );
-
- if ( $expiry < wfTimestampNow() ) {
+ if ( $expiry[$action] < wfTimestampNow() ) {
$this->show( wfMsg( 'protect_expiry_old' ) );
return false;
}
+ }
+
+ # They shouldn't be able to do this anyway, but just to make sure, ensure that cascading restrictions aren't being applied
+ # to a semi-protected page.
+ global $wgGroupPermissions;
+
+ $edit_restriction = $this->mRestrictions['edit'];
+ $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
+ if ($this->mCascade && ($edit_restriction != 'protect') &&
+ !(isset($wgGroupPermissions[$edit_restriction]['protect']) && $wgGroupPermissions[$edit_restriction]['protect'] ) )
+ $this->mCascade = false;
+ if ($this->mTitle->exists()) {
+ $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $reasonstr, $this->mCascade, $expiry );
+ } else {
+ $ok = $this->mTitle->updateTitleProtection( $this->mRestrictions['create'], $reasonstr, $expiry['create'] );
}
- $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason, $this->mCascade, $expiry );
if( !$ok ) {
throw new FatalError( "Unknown error at restriction save time." );
}
-
+
if( $wgRequest->getCheck( 'mwProtectWatch' ) ) {
$this->mArticle->doWatch();
} elseif( $this->mTitle->userIsWatching() ) {
$this->mArticle->doUnwatch();
}
-
return $ok;
}
+ /**
+ * Build the input form
+ *
+ * @return $out string HTML form
+ */
function buildForm() {
- global $wgUser;
+ global $wgUser, $wgLang;
+
+ $mProtectreasonother = Xml::label( wfMsg( 'protectcomment' ), 'wpProtectReasonSelection' );
+ $mProtectreason = Xml::label( wfMsg( 'protect-otherreason' ), 'mwProtect-reason' );
$out = '';
if( !$this->disabled ) {
$out .= $this->buildScript();
- // The submission needs to reenable the move permission selector
- // if it's in locked mode, or some browsers won't submit the data.
- $out .= wfOpenElement( 'form', array(
- 'id' => 'mw-Protect-Form',
- 'action' => $this->mTitle->getLocalUrl( 'action=protect' ),
- 'method' => 'post',
- 'onsubmit' => 'protectEnable(true)' ) );
-
- $out .= wfElement( 'input', array(
- 'type' => 'hidden',
- 'name' => 'wpEditToken',
- 'value' => $wgUser->editToken() ) );
- }
-
- $out .= "";
- $out .= "";
- $out .= "\n";
- foreach( $this->mRestrictions as $action => $required ) {
- /* Not all languages have V_x <-> N_x relation */
- $out .= "" . wfMsgHtml( 'restriction-' . $action ) . " \n";
+ $out .= Xml::openElement( 'form', array( 'method' => 'post',
+ 'action' => $this->mTitle->getLocalUrl( 'action=protect' ),
+ 'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
+ $out .= Xml::hidden( 'wpEditToken',$wgUser->editToken() );
}
- $out .= " \n";
- $out .= "\n";
- foreach( $this->mRestrictions as $action => $selected ) {
- $out .= "\n";
- $out .= $this->buildSelector( $action, $selected );
- $out .= " \n";
- }
- $out .= " \n";
-
- // JavaScript will add another row with a value-chaining checkbox
-
- $out .= " \n";
- $out .= "
\n";
- $out .= "\n";
- $out .= "\n";
+ $out .= Xml::openElement( 'fieldset' ) .
+ Xml::element( 'legend', null, wfMsg( 'protect-legend' ) ) .
+ Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
+ Xml::openElement( 'tbody' );
- global $wgEnableCascadingProtection;
- if( $wgEnableCascadingProtection )
- $out .= '' . $this->buildCascadeInput() . " \n";
+ foreach( $this->mRestrictions as $action => $selected ) {
+ /* Not all languages have V_x <-> N_x relation */
+ $msg = wfMsg( 'restriction-' . $action );
+ if( wfEmptyMsg( 'restriction-' . $action, $msg ) ) {
+ $msg = $action;
+ }
+ $out .= "".
+ Xml::openElement( 'fieldset' ) .
+ Xml::element( 'legend', null, $msg ) .
+ Xml::openElement( 'table', array( 'id' => "mw-protect-table-$action" ) ) .
+ " " . $this->buildSelector( $action, $selected ) . " ";
+
+ $reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
+ wfMsgForContent( 'protect-dropdown' ),
+ wfMsgForContent( 'protect-otherreason-op' ),
+ $this->mReasonSelection,
+ 'mwProtect-reason', 4 );
+ $scExpiryOptions = wfMsgForContent( 'protect-expiry-options' );
+
+ $showProtectOptions = ($scExpiryOptions !== '-' && !$this->disabled);
+
+ $mProtectexpiry = Xml::label( wfMsg( 'protectexpiry' ), "mwProtectExpirySelection-$action" );
+ $mProtectother = Xml::label( wfMsg( 'protect-othertime' ), "mwProtect-$action-expires" );
+
+ $expiryFormOptions = '';
+ if ( $this->mExistingExpiry[$action] && $this->mExistingExpiry[$action] != 'infinity' ) {
+ $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action] );
+ $d = $wgLang->date( $this->mExistingExpiry[$action] );
+ $t = $wgLang->time( $this->mExistingExpiry[$action] );
+ $expiryFormOptions .=
+ Xml::option(
+ wfMsg( 'protect-existing-expiry', $timestamp, $d, $t ),
+ 'existing',
+ $this->mExpirySelection[$action] == 'existing'
+ ) . "\n";
+ }
+
+ $expiryFormOptions .= Xml::option( wfMsg( 'protect-othertime-op' ), "othertime" ) . "\n";
+ foreach( explode(',', $scExpiryOptions) as $option ) {
+ if ( strpos($option, ":") === false ) {
+ $show = $value = $option;
+ } else {
+ list($show, $value) = explode(":", $option);
+ }
+ $show = htmlspecialchars($show);
+ $value = htmlspecialchars($value);
+ $expiryFormOptions .= Xml::option( $show, $value, $this->mExpirySelection[$action] === $value ) . "\n";
+ }
+ # Add expiry dropdown
+ if( $showProtectOptions && !$this->disabled ) {
+ $out .= "
+
+
+ {$mProtectexpiry}
+
+ " .
+ Xml::tags( 'select',
+ array(
+ 'id' => "mwProtectExpirySelection-$action",
+ 'name' => "wpProtectExpirySelection-$action",
+ 'onchange' => "ProtectionForm.updateExpiryList(this)",
+ 'tabindex' => '2' ) + $this->disabledAttrib,
+ $expiryFormOptions ) .
+ "
+
";
+ }
+ # Add custom expiry field
+ $attribs = array( 'id' => "mwProtect-$action-expires", 'onkeyup' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib;
+ $out .= "
+ " .
+ $mProtectother .
+ '
+ ' .
+ Xml::input( "mwProtect-expiry-$action", 50, $this->mExpiry[$action], $attribs ) .
+ '
+
';
+ $out .= " " .
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'fieldset' ) .
+ "";
+ }
- $out .= $this->buildExpiryInput();
+ $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
+ // JavaScript will add another row with a value-chaining checkbox
+ if( $this->mTitle->exists() ) {
+ $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table2' ) ) .
+ Xml::openElement( 'tbody' );
+ $out .= '
+
+ ' .
+ Xml::checkLabel( wfMsg( 'protect-cascade' ), 'mwProtect-cascade', 'mwProtect-cascade',
+ $this->mCascade, $this->disabledAttrib ) .
+ "
+ \n";
+ $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
+ }
+
+ # Add manual and custom reason field/selects as well as submit
if( !$this->disabled ) {
- $out .= "" . $this->buildReasonInput() . " \n";
- $out .= "" . $this->buildWatchInput() . " \n";
- $out .= "" . $this->buildSubmit() . " \n";
+ $out .= Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
+ Xml::openElement( 'tbody' );
+ $out .= "
+
+
+ {$mProtectreasonother}
+
+
+ {$reasonDropDown}
+
+
+
+
+ {$mProtectreason}
+
+ " .
+ Xml::input( 'mwProtect-reason', 60, $this->mReason, array( 'type' => 'text',
+ 'id' => 'mwProtect-reason', 'maxlength' => 255 ) ) .
+ "
+
+
+
+ " .
+ Xml::checkLabel( wfMsg( 'watchthis' ),
+ 'mwProtectWatch', 'mwProtectWatch',
+ $this->mTitle->userIsWatching() || $wgUser->getOption( 'watchdefault' ) ) .
+ "
+
+
+
+ " .
+ Xml::submitButton( wfMsg( 'confirm' ), array( 'id' => 'mw-Protect-submit' ) ) .
+ "
+ \n";
+ $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
+ }
+ $out .= Xml::closeElement( 'fieldset' );
+
+ if ( $wgUser->isAllowed( 'editinterface' ) ) {
+ $linkTitle = Title::makeTitleSafe( NS_MEDIAWIKI, 'protect-dropdown' );
+ $link = $wgUser->getSkin()->Link ( $linkTitle, wfMsgHtml( 'protect-edit-reasonlist' ) );
+ $out .= '' . $link . '
';
}
- $out .= " \n";
- $out .= "
\n";
-
if ( !$this->disabled ) {
- $out .= "\n";
- $out .= $this->buildCleanupScript();
+ $out .= Xml::closeElement( 'form' ) .
+ $this->buildCleanupScript();
}
return $out;
}
function buildSelector( $action, $selected ) {
- global $wgRestrictionLevels;
+ global $wgRestrictionLevels, $wgUser;
+
+ $levels = array();
+ foreach( $wgRestrictionLevels as $key ) {
+ //don't let them choose levels above their own (aka so they can still unprotect and edit the page). but only when the form isn't disabled
+ if( $key == 'sysop' ) {
+ //special case, rewrite sysop to protect and editprotected
+ if( !$wgUser->isAllowed('protect') && !$wgUser->isAllowed('editprotected') && !$this->disabled )
+ continue;
+ } else {
+ if( !$wgUser->isAllowed($key) && !$this->disabled )
+ continue;
+ }
+ $levels[] = $key;
+ }
+
$id = 'mwProtect-level-' . $action;
$attribs = array(
'id' => $id,
'name' => $id,
- 'size' => count( $wgRestrictionLevels ),
- 'onchange' => 'protectLevelsUpdate(this)',
+ 'size' => count( $levels ),
+ 'onchange' => 'ProtectionForm.updateLevels(this)',
) + $this->disabledAttrib;
- $out = wfOpenElement( 'select', $attribs );
- foreach( $wgRestrictionLevels as $key ) {
+ $out = Xml::openElement( 'select', $attribs );
+ foreach( $levels as $key ) {
$out .= Xml::option( $this->getOptionLabel( $key ), $key, $key == $selected );
}
- $out .= "\n";
+ $out .= Xml::closeElement( 'select' );
return $out;
}
@@ -302,56 +510,11 @@
}
}
- function buildReasonInput() {
- $id = 'mwProtect-reason';
- return wfElement( 'label', array(
- 'id' => "$id-label",
- 'for' => $id ),
- wfMsg( 'protectcomment' ) ) .
- '' .
- wfElement( 'input', array(
- 'size' => 60,
- 'name' => $id,
- 'id' => $id,
- 'value' => $this->mReason ) );
- }
-
- function buildCascadeInput() {
- $id = 'mwProtect-cascade';
- $ci = wfCheckLabel( wfMsg( 'protect-cascade' ), $id, $id, $this->mCascade, $this->disabledAttrib);
- return $ci;
- }
-
- function buildExpiryInput() {
- $attribs = array( 'id' => 'expires' ) + $this->disabledAttrib;
- return ' '
- . '' . wfMsgExt( 'protectexpiry', array( 'parseinline' ) ) . ' '
- . '' . Xml::input( 'mwProtect-expiry', 60, $this->mExpiry, $attribs ) . ' '
- . ' ';
- }
-
- function buildWatchInput() {
- global $wgUser;
- return Xml::checkLabel(
- wfMsg( 'watchthis' ),
- 'mwProtectWatch',
- 'mwProtectWatch',
- $this->mTitle->userIsWatching() || $wgUser->getOption( 'watchdefault' )
- );
- }
-
- function buildSubmit() {
- return wfElement( 'input', array(
- 'id' => 'mw-Protect-submit',
- 'type' => 'submit',
- 'value' => wfMsg( 'confirm' ) ) );
- }
-
function buildScript() {
global $wgStylePath, $wgStyleVersion;
- return '';
+ return Xml::tags( 'script', array(
+ 'type' => 'text/javascript',
+ 'src' => $wgStylePath . "/common/protect.js?$wgStyleVersion.1" ), '' );
}
function buildCleanupScript() {
@@ -359,13 +522,21 @@
$script = 'var wgCascadeableLevels=';
$CascadeableLevels = array();
foreach( $wgRestrictionLevels as $key ) {
- if ( isset($wgGroupPermissions[$key]['protect']) && $wgGroupPermissions[$key]['protect'] ) {
- $CascadeableLevels[]="'" . wfEscapeJsString($key) . "'";
+ if ( (isset($wgGroupPermissions[$key]['protect']) && $wgGroupPermissions[$key]['protect']) || $key == 'protect' ) {
+ $CascadeableLevels[] = "'" . Xml::escapeJsString( $key ) . "'";
}
}
$script .= "[" . implode(',',$CascadeableLevels) . "];\n";
- $script .= 'protectInitialize("mwProtectSet","' . wfEscapeJsString( wfMsg( 'protect-unchain' ) ) . '")';
- return '';
+ $options = (object)array(
+ 'tableId' => 'mw-protect-table-move',
+ 'labelText' => wfMsg( 'protect-unchain' ),
+ 'numTypes' => count($this->mApplicableTypes),
+ 'existingMatch' => 1 == count( array_unique( $this->mExistingExpiry ) ),
+ );
+ $encOptions = Xml::encodeJsVar( $options );
+
+ $script .= "ProtectionForm.init($encOptions)";
+ return Xml::tags( 'script', array( 'type' => 'text/javascript' ), $script );
}
/**
@@ -374,13 +545,7 @@
*/
function showLogExtract( &$out ) {
# Show relevant lines from the protection log:
- $out->addHTML( "" . htmlspecialchars( LogPage::logName( 'protect' ) ) . " \n" );
- $logViewer = new LogViewer(
- new LogReader(
- new FauxRequest(
- array( 'page' => $this->mTitle->getPrefixedText(),
- 'type' => 'protect' ) ) ) );
- $logViewer->showList( $out );
+ $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'protect' ) ) );
+ LogEventsList::showLogExtract( $out, 'protect', $this->mTitle->getPrefixedText() );
}
-
-}
\ No newline at end of file
+}
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/ProxyTools.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/ProxyTools.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/ProxyTools.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/ProxyTools.php Wed Dec 10 17:58:24 2008
@@ -1,6 +1,7 @@
$tempValue ) {
+ $set[ strtoupper( $tempName ) ] = $tempValue;
+ }
+ $index = strtoupper ( 'X-Forwarded-For' );
+ $index2 = strtoupper ( 'Client-ip' );
} else {
// Subject to spoofing with headers like X_Forwarded_For
$set = $_SERVER;
$index = 'HTTP_X_FORWARDED_FOR';
$index2 = 'CLIENT-IP';
}
+
#Try a couple of headers
if( isset( $set[$index] ) ) {
return $set[$index];
@@ -39,8 +44,11 @@
function wfGetAgent() {
if( function_exists( 'apache_request_headers' ) ) {
// More reliable than $_SERVER due to case and -/_ folding
- $set = apache_request_headers();
- $index = 'User-Agent';
+ $set = array ();
+ foreach ( apache_request_headers() as $tempName => $tempValue ) {
+ $set[ strtoupper( $tempName ) ] = $tempValue;
+ }
+ $index = strtoupper ( 'User-Agent' );
} else {
// Subject to spoofing with headers like X_Forwarded_For
$set = $_SERVER;
@@ -59,7 +67,7 @@
* @return string
*/
function wfGetIP() {
- global $wgIP;
+ global $wgIP, $wgUsePrivateIPs;
# Return cached result
if ( !empty( $wgIP ) ) {
@@ -83,14 +91,16 @@
$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];
+ if ( isset( $ipchain[$i + 1] ) ) {
+ if( $wgUsePrivateIPs || IP::isPublic( $ipchain[$i + 1 ] ) ) {
+ $ip = $ipchain[$i + 1];
+ }
}
} else {
break;
@@ -112,9 +122,8 @@
function wfIsTrustedProxy( $ip ) {
global $wgSquidServers, $wgSquidServersNoPurge;
- if ( in_array( $ip, $wgSquidServers ) ||
- in_array( $ip, $wgSquidServersNoPurge ) ||
- wfIsAOLProxy( $ip )
+ if ( in_array( $ip, $wgSquidServers ) ||
+ in_array( $ip, $wgSquidServersNoPurge )
) {
$trusted = true;
} else {
@@ -130,7 +139,7 @@
*/
function wfProxyCheck() {
global $wgBlockOpenProxies, $wgProxyPorts, $wgProxyScriptPath;
- global $wgUseMemCached, $wgMemc, $wgProxyMemcExpiry;
+ global $wgMemc, $wgProxyMemcExpiry;
global $wgProxyKey;
if ( !$wgBlockOpenProxies ) {
@@ -140,14 +149,9 @@
$ip = wfGetIP();
# Get MemCached key
- $skip = false;
- if ( $wgUseMemCached ) {
- $mcKey = wfMemcKey( 'proxy', 'ip', $ip );
- $mcValue = $wgMemc->get( $mcKey );
- if ( $mcValue ) {
- $skip = true;
- }
- }
+ $mcKey = wfMemcKey( 'proxy', 'ip', $ip );
+ $mcValue = $wgMemc->get( $mcKey );
+ $skip = (bool)$mcValue;
# Fork the processes
if ( !$skip ) {
@@ -165,9 +169,7 @@
exec( "php $params &>/dev/null &" );
}
# Set MemCached key
- if ( $wgUseMemCached ) {
- $wgMemc->set( $mcKey, 1, $wgProxyMemcExpiry );
- }
+ $wgMemc->set( $mcKey, 1, $wgProxyMemcExpiry );
}
}
@@ -210,54 +212,4 @@
wfProfileOut( $fname );
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.
- * @return bool
- */
-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 -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/QueryPage.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/QueryPage.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/QueryPage.php Tue Sep 4 15:29:47 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/QueryPage.php Fri Dec 19 03:54:51 2008
@@ -1,13 +1,15 @@
value ) ) {
$value = $row->value;
} else {
- $value = '';
+ $value = 0;
}
$insertSql .= '(' .
@@ -305,25 +309,23 @@
# Fetch the timestamp of this update
$tRes = $dbr->select( 'querycache_info', array( 'qci_timestamp' ), array( 'qci_type' => $type ), $fname );
$tRow = $dbr->fetchObject( $tRes );
-
+
if( $tRow ) {
$updated = $wgLang->timeAndDate( $tRow->qci_timestamp, true, true );
- $cacheNotice = wfMsg( 'perfcachedts', $updated );
$wgOut->addMeta( 'Data-Cache-Time', $tRow->qci_timestamp );
$wgOut->addInlineScript( "var dataCacheTime = '{$tRow->qci_timestamp}';" );
+ $wgOut->addWikiMsg( 'perfcachedts', $updated );
} else {
- $cacheNotice = wfMsg( 'perfcached' );
+ $wgOut->addWikiMsg( 'perfcached' );
}
-
- $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' ) );
+ $wgOut->addWikiMsg( 'querypage-no-updates' );
}
-
+
}
}
@@ -335,26 +337,26 @@
$this->preprocessResults( $dbr, $res );
- $wgOut->addHtml( XML::openElement( 'div', array('class' => 'mw-spcontent') ) );
-
+ $wgOut->addHTML( XML::openElement( 'div', array('class' => 'mw-spcontent') ) );
+
# Top header and navigation
if( $shownavigation ) {
- $wgOut->addHtml( $this->getPageHeader() );
+ $wgOut->addHTML( $this->getPageHeader() );
if( $num > 0 ) {
- $wgOut->addHtml( '' . wfShowingResults( $offset, $num ) . '
' );
+ $wgOut->addHTML( '' . wfShowingResults( $offset, $num ) . '
' );
# Disable the "next" link when we reach the end
$paging = wfViewPrevNext( $offset, $limit, $wgContLang->specialPage( $sname ),
wfArrayToCGI( $this->linkParameters() ), ( $num < $limit ) );
- $wgOut->addHtml( '' . $paging . '
' );
+ $wgOut->addHTML( '' . $paging . '
' );
} else {
# No results to show, so don't bother with "showing X of Y" etc.
# -- just let the user know and give up now
- $wgOut->addHtml( '' . wfMsgHtml( 'specialpage-empty' ) . '
' );
- $wgOut->addHtml( XML::closeElement( 'div' ) );
+ $wgOut->addHTML( '' . wfMsgHtml( 'specialpage-empty' ) . '
' );
+ $wgOut->addHTML( XML::closeElement( 'div' ) );
return;
}
}
-
+
# The actual results; specialist subclasses will want to handle this
# with more than a straight list, so we hand them the info, plus
# an OutputPage, and let them get on with it
@@ -367,14 +369,14 @@
# Repeat the paging links at the bottom
if( $shownavigation ) {
- $wgOut->addHtml( '' . $paging . '
' );
+ $wgOut->addHTML( '' . $paging . '
' );
}
- $wgOut->addHtml( XML::closeElement( 'div' ) );
-
+ $wgOut->addHTML( XML::closeElement( 'div' ) );
+
return $num;
}
-
+
/**
* Format and output report results using the given information plus
* OutputPage
@@ -388,12 +390,12 @@
*/
protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
global $wgContLang;
-
+
if( $num > 0 ) {
$html = array();
if( !$this->listoutput )
$html[] = $this->openList( $offset );
-
+
# $res might contain the whole 1,000 rows, so we read up to
# $num [should update this to use a Pager]
for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
@@ -407,7 +409,7 @@
: "{$line} \n";
}
}
-
+
# Flush the final result
if( $this->tryLastResult() ) {
$row = null;
@@ -421,37 +423,47 @@
: "{$line} \n";
}
}
-
+
if( !$this->listoutput )
$html[] = $this->closeList();
-
+
$html = $this->listoutput
? $wgContLang->listToText( $html )
: implode( '', $html );
-
- $out->addHtml( $html );
+
+ $out->addHTML( $html );
}
}
-
+
function openList( $offset ) {
return "\n\n";
}
-
+
function closeList() {
return " \n";
}
/**
* Do any necessary preprocessing of the result object.
- * 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
*/
function doFeed( $class = '', $limit = 50 ) {
- global $wgFeedClasses;
+ global $wgFeed, $wgFeedClasses;
+
+ if ( !$wgFeed ) {
+ global $wgOut;
+ $wgOut->addWikiMsg( 'feed-unavailable' );
+ return;
+ }
+
+ global $wgFeedLimit;
+ if( $limit > $wgFeedLimit ) {
+ $limit = $wgFeedLimit;
+ }
if( isset($wgFeedClasses[$class]) ) {
$feed = new $wgFeedClasses[$class](
@@ -522,7 +534,7 @@
}
function feedDesc() {
- return wfMsg( 'tagline' );
+ return wfMsgExt( 'tagline', 'parsemag' );
}
function feedUrl() {
@@ -530,5 +542,3 @@
return $title->getFullURL();
}
}
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/RawPage.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/RawPage.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/RawPage.php Tue Jul 17 11:50:50 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/RawPage.php Fri Jan 2 06:07:12 2009
@@ -7,6 +7,7 @@
* License: GPL (http://www.gnu.org/copyleft/gpl.html)
*
* @author Gabriel Wicke
+ * @file
*/
/**
@@ -15,41 +16,45 @@
*/
class RawPage {
var $mArticle, $mTitle, $mRequest;
- var $mOldId, $mGen, $mCharset;
+ var $mOldId, $mGen, $mCharset, $mSection;
var $mSmaxage, $mMaxage;
var $mContentType, $mExpandTemplates;
function __construct( &$article, $request = false ) {
- global $wgRequest, $wgInputEncoding, $wgSquidMaxage, $wgJsMimeType;
+ global $wgRequest, $wgInputEncoding, $wgSquidMaxage, $wgJsMimeType, $wgGroupPermissions;
$allowedCTypes = array('text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit');
$this->mArticle =& $article;
$this->mTitle =& $article->mTitle;
- if ( $request === false ) {
+ if( $request === false ) {
$this->mRequest =& $wgRequest;
} else {
$this->mRequest = $request;
}
$ctype = $this->mRequest->getVal( 'ctype' );
- $smaxage = $this->mRequest->getIntOrNull( 'smaxage', $wgSquidMaxage );
+ $smaxage = $this->mRequest->getIntOrNull( 'smaxage' );
$maxage = $this->mRequest->getInt( 'maxage', $wgSquidMaxage );
+
$this->mExpandTemplates = $this->mRequest->getVal( 'templates' ) === 'expand';
$this->mUseMessageCache = $this->mRequest->getBool( 'usemsgcache' );
+ $this->mSection = $this->mRequest->getIntOrNull( 'section' );
+
$oldid = $this->mRequest->getInt( 'oldid' );
- switch ( $wgRequest->getText( 'direction' ) ) {
+
+ switch( $wgRequest->getText( 'direction' ) ) {
case 'next':
# output next revision, or nothing if there isn't one
- if ( $oldid ) {
+ if( $oldid ) {
$oldid = $this->mTitle->getNextRevisionId( $oldid );
}
$oldid = $oldid ? $oldid : -1;
break;
case 'prev':
# output previous revision, or nothing if there isn't one
- if ( ! $oldid ) {
+ if( ! $oldid ) {
# get the current revision so we can get the penultimate one
$this->mArticle->getTouched();
$oldid = $this->mArticle->mLatest;
@@ -62,15 +67,15 @@
break;
}
$this->mOldId = $oldid;
-
+
# special case for 'generated' raw things: user css/js
$gen = $this->mRequest->getVal( 'gen' );
- if($gen == 'css') {
+ if( $gen == 'css' ) {
$this->mGen = $gen;
if( is_null( $smaxage ) ) $smaxage = $wgSquidMaxage;
if($ctype == '') $ctype = 'text/css';
- } elseif ($gen == 'js') {
+ } elseif( $gen == 'js' ) {
$this->mGen = $gen;
if( is_null( $smaxage ) ) $smaxage = $wgSquidMaxage;
if($ctype == '') $ctype = $wgJsMimeType;
@@ -78,14 +83,25 @@
$this->mGen = false;
}
$this->mCharset = $wgInputEncoding;
- $this->mSmaxage = intval( $smaxage );
+
+ # Force caching for CSS and JS raw content, default: 5 minutes
+ if( is_null($smaxage) and ($ctype=='text/css' or $ctype==$wgJsMimeType) ) {
+ global $wgForcedRawSMaxage;
+ $this->mSmaxage = intval($wgForcedRawSMaxage);
+ } else {
+ $this->mSmaxage = intval( $smaxage );
+ }
$this->mMaxage = $maxage;
-
- // Output may contain user-specific data; vary for open sessions
- $this->mPrivateCache = ( $this->mSmaxage == 0 ) ||
- ( session_id() != '' );
-
- if ( $ctype == '' or ! in_array( $ctype, $allowedCTypes ) ) {
+
+ # Output may contain user-specific data;
+ # vary generated content for open sessions and private wikis
+ if( $this->mGen or !$wgGroupPermissions['*']['read'] ) {
+ $this->mPrivateCache = $this->mSmaxage == 0 || session_id() != '';
+ } else {
+ $this->mPrivateCache = false;
+ }
+
+ if( $ctype == '' or ! in_array( $ctype, $allowedCTypes ) ) {
$this->mContentType = 'text/x-wiki';
} else {
$this->mContentType = $ctype;
@@ -110,9 +126,8 @@
} else {
$url = $_SERVER['PHP_SELF'];
}
-
- $ua = @$_SERVER['HTTP_USER_AGENT'];
- if( strcmp( $wgScript, $url ) && strpos( $ua, 'MSIE' ) !== false ) {
+
+ if( strcmp( $wgScript, $url ) ) {
# Internet Explorer will ignore the Content-Type header if it
# thinks it sees a file extension it recognizes. Make sure that
# all raw requests are done through the script node, which will
@@ -134,6 +149,18 @@
# allow the client to cache this for 24 hours
$mode = $this->mPrivateCache ? 'private' : 'public';
header( 'Cache-Control: '.$mode.', s-maxage='.$this->mSmaxage.', max-age='.$this->mMaxage );
+
+ if( HTMLFileCache::useFileCache() ) {
+ $cache = new HTMLFileCache( $this->mTitle, 'raw' );
+ if( $cache->isFileCacheGood( /* Assume up to date */ ) ) {
+ $cache->loadFromFileCache();
+ $wgOut->disable();
+ return;
+ } else {
+ ob_start( array(&$cache, 'saveToFileCache' ) );
+ }
+ }
+
$text = $this->getRawText();
if( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) {
@@ -146,13 +173,15 @@
function getRawText() {
global $wgUser, $wgOut, $wgRequest;
- if($this->mGen) {
+ if( $this->mGen ) {
$sk = $wgUser->getSkin();
- $sk->initPage($wgOut);
- if($this->mGen == 'css') {
- return $sk->getUserStylesheet();
- } else if($this->mGen == 'js') {
- return $sk->getUserJs();
+ if( !StubObject::isRealObject( $wgOut ) )
+ $wgOut->_unstub( 2 );
+ $sk->initPage( $wgOut );
+ if( $this->mGen == 'css' ) {
+ return $sk->generateUserStylesheet();
+ } else if( $this->mGen == 'js' ) {
+ return $sk->generateUserJs();
}
} else {
return $this->getArticleText();
@@ -164,7 +193,7 @@
$text = '';
if( $this->mTitle ) {
// If it's a MediaWiki message we can just hit the message cache
- if ( $this->mUseMessageCache && $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ if( $this->mUseMessageCache && $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
$key = $this->mTitle->getDBkey();
$text = wfMsgForContentNoTrans( $key );
# If the message doesn't exist, return a blank
@@ -174,10 +203,15 @@
} else {
// Get it from the DB
$rev = Revision::newFromTitle( $this->mTitle, $this->mOldId );
- if ( $rev ) {
+ if( $rev ) {
$lastmod = wfTimestamp( TS_RFC2822, $rev->getTimestamp() );
header( "Last-modified: $lastmod" );
- $text = $rev->getText();
+
+ if( !is_null($this->mSection ) ) {
+ global $wgParser;
+ $text = $wgParser->getSection ( $rev->getText(), $this->mSection );
+ } else
+ $text = $rev->getText();
$found = true;
}
}
@@ -191,7 +225,7 @@
# have the pages.
header( "HTTP/1.0 404 Not Found" );
}
-
+
// Special-case for empty CSS/JS
//
// Internet Explorer for Mac handles empty files badly;
@@ -205,19 +239,18 @@
$this->mContentType == 'text/javascript' ) ) {
return "/* Empty */";
}
-
+
return $this->parseArticleText( $text );
}
function parseArticleText( $text ) {
- if ( $text === '' )
+ if( $text === '' )
return '';
else
- if ( $this->mExpandTemplates ) {
+ if( $this->mExpandTemplates ) {
global $wgParser;
return $wgParser->preprocess( $text, $this->mTitle, new ParserOptions() );
} else
return $text;
}
}
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/RecentChange.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/RecentChange.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/RecentChange.php Mon Aug 20 23:57:54 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/RecentChange.php Tue Dec 23 15:56:19 2008
@@ -1,7 +1,4 @@
loadFromRow( $row );
return $rc;
}
- public static function newFromCurRow( $row, $rc_this_oldid = 0 )
- {
+ public static function newFromCurRow( $row ) {
$rc = new RecentChange;
- $rc->loadFromCurRow( $row, $rc_this_oldid );
+ $rc->loadFromCurRow( $row );
$rc->notificationtimestamp = false;
$rc->numberofWatchingusers = false;
return $rc;
}
-
+
/**
* Obtain the recent change with a given rc_id value
*
@@ -80,7 +80,7 @@
return NULL;
}
}
-
+
/**
* Find the first recent change matching some specific conditions
*
@@ -108,27 +108,23 @@
# Accessors
- function setAttribs( $attribs )
- {
+ public function setAttribs( $attribs ) {
$this->mAttribs = $attribs;
}
- function setExtra( $extra )
- {
+ public function setExtra( $extra ) {
$this->mExtra = $extra;
}
- function &getTitle()
- {
- if ( $this->mTitle === false ) {
+ public function &getTitle() {
+ if( $this->mTitle === false ) {
$this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
}
return $this->mTitle;
}
- function getMovedToTitle()
- {
- if ( $this->mMovedToTitle === false ) {
+ public function getMovedToTitle() {
+ if( $this->mMovedToTitle === false ) {
$this->mMovedToTitle = Title::makeTitle( $this->mAttribs['rc_moved_to_ns'],
$this->mAttribs['rc_moved_to_title'] );
}
@@ -136,23 +132,22 @@
}
# Writes the data in this object to the database
- function save()
- {
- global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPPort, $wgRC2UDPPrefix;
+ public function save() {
+ global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPOmitBots;
$fname = 'RecentChange::save';
$dbw = wfGetDB( DB_MASTER );
- if ( !is_array($this->mExtra) ) {
+ if( !is_array($this->mExtra) ) {
$this->mExtra = array();
}
$this->mExtra['lang'] = $wgLocalInterwiki;
- if ( !$wgPutIPinRC ) {
+ if( !$wgPutIPinRC ) {
$this->mAttribs['rc_ip'] = '';
}
- ## If our database is strict about IP addresses, use NULL instead of an empty string
- if ( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) {
+ # If our database is strict about IP addresses, use NULL instead of an empty string
+ if( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) {
unset( $this->mAttribs['rc_ip'] );
}
@@ -162,7 +157,7 @@
$this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'rc_rc_id_seq' );
## If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
- if ( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id']==0 ) {
+ if( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id']==0 ) {
unset ( $this->mAttribs['rc_cur_id'] );
}
@@ -172,59 +167,27 @@
# Set the ID
$this->mAttribs['rc_id'] = $dbw->insertId();
- # Update old rows, if necessary
- if ( $this->mAttribs['rc_type'] == RC_EDIT ) {
- $lastTime = $this->mExtra['lastTimestamp'];
- #$now = $this->mAttribs['rc_timestamp'];
- #$curId = $this->mAttribs['rc_cur_id'];
-
- # Don't bother looking for entries that have probably
- # been purged, it just locks up the indexes needlessly.
- global $wgRCMaxAge;
- $age = time() - wfTimestamp( TS_UNIX, $lastTime );
- if( $age < $wgRCMaxAge ) {
- # live hack, will commit once tested - kate
- # Update rc_this_oldid for the entries which were current
- #
- #$oldid = $this->mAttribs['rc_last_oldid'];
- #$ns = $this->mAttribs['rc_namespace'];
- #$title = $this->mAttribs['rc_title'];
- #
- #$dbw->update( 'recentchanges',
- # array( /* SET */
- # 'rc_this_oldid' => $oldid
- # ), array( /* WHERE */
- # 'rc_namespace' => $ns,
- # 'rc_title' => $title,
- # 'rc_timestamp' => $dbw->timestamp( $lastTime )
- # ), $fname
- #);
- }
-
- # Update rc_cur_time
- #$dbw->update( 'recentchanges', array( 'rc_cur_time' => $now ),
- # array( 'rc_cur_id' => $curId ), $fname );
- }
-
# Notify external application via UDP
- if ( $wgRC2UDPAddress ) {
- $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- if ( $conn ) {
- $line = $wgRC2UDPPrefix . $this->getIRCLine();
- socket_sendto( $conn, $line, strlen($line), 0, $wgRC2UDPAddress, $wgRC2UDPPort );
- socket_close( $conn );
- }
+ if( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) {
+ self::sendToUDP( $this->getIRCLine() );
}
# E-mail notifications
- global $wgUseEnotif;
- if( $wgUseEnotif ) {
- # this would be better as an extension hook
- global $wgUser;
- include_once( "UserMailer.php" );
+ global $wgUseEnotif, $wgShowUpdatedMarker, $wgUser;
+ if( $wgUseEnotif || $wgShowUpdatedMarker ) {
+ // Users
+ if( $this->mAttribs['rc_user'] ) {
+ $editor = ($wgUser->getId() == $this->mAttribs['rc_user']) ?
+ $wgUser : User::newFromID( $this->mAttribs['rc_user'] );
+ // Anons
+ } else {
+ $editor = ($wgUser->getName() == $this->mAttribs['rc_user_text']) ?
+ $wgUser : User::newFromName( $this->mAttribs['rc_user_text'], false );
+ }
+ # FIXME: this would be better as an extension hook
$enotif = new EmailNotification();
$title = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
- $enotif->notifyOnPageChange( $wgUser, $title,
+ $enotif->notifyOnPageChange( $editor, $title,
$this->mAttribs['rc_timestamp'],
$this->mAttribs['rc_comment'],
$this->mAttribs['rc_minor'],
@@ -236,14 +199,105 @@
}
/**
+ * Send some text to UDP
+ * @param string $line
+ * @param string $prefix
+ * @param string $address
+ * @return bool success
+ */
+ public static function sendToUDP( $line, $address = '', $prefix = '' ) {
+ global $wgRC2UDPAddress, $wgRC2UDPPrefix, $wgRC2UDPPort;
+ # Assume default for standard RC case
+ $address = $address ? $address : $wgRC2UDPAddress;
+ $prefix = $prefix ? $prefix : $wgRC2UDPPrefix;
+ # Notify external application via UDP
+ if( $address ) {
+ $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
+ if( $conn ) {
+ $line = $prefix . $line;
+ wfDebug( __METHOD__ . ": sending UDP line: $line\n" );
+ socket_sendto( $conn, $line, strlen($line), 0, $address, $wgRC2UDPPort );
+ socket_close( $conn );
+ return true;
+ } else {
+ wfDebug( __METHOD__ . ": failed to create UDP socket\n" );
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove newlines and carriage returns
+ * @param string $line
+ * @return string
+ */
+ public static function cleanupForIRC( $text ) {
+ return str_replace(array("\n", "\r"), array("", ""), $text);
+ }
+
+ /**
* Mark a given change as patrolled
*
* @param mixed $change RecentChange or corresponding rc_id
+ * @param bool $auto for automatic patrol
+ * @return See doMarkPatrolled(), or null if $change is not an existing rc_id
*/
- public static function markPatrolled( $change ) {
- $rcid = $change instanceof RecentChange
- ? $change->mAttribs['rc_id']
- : $change;
+ public static function markPatrolled( $change, $auto = false ) {
+ $change = $change instanceof RecentChange
+ ? $change
+ : RecentChange::newFromId($change);
+ if( !$change instanceof RecentChange ) {
+ return null;
+ }
+ return $change->doMarkPatrolled( $auto );
+ }
+
+ /**
+ * Mark this RecentChange as patrolled
+ *
+ * NOTE: Can also return 'rcpatroldisabled', 'hookaborted' and 'markedaspatrollederror-noautopatrol' as errors
+ * @param bool $auto for automatic patrol
+ * @return array of permissions errors, see Title::getUserPermissionsErrors()
+ */
+ public function doMarkPatrolled( $auto = false ) {
+ global $wgUser, $wgUseRCPatrol, $wgUseNPPatrol;
+ $errors = array();
+ // If recentchanges patrol is disabled, only new pages
+ // can be patrolled
+ if( !$wgUseRCPatrol && ( !$wgUseNPPatrol || $this->getAttribute('rc_type') != RC_NEW ) ) {
+ $errors[] = array('rcpatroldisabled');
+ }
+ // Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
+ $right = $auto ? 'autopatrol' : 'patrol';
+ $errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $wgUser ) );
+ if( !wfRunHooks('MarkPatrolled', array($this->getAttribute('rc_id'), &$wgUser, false)) ) {
+ $errors[] = array('hookaborted');
+ }
+ // Users without the 'autopatrol' right can't patrol their
+ // own revisions
+ if( $wgUser->getName() == $this->getAttribute('rc_user_text') && !$wgUser->isAllowed('autopatrol') ) {
+ $errors[] = array('markedaspatrollederror-noautopatrol');
+ }
+ if( $errors ) {
+ return $errors;
+ }
+ // If the change was patrolled already, do nothing
+ if( $this->getAttribute('rc_patrolled') ) {
+ return array();
+ }
+ // Actually set the 'patrolled' flag in RC
+ $this->reallyMarkPatrolled();
+ // Log this patrol event
+ PatrolLog::record( $this, $auto );
+ wfRunHooks( 'MarkPatrolledComplete', array($this->getAttribute('rc_id'), &$wgUser, false) );
+ return array();
+ }
+
+ /**
+ * Mark this RecentChange patrolled, without error checking
+ * @return int Number of affected rows
+ */
+ public function reallyMarkPatrolled() {
$dbw = wfGetDB( DB_MASTER );
$dbw->update(
'recentchanges',
@@ -251,25 +305,20 @@
'rc_patrolled' => 1
),
array(
- 'rc_id' => $rcid
+ 'rc_id' => $this->getAttribute('rc_id')
),
__METHOD__
);
+ return $dbw->affectedRows();
}
# Makes an entry in the database corresponding to an edit
- public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment,
- $oldId, $lastTimestamp, $bot = "default", $ip = '', $oldSize = 0, $newSize = 0,
- $newId = 0)
+ public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId,
+ $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0 )
{
-
- if ( $bot === 'default' ) {
- $bot = $user->isAllowed( 'bot' );
- }
-
- if ( !$ip ) {
+ if( !$ip ) {
$ip = wfGetIP();
- if ( !$ip ) {
+ if( !$ip ) {
$ip = '';
}
}
@@ -283,7 +332,7 @@
'rc_type' => RC_EDIT,
'rc_minor' => $minor ? 1 : 0,
'rc_cur_id' => $title->getArticleID(),
- 'rc_user' => $user->getID(),
+ 'rc_user' => $user->getId(),
'rc_user_text' => $user->getName(),
'rc_comment' => $comment,
'rc_this_oldid' => $newId,
@@ -292,10 +341,15 @@
'rc_moved_to_ns' => 0,
'rc_moved_to_title' => '',
'rc_ip' => $ip,
- 'rc_patrolled' => 0,
+ 'rc_patrolled' => intval($patrol),
'rc_new' => 0, # obsolete
'rc_old_len' => $oldSize,
- 'rc_new_len' => $newSize
+ 'rc_new_len' => $newSize,
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => ''
);
$rc->mExtra = array(
@@ -305,7 +359,7 @@
'newSize' => $newSize,
);
$rc->save();
- return( $rc->mAttribs['rc_id'] );
+ return $rc;
}
/**
@@ -313,18 +367,15 @@
* Note: the title object must be loaded with the new id using resetArticleID()
* @todo Document parameters and return
*/
- public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot = 'default',
- $ip='', $size = 0, $newId = 0 )
+ public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot,
+ $ip='', $size=0, $newId=0, $patrol=0 )
{
- if ( !$ip ) {
+ if( !$ip ) {
$ip = wfGetIP();
- if ( !$ip ) {
+ if( !$ip ) {
$ip = '';
}
}
- if ( $bot === 'default' ) {
- $bot = $user->isAllowed( 'bot' );
- }
$rc = new RecentChange;
$rc->mAttribs = array(
@@ -335,7 +386,7 @@
'rc_type' => RC_NEW,
'rc_minor' => $minor ? 1 : 0,
'rc_cur_id' => $title->getArticleID(),
- 'rc_user' => $user->getID(),
+ 'rc_user' => $user->getId(),
'rc_user_text' => $user->getName(),
'rc_comment' => $comment,
'rc_this_oldid' => $newId,
@@ -344,10 +395,15 @@
'rc_moved_to_ns' => 0,
'rc_moved_to_title' => '',
'rc_ip' => $ip,
- 'rc_patrolled' => 0,
- 'rc_new' => 1, # obsolete
+ 'rc_patrolled' => intval($patrol),
+ 'rc_new' => 1, # obsolete
'rc_old_len' => 0,
- 'rc_new_len' => $size
+ 'rc_new_len' => $size,
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => ''
);
$rc->mExtra = array(
@@ -357,15 +413,17 @@
'newSize' => $size
);
$rc->save();
- return( $rc->mAttribs['rc_id'] );
+ return $rc;
}
# Makes an entry in the database corresponding to a rename
public static function notifyMove( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='', $overRedir = false )
{
- if ( !$ip ) {
+ global $wgRequest;
+
+ if( !$ip ) {
$ip = wfGetIP();
- if ( !$ip ) {
+ if( !$ip ) {
$ip = '';
}
}
@@ -379,12 +437,12 @@
'rc_type' => $overRedir ? RC_MOVE_OVER_REDIRECT : RC_MOVE,
'rc_minor' => 0,
'rc_cur_id' => $oldTitle->getArticleID(),
- 'rc_user' => $user->getID(),
+ 'rc_user' => $user->getId(),
'rc_user_text' => $user->getName(),
'rc_comment' => $comment,
'rc_this_oldid' => 0,
'rc_last_oldid' => 0,
- 'rc_bot' => $user->isAllowed( 'bot' ) ? 1 : 0,
+ 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot' , true ) : 0,
'rc_moved_to_ns' => $newTitle->getNamespace(),
'rc_moved_to_title' => $newTitle->getDBkey(),
'rc_ip' => $ip,
@@ -392,6 +450,11 @@
'rc_patrolled' => 1,
'rc_old_len' => NULL,
'rc_new_len' => NULL,
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0, # notifyMove not used anymore
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => ''
);
$rc->mExtra = array(
@@ -410,14 +473,14 @@
RecentChange::notifyMove( $timestamp, $oldTitle, $newTitle, $user, $comment, $ip, true );
}
- # A log entry is different to an edit in that previous revisions are
- # not kept
- public static function notifyLog( $timestamp, &$title, &$user, $comment, $ip='',
- $type, $action, $target, $logComment, $params )
+ public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip='',
+ $type, $action, $target, $logComment, $params, $newId=0 )
{
- if ( !$ip ) {
+ global $wgRequest;
+
+ if( !$ip ) {
$ip = wfGetIP();
- if ( !$ip ) {
+ if( !$ip ) {
$ip = '';
}
}
@@ -426,17 +489,17 @@
$rc->mAttribs = array(
'rc_timestamp' => $timestamp,
'rc_cur_time' => $timestamp,
- 'rc_namespace' => $title->getNamespace(),
- 'rc_title' => $title->getDBkey(),
+ 'rc_namespace' => $target->getNamespace(),
+ 'rc_title' => $target->getDBkey(),
'rc_type' => RC_LOG,
'rc_minor' => 0,
- 'rc_cur_id' => $title->getArticleID(),
- 'rc_user' => $user->getID(),
+ 'rc_cur_id' => $target->getArticleID(),
+ 'rc_user' => $user->getId(),
'rc_user_text' => $user->getName(),
- 'rc_comment' => $comment,
+ 'rc_comment' => $logComment,
'rc_this_oldid' => 0,
'rc_last_oldid' => 0,
- 'rc_bot' => $user->isAllowed( 'bot' ) ? 1 : 0,
+ 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0,
'rc_moved_to_ns' => 0,
'rc_moved_to_title' => '',
'rc_ip' => $ip,
@@ -444,30 +507,29 @@
'rc_new' => 0, # obsolete
'rc_old_len' => NULL,
'rc_new_len' => NULL,
+ 'rc_deleted' => 0,
+ 'rc_logid' => $newId,
+ 'rc_log_type' => $type,
+ 'rc_log_action' => $action,
+ 'rc_params' => $params
);
$rc->mExtra = array(
'prefixedDBkey' => $title->getPrefixedDBkey(),
'lastTimestamp' => 0,
- 'logType' => $type,
- 'logAction' => $action,
- 'logComment' => $logComment,
- 'logTarget' => $target,
- 'logParams' => $params
+ 'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
);
$rc->save();
}
# Initialises the members of this object from a mysql row object
- function loadFromRow( $row )
- {
+ public function loadFromRow( $row ) {
$this->mAttribs = get_object_vars( $row );
- $this->mAttribs["rc_timestamp"] = wfTimestamp(TS_MW, $this->mAttribs["rc_timestamp"]);
- $this->mExtra = array();
+ $this->mAttribs['rc_timestamp'] = wfTimestamp(TS_MW, $this->mAttribs['rc_timestamp']);
+ $this->mAttribs['rc_deleted'] = $row->rc_deleted; // MUST be set
}
# Makes a pseudo-RC entry from a cur row
- function loadFromCurRow( $row )
- {
+ public function loadFromCurRow( $row ) {
$this->mAttribs = array(
'rc_timestamp' => wfTimestamp(TS_MW, $row->rev_timestamp),
'rc_cur_time' => $row->rev_timestamp,
@@ -490,9 +552,12 @@
'rc_new' => $row->page_is_new, # obsolete
'rc_old_len' => $row->rc_old_len,
'rc_new_len' => $row->rc_new_len,
+ 'rc_params' => isset($row->rc_params) ? $row->rc_params : '',
+ 'rc_log_type' => isset($row->rc_log_type) ? $row->rc_log_type : null,
+ 'rc_log_action' => isset($row->rc_log_action) ? $row->rc_log_action : null,
+ 'rc_log_id' => isset($row->rc_log_id) ? $row->rc_log_id: 0,
+ 'rc_deleted' => $row->rc_deleted // MUST be set
);
-
- $this->mExtra = array();
}
/**
@@ -509,12 +574,11 @@
* Gets the end part of the diff URL associated with this object
* Blank if no diff link should be displayed
*/
- function diffLinkTrail( $forceCur )
- {
- if ( $this->mAttribs['rc_type'] == RC_EDIT ) {
+ public function diffLinkTrail( $forceCur ) {
+ if( $this->mAttribs['rc_type'] == RC_EDIT ) {
$trail = "curid=" . (int)($this->mAttribs['rc_cur_id']) .
"&oldid=" . (int)($this->mAttribs['rc_last_oldid']);
- if ( $forceCur ) {
+ if( $forceCur ) {
$trail .= '&diff=0' ;
} else {
$trail .= '&diff=' . (int)($this->mAttribs['rc_this_oldid']);
@@ -525,49 +589,45 @@
return $trail;
}
- function cleanupForIRC( $text ) {
- return str_replace(array("\n", "\r"), array("", ""), $text);
- }
-
- function getIRCLine() {
- global $wgUseRCPatrol;
+ protected function getIRCLine() {
+ global $wgUseRCPatrol, $wgUseNPPatrol, $wgRC2UDPInterwikiPrefix, $wgLocalInterwiki;
// FIXME: Would be good to replace these 2 extract() calls with something more explicit
// e.g. list ($rc_type, $rc_id) = array_values ($this->mAttribs); [or something like that]
extract($this->mAttribs);
extract($this->mExtra);
- $titleObj =& $this->getTitle();
- if ( $rc_type == RC_LOG ) {
- $title = Namespace::getCanonicalName( $titleObj->getNamespace() ) . $titleObj->getText();
+ if( $rc_type == RC_LOG ) {
+ $titleObj = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
} else {
- $title = $titleObj->getPrefixedText();
+ $titleObj =& $this->getTitle();
}
- $title = $this->cleanupForIRC( $title );
-
- $bad = array("\n", "\r");
- $empty = array("", "");
$title = $titleObj->getPrefixedText();
- $title = str_replace($bad, $empty, $title);
+ $title = self::cleanupForIRC( $title );
- // FIXME: *HACK* these should be getFullURL(), hacked for SSL madness --brion 2005-12-26
- if ( $rc_type == RC_LOG ) {
+ if( $rc_type == RC_LOG ) {
$url = '';
- } elseif ( $rc_new && $wgUseRCPatrol ) {
- $url = $titleObj->getInternalURL("rcid=$rc_id");
- } else if ( $rc_new ) {
- $url = $titleObj->getInternalURL();
- } else if ( $wgUseRCPatrol ) {
- $url = $titleObj->getInternalURL("diff=$rc_this_oldid&oldid=$rc_last_oldid&rcid=$rc_id");
} else {
- $url = $titleObj->getInternalURL("diff=$rc_this_oldid&oldid=$rc_last_oldid");
+ if( $rc_type == RC_NEW ) {
+ $url = "oldid=$rc_this_oldid";
+ } else {
+ $url = "diff=$rc_this_oldid&oldid=$rc_last_oldid";
+ }
+ if( $wgUseRCPatrol || ($rc_type == RC_NEW && $wgUseNPPatrol) ) {
+ $url .= "&rcid=$rc_id";
+ }
+ // XXX: *HACK* this should use getFullURL(), hacked for SSL madness --brion 2005-12-26
+ // XXX: *HACK^2* the preg_replace() undoes much of what getInternalURL() does, but we
+ // XXX: need to call it so that URL paths on the Wikimedia secure server can be fixed
+ // XXX: by a custom GetInternalURL hook --vyznev 2008-12-10
+ $url = preg_replace( '/title=[^&]*&/', '', $titleObj->getInternalURL( $url ) );
}
- if ( isset( $oldSize ) && isset( $newSize ) ) {
+ if( isset( $oldSize ) && isset( $newSize ) ) {
$szdiff = $newSize - $oldSize;
- if ($szdiff < -500) {
+ if($szdiff < -500) {
$szdiff = "\002$szdiff\002";
- } elseif ($szdiff >= 0) {
+ } elseif($szdiff >= 0) {
$szdiff = '+' . $szdiff ;
}
$szdiff = '(' . $szdiff . ')' ;
@@ -575,20 +635,35 @@
$szdiff = '';
}
- $user = $this->cleanupForIRC( $rc_user_text );
+ $user = self::cleanupForIRC( $rc_user_text );
+
+ if( $rc_type == RC_LOG ) {
+ $targetText = $this->getTitle()->getPrefixedText();
+ $comment = self::cleanupForIRC( str_replace("[[$targetText]]","[[\00302$targetText\00310]]",$actionComment) );
+ $flag = $rc_log_action;
+ } else {
+ $comment = self::cleanupForIRC( $rc_comment );
+ $flag = ($rc_new ? "N" : "") . ($rc_minor ? "M" : "") . ($rc_bot ? "B" : "");
+ }
- if ( $rc_type == RC_LOG ) {
- $logTargetText = $logTarget->getPrefixedText();
- $comment = $this->cleanupForIRC( str_replace( $logTargetText, "\00302$logTargetText\00310", $rc_comment ) );
- $flag = $logAction;
+ if ( $wgRC2UDPInterwikiPrefix === true ) {
+ $prefix = $wgLocalInterwiki;
+ } elseif ( $wgRC2UDPInterwikiPrefix ) {
+ $prefix = $wgRC2UDPInterwikiPrefix;
} else {
- $comment = $this->cleanupForIRC( $rc_comment );
- $flag = ($rc_minor ? "M" : "") . ($rc_new ? "N" : "");
+ $prefix = false;
}
+ if ( $prefix !== false ) {
+ $titleString = "\00314[[\00303$prefix:\00307$title\00314]]";
+ } else {
+ $titleString = "\00314[[\00307$title\00314]]";
+ }
+
# see http://www.irssi.org/documentation/formats for some colour codes. prefix is \003,
# no colour (\003) switches back to the term default
- $fullString = "\00314[[\00307$title\00314]]\0034 $flag\00310 " .
+ $fullString = "$titleString\0034 $flag\00310 " .
"\00302$url\003 \0035*\003 \00303$user\003 \0035*\003 $szdiff \00310$comment\003\n";
+
return $fullString;
}
@@ -596,33 +671,16 @@
* Returns the change size (HTML).
* The lengths can be given optionally.
*/
- function getCharacterDifference( $old = 0, $new = 0 ) {
- global $wgRCChangedSizeThreshold, $wgLang;
-
+ public function getCharacterDifference( $old = 0, $new = 0 ) {
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 '(' . $formatedSize . ') ';
- } elseif( $szdiff === 0 ) {
- return '(' . $formatedSize . ') ';
- } elseif( $szdiff > 0 ) {
- return '(+' . $formatedSize . ') ';
- } else {
- return '(' . $formatedSize . ') ';
- }
+ return ChangesList::showCharacterDifference( $old, $new );
}
}
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/RefreshLinksJob.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/RefreshLinksJob.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/RefreshLinksJob.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/RefreshLinksJob.php Fri Sep 12 05:37:31 2008
@@ -2,6 +2,8 @@
/**
* Background job to update links for a given title.
+ *
+ * @ingroup JobQueue
*/
class RefreshLinksJob extends Job {
@@ -17,7 +19,7 @@
global $wgParser;
wfProfileIn( __METHOD__ );
- $linkCache =& LinkCache::singleton();
+ $linkCache = LinkCache::singleton();
$linkCache->clear();
if ( is_null( $this->title ) ) {
@@ -46,3 +48,86 @@
}
}
+/**
+ * Background job to update links for a given title.
+ * Newer version for high use templates.
+ *
+ * @ingroup JobQueue
+ */
+class RefreshLinksJob2 extends Job {
+
+ function __construct( $title, $params, $id = 0 ) {
+ parent::__construct( 'refreshLinks2', $title, $params, $id );
+ }
+
+ /**
+ * Run a refreshLinks2 job
+ * @return boolean success
+ */
+ function run() {
+ global $wgParser;
+
+ wfProfileIn( __METHOD__ );
+
+ $linkCache = LinkCache::singleton();
+ $linkCache->clear();
+
+ if( is_null( $this->title ) ) {
+ $this->error = "refreshLinks2: Invalid title";
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ if( !isset($this->params['start']) || !isset($this->params['end']) ) {
+ $this->error = "refreshLinks2: Invalid params";
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $start = intval($this->params['start']);
+ $end = intval($this->params['end']);
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( array( 'templatelinks', 'page' ),
+ array( 'page_namespace', 'page_title' ),
+ array(
+ 'page_id=tl_from',
+ "tl_from >= '$start'",
+ "tl_from <= '$end'",
+ 'tl_namespace' => $this->title->getNamespace(),
+ 'tl_title' => $this->title->getDBkey()
+ ), __METHOD__
+ );
+
+ # Not suitable for page load triggered job running!
+ # Gracefully switch to refreshLinks jobs if this happens.
+ if( php_sapi_name() != 'cli' ) {
+ $jobs = array();
+ while( $row = $dbr->fetchObject( $res ) ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $jobs[] = new RefreshLinksJob( $title, '' );
+ }
+ Job::batchInsert( $jobs );
+ return true;
+ }
+ # Re-parse each page that transcludes this page and update their tracking links...
+ while( $row = $dbr->fetchObject( $res ) ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $revision = Revision::newFromTitle( $title );
+ if ( !$revision ) {
+ $this->error = 'refreshLinks: Article not found "' . $title->getPrefixedDBkey() . '"';
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ wfProfileIn( __METHOD__.'-parse' );
+ $options = new ParserOptions;
+ $parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() );
+ wfProfileOut( __METHOD__.'-parse' );
+ wfProfileIn( __METHOD__.'-update' );
+ $update = new LinksUpdate( $title, $parserOutput, false );
+ $update->doUpdate();
+ wfProfileOut( __METHOD__.'-update' );
+ wfProfileOut( __METHOD__ );
+ }
+
+ return true;
+ }
+}
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Revision.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Revision.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Revision.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Revision.php Tue Dec 30 16:43:54 2008
@@ -1,6 +1,7 @@
$title->getNamespace(),
+ 'page_title' => $title->getDBkey()
+ );
+ if ( $id ) {
+ // Use the specified ID
+ $conds['rev_id'] = $id;
+ } elseif ( wfGetLB()->getServerCount() > 1 ) {
+ // Get the latest revision ID from the master
+ $dbw = wfGetDB( DB_MASTER );
+ $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
+ $conds['rev_id'] = $latest;
} else {
- $matchId = 'page_latest';
+ // Use a join to get the latest revision
+ $conds[] = 'rev_id=page_latest';
}
- return Revision::newFromConds(
- array( "rev_id=$matchId",
- 'page_id=rev_page',
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDbkey() ) );
+ $conds[] = 'page_id=rev_page';
+ return Revision::newFromConds( $conds );
}
/**
@@ -59,7 +71,7 @@
* @access public
* @static
*/
- public static function loadFromId( &$db, $id ) {
+ public static function loadFromId( $db, $id ) {
return Revision::loadFromConds( $db,
array( 'page_id=rev_page',
'rev_id' => intval( $id ) ) );
@@ -99,7 +111,7 @@
* @access public
* @static
*/
- public static function loadFromTitle( &$db, $title, $id = 0 ) {
+ public static function loadFromTitle( $db, $title, $id = 0 ) {
if( $id ) {
$matchId = intval( $id );
} else {
@@ -110,7 +122,7 @@
array( "rev_id=$matchId",
'page_id=rev_page',
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDbkey() ) );
+ 'page_title' => $title->getDBkey() ) );
}
/**
@@ -125,13 +137,13 @@
* @access public
* @static
*/
- public static function loadFromTimestamp( &$db, &$title, $timestamp ) {
+ public static function loadFromTimestamp( $db, $title, $timestamp ) {
return Revision::loadFromConds(
$db,
array( 'rev_timestamp' => $db->timestamp( $timestamp ),
'page_id=rev_page',
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDbkey() ) );
+ 'page_title' => $title->getDBkey() ) );
}
/**
@@ -145,7 +157,7 @@
private static function newFromConds( $conditions ) {
$db = wfGetDB( DB_SLAVE );
$row = Revision::loadFromConds( $db, $conditions );
- if( is_null( $row ) ) {
+ if( is_null( $row ) && wfGetLB()->getServerCount() > 1 ) {
$dbw = wfGetDB( DB_MASTER );
$row = Revision::loadFromConds( $dbw, $conditions );
}
@@ -186,11 +198,11 @@
* @access public
* @static
*/
- public static function fetchAllRevisions( &$title ) {
+ public static function fetchAllRevisions( $title ) {
return Revision::fetchFromConds(
wfGetDB( DB_SLAVE ),
array( 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDbkey(),
+ 'page_title' => $title->getDBkey(),
'page_id=rev_page' ) );
}
@@ -204,12 +216,12 @@
* @access public
* @static
*/
- public static function fetchRevision( &$title ) {
+ public static function fetchRevision( $title ) {
return Revision::fetchFromConds(
wfGetDB( DB_SLAVE ),
array( 'rev_id=page_latest',
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDbkey(),
+ 'page_title' => $title->getDBkey(),
'page_id=rev_page' ) );
}
@@ -225,44 +237,58 @@
* @static
*/
private static function fetchFromConds( $db, $conditions ) {
+ $fields = self::selectFields();
+ $fields[] = 'page_namespace';
+ $fields[] = 'page_title';
+ $fields[] = 'page_latest';
$res = $db->select(
array( 'page', 'revision' ),
- array( 'page_namespace',
- 'page_title',
- 'page_latest',
- 'rev_id',
- 'rev_page',
- 'rev_text_id',
- 'rev_comment',
- 'rev_user_text',
- 'rev_user',
- 'rev_minor_edit',
- 'rev_timestamp',
- 'rev_deleted',
- 'rev_len' ),
+ $fields,
$conditions,
- 'Revision::fetchRow',
+ __METHOD__,
array( 'LIMIT' => 1 ) );
$ret = $db->resultObject( $res );
return $ret;
}
/**
- * Return the list of revision fields that should be selected to create
+ * Return the list of revision fields that should be selected to create
* a new revision.
*/
static function selectFields() {
- return array(
+ return array(
'rev_id',
'rev_page',
'rev_text_id',
'rev_timestamp',
'rev_comment',
- 'rev_minor_edit',
- 'rev_user',
'rev_user_text,'.
+ 'rev_user',
+ 'rev_minor_edit',
'rev_deleted',
- 'rev_len'
+ 'rev_len',
+ 'rev_parent_id'
+ );
+ }
+
+ /**
+ * Return the list of text fields that should be selected to read the
+ * revision text
+ */
+ static function selectTextFields() {
+ return array(
+ 'old_text',
+ 'old_flags'
+ );
+ }
+ /**
+ * Return the list of page fields that should be selected from page table
+ */
+ static function selectPageFields() {
+ return array(
+ 'page_namespace',
+ 'page_title',
+ 'page_latest'
);
}
@@ -281,16 +307,21 @@
$this->mMinorEdit = intval( $row->rev_minor_edit );
$this->mTimestamp = $row->rev_timestamp;
$this->mDeleted = intval( $row->rev_deleted );
-
+
+ if( !isset( $row->rev_parent_id ) )
+ $this->mParentId = is_null($row->rev_parent_id) ? null : 0;
+ else
+ $this->mParentId = intval( $row->rev_parent_id );
+
if( !isset( $row->rev_len ) || is_null( $row->rev_len ) )
$this->mSize = null;
else
- $this->mSize = intval( $row->rev_len );
+ $this->mSize = intval( $row->rev_len );
if( isset( $row->page_latest ) ) {
- $this->mCurrent = ( $row->rev_id == $row->page_latest );
- $this->mTitle = Title::makeTitle( $row->page_namespace,
- $row->page_title );
+ $this->mCurrent = ( $row->rev_id == $row->page_latest );
+ $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $this->mTitle->resetArticleID( $this->mPage );
} else {
$this->mCurrent = false;
$this->mTitle = null;
@@ -317,7 +348,8 @@
$this->mTimestamp = isset( $row['timestamp'] ) ? strval( $row['timestamp'] ) : wfTimestamp( TS_MW );
$this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
$this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
-
+ $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
+
// 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;
@@ -338,23 +370,34 @@
*/
/**
+ * Get revision ID
* @return int
*/
- function getId() {
+ public function getId() {
return $this->mId;
}
/**
+ * Get text row ID
* @return int
*/
- function getTextId() {
+ public function getTextId() {
return $this->mTextId;
}
/**
+ * Get parent revision ID (the original previous page revision)
+ * @return int
+ */
+ public function getParentId() {
+ return $this->mParentId;
+ }
+
+ /**
* Returns the length of the text in this revision, or null if unknown.
+ * @return int
*/
- function getSize() {
+ public function getSize() {
return $this->mSize;
}
@@ -362,7 +405,7 @@
* Returns the title of the page associated with this entry.
* @return Title
*/
- function getTitle() {
+ public function getTitle() {
if( isset( $this->mTitle ) ) {
return $this->mTitle;
}
@@ -375,7 +418,7 @@
'Revision::getTitle' );
if( $row ) {
$this->mTitle = Title::makeTitle( $row->page_namespace,
- $row->page_title );
+ $row->page_title );
}
return $this->mTitle;
}
@@ -384,23 +427,35 @@
* Set the title of the revision
* @param Title $title
*/
- function setTitle( $title ) {
+ public function setTitle( $title ) {
$this->mTitle = $title;
}
/**
+ * Get the page ID
* @return int
*/
- function getPage() {
+ public function getPage() {
return $this->mPage;
}
/**
- * Fetch revision's user id if it's available to all users
+ * Fetch revision's user id if it's available to the specified audience.
+ * If the specified audience does not have access to it, zero will be
+ * returned.
+ *
+ * @param integer $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the ID regardless of permissions
+ *
+ *
* @return int
*/
- function getUser() {
- if( $this->isDeleted( self::DELETED_USER ) ) {
+ public function getUser( $audience = self::FOR_PUBLIC ) {
+ if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
+ return 0;
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER ) ) {
return 0;
} else {
return $this->mUser;
@@ -411,16 +466,26 @@
* Fetch revision's user id without regard for the current user's permissions
* @return string
*/
- function getRawUser() {
+ public function getRawUser() {
return $this->mUser;
}
/**
- * Fetch revision's username if it's available to all users
+ * Fetch revision's username if it's available to the specified audience.
+ * If the specified audience does not have access to the username, an
+ * empty string will be returned.
+ *
+ * @param integer $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ *
* @return string
*/
- function getUserText() {
- if( $this->isDeleted( self::DELETED_USER ) ) {
+ public function getUserText( $audience = self::FOR_PUBLIC ) {
+ if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
+ return "";
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER ) ) {
return "";
} else {
return $this->mUserText;
@@ -431,16 +496,26 @@
* Fetch revision's username without regard for view restrictions
* @return string
*/
- function getRawUserText() {
+ public function getRawUserText() {
return $this->mUserText;
}
-
+
/**
- * Fetch revision comment if it's available to all users
+ * Fetch revision comment if it's available to the specified audience.
+ * If the specified audience does not have access to the comment, an
+ * empty string will be returned.
+ *
+ * @param integer $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ *
* @return string
*/
- function getComment() {
- if( $this->isDeleted( self::DELETED_COMMENT ) ) {
+ function getComment( $audience = self::FOR_PUBLIC ) {
+ if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
+ return "";
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT ) ) {
return "";
} else {
return $this->mComment;
@@ -451,14 +526,14 @@
* Fetch revision comment without regard for the current user's permissions
* @return string
*/
- function getRawComment() {
+ public function getRawComment() {
return $this->mComment;
}
/**
* @return bool
*/
- function isMinor() {
+ public function isMinor() {
return (bool)$this->mMinorEdit;
}
@@ -466,97 +541,135 @@
* int $field one of DELETED_* bitfield constants
* @return bool
*/
- function isDeleted( $field ) {
+ public function isDeleted( $field ) {
return ($this->mDeleted & $field) == $field;
}
+
+ /**
+ * Get the deletion bitfield of the revision
+ */
+ public function getVisibility() {
+ return (int)$this->mDeleted;
+ }
/**
- * Fetch revision text if it's available to all users
+ * Fetch revision text if it's available to the specified audience.
+ * If the specified audience does not have the ability to view this
+ * revision, an empty string will be returned.
+ *
+ * @param integer $audience One of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ *
+ *
* @return string
*/
- function getText() {
- if( $this->isDeleted( self::DELETED_TEXT ) ) {
+ public function getText( $audience = self::FOR_PUBLIC ) {
+ if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
+ return "";
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT ) ) {
return "";
} else {
return $this->getRawText();
}
}
-
+
+ /**
+ * Alias for getText(Revision::FOR_THIS_USER)
+ */
+ public function revText() {
+ return $this->getText( self::FOR_THIS_USER );
+ }
+
/**
* Fetch revision text without regard for view restrictions
* @return string
*/
- function getRawText() {
+ public function getRawText() {
if( is_null( $this->mText ) ) {
// Revision text is immutable. Load on demand:
$this->mText = $this->loadText();
}
return $this->mText;
}
-
- /**
- * Fetch revision text if it's available to THIS user
- * @return string
- */
- function revText() {
- if( !$this->userCan( self::DELETED_TEXT ) ) {
- return "";
- } else {
- return $this->getRawText();
- }
- }
/**
* @return string
*/
- function getTimestamp() {
+ public function getTimestamp() {
return wfTimestamp(TS_MW, $this->mTimestamp);
}
/**
* @return bool
*/
- function isCurrent() {
+ public function isCurrent() {
return $this->mCurrent;
}
/**
+ * Get previous revision for this title
* @return Revision
*/
- function getPrevious() {
- $prev = $this->mTitle->getPreviousRevisionID( $this->mId );
- if ( $prev ) {
- return Revision::newFromTitle( $this->mTitle, $prev );
- } else {
- return null;
+ public function getPrevious() {
+ if( $this->getTitle() ) {
+ $prev = $this->getTitle()->getPreviousRevisionID( $this->getId() );
+ if( $prev ) {
+ return Revision::newFromTitle( $this->getTitle(), $prev );
+ }
}
+ return null;
}
/**
* @return Revision
*/
- function getNext() {
- $next = $this->mTitle->getNextRevisionID( $this->mId );
- if ( $next ) {
- return Revision::newFromTitle( $this->mTitle, $next );
- } else {
- return null;
+ public function getNext() {
+ if( $this->getTitle() ) {
+ $next = $this->getTitle()->getNextRevisionID( $this->getId() );
+ if ( $next ) {
+ return Revision::newFromTitle( $this->getTitle(), $next );
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get previous revision Id for this page_id
+ * This is used to populate rev_parent_id on save
+ * @param Database $db
+ * @return int
+ */
+ private function getPreviousRevisionId( $db ) {
+ if( is_null($this->mPage) ) {
+ return 0;
}
+ # Use page_latest if ID is not given
+ if( !$this->mId ) {
+ $prevId = $db->selectField( 'page', 'page_latest',
+ array( 'page_id' => $this->mPage ),
+ __METHOD__ );
+ } else {
+ $prevId = $db->selectField( 'revision', 'rev_id',
+ array( 'rev_page' => $this->mPage, 'rev_id < ' . $this->mId ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_id DESC' ) );
+ }
+ return intval($prevId);
}
- /**#@-*/
/**
* Get revision text associated with an old or archive row
* $row is usually an object from wfFetchRow(), both the flags and the text
* field must be included
- * @static
- * @param integer $row Id of a row
+ *
+ * @param object $row The text data
* @param string $prefix table prefix (default 'old_')
* @return string $text|false the text requested
*/
public static function getRevisionText( $row, $prefix = 'old_' ) {
- $fname = 'Revision::getRevisionText';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
# Get data
$textField = $prefix . 'text';
@@ -571,7 +684,7 @@
if( isset( $row->$textField ) ) {
$text = $row->$textField;
} else {
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return false;
}
@@ -580,7 +693,7 @@
$url=$text;
@list(/* $proto */,$path)=explode('://',$url,2);
if ($path=="") {
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return false;
}
$text=ExternalStore::fetchFromURL($url);
@@ -600,21 +713,23 @@
$obj = unserialize( $text );
if ( !is_object( $obj ) ) {
// Invalid object
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return false;
}
$text = $obj->getText();
}
global $wgLegacyEncoding;
- if( $wgLegacyEncoding && !in_array( 'utf-8', $flags ) ) {
+ if( $wgLegacyEncoding && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags ) ) {
# Old revisions kept around in a legacy encoding?
# Upconvert on demand.
+ # ("utf8" checked for compatibility with some broken
+ # conversion scripts 2008-12-30)
global $wgInputEncoding, $wgContLang;
$text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding, $text );
}
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $text;
}
@@ -625,11 +740,10 @@
* data is compressed, and 'utf-8' if we're saving in UTF-8
* mode.
*
- * @static
* @param mixed $text reference to a text
* @return string
*/
- function compressRevisionText( &$text ) {
+ public static function compressRevisionText( &$text ) {
global $wgCompressRevisions;
$flags = array();
@@ -655,30 +769,22 @@
* @param Database $dbw
* @return int
*/
- function insertOn( &$dbw ) {
+ public function insertOn( $dbw ) {
global $wgDefaultExternalStore;
-
- $fname = 'Revision::insertOn';
- wfProfileIn( $fname );
+
+ wfProfileIn( __METHOD__ );
$data = $this->mText;
$flags = Revision::compressRevisionText( $data );
# Write to external storage if required
- if ( $wgDefaultExternalStore ) {
- if ( is_array( $wgDefaultExternalStore ) ) {
- // Distribute storage across multiple clusters
- $store = $wgDefaultExternalStore[mt_rand(0, count( $wgDefaultExternalStore ) - 1)];
- } else {
- $store = $wgDefaultExternalStore;
- }
+ if( $wgDefaultExternalStore ) {
// Store and get the URL
- $data = ExternalStore::insert( $store, $data );
- if ( !$data ) {
- # This should only happen in the case of a configuration error, where the external store is not valid
- throw new MWException( "Unable to store text to external storage $store" );
+ $data = ExternalStore::insertToDefault( $data );
+ if( !$data ) {
+ throw new MWException( "Unable to store text to external storage" );
}
- if ( $flags ) {
+ if( $flags ) {
$flags .= ',';
}
$flags .= 'external';
@@ -692,7 +798,7 @@
'old_id' => $old_id,
'old_text' => $data,
'old_flags' => $flags,
- ), $fname
+ ), __METHOD__
);
$this->mTextId = $dbw->insertId();
}
@@ -713,11 +819,15 @@
'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
'rev_deleted' => $this->mDeleted,
'rev_len' => $this->mSize,
- ), $fname
+ 'rev_parent_id' => $this->mParentId ? $this->mParentId : $this->getPreviousRevisionId( $dbw )
+ ), __METHOD__
);
$this->mId = !is_null($rev_id) ? $rev_id : $dbw->insertId();
- wfProfileOut( $fname );
+
+ wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
+
+ wfProfileOut( __METHOD__ );
return $this->mId;
}
@@ -726,23 +836,21 @@
* Currently hardcoded to the 'text' table storage engine.
*
* @return string
- * @access private
*/
- function loadText() {
- $fname = 'Revision::loadText';
- wfProfileIn( $fname );
-
+ private function loadText() {
+ wfProfileIn( __METHOD__ );
+
// Caching may be beneficial for massive use of external storage
global $wgRevisionCacheExpiry, $wgMemc;
$key = wfMemcKey( 'revisiontext', 'textid', $this->getTextId() );
if( $wgRevisionCacheExpiry ) {
$text = $wgMemc->get( $key );
if( is_string( $text ) ) {
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $text;
}
}
-
+
// If we kept data for lazy extraction, use it now...
if ( isset( $this->mTextRow ) ) {
$row = $this->mTextRow;
@@ -750,32 +858,33 @@
} else {
$row = null;
}
-
+
if( !$row ) {
// Text data is immutable; check slaves first.
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $this->getTextId() ),
- $fname);
+ __METHOD__ );
}
- if( !$row ) {
+ if( !$row && wfGetLB()->getServerCount() > 1 ) {
// Possible slave lag!
$dbw = wfGetDB( DB_MASTER );
$row = $dbw->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $this->getTextId() ),
- $fname);
+ __METHOD__ );
}
- $text = Revision::getRevisionText( $row );
-
- if( $wgRevisionCacheExpiry ) {
+ $text = self::getRevisionText( $row );
+
+ # No negative caching -- negative hits on text rows may be due to corrupted slave servers
+ if( $wgRevisionCacheExpiry && $text !== false ) {
$wgMemc->set( $key, $text, $wgRevisionCacheExpiry );
}
-
- wfProfileOut( $fname );
+
+ wfProfileOut( __METHOD__ );
return $text;
}
@@ -794,18 +903,17 @@
* @param bool $minor
* @return Revision
*/
- function newNullRevision( &$dbw, $pageId, $summary, $minor ) {
- $fname = 'Revision::newNullRevision';
- wfProfileIn( $fname );
+ public static function newNullRevision( $dbw, $pageId, $summary, $minor ) {
+ wfProfileIn( __METHOD__ );
$current = $dbw->selectRow(
array( 'page', 'revision' ),
- array( 'page_latest', 'rev_text_id' ),
+ array( 'page_latest', 'rev_text_id', 'rev_len' ),
array(
'page_id' => $pageId,
'page_latest=rev_id',
),
- $fname );
+ __METHOD__ );
if( $current ) {
$revision = new Revision( array(
@@ -813,15 +921,17 @@
'comment' => $summary,
'minor_edit' => $minor,
'text_id' => $current->rev_text_id,
+ 'parent_id' => $current->page_latest,
+ 'len' => $current->rev_len
) );
} else {
$revision = null;
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return $revision;
}
-
+
/**
* Determine if the current user is allowed to view a particular
* field of this revision, if it's marked as deleted.
@@ -830,11 +940,11 @@
* self::DELETED_USER
* @return bool
*/
- function userCan( $field ) {
+ public function userCan( $field ) {
if( ( $this->mDeleted & $field ) == $field ) {
global $wgUser;
$permission = ( $this->mDeleted & self::DELETED_RESTRICTED ) == self::DELETED_RESTRICTED
- ? 'hiderevision'
+ ? 'suppressrevision'
: 'deleterevision';
wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
return $wgUser->isAllowed( $permission );
@@ -846,21 +956,27 @@
/**
* Get rev_timestamp from rev_id, without loading the rest of the row
+ * @param Title $title
* @param integer $id
*/
- static function getTimestampFromID( $id ) {
+ static function getTimestampFromId( $title, $id ) {
$dbr = wfGetDB( DB_SLAVE );
- $timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
- array( 'rev_id' => $id ), __METHOD__ );
- if ( $timestamp === false ) {
+ $conds = array( 'rev_id' => $id );
+ $conds['rev_page'] = $title->getArticleId();
+ $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
+ if ( $timestamp === false && wfGetLB()->getServerCount() > 1 ) {
# Not in slave, try master
$dbw = wfGetDB( DB_MASTER );
- $timestamp = $dbw->selectField( 'revision', 'rev_timestamp',
- array( 'rev_id' => $id ), __METHOD__ );
+ $timestamp = $dbw->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
}
- return $timestamp;
+ return wfTimestamp( TS_MW, $timestamp );
}
-
+
+ /**
+ * Get count of revisions per page...not very efficient
+ * @param Database $db
+ * @param int $id, page id
+ */
static function countByPageId( $db, $id ) {
$row = $db->selectRow( 'revision', 'COUNT(*) AS revCount',
array( 'rev_page' => $id ), __METHOD__ );
@@ -869,7 +985,12 @@
}
return 0;
}
-
+
+ /**
+ * Get count of revisions per page...not very efficient
+ * @param Database $db
+ * @param Title $title
+ */
static function countByTitle( $db, $title ) {
$id = $title->getArticleId();
if( $id ) {
@@ -886,6 +1007,3 @@
define( 'MW_REV_DELETED_COMMENT', Revision::DELETED_COMMENT );
define( 'MW_REV_DELETED_USER', Revision::DELETED_USER );
define( 'MW_REV_DELETED_RESTRICTED', Revision::DELETED_RESTRICTED );
-
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Sanitizer.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Sanitizer.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Sanitizer.php Fri Aug 31 00:48:45 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Sanitizer.php Tue Jan 6 21:31:30 2009
@@ -20,7 +20,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @addtogroup Parser
+ * @file
+ * @ingroup Parser
*/
/**
@@ -327,12 +328,9 @@
/**
* XHTML sanitizer for MediaWiki
- * @addtogroup Parser
+ * @ingroup Parser
*/
class Sanitizer {
- const NONE = 0;
- const INITIAL_NONLETTER = 1;
-
/**
* Cleans up HTML, removes dangerous tags and attributes, and
* removes HTML comments
@@ -383,7 +381,7 @@
$htmlelements = array_merge( $htmlsingle, $htmlpairs, $htmlnest );
# Convert them all to hashtables for faster lookup
- $vars = array( 'htmlpairs', 'htmlsingle', 'htmlsingleonly', 'htmlnest', 'tabletags',
+ $vars = array( 'htmlpairs', 'htmlsingle', 'htmlsingleonly', 'htmlnest', 'tabletags',
'htmllist', 'listtags', 'htmlsingleallowed', 'htmlelements' );
foreach ( $vars as $var ) {
$$var = array_flip( $$var );
@@ -419,7 +417,7 @@
$optstack = array();
array_push ($optstack, $ot);
while ( ( ( $ot = @array_pop( $tagstack ) ) != $t ) &&
- isset( $htmlsingleallowed[$ot] ) )
+ isset( $htmlsingleallowed[$ot] ) )
{
array_push ($optstack, $ot);
}
@@ -582,7 +580,7 @@
return Sanitizer::validateAttributes( $attribs,
Sanitizer::attributeWhitelist( $element ) );
}
-
+
/**
* Take an array of attribute names and values and normalize or discard
* illegal values for the given whitelist.
@@ -615,8 +613,11 @@
}
}
- if ( $attribute === 'id' )
- $value = Sanitizer::escapeId( $value );
+ if ( $attribute === 'id' ) {
+ global $wgEnforceHtmlIds;
+ $value = Sanitizer::escapeId( $value,
+ $wgEnforceHtmlIds ? 'noninitial' : 'xml' );
+ }
// If this attribute was previously set, override it.
// Output should only have one attribute of each name.
@@ -624,12 +625,11 @@
}
return $out;
}
-
+
/**
- * Merge two sets of HTML attributes.
- * Conflicting items in the second set will override those
- * in the first, except for 'class' attributes which will be
- * combined.
+ * Merge two sets of HTML attributes. Conflicting items in the second set
+ * will override those in the first, except for 'class' attributes which
+ * will be combined (if they're both strings).
*
* @todo implement merging for other attributes such as style
* @param array $a
@@ -638,20 +638,16 @@
*/
static function mergeAttributes( $a, $b ) {
$out = array_merge( $a, $b );
- if( isset( $a['class'] )
- && isset( $b['class'] )
- && $a['class'] !== $b['class'] ) {
-
- $out['class'] = implode( ' ',
- array_unique(
- preg_split( '/\s+/',
- $a['class'] . ' ' . $b['class'],
- -1,
- PREG_SPLIT_NO_EMPTY ) ) );
+ if( isset( $a['class'] ) && isset( $b['class'] )
+ && is_string( $a['class'] ) && is_string( $b['class'] )
+ && $a['class'] !== $b['class'] ) {
+ $classes = preg_split( '/\s+/', "{$a['class']} {$b['class']}",
+ -1, PREG_SPLIT_NO_EMPTY );
+ $out['class'] = implode( ' ', array_unique( $classes ) );
}
return $out;
}
-
+
/**
* Pick apart some CSS and check it for forbidden or unsafe structures.
* Returns a sanitized string, or false if it was just too evil.
@@ -666,7 +662,7 @@
// Remove any comments; IE gets token splitting wrong
$stripped = StringUtils::delimiterReplace( '/*', '*/', ' ', $stripped );
-
+
$value = $stripped;
// ... and continue checks
@@ -678,7 +674,7 @@
# haxx0r
return false;
}
-
+
return $value;
}
@@ -725,7 +721,7 @@
* @return HTML-encoded text fragment
*/
static function encodeAttribute( $text ) {
- $encValue = htmlspecialchars( $text );
+ $encValue = htmlspecialchars( $text, ENT_QUOTES );
// Whitespace is normalized during attribute decoding,
// so if we've been passed non-spaces we must encode them
@@ -781,28 +777,55 @@
* name attributes
* @see http://www.w3.org/TR/html401/struct/links.html#h-12.2.3 Anchors with the id attribute
*
- * @param string $id Id to validate
- * @param int $flags Currently only two values: Sanitizer::INITIAL_NONLETTER
- * (default) permits initial non-letter characters,
- * such as if you're adding a prefix to them.
- * Sanitizer::NONE will prepend an 'x' if the id
- * would otherwise start with a nonletter.
+ * @param string $id Id to validate
+ * @param mixed $options String or array of strings (default is array()):
+ * 'noninitial': This is a non-initial fragment of an id, not a full id,
+ * so don't pay attention if the first character isn't valid at the
+ * beginning of an id.
+ * 'xml': Don't restrict the id to be HTML4-compatible. This option
+ * allows any alphabetic character to be used, per the XML standard.
+ * Therefore, it also completely changes the type of escaping: instead
+ * of weird dot-encoding, runs of invalid characters (mostly
+ * whitespace) are just compressed into a single underscore.
* @return string
*/
- static function escapeId( $id, $flags = Sanitizer::INITIAL_NONLETTER ) {
- static $replace = array(
- '%3A' => ':',
- '%' => '.'
- );
-
- $id = urlencode( Sanitizer::decodeCharReferences( strtr( $id, ' ', '_' ) ) );
- $id = str_replace( array_keys( $replace ), array_values( $replace ), $id );
-
- if( ~$flags & Sanitizer::INITIAL_NONLETTER
- && !preg_match( '/[a-zA-Z]/', $id[0] ) ) {
- // Initial character must be a letter!
- $id = "x$id";
+ static function escapeId( $id, $options = array() ) {
+ $options = (array)$options;
+
+ if ( !in_array( 'xml', $options ) ) {
+ # HTML4-style escaping
+ static $replace = array(
+ '%3A' => ':',
+ '%' => '.'
+ );
+
+ $id = urlencode( Sanitizer::decodeCharReferences( strtr( $id, ' ', '_' ) ) );
+ $id = str_replace( array_keys( $replace ), array_values( $replace ), $id );
+
+ if ( !preg_match( '/^[a-zA-Z]/', $id )
+ && !in_array( 'noninitial', $options ) ) {
+ // Initial character must be a letter!
+ $id = "x$id";
+ }
+ return $id;
}
+
+ # XML-style escaping. For the patterns used, see the XML 1.0 standard,
+ # 5th edition, NameStartChar and NameChar:
+ $nameStartChar = ':a-zA-Z_\xC0-\xD6\xD8-\xF6\xF8-\x{2FF}\x{370}-\x{37D}'
+ . '\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}'
+ . '\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}';
+ $nameChar = $nameStartChar . '.\-0-9\xB7\x{0300}-\x{036F}'
+ . '\x{203F}-\x{2040}';
+ # Replace _ as well so we don't get multiple consecutive underscores
+ $id = preg_replace( "/([^$nameChar]|_)+/u", '_', $id );
+ $id = trim( $id, '_' );
+
+ if ( !preg_match( "/^[$nameStartChar]/u", $id )
+ && !in_array( 'noninitial', $options ) ) {
+ $id = "_$id";
+ }
+
return $id;
}
@@ -826,6 +849,22 @@
}
/**
+ * Given HTML input, escape with htmlspecialchars but un-escape entites.
+ * This allows (generally harmless) entities like to survive.
+ *
+ * @param string $html String to escape
+ * @return string Escaped input
+ */
+ static function escapeHtmlAllowEntities( $html ) {
+ # It seems wise to escape ' as well as ", as a matter of course. Can't
+ # hurt.
+ $html = htmlspecialchars( $html, ENT_QUOTES );
+ $html = str_replace( '&', '&', $html );
+ $html = Sanitizer::normalizeCharReferences( $html );
+ return $html;
+ }
+
+ /**
* Regex replace callback for armoring links against further processing.
* @param array $matches
* @return string
@@ -843,7 +882,7 @@
* @param string
* @return array
*/
- static function decodeTagAttributes( $text ) {
+ public static function decodeTagAttributes( $text ) {
$attribs = array();
if( trim( $text ) == '' ) {
@@ -920,7 +959,7 @@
self::normalizeWhitespace(
Sanitizer::normalizeCharReferences( $text ) ) );
}
-
+
private static function normalizeWhitespace( $text ) {
return preg_replace(
'/\r\n|[\x20\x0d\x0a\x09]/',
@@ -972,8 +1011,8 @@
/**
* If the named entity is defined in the HTML 4.0/XHTML 1.0 DTD,
- * return the named entity reference as is. If the entity is a
- * MediaWiki-specific alias, returns the HTML equivalent. Otherwise,
+ * return the named entity reference as is. If the entity is a
+ * MediaWiki-specific alias, returns the HTML equivalent. Otherwise,
* returns HTML-escaped text of pseudo-entity source (eg &foo;)
*
* @param string $name
@@ -1110,7 +1149,8 @@
}
/**
- * @todo Document it a bit
+ * Foreach array key (an allowed HTML element), return an array
+ * of allowed attributes
* @return array
*/
static function setupAttributeWhitelist() {
@@ -1219,7 +1259,7 @@
# 11.2.6
'td' => array_merge( $common, $tablecell, $tablealign ),
'th' => array_merge( $common, $tablecell, $tablealign ),
-
+
# 13.2
# Not usually allowed, but may be used for extension-style hooks
# such as when it is rasterized
@@ -1250,7 +1290,7 @@
'rb' => $common,
'rt' => $common, #array_merge( $common, array( 'rbspan' ) ),
'rp' => $common,
-
+
# MathML root element, where used for extensions
# 'title' may not be 100% valid here; it's XHTML
# http://www.w3.org/TR/REC-MathML/
@@ -1300,7 +1340,7 @@
return $out;
}
- static function cleanUrl( $url, $hostname=true ) {
+ static function cleanUrl( $url ) {
# Normalize any HTML entities in input. They will be
# re-escaped by makeExternalLink().
$url = Sanitizer::decodeCharReferences( $url );
@@ -1343,5 +1383,3 @@
}
}
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchEngine.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchEngine.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchEngine.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchEngine.php Mon Jan 5 15:46:43 2009
@@ -1,11 +1,19 @@
test prefix:Main Page/Archive
+ */
+ function transformSearchTerm( $term ) {
+ return $term;
+ }
+
/**
* If an exact title match can be find, or a very slightly close match,
* return the title. If no match, returns NULL.
@@ -59,48 +80,42 @@
if (is_null($title))
return NULL;
- if ( $title->getNamespace() == NS_SPECIAL || $title->exists() ) {
+ if ( $title->getNamespace() == NS_SPECIAL || $title->isExternal()
+ || $title->exists() ) {
return $title;
}
# Now try all lower case (i.e. first letter capitalized)
#
$title = Title::newFromText( $wgContLang->lc( $term ) );
- if ( $title->exists() ) {
+ if ( $title && $title->exists() ) {
return $title;
}
# Now try capitalized string
#
$title = Title::newFromText( $wgContLang->ucwords( $term ) );
- if ( $title->exists() ) {
+ if ( $title && $title->exists() ) {
return $title;
}
# Now try all upper case
#
$title = Title::newFromText( $wgContLang->uc( $term ) );
- if ( $title->exists() ) {
+ if ( $title && $title->exists() ) {
return $title;
}
# Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
$title = Title::newFromText( $wgContLang->ucwordbreaks($term) );
- if ( $title->exists() ) {
+ if ( $title && $title->exists() ) {
return $title;
}
- global $wgCapitalLinks, $wgContLang;
- if( !$wgCapitalLinks ) {
- // Catch differs-by-first-letter-case-only
- $title = Title::newFromText( $wgContLang->ucfirst( $term ) );
- if ( $title->exists() ) {
- return $title;
- }
- $title = Title::newFromText( $wgContLang->lcfirst( $term ) );
- if ( $title->exists() ) {
- return $title;
- }
+ // Give hooks a chance at better match variants
+ $title = null;
+ if( !wfRunHooks( 'SearchGetNearMatch', array( $term, &$title ) ) ) {
+ return $title;
}
}
@@ -109,7 +124,7 @@
# Entering an IP address goes to the contributions page
if ( ( $title->getNamespace() == NS_USER && User::isIP($title->getText() ) )
|| User::isIP( trim( $searchterm ) ) ) {
- return SpecialPage::getTitleFor( 'Contributions', $title->getDbkey() );
+ return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
}
@@ -117,11 +132,11 @@
if ( $title->getNamespace() == NS_USER ) {
return $title;
}
-
+
# Go to images that exist even if there's no local page.
# There may have been a funny upload, or it may be on a shared
# file repository such as Wikimedia Commons.
- if( $title->getNamespace() == NS_IMAGE ) {
+ if( $title->getNamespace() == NS_FILE ) {
$image = wfFindFile( $title );
if( $image ) {
return $title;
@@ -139,12 +154,12 @@
if( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
return SearchEngine::getNearMatch( $matches[1] );
}
-
+
return NULL;
}
public static function legalSearchChars() {
- return "A-Za-z_'0-9\\x80-\\xFF\\-";
+ return "A-Za-z_'.0-9\\x80-\\xFF\\-";
}
/**
@@ -172,6 +187,37 @@
}
/**
+ * Parse some common prefixes: all (search everything)
+ * or namespace names
+ *
+ * @param string $query
+ */
+ function replacePrefixes( $query ){
+ global $wgContLang;
+
+ if( strpos($query,':') === false )
+ return $query; // nothing to do
+
+ $parsed = $query;
+ $allkeyword = wfMsgForContent('searchall').":";
+ if( strncmp($query, $allkeyword, strlen($allkeyword)) == 0 ){
+ $this->namespaces = null;
+ $parsed = substr($query,strlen($allkeyword));
+ } else if( strpos($query,':') !== false ) {
+ $prefix = substr($query,0,strpos($query,':'));
+ $index = $wgContLang->getNsIndex($prefix);
+ if($index !== false){
+ $this->namespaces = array($index);
+ $parsed = substr($query,strlen($prefix)+1);
+ }
+ }
+ if(trim($parsed) == '')
+ return $query; // prefix was the whole query
+
+ return $parsed;
+ }
+
+ /**
* Make a list of searchable namespaces and their canonical names.
* @return array
*/
@@ -185,7 +231,96 @@
}
return $arr;
}
-
+
+ /**
+ * Extract default namespaces to search from the given user's
+ * settings, returning a list of index numbers.
+ *
+ * @param User $user
+ * @return array
+ * @static
+ */
+ public static function userNamespaces( &$user ) {
+ $arr = array();
+ foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
+ if( $user->getOption( 'searchNs' . $ns ) ) {
+ $arr[] = $ns;
+ }
+ }
+ return $arr;
+ }
+
+ /**
+ * Find snippet highlight settings for a given user
+ *
+ * @param User $user
+ * @return array contextlines, contextchars
+ * @static
+ */
+ public static function userHighlightPrefs( &$user ){
+ //$contextlines = $user->getOption( 'contextlines', 5 );
+ //$contextchars = $user->getOption( 'contextchars', 50 );
+ $contextlines = 2; // Hardcode this. Old defaults sucked. :)
+ $contextchars = 75; // same as above.... :P
+ return array($contextlines, $contextchars);
+ }
+
+ /**
+ * An array of namespaces indexes to be searched by default
+ *
+ * @return array
+ * @static
+ */
+ public static function defaultNamespaces(){
+ global $wgNamespacesToBeSearchedDefault;
+
+ return array_keys($wgNamespacesToBeSearchedDefault, true);
+ }
+
+ /**
+ * Get a list of namespace names useful for showing in tooltips
+ * and preferences
+ *
+ * @param unknown_type $namespaces
+ */
+ public static function namespacesAsText( $namespaces ){
+ global $wgContLang;
+
+ $formatted = array_map( array($wgContLang,'getFormattedNsText'), $namespaces );
+ foreach( $formatted as $key => $ns ){
+ if ( empty($ns) )
+ $formatted[$key] = wfMsg( 'blanknamespace' );
+ }
+ return $formatted;
+ }
+
+ /**
+ * An array of "project" namespaces indexes typically searched
+ * by logged-in users
+ *
+ * @return array
+ * @static
+ */
+ public static function projectNamespaces() {
+ global $wgNamespacesToBeSearchedDefault, $wgNamespacesToBeSearchedProject;
+
+ return array_keys( $wgNamespacesToBeSearchedProject, true );
+ }
+
+ /**
+ * An array of "project" namespaces indexes typically searched
+ * by logged-in users in addition to the default namespaces
+ *
+ * @return array
+ * @static
+ */
+ public static function defaultAndProjectNamespaces() {
+ global $wgNamespacesToBeSearchedDefault, $wgNamespacesToBeSearchedProject;
+
+ return array_keys( $wgNamespacesToBeSearchedDefault +
+ $wgNamespacesToBeSearchedProject, true);
+ }
+
/**
* Return a 'cleaned up' search string
*
@@ -203,19 +338,14 @@
* @return SearchEngine
*/
public static function create() {
- global $wgDBtype, $wgSearchType;
+ global $wgSearchType;
+ $dbr = wfGetDB( DB_SLAVE );
if( $wgSearchType ) {
$class = $wgSearchType;
- } elseif( $wgDBtype == 'mysql' ) {
- $class = 'SearchMySQL4';
- } else if ( $wgDBtype == 'postgres' ) {
- $class = 'SearchPostgres';
- } else if ( $wgDBtype == 'oracle' ) {
- $class = 'SearchOracle';
} else {
- $class = 'SearchEngineDummy';
+ $class = $dbr->getSearchEngine();
}
- $search = new $class( wfGetDB( DB_SLAVE ) );
+ $search = new $class( $dbr );
$search->setLimitOffset(0,0);
return $search;
}
@@ -244,11 +374,41 @@
function updateTitle( $id, $title ) {
// no-op
}
+
+ /**
+ * Get OpenSearch suggestion template
+ *
+ * @return string
+ * @static
+ */
+ public static function getOpenSearchTemplate() {
+ global $wgOpenSearchTemplate, $wgServer, $wgScriptPath;
+ if( $wgOpenSearchTemplate ) {
+ return $wgOpenSearchTemplate;
+ } else {
+ $ns = implode( '|', SearchEngine::defaultNamespaces() );
+ if( !$ns ) $ns = "0";
+ return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace='.$ns;
+ }
+ }
+
+ /**
+ * Get internal MediaWiki Suggest template
+ *
+ * @return string
+ * @static
+ */
+ public static function getMWSuggestTemplate() {
+ global $wgMWSuggestTemplate, $wgServer, $wgScriptPath;
+ if($wgMWSuggestTemplate)
+ return $wgMWSuggestTemplate;
+ else
+ return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace={namespaces}';
+ }
}
-
/**
- * @addtogroup Search
+ * @ingroup Search
*/
class SearchResultSet {
/**
@@ -303,15 +463,47 @@
}
/**
- * Some search modes return a suggested alternate term if there are
- * no exact hits. Check hasSuggestion() first.
+ * @return string suggested query, null if none
+ */
+ function getSuggestionQuery(){
+ return null;
+ }
+
+ /**
+ * @return string HTML highlighted suggested query, '' if none
+ */
+ function getSuggestionSnippet(){
+ return '';
+ }
+
+ /**
+ * Return information about how and from where the results were fetched,
+ * should be useful for diagnostics and debugging
*
* @return string
- * @access public
*/
- function getSuggestion() {
- return '';
+ function getInfo() {
+ return null;
+ }
+
+ /**
+ * Return a result set of hits on other (multiple) wikis associated with this one
+ *
+ * @return SearchResultSet
+ */
+ function getInterwikiResults() {
+ return null;
+ }
+
+ /**
+ * Check if there are results on other wikis
+ *
+ * @return boolean
+ */
+ function hasInterwikiResults() {
+ return $this->getInterwikiResults() != null;
}
+
/**
* Fetches next search result, or false.
@@ -322,7 +514,7 @@
function next() {
return false;
}
-
+
/**
* Frees the result set, if applicable.
* @ access public
@@ -334,11 +526,52 @@
/**
- * @addtogroup Search
+ * @ingroup Search
+ */
+class SearchResultTooMany {
+ ## Some search engines may bail out if too many matches are found
+}
+
+
+/**
+ * @fixme This class is horribly factored. It would probably be better to have
+ * a useful base class to which you pass some standard information, then let
+ * the fancy self-highlighters extend that.
+ * @ingroup Search
*/
class SearchResult {
- function SearchResult( $row ) {
+ var $mRevision = null;
+ var $mImage = null;
+
+ function __construct( $row ) {
$this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
+ if( !is_null($this->mTitle) ){
+ $this->mRevision = Revision::newFromTitle( $this->mTitle );
+ if( $this->mTitle->getNamespace() === NS_FILE )
+ $this->mImage = wfFindFile( $this->mTitle );
+ }
+ }
+
+ /**
+ * Check if this is result points to an invalid title
+ *
+ * @return boolean
+ * @access public
+ */
+ function isBrokenTitle(){
+ if( is_null($this->mTitle) )
+ return true;
+ return false;
+ }
+
+ /**
+ * Check if target page is missing, happens when index is out of date
+ *
+ * @return boolean
+ * @access public
+ */
+ function isMissingRevision(){
+ return !$this->mRevision && !$this->mImage;
}
/**
@@ -355,20 +588,613 @@
function getScore() {
return null;
}
+
+ /**
+ * Lazy initialization of article text from DB
+ */
+ protected function initText(){
+ if( !isset($this->mText) ){
+ if($this->mRevision != null)
+ $this->mText = $this->mRevision->getText();
+ else // TODO: can we fetch raw wikitext for commons images?
+ $this->mText = '';
+
+ }
+ }
+
+ /**
+ * @param array $terms terms to highlight
+ * @return string highlighted text snippet, null (and not '') if not supported
+ */
+ function getTextSnippet($terms){
+ global $wgUser, $wgAdvancedSearchHighlighting;
+ $this->initText();
+ list($contextlines,$contextchars) = SearchEngine::userHighlightPrefs($wgUser);
+ $h = new SearchHighlighter();
+ if( $wgAdvancedSearchHighlighting )
+ return $h->highlightText( $this->mText, $terms, $contextlines, $contextchars );
+ else
+ return $h->highlightSimple( $this->mText, $terms, $contextlines, $contextchars );
+ }
+
+ /**
+ * @param array $terms terms to highlight
+ * @return string highlighted title, '' if not supported
+ */
+ function getTitleSnippet($terms){
+ return '';
+ }
+
+ /**
+ * @param array $terms terms to highlight
+ * @return string highlighted redirect name (redirect to this page), '' if none or not supported
+ */
+ function getRedirectSnippet($terms){
+ return '';
+ }
+
+ /**
+ * @return Title object for the redirect to this page, null if none or not supported
+ */
+ function getRedirectTitle(){
+ return null;
+ }
+
+ /**
+ * @return string highlighted relevant section name, null if none or not supported
+ */
+ function getSectionSnippet(){
+ return '';
+ }
+
+ /**
+ * @return Title object (pagename+fragment) for the section, null if none or not supported
+ */
+ function getSectionTitle(){
+ return null;
+ }
+
+ /**
+ * @return string timestamp
+ */
+ function getTimestamp(){
+ if( $this->mRevision )
+ return $this->mRevision->getTimestamp();
+ else if( $this->mImage )
+ return $this->mImage->getTimestamp();
+ return '';
+ }
+
+ /**
+ * @return int number of words
+ */
+ function getWordCount(){
+ $this->initText();
+ return str_word_count( $this->mText );
+ }
+
+ /**
+ * @return int size in bytes
+ */
+ function getByteSize(){
+ $this->initText();
+ return strlen( $this->mText );
+ }
+
+ /**
+ * @return boolean if hit has related articles
+ */
+ function hasRelated(){
+ return false;
+ }
+
+ /**
+ * @return interwiki prefix of the title (return iw even if title is broken)
+ */
+ function getInterwikiPrefix(){
+ return '';
+ }
}
/**
- * @addtogroup Search
+ * Highlight bits of wikitext
+ *
+ * @ingroup Search
*/
-class SearchEngineDummy {
- function search( $term ) {
- return null;
+class SearchHighlighter {
+ var $mCleanWikitext = true;
+
+ function SearchHighlighter($cleanupWikitext = true){
+ $this->mCleanWikitext = $cleanupWikitext;
}
- function setLimitOffset($l, $o) {}
- function legalSearchChars() {}
- function update() {}
- function setnamespaces() {}
- function searchtitle() {}
- function searchtext() {}
+
+ /**
+ * Default implementation of wikitext highlighting
+ *
+ * @param string $text
+ * @param array $terms Terms to highlight (unescaped)
+ * @param int $contextlines
+ * @param int $contextchars
+ * @return string
+ */
+ public function highlightText( $text, $terms, $contextlines, $contextchars ) {
+ global $wgLang, $wgContLang;
+ global $wgSearchHighlightBoundaries;
+ $fname = __METHOD__;
+
+ if($text == '')
+ return '';
+
+ // spli text into text + templates/links/tables
+ $spat = "/(\\{\\{)|(\\[\\[[^\\]:]+:)|(\n\\{\\|)";
+ // first capture group is for detecting nested templates/links/tables/references
+ $endPatterns = array(
+ 1 => '/(\{\{)|(\}\})/', // template
+ 2 => '/(\[\[)|(\]\])/', // image
+ 3 => "/(\n\\{\\|)|(\n\\|\\})/"); // table
+
+ // FIXME: this should prolly be a hook or something
+ if(function_exists('wfCite')){
+ $spat .= '|([)'; // references via cite extension
+ $endPatterns[4] = '/(][)|(<\/ref>)/';
+ }
+ $spat .= '/';
+ $textExt = array(); // text extracts
+ $otherExt = array(); // other extracts
+ wfProfileIn( "$fname-split" );
+ $start = 0;
+ $textLen = strlen($text);
+ $count = 0; // sequence number to maintain ordering
+ while( $start < $textLen ){
+ // find start of template/image/table
+ if( preg_match( $spat, $text, $matches, PREG_OFFSET_CAPTURE, $start ) ){
+ $epat = '';
+ foreach($matches as $key => $val){
+ if($key > 0 && $val[1] != -1){
+ if($key == 2){
+ // see if this is an image link
+ $ns = substr($val[0],2,-1);
+ if( $wgContLang->getNsIndex($ns) != NS_FILE )
+ break;
+
+ }
+ $epat = $endPatterns[$key];
+ $this->splitAndAdd( $textExt, $count, substr( $text, $start, $val[1] - $start ) );
+ $start = $val[1];
+ break;
+ }
+ }
+ if( $epat ){
+ // find end (and detect any nested elements)
+ $level = 0;
+ $offset = $start + 1;
+ $found = false;
+ while( preg_match( $epat, $text, $endMatches, PREG_OFFSET_CAPTURE, $offset ) ){
+ if( array_key_exists(2,$endMatches) ){
+ // found end
+ if($level == 0){
+ $len = strlen($endMatches[2][0]);
+ $off = $endMatches[2][1];
+ $this->splitAndAdd( $otherExt, $count,
+ substr( $text, $start, $off + $len - $start ) );
+ $start = $off + $len;
+ $found = true;
+ break;
+ } else{
+ // end of nested element
+ $level -= 1;
+ }
+ } else{
+ // nested
+ $level += 1;
+ }
+ $offset = $endMatches[0][1] + strlen($endMatches[0][0]);
+ }
+ if( ! $found ){
+ // couldn't find appropriate closing tag, skip
+ $this->splitAndAdd( $textExt, $count, substr( $text, $start, strlen($matches[0][0]) ) );
+ $start += strlen($matches[0][0]);
+ }
+ continue;
+ }
+ }
+ // else: add as text extract
+ $this->splitAndAdd( $textExt, $count, substr($text,$start) );
+ break;
+ }
+
+ $all = $textExt + $otherExt; // these have disjunct key sets
+
+ wfProfileOut( "$fname-split" );
+
+ // prepare regexps
+ foreach( $terms as $index => $term ) {
+ // manually do upper/lowercase stuff for utf-8 since PHP won't do it
+ if(preg_match('/[\x80-\xff]/', $term) ){
+ $terms[$index] = preg_replace_callback('/./us',array($this,'caseCallback'),$terms[$index]);
+ } else {
+ $terms[$index] = $term;
+ }
+ }
+ $anyterm = implode( '|', $terms );
+ $phrase = implode("$wgSearchHighlightBoundaries+", $terms );
+
+ // FIXME: a hack to scale contextchars, a correct solution
+ // would be to have contextchars actually be char and not byte
+ // length, and do proper utf-8 substrings and lengths everywhere,
+ // but PHP is making that very hard and unclean to implement :(
+ $scale = strlen($anyterm) / mb_strlen($anyterm);
+ $contextchars = intval( $contextchars * $scale );
+
+ $patPre = "(^|$wgSearchHighlightBoundaries)";
+ $patPost = "($wgSearchHighlightBoundaries|$)";
+
+ $pat1 = "/(".$phrase.")/ui";
+ $pat2 = "/$patPre(".$anyterm.")$patPost/ui";
+
+ wfProfileIn( "$fname-extract" );
+
+ $left = $contextlines;
+
+ $snippets = array();
+ $offsets = array();
+
+ // show beginning only if it contains all words
+ $first = 0;
+ $firstText = '';
+ foreach($textExt as $index => $line){
+ if(strlen($line)>0 && $line[0] != ';' && $line[0] != ':'){
+ $firstText = $this->extract( $line, 0, $contextchars * $contextlines );
+ $first = $index;
+ break;
+ }
+ }
+ if( $firstText ){
+ $succ = true;
+ // check if first text contains all terms
+ foreach($terms as $term){
+ if( ! preg_match("/$patPre".$term."$patPost/ui", $firstText) ){
+ $succ = false;
+ break;
+ }
+ }
+ if( $succ ){
+ $snippets[$first] = $firstText;
+ $offsets[$first] = 0;
+ }
+ }
+ if( ! $snippets ) {
+ // match whole query on text
+ $this->process($pat1, $textExt, $left, $contextchars, $snippets, $offsets);
+ // match whole query on templates/tables/images
+ $this->process($pat1, $otherExt, $left, $contextchars, $snippets, $offsets);
+ // match any words on text
+ $this->process($pat2, $textExt, $left, $contextchars, $snippets, $offsets);
+ // match any words on templates/tables/images
+ $this->process($pat2, $otherExt, $left, $contextchars, $snippets, $offsets);
+
+ ksort($snippets);
+ }
+
+ // add extra chars to each snippet to make snippets constant size
+ $extended = array();
+ if( count( $snippets ) == 0){
+ // couldn't find the target words, just show beginning of article
+ $targetchars = $contextchars * $contextlines;
+ $snippets[$first] = '';
+ $offsets[$first] = 0;
+ } else{
+ // if begin of the article contains the whole phrase, show only that !!
+ if( array_key_exists($first,$snippets) && preg_match($pat1,$snippets[$first])
+ && $offsets[$first] < $contextchars * 2 ){
+ $snippets = array ($first => $snippets[$first]);
+ }
+
+ // calc by how much to extend existing snippets
+ $targetchars = intval( ($contextchars * $contextlines) / count ( $snippets ) );
+ }
+
+ foreach($snippets as $index => $line){
+ $extended[$index] = $line;
+ $len = strlen($line);
+ if( $len < $targetchars - 20 ){
+ // complete this line
+ if($len < strlen( $all[$index] )){
+ $extended[$index] = $this->extract( $all[$index], $offsets[$index], $offsets[$index]+$targetchars, $offsets[$index]);
+ $len = strlen( $extended[$index] );
+ }
+
+ // add more lines
+ $add = $index + 1;
+ while( $len < $targetchars - 20
+ && array_key_exists($add,$all)
+ && !array_key_exists($add,$snippets) ){
+ $offsets[$add] = 0;
+ $tt = "\n".$this->extract( $all[$add], 0, $targetchars - $len, $offsets[$add] );
+ $extended[$add] = $tt;
+ $len += strlen( $tt );
+ $add++;
+ }
+ }
+ }
+
+ //$snippets = array_map('htmlspecialchars', $extended);
+ $snippets = $extended;
+ $last = -1;
+ $extract = '';
+ foreach($snippets as $index => $line){
+ if($last == -1)
+ $extract .= $line; // first line
+ elseif($last+1 == $index && $offsets[$last]+strlen($snippets[$last]) >= strlen($all[$last]))
+ $extract .= " ".$line; // continous lines
+ else
+ $extract .= '] ... ' . $line;
+
+ $last = $index;
+ }
+ if( $extract )
+ $extract .= ' ... ';
+
+ $processed = array();
+ foreach($terms as $term){
+ if( ! isset($processed[$term]) ){
+ $pat3 = "/$patPre(".$term.")$patPost/ui"; // highlight word
+ $extract = preg_replace( $pat3,
+ "\\1\\2 \\3", $extract );
+ $processed[$term] = true;
+ }
+ }
+
+ wfProfileOut( "$fname-extract" );
+
+ return $extract;
+ }
+
+ /**
+ * Split text into lines and add it to extracts array
+ *
+ * @param array $extracts index -> $line
+ * @param int $count
+ * @param string $text
+ */
+ function splitAndAdd(&$extracts, &$count, $text){
+ $split = explode( "\n", $this->mCleanWikitext? $this->removeWiki($text) : $text );
+ foreach($split as $line){
+ $tt = trim($line);
+ if( $tt )
+ $extracts[$count++] = $tt;
+ }
+ }
+
+ /**
+ * Do manual case conversion for non-ascii chars
+ *
+ * @param unknown_type $matches
+ */
+ function caseCallback($matches){
+ global $wgContLang;
+ if( strlen($matches[0]) > 1 ){
+ return '['.$wgContLang->lc($matches[0]).$wgContLang->uc($matches[0]).']';
+ } else
+ return $matches[0];
+ }
+
+ /**
+ * Extract part of the text from start to end, but by
+ * not chopping up words
+ * @param string $text
+ * @param int $start
+ * @param int $end
+ * @param int $posStart (out) actual start position
+ * @param int $posEnd (out) actual end position
+ * @return string
+ */
+ function extract($text, $start, $end, &$posStart = null, &$posEnd = null ){
+ global $wgContLang;
+
+ if( $start != 0)
+ $start = $this->position( $text, $start, 1 );
+ if( $end >= strlen($text) )
+ $end = strlen($text);
+ else
+ $end = $this->position( $text, $end );
+
+ if(!is_null($posStart))
+ $posStart = $start;
+ if(!is_null($posEnd))
+ $posEnd = $end;
+
+ if($end > $start)
+ return substr($text, $start, $end-$start);
+ else
+ return '';
+ }
+
+ /**
+ * Find a nonletter near a point (index) in the text
+ *
+ * @param string $text
+ * @param int $point
+ * @param int $offset to found index
+ * @return int nearest nonletter index, or beginning of utf8 char if none
+ */
+ function position($text, $point, $offset=0 ){
+ $tolerance = 10;
+ $s = max( 0, $point - $tolerance );
+ $l = min( strlen($text), $point + $tolerance ) - $s;
+ $m = array();
+ if( preg_match('/[ ,.!?~!@#$%^&*\(\)+=\-\\\|\[\]"\'<>]/', substr($text,$s,$l), $m, PREG_OFFSET_CAPTURE ) ){
+ return $m[0][1] + $s + $offset;
+ } else{
+ // check if point is on a valid first UTF8 char
+ $char = ord( $text[$point] );
+ while( $char >= 0x80 && $char < 0xc0 ) {
+ // skip trailing bytes
+ $point++;
+ if($point >= strlen($text))
+ return strlen($text);
+ $char = ord( $text[$point] );
+ }
+ return $point;
+
+ }
+ }
+
+ /**
+ * Search extracts for a pattern, and return snippets
+ *
+ * @param string $pattern regexp for matching lines
+ * @param array $extracts extracts to search
+ * @param int $linesleft number of extracts to make
+ * @param int $contextchars length of snippet
+ * @param array $out map for highlighted snippets
+ * @param array $offsets map of starting points of snippets
+ * @protected
+ */
+ function process( $pattern, $extracts, &$linesleft, &$contextchars, &$out, &$offsets ){
+ if($linesleft == 0)
+ return; // nothing to do
+ foreach($extracts as $index => $line){
+ if( array_key_exists($index,$out) )
+ continue; // this line already highlighted
+
+ $m = array();
+ if ( !preg_match( $pattern, $line, $m, PREG_OFFSET_CAPTURE ) )
+ continue;
+
+ $offset = $m[0][1];
+ $len = strlen($m[0][0]);
+ if($offset + $len < $contextchars)
+ $begin = 0;
+ elseif( $len > $contextchars)
+ $begin = $offset;
+ else
+ $begin = $offset + intval( ($len - $contextchars) / 2 );
+
+ $end = $begin + $contextchars;
+
+ $posBegin = $begin;
+ // basic snippet from this line
+ $out[$index] = $this->extract($line,$begin,$end,$posBegin);
+ $offsets[$index] = $posBegin;
+ $linesleft--;
+ if($linesleft == 0)
+ return;
+ }
+ }
+
+ /**
+ * Basic wikitext removal
+ * @protected
+ */
+ function removeWiki($text) {
+ $fname = __METHOD__;
+ wfProfileIn( $fname );
+
+ //$text = preg_replace("/'{2,5}/", "", $text);
+ //$text = preg_replace("/\[[a-z]+:\/\/[^ ]+ ([^]]+)\]/", "\\2", $text);
+ //$text = preg_replace("/\[\[([^]|]+)\]\]/", "\\1", $text);
+ //$text = preg_replace("/\[\[([^]]+\|)?([^|]]+)\]\]/", "\\2", $text);
+ //$text = preg_replace("/\\{\\|(.*?)\\|\\}/", "", $text);
+ //$text = preg_replace("/\\[\\[[A-Za-z_-]+:([^|]+?)\\]\\]/", "", $text);
+ $text = preg_replace("/\\{\\{([^|]+?)\\}\\}/", "", $text);
+ $text = preg_replace("/\\{\\{([^|]+\\|)(.*?)\\}\\}/", "\\2", $text);
+ $text = preg_replace("/\\[\\[([^|]+?)\\]\\]/", "\\1", $text);
+ $text = preg_replace_callback("/\\[\\[([^|]+\\|)(.*?)\\]\\]/", array($this,'linkReplace'), $text);
+ //$text = preg_replace("/\\[\\[([^|]+\\|)(.*?)\\]\\]/", "\\2", $text);
+ $text = preg_replace("/<\/?[^>]+>/", "", $text);
+ $text = preg_replace("/'''''/", "", $text);
+ $text = preg_replace("/('''|<\/?[iIuUbB]>)/", "", $text);
+ $text = preg_replace("/''/", "", $text);
+
+ wfProfileOut( $fname );
+ return $text;
+ }
+
+ /**
+ * callback to replace [[target|caption]] kind of links, if
+ * the target is category or image, leave it
+ *
+ * @param array $matches
+ */
+ function linkReplace($matches){
+ $colon = strpos( $matches[1], ':' );
+ if( $colon === false )
+ return $matches[2]; // replace with caption
+ global $wgContLang;
+ $ns = substr( $matches[1], 0, $colon );
+ $index = $wgContLang->getNsIndex($ns);
+ if( $index !== false && ($index == NS_FILE || $index == NS_CATEGORY) )
+ return $matches[0]; // return the whole thing
+ else
+ return $matches[2];
+
+ }
+
+ /**
+ * Simple & fast snippet extraction, but gives completely unrelevant
+ * snippets
+ *
+ * @param string $text
+ * @param array $terms
+ * @param int $contextlines
+ * @param int $contextchars
+ * @return string
+ */
+ public function highlightSimple( $text, $terms, $contextlines, $contextchars ) {
+ global $wgLang, $wgContLang;
+ $fname = __METHOD__;
+
+ $lines = explode( "\n", $text );
+
+ $terms = implode( '|', $terms );
+ $max = intval( $contextchars ) + 1;
+ $pat1 = "/(.*)($terms)(.{0,$max})/i";
+
+ $lineno = 0;
+
+ $extract = "";
+ wfProfileIn( "$fname-extract" );
+ foreach ( $lines as $line ) {
+ if ( 0 == $contextlines ) {
+ break;
+ }
+ ++$lineno;
+ $m = array();
+ if ( ! preg_match( $pat1, $line, $m ) ) {
+ continue;
+ }
+ --$contextlines;
+ $pre = $wgContLang->truncate( $m[1], -$contextchars, ' ... ' );
+
+ if ( count( $m ) < 3 ) {
+ $post = '';
+ } else {
+ $post = $wgContLang->truncate( $m[3], $contextchars, ' ... ' );
+ }
+
+ $found = $m[2];
+
+ $line = htmlspecialchars( $pre . $found . $post );
+ $pat2 = '/(' . $terms . ")/i";
+ $line = preg_replace( $pat2,
+ "\\1 ", $line );
+
+ $extract .= "${line}\n";
+ }
+ wfProfileOut( "$fname-extract" );
+
+ return $extract;
+ }
+
}
+/**
+ * Dummy class to be used when non-supported Database engine is present.
+ * @fixme Dummy class should probably try something at least mildly useful,
+ * such as a LIKE search through titles.
+ * @ingroup Search
+ */
+class SearchEngineDummy extends SearchEngine {
+ // no-op
+}
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchMySQL.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchMySQL.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchMySQL.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchMySQL.php Mon Dec 22 07:31:15 2008
@@ -18,11 +18,71 @@
# http://www.gnu.org/copyleft/gpl.html
/**
- * Search engine hook base class for MySQL.
- * Specific bits for MySQL 3 and 4 variants are in child classes.
- * @addtogroup Search
+ * @file
+ * @ingroup Search
+ */
+
+/**
+ * Search engine hook for MySQL 4+
+ * @ingroup Search
*/
class SearchMySQL extends SearchEngine {
+ var $strictMatching = true;
+
+ /** @todo document */
+ function __construct( $db ) {
+ $this->db = $db;
+ }
+
+ /**
+ * Parse the user's query and transform it into an SQL fragment which will
+ * become part of a WHERE clause
+ */
+ function parseQuery( $filteredText, $fulltext ) {
+ global $wgContLang;
+ $lc = SearchEngine::legalSearchChars(); // Minus format chars
+ $searchon = '';
+ $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 ) {
+ if( $searchon !== '' ) $searchon .= ' ';
+ if( $this->strictMatching && ($terms[1] == '') ) {
+ $terms[1] = '+';
+ }
+ $searchon .= $terms[1] . $wgContLang->stripForSearch( $terms[2] );
+ if( !empty( $terms[3] ) ) {
+ // Match individual terms in result highlighting...
+ $regexp = preg_quote( $terms[3], '/' );
+ if( $terms[4] ) {
+ $regexp = "\b$regexp"; // foo*
+ } else {
+ $regexp = "\b$regexp\b";
+ }
+ } else {
+ // Match the quoted term in result highlighting...
+ $regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' );
+ }
+ $this->searchTerms[] = $regexp;
+ }
+ wfDebug( "Would search with '$searchon'\n" );
+ wfDebug( 'Match with /' . implode( '|', $this->searchTerms ) . "/\n" );
+ } else {
+ wfDebug( "Can't understand search query '{$filteredText}'\n" );
+ }
+
+ $searchon = $this->db->strencode( $searchon );
+ $field = $this->getIndexField( $fulltext );
+ return " MATCH($field) AGAINST('$searchon' IN BOOLEAN MODE) ";
+ }
+
+ public static function legalSearchChars() {
+ return "\"*" . parent::legalSearchChars();
+ }
+
/**
* Perform a full text search query and return a result set.
*
@@ -67,9 +127,12 @@
* @private
*/
function queryNamespaces() {
- $namespaces = implode( ',', $this->namespaces );
- if ($namespaces == '') {
+ if( is_null($this->namespaces) )
+ return ''; # search all
+ if ( !count( $this->namespaces ) ) {
$namespaces = '0';
+ } else {
+ $namespaces = $this->db->makeList( $this->namespaces );
}
return 'AND page_namespace IN (' . $namespaces . ')';
}
@@ -154,7 +217,7 @@
'si_page' => $id,
'si_title' => $title,
'si_text' => $text
- ), 'SearchMySQL4::update' );
+ ), __METHOD__ );
}
/**
@@ -170,13 +233,13 @@
$dbw->update( 'searchindex',
array( 'si_title' => $title ),
array( 'si_page' => $id ),
- 'SearchMySQL4::updateTitle',
+ __METHOD__,
array( $dbw->lowPriorityOption() ) );
}
}
/**
- * @addtogroup Search
+ * @ingroup Search
*/
class MySQLSearchResultSet extends SearchResultSet {
function MySQLSearchResultSet( $resultSet, $terms ) {
@@ -200,10 +263,8 @@
return new SearchResult( $row );
}
}
-
+
function free() {
$this->mResultSet->free();
}
}
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchMySQL4.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchMySQL4.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchMySQL4.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchMySQL4.php Thu Jun 19 17:02:23 2008
@@ -18,51 +18,17 @@
# http://www.gnu.org/copyleft/gpl.html
/**
+ * @file
+ * @ingroup Search
+ */
+
+/**
* Search engine hook for MySQL 4+
- * @addtogroup Search
+ * This class retained for backwards compatibility...
+ * The meat's been moved to SearchMySQL, since the 3.x variety is gone.
+ * @ingroup Search
+ * @deprecated
*/
class SearchMySQL4 extends SearchMySQL {
- var $strictMatching = true;
-
- /** @todo document */
- function SearchMySQL4( $db ) {
- $this->db = $db;
- }
-
- /** @todo document */
- function parseQuery( $filteredText, $fulltext ) {
- global $wgContLang;
- $lc = SearchEngine::legalSearchChars();
- $searchon = '';
- $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 ) {
- if( $searchon !== '' ) $searchon .= ' ';
- if( $this->strictMatching && ($terms[1] == '') ) {
- $terms[1] = '+';
- }
- $searchon .= $terms[1] . $wgContLang->stripForSearch( $terms[2] );
- if( !empty( $terms[3] ) ) {
- $regexp = preg_quote( $terms[3], '/' );
- if( $terms[4] ) $regexp .= "[0-9A-Za-z_]+";
- } else {
- $regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' );
- }
- $this->searchTerms[] = $regexp;
- }
- wfDebug( "Would search with '$searchon'\n" );
- wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" );
- } else {
- wfDebug( "Can't understand search query '{$filteredText}'\n" );
- }
-
- $searchon = $this->db->strencode( $searchon );
- $field = $this->getIndexField( $fulltext );
- return " MATCH($field) AGAINST('$searchon' IN BOOLEAN MODE) ";
- }
+ /* whee */
}
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchOracle.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchOracle.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchOracle.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchOracle.php Mon Dec 22 07:31:15 2008
@@ -18,8 +18,13 @@
# http://www.gnu.org/copyleft/gpl.html
/**
+ * @file
+ * @ingroup Search
+ */
+
+/**
* Search engine hook base class for Oracle (ConText).
- * @addtogroup Search
+ * @ingroup Search
*/
class SearchOracle extends SearchEngine {
function __construct($db) {
@@ -70,9 +75,12 @@
* @private
*/
function queryNamespaces() {
- $namespaces = implode(',', $this->namespaces);
- if ($namespaces == '') {
+ if( is_null($this->namespaces) )
+ return '';
+ if ( !count( $this->namespaces ) ) {
$namespaces = '0';
+ } else {
+ $namespaces = $this->db->makeList( $this->namespaces );
}
return 'AND page_namespace IN (' . $namespaces . ')';
}
@@ -137,7 +145,10 @@
'WHERE page_id=si_page AND ' . $match;
}
- /** @todo document */
+ /**
+ * Parse a user input search string, and return an SQL fragment to be used
+ * as part of a WHERE clause
+ */
function parseQuery($filteredText, $fulltext) {
global $wgContLang;
$lc = SearchEngine::legalSearchChars();
@@ -163,9 +174,9 @@
}
}
- $searchon = $this->db->strencode(join(',', $q));
+ $searchon = $this->db->addQuotes(join(',', $q));
$field = $this->getIndexField($fulltext);
- return " CONTAINS($field, '$searchon', 1) > 0 ";
+ return " CONTAINS($field, $searchon, 1) > 0 ";
}
/**
@@ -208,7 +219,7 @@
}
/**
- * @addtogroup Search
+ * @ingroup Search
*/
class OracleSearchResultSet extends SearchResultSet {
function __construct($resultSet, $terms) {
@@ -231,5 +242,3 @@
return new SearchResult($row);
}
}
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchPostgres.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchPostgres.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchPostgres.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchPostgres.php Mon Dec 22 07:31:15 2008
@@ -18,18 +18,23 @@
# http://www.gnu.org/copyleft/gpl.html
/**
+ * @file
+ * @ingroup Search
+ */
+
+/**
* Search engine hook base class for Postgres
- * @addtogroup Search
+ * @ingroup Search
*/
class SearchPostgres extends SearchEngine {
- function SearchPostgres( $db ) {
+ function __construct( $db ) {
$this->db = $db;
}
/**
* Perform a full text search query via tsearch2 and return a result set.
- * Currently searches a page's current title (page.page_title) and
+ * Currently searches a page's current title (page.page_title) and
* latest revision article text (pagecontent.old_text)
*
* @param string $term - Raw search term
@@ -37,17 +42,31 @@
* @access public
*/
function searchTitle( $term ) {
- $resultSet = $this->db->resultObject( $this->db->query( $this->searchQuery( $term , 'titlevector', 'page_title' )));
+ $q = $this->searchQuery( $term , 'titlevector', 'page_title' );
+ $olderror = error_reporting(E_ERROR);
+ $resultSet = $this->db->resultObject( $this->db->query( $q, 'SearchPostgres', true ) );
+ error_reporting($olderror);
+ if (!$resultSet) {
+ // Needed for "Query requires full scan, GIN doesn't support it"
+ return new SearchResultTooMany();
+ }
return new PostgresSearchResultSet( $resultSet, $this->searchTerms );
}
function searchText( $term ) {
- $resultSet = $this->db->resultObject( $this->db->query( $this->searchQuery( $term, 'textvector', 'old_text' )));
+ $q = $this->searchQuery( $term, 'textvector', 'old_text' );
+ $olderror = error_reporting(E_ERROR);
+ $resultSet = $this->db->resultObject( $this->db->query( $q, 'SearchPostgres', true ) );
+ error_reporting($olderror);
+ if (!$resultSet) {
+ return new SearchResultTooMany();
+ }
return new PostgresSearchResultSet( $resultSet, $this->searchTerms );
}
/*
* Transform the user's search string into a better form for tsearch2
+ * Returns an SQL fragment consisting of quoted text to search for.
*/
function parseQuery( $term ) {
@@ -122,11 +141,13 @@
$this->db->getServerVersion();
$wgDBversion = $this->db->numeric_version;
}
+ $prefix = $wgDBversion < 8.3 ? "'default'," : '';
+ # Get the SQL fragment for the given term
$searchstring = $this->parseQuery( $term );
## We need a separate query here so gin does not complain about empty searches
- $SQL = "SELECT to_tsquery('default',$searchstring)";
+ $SQL = "SELECT to_tsquery($prefix $searchstring)";
$res = $this->db->doQuery($SQL);
if (!$res) {
## TODO: Better output (example to catch: one 'two)
@@ -148,22 +169,25 @@
}
$rankscore = $wgDBversion > 8.2 ? 5 : 1;
+ $rank = $wgDBversion < 8.3 ? 'rank' : 'ts_rank';
$query = "SELECT page_id, page_namespace, page_title, ".
- "rank($fulltext, to_tsquery('default',$searchstring), $rankscore) AS score ".
+ "$rank($fulltext, to_tsquery($prefix $searchstring), $rankscore) AS score ".
"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',$searchstring)";
+ "AND r.rev_text_id = c.old_id AND $fulltext @@ to_tsquery($prefix $searchstring)";
}
## Redirects
if (! $this->showRedirects)
- $query .= ' AND page_is_redirect = 0'; ## IS FALSE
+ $query .= ' AND page_is_redirect = 0';
## Namespaces - defaults to 0
- if ( count($this->namespaces) < 1)
- $query .= ' AND page_namespace = 0';
- else {
- $namespaces = implode( ',', $this->namespaces );
- $query .= " AND page_namespace IN ($namespaces)";
+ if( !is_null($this->namespaces) ){ // null -> search all
+ if ( count($this->namespaces) < 1)
+ $query .= ' AND page_namespace = 0';
+ else {
+ $namespaces = $this->db->makeList( $this->namespaces );
+ $query .= " AND page_namespace IN ($namespaces)";
+ }
}
$query .= " ORDER BY score DESC, page_id DESC";
@@ -179,9 +203,9 @@
function update( $pageid, $title, $text ) {
## We don't want to index older revisions
- $SQL = "UPDATE pagecontent SET textvector = NULL WHERE old_id = ".
- "(SELECT rev_text_id FROM revision WHERE rev_page = $pageid ".
- "ORDER BY rev_text_id DESC LIMIT 1 OFFSET 1)";
+ $SQL = "UPDATE pagecontent SET textvector = NULL WHERE old_id IN ".
+ "(SELECT rev_text_id FROM revision WHERE rev_page = " . intval( $pageid ) .
+ " ORDER BY rev_text_id DESC OFFSET 1)";
$this->db->doQuery($SQL);
return true;
}
@@ -193,11 +217,11 @@
} ## end of the SearchPostgres class
/**
- * @addtogroup Search
+ * @ingroup Search
*/
class PostgresSearchResult extends SearchResult {
- function PostgresSearchResult( $row ) {
- $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
+ function __construct( $row ) {
+ parent::__construct($row);
$this->score = $row->score;
}
function getScore() {
@@ -206,10 +230,10 @@
}
/**
- * @addtogroup Search
+ * @ingroup Search
*/
class PostgresSearchResultSet extends SearchResultSet {
- function PostgresSearchResultSet( $resultSet, $terms ) {
+ function __construct( $resultSet, $terms ) {
$this->mResultSet = $resultSet;
$this->mTerms = $terms;
}
@@ -231,6 +255,3 @@
}
}
}
-
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchUpdate.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchUpdate.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SearchUpdate.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SearchUpdate.php Thu Jun 19 17:02:23 2008
@@ -1,7 +1,7 @@
mId, $this->mNamespace, $this->mTitle, &$text ) );
-
+
# Perform the actual update
$search->update($this->mId, Title::indexTitle( $this->mNamespace, $this->mTitle ),
$text);
-
+
wfProfileOut( $fname );
}
}
/**
* Placeholder class
- * @addtogroup Search
+ * @ingroup Search
*/
class SearchUpdateMyISAM extends SearchUpdate {
# Inherits everything
}
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Setup.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Setup.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Setup.php Sun Jul 22 10:45:12 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Setup.php Tue Dec 23 18:34:35 2008
@@ -10,7 +10,7 @@
if( !defined( 'MEDIAWIKI' ) ) {
echo "This file is part of MediaWiki, it is not a valid entry point.\n";
exit( 1 );
-}
+}
# The main wiki script and things like database
# conversion and maintenance scripts all share a
@@ -58,12 +58,28 @@
$wgFileStore['deleted']['directory'] = "{$wgUploadDirectory}/deleted";
}
+/**
+ * Unconditional protection for NS_MEDIAWIKI since otherwise it's too easy for a
+ * sysadmin to set $wgNamespaceProtection incorrectly and leave the wiki insecure.
+ *
+ * Note that this is the definition of editinterface and it can be granted to
+ * all users if desired.
+ */
+$wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
+
+/**
+ * The canonical names of namespaces 6 and 7 are, as of v1.14, "File"
+ * and "File_talk". The old names "Image" and "Image_talk" are
+ * retained as aliases for backwards compatibility.
+ */
+$wgNamespaceAliases['Image'] = NS_FILE;
+$wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
/**
* Initialise $wgLocalFileRepo from backwards-compatible settings
*/
if ( !$wgLocalFileRepo ) {
- $wgLocalFileRepo = array(
+ $wgLocalFileRepo = array(
'class' => 'LocalRepo',
'name' => 'local',
'directory' => $wgUploadDirectory,
@@ -101,7 +117,7 @@
'fetchDescription' => $wgFetchCommonsDescriptions,
);
} else {
- $wgForeignFileRepos[] = array(
+ $wgForeignFileRepos[] = array(
'class' => 'FSRepo',
'name' => 'shared',
'directory' => $wgSharedUploadDirectory,
@@ -114,8 +130,9 @@
);
}
}
-
-require_once( "$IP/includes/AutoLoader.php" );
+if ( !class_exists( 'AutoLoader' ) ) {
+ require_once( "$IP/includes/AutoLoader.php" );
+}
wfProfileIn( $fname.'-exception' );
require_once( "$IP/includes/Exception.php" );
@@ -137,12 +154,6 @@
$wgIP = false; # Load on demand
# Can't stub this one, it sets up $_GET and $_REQUEST in its constructor
$wgRequest = new WebRequest;
-if ( function_exists( 'posix_uname' ) ) {
- $wguname = posix_uname();
- $wgNodeName = $wguname['nodename'];
-} else {
- $wgNodeName = '';
-}
# Useful debug output
if ( $wgCommandLineMode ) {
@@ -159,6 +170,19 @@
wfDebug( $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . "\n" );
}
+if( $wgRCFilterByAge ) {
+ ## Trim down $wgRCLinkDays so that it only lists links which are valid
+ ## as determined by $wgRCMaxAge.
+ ## Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
+ sort($wgRCLinkDays);
+ for( $i = 0; $i < count($wgRCLinkDays); $i++ ) {
+ if( $wgRCLinkDays[$i] >= $wgRCMaxAge / ( 3600 * 24 ) ) {
+ $wgRCLinkDays = array_slice( $wgRCLinkDays, 0, $i+1, false );
+ break;
+ }
+ }
+}
+
if ( $wgSkipSkin ) {
$wgSkipSkins[] = $wgSkipSkin;
}
@@ -181,24 +205,35 @@
$parserMemc =& wfGetParserCacheStorage();
wfDebug( 'Main cache: ' . get_class( $wgMemc ) .
- "\nMessage cache: " . get_class( $messageMemc ) .
- "\nParser cache: " . get_class( $parserMemc ) . "\n" );
+ "\nMessage cache: " . get_class( $messageMemc ) .
+ "\nParser cache: " . get_class( $parserMemc ) . "\n" );
wfProfileOut( $fname.'-memcached' );
+
+## Most of the config is out, some might want to run hooks here.
+wfRunHooks( 'SetupAfterCache' );
+
wfProfileIn( $fname.'-SetupSession' );
-if ( $wgDBprefix ) {
- $wgCookiePrefix = $wgDBname . '_' . $wgDBprefix;
-} elseif ( $wgSharedDB ) {
- $wgCookiePrefix = $wgSharedDB;
-} else {
- $wgCookiePrefix = $wgDBname;
+# Set default shared prefix
+if( $wgSharedPrefix === false ) $wgSharedPrefix = $wgDBprefix;
+
+if( !$wgCookiePrefix ) {
+ if ( $wgSharedDB && $wgSharedPrefix && in_array('user',$wgSharedTables) ) {
+ $wgCookiePrefix = $wgSharedDB . '_' . $wgSharedPrefix;
+ } elseif ( $wgSharedDB && in_array('user',$wgSharedTables) ) {
+ $wgCookiePrefix = $wgSharedDB;
+ } elseif ( $wgDBprefix ) {
+ $wgCookiePrefix = $wgDBname . '_' . $wgDBprefix;
+ } else {
+ $wgCookiePrefix = $wgDBname;
+ }
}
$wgCookiePrefix = strtr($wgCookiePrefix, "=,; +.\"'\\[", "__________");
# If session.auto_start is there, we can't touch session name
#
-if( !ini_get( 'session.auto_start' ) )
+if( !wfIniGetBool( 'session.auto_start' ) )
session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' );
if( !$wgCommandLineMode && ( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) ) {
@@ -213,20 +248,6 @@
wfProfileOut( $fname.'-SetupSession' );
wfProfileIn( $fname.'-globals' );
-if ( !$wgDBservers ) {
- $wgDBservers = array(array(
- 'host' => $wgDBserver,
- 'user' => $wgDBuser,
- 'password' => $wgDBpassword,
- 'dbname' => $wgDBname,
- 'type' => $wgDBtype,
- 'load' => 1,
- 'flags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT
- ));
-}
-
-$wgLoadBalancer = new StubObject( 'wgLoadBalancer', 'LoadBalancer',
- array( $wgDBservers, false, $wgMasterWaitTimeout, true ) );
$wgContLang = new StubContLang;
// Now that variant lists may be available...
@@ -235,9 +256,10 @@
$wgUser = new StubUser;
$wgLang = new StubUserLang;
$wgOut = new StubObject( 'wgOut', 'OutputPage' );
-$wgParser = new StubObject( 'wgParser', 'Parser' );
-$wgMessageCache = new StubObject( 'wgMessageCache', 'MessageCache',
- array( $parserMemc, $wgUseDatabaseMessages, $wgMsgCacheExpiry, wfWikiID() ) );
+$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
+
+$wgMessageCache = new StubObject( 'wgMessageCache', 'MessageCache',
+ array( $messageMemc, $wgUseDatabaseMessages, $wgMsgCacheExpiry, wfWikiID() ) );
wfProfileOut( $fname.'-globals' );
wfProfileIn( $fname.'-User' );
@@ -245,7 +267,7 @@
# Skin setup functions
# Entries can be added to this variable during the inclusion
# of the extension file. Skins can then perform any necessary initialisation.
-#
+#
foreach ( $wgSkinExtensionFunctions as $func ) {
call_user_func( $func );
}
@@ -262,14 +284,11 @@
$wgDeferredUpdateList = array();
$wgPostCommitUpdateList = array();
-if ( $wgAjaxSearch ) $wgAjaxExportList[] = 'wfSajaxSearch';
if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch';
if ( $wgAjaxUploadDestCheck ) $wgAjaxExportList[] = 'UploadForm::ajaxGetExistsWarning';
if( $wgAjaxLicensePreview )
$wgAjaxExportList[] = 'UploadForm::ajaxGetLicensePreview';
-wfSeedRandom();
-
# Placeholders in case of DB error
$wgTitle = null;
$wgArticle = null;
@@ -294,10 +313,18 @@
wfRunHooks( 'LogPageLogHeader', array( &$wgLogHeaders ) );
wfRunHooks( 'LogPageActionText', array( &$wgLogActions ) );
+if( !empty($wgNewUserLog) ) {
+ # Add a new log type
+ $wgLogTypes[] = 'newusers';
+ $wgLogNames['newusers'] = 'newuserlogpage';
+ $wgLogHeaders['newusers'] = 'newuserlogpagetext';
+ $wgLogActions['newusers/newusers'] = 'newuserlogentry'; // For compatibility with older log entries
+ $wgLogActions['newusers/create'] = 'newuserlog-create-entry';
+ $wgLogActions['newusers/create2'] = 'newuserlog-create2-entry';
+ $wgLogActions['newusers/autocreate'] = 'newuserlog-autocreate-entry';
+}
wfDebug( "Fully initialised\n" );
$wgFullyInitialised = true;
wfProfileOut( $fname.'-extensions' );
wfProfileOut( $fname );
-
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SiteConfiguration.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SiteConfiguration.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SiteConfiguration.php Thu Jun 28 21:19:14 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SiteConfiguration.php Tue Aug 26 11:10:12 2008
@@ -5,101 +5,347 @@
* meaning that require_once() fails to detect that it is including the same
* file again. We use DIY C-style protection as a workaround.
*/
-if (!defined('SITE_CONFIGURATION')) {
-define('SITE_CONFIGURATION', 1);
+
+// Hide this pattern from Doxygen, which spazzes out at it
+/// @cond
+if( !defined( 'SITE_CONFIGURATION' ) ){
+define( 'SITE_CONFIGURATION', 1 );
+/// @endcond
/**
* This is a class used to hold configuration settings, particularly for multi-wiki sites.
- *
*/
class SiteConfiguration {
- var $suffixes = array();
- var $wikis = array();
- var $settings = array();
- var $localVHosts = array();
-
- /** */
- function get( $setting, $wiki, $suffix, $params = array() ) {
- if ( array_key_exists( $setting, $this->settings ) ) {
- if ( array_key_exists( $wiki, $this->settings[$setting] ) ) {
- $retval = $this->settings[$setting][$wiki];
- } elseif ( array_key_exists( $suffix, $this->settings[$setting] ) ) {
- $retval = $this->settings[$setting][$suffix];
- } elseif ( array_key_exists( 'default', $this->settings[$setting] ) ) {
- $retval = $this->settings[$setting]['default'];
- } else {
- $retval = NULL;
- }
- } else {
- $retval = NULL;
+
+ /**
+ * Array of suffixes, for self::siteFromDB()
+ */
+ public $suffixes = array();
+
+ /**
+ * Array of wikis, should be the same as $wgLocalDatabases
+ */
+ public $wikis = array();
+
+ /**
+ * The whole array of settings
+ */
+ public $settings = array();
+
+ /**
+ * Array of domains that are local and can be handled by the same server
+ */
+ public $localVHosts = array();
+
+ /**
+ * A callback function that returns an array with the following keys (all
+ * optional):
+ * - suffix: site's suffix
+ * - lang: site's lang
+ * - tags: array of wiki tags
+ * - params: array of parameters to be replaced
+ * The function will receive the SiteConfiguration instance in the first
+ * argument and the wiki in the second one.
+ * if suffix and lang are passed they will be used for the return value of
+ * self::siteFromDB() and self::$suffixes will be ignored
+ */
+ public $siteParamsCallback = null;
+
+ /**
+ * Retrieves a configuration setting for a given wiki.
+ * @param $settingName String ID of the setting name to retrieve
+ * @param $wiki String Wiki ID of the wiki in question.
+ * @param $suffix String The suffix of the wiki in question.
+ * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param $wikiTags Array The tags assigned to the wiki.
+ * @return Mixed the value of the setting requested.
+ */
+ public function get( $settingName, $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
+ $params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
+ return $this->getSetting( $settingName, $wiki, $params );
+ }
+
+ /**
+ * Really retrieves a configuration setting for a given wiki.
+ *
+ * @param $settingName String ID of the setting name to retrieve.
+ * @param $wiki String Wiki ID of the wiki in question.
+ * @param $params Array: array of parameters.
+ * @return Mixed the value of the setting requested.
+ */
+ protected function getSetting( $settingName, $wiki, /*array*/ $params ){
+ $retval = null;
+ if( array_key_exists( $settingName, $this->settings ) ) {
+ $thisSetting =& $this->settings[$settingName];
+ do {
+ // Do individual wiki settings
+ if( array_key_exists( $wiki, $thisSetting ) ) {
+ $retval = $thisSetting[$wiki];
+ break;
+ } elseif( array_key_exists( "+$wiki", $thisSetting ) && is_array( $thisSetting["+$wiki"] ) ) {
+ $retval = $thisSetting["+$wiki"];
+ }
+
+ // Do tag settings
+ foreach( $params['tags'] as $tag ) {
+ if( array_key_exists( $tag, $thisSetting ) ) {
+ if ( isset( $retval ) && is_array( $retval ) && is_array( $thisSetting[$tag] ) ) {
+ $retval = self::arrayMerge( $retval, $thisSetting[$tag] );
+ } else {
+ $retval = $thisSetting[$tag];
+ }
+ break 2;
+ } elseif( array_key_exists( "+$tag", $thisSetting ) && is_array($thisSetting["+$tag"]) ) {
+ if( !isset( $retval ) )
+ $retval = array();
+ $retval = self::arrayMerge( $retval, $thisSetting["+$tag"] );
+ }
+ }
+ // Do suffix settings
+ $suffix = $params['suffix'];
+ if( !is_null( $suffix ) ) {
+ if( array_key_exists( $suffix, $thisSetting ) ) {
+ if ( isset($retval) && is_array($retval) && is_array($thisSetting[$suffix]) ) {
+ $retval = self::arrayMerge( $retval, $thisSetting[$suffix] );
+ } else {
+ $retval = $thisSetting[$suffix];
+ }
+ break;
+ } elseif( array_key_exists( "+$suffix", $thisSetting ) && is_array($thisSetting["+$suffix"]) ) {
+ if (!isset($retval))
+ $retval = array();
+ $retval = self::arrayMerge( $retval, $thisSetting["+$suffix"] );
+ }
+ }
+
+ // Fall back to default.
+ if( array_key_exists( 'default', $thisSetting ) ) {
+ if( is_array( $retval ) && is_array( $thisSetting['default'] ) ) {
+ $retval = self::arrayMerge( $retval, $thisSetting['default'] );
+ } else {
+ $retval = $thisSetting['default'];
+ }
+ break;
+ }
+ } while ( false );
}
- if ( !is_null( $retval ) && count( $params ) ) {
- foreach ( $params as $key => $value ) {
- $retval = str_replace( '$' . $key, $value, $retval );
+ if( !is_null( $retval ) && count( $params['params'] ) ) {
+ foreach ( $params['params'] as $key => $value ) {
+ $retval = $this->doReplace( '$' . $key, $value, $retval );
}
}
return $retval;
}
- /** */
- function getAll( $wiki, $suffix, $params ) {
+ /**
+ * Type-safe string replace; won't do replacements on non-strings
+ * private?
+ */
+ function doReplace( $from, $to, $in ) {
+ if( is_string( $in ) ) {
+ return str_replace( $from, $to, $in );
+ } elseif( is_array( $in ) ) {
+ foreach( $in as $key => $val ) {
+ $in[$key] = $this->doReplace( $from, $to, $val );
+ }
+ return $in;
+ } else {
+ return $in;
+ }
+ }
+
+ /**
+ * Gets all settings for a wiki
+ * @param $wiki String Wiki ID of the wiki in question.
+ * @param $suffix String The suffix of the wiki in question.
+ * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param $wikiTags Array The tags assigned to the wiki.
+ * @return Array Array of settings requested.
+ */
+ public function getAll( $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
+ $params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
$localSettings = array();
- foreach ( $this->settings as $varname => $stuff ) {
- $value = $this->get( $varname, $wiki, $suffix, $params );
+ foreach( $this->settings as $varname => $stuff ) {
+ $append = false;
+ $var = $varname;
+ if ( substr( $varname, 0, 1 ) == '+' ) {
+ $append = true;
+ $var = substr( $varname, 1 );
+ }
+
+ $value = $this->getSetting( $varname, $wiki, $params );
+ if ( $append && is_array( $value ) && is_array( $GLOBALS[$var] ) )
+ $value = self::arrayMerge( $value, $GLOBALS[$var] );
if ( !is_null( $value ) ) {
- $localSettings[$varname] = $value;
+ $localSettings[$var] = $value;
}
}
return $localSettings;
}
- /** */
- function getBool( $setting, $wiki, $suffix ) {
- return (bool)($this->get( $setting, $wiki, $suffix ));
+ /**
+ * Retrieves a configuration setting for a given wiki, forced to a boolean.
+ * @param $settingName String ID of the setting name to retrieve
+ * @param $wiki String Wiki ID of the wiki in question.
+ * @param $suffix String The suffix of the wiki in question.
+ * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param $wikiTags Array The tags assigned to the wiki.
+ * @return bool The value of the setting requested.
+ */
+ public function getBool( $setting, $wiki, $suffix = null, $wikiTags = array() ) {
+ return (bool)($this->get( $setting, $wiki, $suffix, array(), $wikiTags ) );
}
- /** */
+ /** Retrieves an array of local databases */
function &getLocalDatabases() {
return $this->wikis;
}
- /** */
+ /** A no-op */
function initialise() {
}
- /** */
- function extractVar( $setting, $wiki, $suffix, &$var, $params ) {
- $value = $this->get( $setting, $wiki, $suffix, $params );
+ /**
+ * Retrieves the value of a given setting, and places it in a variable passed by reference.
+ * @param $settingName String ID of the setting name to retrieve
+ * @param $wiki String Wiki ID of the wiki in question.
+ * @param $suffix String The suffix of the wiki in question.
+ * @param $var Reference The variable to insert the value into.
+ * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param $wikiTags Array The tags assigned to the wiki.
+ */
+ public function extractVar( $setting, $wiki, $suffix, &$var, $params = array(), $wikiTags = array() ) {
+ $value = $this->get( $setting, $wiki, $suffix, $params, $wikiTags );
if ( !is_null( $value ) ) {
$var = $value;
}
}
- /** */
- function extractGlobal( $setting, $wiki, $suffix, $params ) {
- $value = $this->get( $setting, $wiki, $suffix, $params );
+ /**
+ * Retrieves the value of a given setting, and places it in its corresponding global variable.
+ * @param $settingName String ID of the setting name to retrieve
+ * @param $wiki String Wiki ID of the wiki in question.
+ * @param $suffix String The suffix of the wiki in question.
+ * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param $wikiTags Array The tags assigned to the wiki.
+ */
+ public function extractGlobal( $setting, $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
+ $params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
+ $this->extractGlobalSetting( $setting, $wiki, $params );
+ }
+
+ public function extractGlobalSetting( $setting, $wiki, $params ) {
+ $value = $this->getSetting( $setting, $wiki, $params );
if ( !is_null( $value ) ) {
- $GLOBALS[$setting] = $value;
+ if (substr($setting,0,1) == '+' && is_array($value)) {
+ $setting = substr($setting,1);
+ if ( is_array($GLOBALS[$setting]) ) {
+ $GLOBALS[$setting] = self::arrayMerge( $GLOBALS[$setting], $value );
+ } else {
+ $GLOBALS[$setting] = $value;
+ }
+ } else {
+ $GLOBALS[$setting] = $value;
+ }
}
}
- /** */
- function extractAllGlobals( $wiki, $suffix, $params ) {
+ /**
+ * Retrieves the values of all settings, and places them in their corresponding global variables.
+ * @param $wiki String Wiki ID of the wiki in question.
+ * @param $suffix String The suffix of the wiki in question.
+ * @param $params Array List of parameters. $.'key' is replaced by $value in all returned data.
+ * @param $wikiTags Array The tags assigned to the wiki.
+ */
+ public function extractAllGlobals( $wiki, $suffix = null, $params = array(), $wikiTags = array() ) {
+ $params = $this->mergeParams( $wiki, $suffix, $params, $wikiTags );
foreach ( $this->settings as $varName => $setting ) {
- $this->extractGlobal( $varName, $wiki, $suffix, $params );
+ $this->extractGlobalSetting( $varName, $wiki, $params );
}
}
/**
+ * Return specific settings for $wiki
+ * See the documentation of self::$siteParamsCallback for more in-depth
+ * documentation about this function
+ *
+ * @param $wiki String
+ * @return array
+ */
+ protected function getWikiParams( $wiki ){
+ static $default = array(
+ 'suffix' => null,
+ 'lang' => null,
+ 'tags' => array(),
+ 'params' => array(),
+ );
+
+ if( !is_callable( $this->siteParamsCallback ) )
+ return $default;
+
+ $ret = call_user_func_array( $this->siteParamsCallback, array( $this, $wiki ) );
+ # Validate the returned value
+ if( !is_array( $ret ) )
+ return $default;
+
+ foreach( $default as $name => $def ){
+ if( !isset( $ret[$name] ) || ( is_array( $default[$name] ) && !is_array( $ret[$name] ) ) )
+ $ret[$name] = $default[$name];
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Merge params beetween the ones passed to the function and the ones given
+ * by self::$siteParamsCallback for backward compatibility
+ * Values returned by self::getWikiParams() have the priority.
+ *
+ * @param $wiki String Wiki ID of the wiki in question.
+ * @param $suffix String The suffix of the wiki in question.
+ * @param $params Array List of parameters. $.'key' is replaced by $value in
+ * all returned data.
+ * @param $wikiTags Array The tags assigned to the wiki.
+ * @return array
+ */
+ protected function mergeParams( $wiki, $suffix, /*array*/ $params, /*array*/ $wikiTags ){
+ $ret = $this->getWikiParams( $wiki );
+
+ if( is_null( $ret['suffix'] ) )
+ $ret['suffix'] = $suffix;
+
+ $ret['tags'] = array_unique( array_merge( $ret['tags'], $wikiTags ) );
+
+ $ret['params'] += $params;
+
+ // Automatically fill that ones if needed
+ if( !isset( $ret['params']['lang'] ) && !is_null( $ret['lang'] ) )
+ $ret['params']['lang'] = $ret['lang'];
+ if( !isset( $ret['params']['site'] ) && !is_null( $ret['suffix'] ) )
+ $ret['params']['site'] = $ret['suffix'];
+
+ return $ret;
+ }
+
+ /**
* Work out the site and language name from a database name
* @param $db
*/
- function siteFromDB( $db ) {
- $site = NULL;
- $lang = NULL;
+ public function siteFromDB( $db ) {
+ // Allow override
+ $def = $this->getWikiParams( $db );
+ if( !is_null( $def['suffix'] ) && !is_null( $def['lang'] ) )
+ return array( $def['suffix'], $def['lang'] );
+
+ $site = null;
+ $lang = null;
foreach ( $this->suffixes as $suffix ) {
- if ( substr( $db, -strlen( $suffix ) ) == $suffix ) {
+ if ( $suffix === '' ) {
+ $site = '';
+ $lang = $db;
+ break;
+ } elseif ( substr( $db, -strlen( $suffix ) ) == $suffix ) {
$site = $suffix == 'wiki' ? 'wikipedia' : $suffix;
$lang = substr( $db, 0, strlen( $db ) - strlen( $suffix ) );
break;
@@ -109,11 +355,37 @@
return array( $site, $lang );
}
- /** */
- function isLocalVHost( $vhost ) {
+ /**
+ * Returns true if the given vhost is handled locally.
+ * @param $vhost String
+ * @return bool
+ */
+ public function isLocalVHost( $vhost ) {
return in_array( $vhost, $this->localVHosts );
}
-}
-}
+ /**
+ * Merge multiple arrays together.
+ * On encountering duplicate keys, merge the two, but ONLY if they're arrays.
+ * PHP's array_merge_recursive() merges ANY duplicate values into arrays,
+ * which is not fun
+ */
+ static function arrayMerge( $array1/* ... */ ) {
+ $out = $array1;
+ for( $i=1; $i < func_num_args(); $i++ ) {
+ foreach( func_get_arg( $i ) as $key => $value ) {
+ if ( isset($out[$key]) && is_array($out[$key]) && is_array($value) ) {
+ $out[$key] = self::arrayMerge( $out[$key], $value );
+ } elseif ( !isset($out[$key]) || !$out[$key] && !is_numeric($key) ) {
+ // Values that evaluate to true given precedence, for the primary purpose of merging permissions arrays.
+ $out[$key] = $value;
+ } elseif ( is_numeric( $key ) ) {
+ $out[] = $value;
+ }
+ }
+ }
+ return $out;
+ }
+}
+}
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SiteStats.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SiteStats.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/SiteStats.php Thu Aug 9 08:27:50 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/SiteStats.php Fri Sep 26 11:35:11 2008
@@ -7,6 +7,7 @@
static $row, $loaded = false;
static $admins, $jobs;
static $pageCount = array();
+ static $groupMemberCounts = array();
static function recache() {
self::load( true );
@@ -27,10 +28,10 @@
$dbr = wfGetDB( DB_SLAVE );
self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ );
}
-
+
self::$loaded = true;
}
-
+
static function loadAndLazyInit() {
wfDebug( __METHOD__ . ": reading site_stats from slave\n" );
$row = self::doLoad( wfGetDB( DB_SLAVE ) );
@@ -40,24 +41,24 @@
wfDebug( __METHOD__ . ": site_stats damaged or missing on slave\n" );
$row = self::doLoad( wfGetDB( DB_MASTER ) );
}
-
+
if( !self::isSane( $row ) ) {
// Normally the site_stats table is initialized at install time.
// Some manual construction scenarios may leave the table empty or
// broken, however, for instance when importing from a dump into a
// clean schema with mwdumper.
wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" );
-
+
global $IP;
require_once "$IP/maintenance/initStats.inc";
-
+
ob_start();
wfInitStats();
ob_end_clean();
-
+
$row = self::doLoad( wfGetDB( DB_MASTER ) );
}
-
+
if( !self::isSane( $row ) ) {
wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" );
}
@@ -93,17 +94,43 @@
return self::$row->ss_users;
}
+ static function activeUsers() {
+ self::load();
+ return self::$row->ss_active_users;
+ }
+
static function images() {
self::load();
return self::$row->ss_images;
}
+ /**
+ * @deprecated Use self::numberingroup('sysop') instead
+ */
static function admins() {
- if ( !isset( self::$admins ) ) {
- $dbr = wfGetDB( DB_SLAVE );
- self::$admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), __METHOD__ );
+ wfDeprecated(__METHOD__);
+ return self::numberingroup('sysop');
+ }
+
+ /**
+ * Find the number of users in a given user group.
+ * @param string $group Name of group
+ * @return int
+ */
+ static function numberingroup($group) {
+ if ( !isset( self::$groupMemberCounts[$group] ) ) {
+ global $wgMemc;
+ $key = wfMemcKey( 'SiteStats', 'groupcounts', $group );
+ $hit = $wgMemc->get( $key );
+ if ( !$hit ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $hit = $dbr->selectField( 'user_groups', 'COUNT(*)',
+ array( 'ug_group' => $group ), __METHOD__ );
+ $wgMemc->set( $key, $hit, 3600 );
+ }
+ self::$groupMemberCounts[$group] = $hit;
}
- return self::$admins;
+ return self::$groupMemberCounts[$group];
}
static function jobs() {
@@ -117,7 +144,7 @@
}
return self::$jobs;
}
-
+
static function pagesInNs( $ns ) {
wfProfileIn( __METHOD__ );
if( !isset( self::$pageCount[$ns] ) ) {
@@ -185,55 +212,35 @@
$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 );
+ $this->appendUpdate( $updates, 'ss_total_pages', $this->mPages );
+ $this->appendUpdate( $updates, 'ss_users', $this->mUsers );
- 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);
+
+ # Need a separate transaction because this a global lock
$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' );
- }
- */
+ }
+
+ public static function cacheUpdate( $dbw ) {
+ $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow') );
+ # Get non-bot users than did some recent action other than making accounts.
+ # If account creation is included, the number gets inflated ~20+ fold on enwiki.
+ $activeUsers = $dbr->selectField( 'recentchanges', 'COUNT( DISTINCT rc_user_text )',
+ array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers' OR rc_log_type IS NULL" ),
+ __METHOD__ );
+ $dbw->update( 'site_stats',
+ array( 'ss_active_users' => intval($activeUsers) ),
+ array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 )
+ );
}
}
-
diff -urN mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Skin.php mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Skin.php
--- mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.11.0/includes/Skin.php Thu Aug 23 18:34:12 2007
+++ mediawiki-1.11.0-to-1.14.0.proposal/mediawiki-1.14.0/includes/Skin.php Tue Jan 6 22:37:01 2009
@@ -1,26 +1,26 @@
addLink( array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
+ }
if( false !== $wgFavicon ) {
$out->addLink( array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
}
# OpenSearch description link
- $out->addLink( array(
- 'rel' => 'search',
+ $out->addLink( array(
+ 'rel' => 'search',
'type' => 'application/opensearchdescription+xml',
- 'href' => "$wgScriptPath/opensearch_desc.php",
- 'title' => "$wgSitename ({$wgLanguageNames[$wgLanguageCode]})",
+ 'href' => wfScript( 'opensearch_desc' ),
+ 'title' => wfMsgForContent( 'opensearch-desc' ),
));
$this->addMetadataLinks($out);
$this->mRevisionId = $out->mRevisionId;
-
+
$this->preloadExistence();
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
}
/**
@@ -203,8 +226,8 @@
$lb = new LinkBatch( $titles );
$lb->execute();
}
-
- function addMetadataLinks( &$out ) {
+
+ function addMetadataLinks( OutputPage $out ) {
global $wgTitle, $wgEnableDublinCoreRdf, $wgEnableCreativeCommonsRdf;
global $wgRightsPage, $wgRightsUrl;
@@ -240,13 +263,25 @@
}
}
- function outputPage( &$out ) {
- global $wgDebugComments;
+ function setMembers(){
+ global $wgTitle, $wgUser;
+ $this->mTitle = $wgTitle;
+ $this->mUser = $wgUser;
+ $this->userpage = $wgUser->getUserPage()->getPrefixedText();
+ $this->usercss = false;
+ }
+ function outputPage( OutputPage $out ) {
+ global $wgDebugComments;
wfProfileIn( __METHOD__ );
+
+ $this->setMembers();
$this->initPage( $out );
- $out->out( $out->headElement() );
+ // See self::afterContentHook() for documentation
+ $afterContent = $this->afterContentHook();
+
+ $out->out( $out->headElement( $this ) );
$out->out( "\ngetBodyOptions();
@@ -264,10 +299,12 @@
$out->out( $out->mBodytext . "\n" );
$out->out( $this->afterContent() );
+
+ $out->out( $afterContent );
$out->out( $this->bottomScripts() );
- $out->out( $out->reportTime() );
+ $out->out( wfReportTime() );
$out->out( "\n" );
wfProfileOut( __METHOD__ );
@@ -276,14 +313,14 @@
static function makeVariablesScript( $data ) {
global $wgJsMimeType;
- $r = "\n";
+ $r[] = "/*]]>*/\n";
- return $r;
+ return implode( "\n\t\t", $r );
}
/**
@@ -296,27 +333,42 @@
global $wgScript, $wgStylePath, $wgUser;
global $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgLang;
global $wgTitle, $wgCanonicalNamespaceNames, $wgOut, $wgArticle;
- global $wgBreakFrames, $wgRequest;
+ global $wgBreakFrames, $wgRequest, $wgVariantArticlePath, $wgActionPaths;
global $wgUseAjax, $wgAjaxWatch;
+ global $wgVersion, $wgEnableAPI, $wgEnableWriteAPI;
+ global $wgRestrictionTypes, $wgLivePreview;
+ global $wgMWSuggestTemplate, $wgDBname, $wgEnableMWSuggest;
$ns = $wgTitle->getNamespace();
$nsname = isset( $wgCanonicalNamespaceNames[ $ns ] ) ? $wgCanonicalNamespaceNames[ $ns ] : $wgTitle->getNsText();
+ $separatorTransTable = $wgContLang->separatorTransformTable();
+ $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
+ $compactSeparatorTransTable = array(
+ implode( "\t", array_keys( $separatorTransTable ) ),
+ implode( "\t", $separatorTransTable ),
+ );
+ $digitTransTable = $wgContLang->digitTransformTable();
+ $digitTransTable = $digitTransTable ? $digitTransTable : array();
+ $compactDigitTransTable = array(
+ implode( "\t", array_keys( $digitTransTable ) ),
+ implode( "\t", $digitTransTable ),
+ );
- $vars = array(
+ $vars = array(
'skin' => $data['skinname'],
'stylepath' => $wgStylePath,
'wgArticlePath' => $wgArticlePath,
'wgScriptPath' => $wgScriptPath,
'wgScript' => $wgScript,
+ 'wgVariantArticlePath' => $wgVariantArticlePath,
+ 'wgActionPaths' => (object)$wgActionPaths,
'wgServer' => $wgServer,
'wgCanonicalNamespace' => $nsname,
- 'wgCanonicalSpecialPageName' => SpecialPage::resolveAlias( $wgTitle->getDBKey() ),
+ 'wgCanonicalSpecialPageName' => SpecialPage::resolveAlias( $wgTitle->getDBkey() ),
'wgNamespaceNumber' => $wgTitle->getNamespace(),
'wgPageName' => $wgTitle->getPrefixedDBKey(),
'wgTitle' => $wgTitle->getText(),
'wgAction' => $wgRequest->getText( 'action', 'view' ),
- 'wgRestrictionEdit' => $wgTitle->getRestrictions( 'edit' ),
- 'wgRestrictionMove' => $wgTitle->getRestrictions( 'move' ),
'wgArticleId' => $wgTitle->getArticleId(),
'wgIsArticle' => $wgOut->isArticle(),
'wgUserName' => $wgUser->isAnon() ? NULL : $wgUser->getName(),
@@ -325,9 +377,23 @@
'wgContentLanguage' => $wgContLang->getCode(),
'wgBreakFrames' => $wgBreakFrames,
'wgCurRevisionId' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0,
+ 'wgVersion' => $wgVersion,
+ 'wgEnableAPI' => $wgEnableAPI,
+ 'wgEnableWriteAPI' => $wgEnableWriteAPI,
+ 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
+ 'wgDigitTransformTable' => $compactDigitTransTable,
);
+
+ if( $wgUseAjax && $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false )){
+ $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate();
+ $vars['wgDBname'] = $wgDBname;
+ $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $wgUser );
+ $vars['wgMWSuggestMessages'] = array( wfMsg('search-mwsuggest-enabled'), wfMsg('search-mwsuggest-disabled'));
+ }
+
+ foreach( $wgRestrictionTypes as $type )
+ $vars['wgRestriction' . ucfirst( $type )] = $wgTitle->getRestrictions( $type );
- global $wgLivePreview;
if ( $wgLivePreview && $wgUser->getOption( 'uselivepreview' ) ) {
$vars['wgLivepreviewMessageLoading'] = wfMsg( 'livepreview-loading' );
$vars['wgLivepreviewMessageReady'] = wfMsg( 'livepreview-ready' );
@@ -343,32 +409,34 @@
$vars['wgAjaxWatch'] = $msgs;
}
+ wfRunHooks('MakeGlobalVariablesScript', array(&$vars));
+
return self::makeVariablesScript( $vars );
}
function getHeadScripts( $allowUserJs ) {
global $wgStylePath, $wgUser, $wgJsMimeType, $wgStyleVersion;
- $r = self::makeGlobalVariablesScript( array( 'skinname' => $this->getSkinName() ) );
+ $vars = self::makeGlobalVariablesScript( array( 'skinname' => $this->getSkinName() ) );
- $r .= "\n";
+ $r = array( "" );
global $wgUseSiteJs;
if ($wgUseSiteJs) {
$jsCache = $wgUser->isLoggedIn() ? '&smaxage=0' : '';
- $r .= "\n";
+ "\">";
}
if( $allowUserJs && $wgUser->isLoggedIn() ) {
$userpage = $wgUser->getUserPage();
$userjs = htmlspecialchars( self::makeUrl(
$userpage->getPrefixedText().'/'.$this->getSkinName().'.js',
'action=raw&ctype='.$wgJsMimeType));
- $r .= '\n";
+ $r[] = '";
}
- return $r;
+ return $vars . "\t\t" . implode ( "\n\t\t", $r );
}
/**
@@ -395,38 +463,24 @@
$wgRequest->getVal( 'wpEditToken' ) );
}
- # get the user/site-specific stylesheet, SkinTemplate loads via RawPage.php (settings are cached that way)
- function getUserStylesheet() {
- global $wgStylePath, $wgRequest, $wgContLang, $wgSquidMaxage, $wgStyleVersion;
- $sheet = $this->getStylesheet();
- $s = "@import \"$wgStylePath/common/shared.css?$wgStyleVersion\";\n";
- $s .= "@import \"$wgStylePath/common/oldshared.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" .
- '@import "' . self::makeNSUrl( ucfirst( $this->getSkinName() . '.css' ), $query, NS_MEDIAWIKI ) . "\";\n";
-
- $s .= $this->doGetUserStyles();
- return $s."\n";
- }
-
/**
- * This returns MediaWiki:Common.js, and derived classes may add other JS.
- * Despite its name, it does *not* return any custom user JS from user
- * subpages. The returned script is sitewide and publicly cacheable and
- * therefore must not include anything that varies according to user,
- * interface language, etc. (although it may vary by skin). See
- * makeGlobalVariablesScript for things that can vary per page view and are
- * not cacheable.
+ * generated JavaScript action=raw&gen=js
+ * 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?
*
- * @return string Raw JavaScript to be returned
+ * 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
*/
- public function getUserJs() {
+ public function generateUserJs() {
+ global $wgStylePath;
+
wfProfileIn( __METHOD__ );
- global $wgStylePath;
$s = "/* generated javascript */\n";
$s .= "var skin = '" . Xml::escapeJsString( $this->getSkinName() ) . "';\n";
$s .= "var stylepath = '" . Xml::escapeJsString( $wgStylePath ) . "';";
@@ -435,48 +489,38 @@
if ( !wfEmptyMsg ( 'common.js', $commonJs ) ) {
$s .= $commonJs;
}
+
+ $s .= "\n\n/* MediaWiki:".ucfirst( $this->getSkinName() ).".js */\n";
+ // avoid inclusion of non defined user JavaScript (with custom skins only)
+ // by checking for default message content
+ $msgKey = ucfirst( $this->getSkinName() ).'.js';
+ $userJS = wfMsgForContent($msgKey);
+ if ( !wfEmptyMsg( $msgKey, $userJS ) ) {
+ $s .= $userJS;
+ }
+
wfProfileOut( __METHOD__ );
return $s;
}
/**
- * Return html code that include User stylesheets
+ * generate user stylesheet for action=raw&gen=css
*/
- function getUserStyles() {
- $s = "\n";
+ public function generateUserStylesheet() {
+ wfProfileIn( __METHOD__ );
+ $s = "/* generated user stylesheet */\n" .
+ $this->reallyGenerateUserStylesheet();
+ wfProfileOut( __METHOD__ );
return $s;
}
-
+
/**
- * Some styles that are set by user through the user settings interface.
+ * Split for easier subclassing in SkinSimple, SkinStandard and SkinCologneBlue
*/
- function doGetUserStyles() {
- global $wgUser, $wgUser, $wgRequest, $wgTitle, $wgAllowUserCss;
-
- $s = '';
-
- if( $wgAllowUserCss && $wgUser->isLoggedIn() ) { # logged in
- if($wgTitle->isCssSubpage() && $this->userCanPreview( $wgRequest->getText( 'action' ) ) ) {
- $s .= $wgRequest->getText('wpTextbox1');
- } else {
- $userpage = $wgUser->getUserPage();
- $s.= '@import "'.self::makeUrl(
- $userpage->getPrefixedText().'/'.$this->getSkinName().'.css',
- 'action=raw&ctype=text/css').'";'."\n";
- }
- }
-
- return $s . $this->reallyDoGetUserStyles();
- }
-
- function reallyDoGetUserStyles() {
+ protected function reallyGenerateUserStylesheet(){
global $wgUser;
$s = '';
- if (($undopt = $wgUser->getOption("underline")) != 2) {
+ if (($undopt = $wgUser->getOption("underline")) < 2) {
$underline = $undopt ? 'underline' : 'none';
$s .= "a { text-decoration: $underline; }\n";
}
@@ -487,22 +531,19 @@
a.new, #quickbar a.new,
a.stub, #quickbar a.stub {
color: inherit;
- text-decoration: inherit;
}
a.new:after, #quickbar a.new:after {
content: "?";
color: #CC2200;
- text-decoration: $underline;
}
a.stub:after, #quickbar a.stub:after {
content: "!";
color: #772233;
- text-decoration: $underline;
}
END;
}
if( $wgUser->getOption( 'justify' ) ) {
- $s .= "#article, #bodyContent { text-align: justify; }\n";
+ $s .= "#article, #bodyContent, #mw_content { text-align: justify; }\n";
}
if( !$wgUser->getOption( 'showtoc' ) ) {
$s .= "#toc { display: none; }\n";
@@ -513,6 +554,86 @@
return $s;
}
+ /**
+ * @private
+ */
+ function setupUserCss( OutputPage $out ) {
+ global $wgRequest, $wgContLang, $wgUser;
+ global $wgAllowUserCss, $wgUseSiteCss, $wgSquidMaxage, $wgStylePath;
+
+ wfProfileIn( __METHOD__ );
+
+ $this->setupSkinUserCss( $out );
+
+ $siteargs = array(
+ 'action' => 'raw',
+ 'maxage' => $wgSquidMaxage,
+ );
+
+ // Add any extension CSS
+ foreach( $out->getExtStyle() as $tag ) {
+ $out->addStyle( $tag['href'] );
+ }
+
+ // If we use the site's dynamic CSS, throw that in, too
+ // Per-site custom styles
+ if( $wgUseSiteCss ) {
+ global $wgHandheldStyle;
+ $query = wfArrayToCGI( array(
+ 'usemsgcache' => 'yes',
+ 'ctype' => 'text/css',
+ 'smaxage' => $wgSquidMaxage
+ ) + $siteargs );
+ # Site settings must override extension css! (bug 15025)
+ $out->addStyle( self::makeNSUrl( 'Common.css', $query, NS_MEDIAWIKI ) );
+ $out->addStyle( self::makeNSUrl( 'Print.css', $query, NS_MEDIAWIKI ), 'print' );
+ if( $wgHandheldStyle ) {
+ $out->addStyle( self::makeNSUrl( 'Handheld.css', $query, NS_MEDIAWIKI ), 'handheld' );
+ }
+ $out->addStyle( self::makeNSUrl( $this->getSkinName() . '.css', $query, NS_MEDIAWIKI ) );
+ }
+
+ if( $wgUser->isLoggedIn() ) {
+ // Ensure that logged-in users' generated CSS isn't clobbered
+ // by anons' publicly cacheable generated CSS.
+ $siteargs['smaxage'] = '0';
+ $siteargs['ts'] = $wgUser->mTouched;
+ }
+ // Per-user styles based on preferences
+ $siteargs['gen'] = 'css';
+ if( ( $us = $wgRequest->getVal( 'useskin', '' ) ) !== '' ) {
+ $siteargs['useskin'] = $us;
+ }
+ $out->addStyle( self::makeUrl( '-', wfArrayToCGI( $siteargs ) ) );
+
+ // Per-user custom style pages
+ if( $wgAllowUserCss && $wgUser->isLoggedIn() ) {
+ $action = $wgRequest->getVal('action');
+ # If we're previewing the CSS page, use it
+ if( $this->mTitle->isCssSubpage() && $this->userCanPreview( $action ) ) {
+ $previewCss = $wgRequest->getText('wpTextbox1');
+ // @FIXME: properly escape the cdata!
+ $this->usercss = "/**/";
+ } else {
+ $out->addStyle( self::makeUrl($this->userpage . '/' . $this->getSkinName() .'.css',
+ 'action=raw&ctype=text/css' ) );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Add skin specific stylesheets
+ * @param $out OutputPage
+ */
+ function setupSkinUserCss( OutputPage $out ) {
+ $out->addStyle( 'common/shared.css' );
+ $out->addStyle( 'common/oldshared.css' );
+ $out->addStyle( $this->getStylesheet() );
+ $out->addStyle( 'common/common_rtl.css', '', '', 'rtl' );
+ }
+
function getBodyOptions() {
global $wgUser, $wgTitle, $wgOut, $wgRequest, $wgContLang;
@@ -523,23 +644,33 @@
}
else $a = array( 'bgcolor' => '#FFFFFF' );
if($wgOut->isArticle() && $wgUser->getOption('editondblclick') &&
- $wgTitle->userCan( 'edit' ) ) {
+ $wgTitle->quickUserCan( 'edit' ) ) {
$s = $wgTitle->getFullURL( $this->editUrlOptions() );
- $s = 'document.location = "' .wfEscapeJSString( $s ) .'";';
+ $s = 'document.location = "' .Xml::escapeJsString( $s ) .'";';
$a += array ('ondblclick' => $s);
}
$a['onload'] = $wgOut->getOnloadHandler();
- if( $wgUser->getOption( 'editsectiononrightclick' ) ) {
- if( $a['onload'] != '' ) {
- $a['onload'] .= ';';
- }
- $a['onload'] .= 'setupRightClickEdit()';
- }
- $a['class'] = 'ns-'.$wgTitle->getNamespace().' '.($wgContLang->isRTL() ? "rtl" : "ltr").
- ' '.Sanitizer::escapeClass( 'page-'.$wgTitle->getPrefixedText() );
+ $a['class'] =
+ 'mediawiki' .
+ ' '.( $wgContLang->isRTL() ? "rtl" : "ltr" ).
+ ' '.$this->getPageClasses( $wgTitle ) .
+ ' skin-'. Sanitizer::escapeClass( $this->getSkinName( ) );
return $a;
}
+
+ function getPageClasses( $title ) {
+ $numeric = 'ns-'.$title->getNamespace();
+ if( $title->getNamespace() == NS_SPECIAL ) {
+ $type = "ns-special";
+ } elseif( $title->isTalkPage() ) {
+ $type = "ns-talk";
+ } else {
+ $type = "ns-subject";
+ }
+ $name = Sanitizer::escapeClass( 'page-'.$title->getPrefixedText() );
+ return "$numeric $type $name";
+ }
/**
* URL to the logo
@@ -577,11 +708,11 @@
$s .= "\n\n
\n" .
"
\n\n";
- $shove = ($qb != 0);
- $left = ($qb == 1 || $qb == 3);
- if($wgContLang->isRTL()) $left = !$left;
+ $shove = ( $qb != 0 );
+ $left = ( $qb == 1 || $qb == 3 );
+ if( $wgContLang->isRTL() ) $left = !$left;
- if ( !$shove ) {
+ if( !$shove ) {
$s .= "\n" .
$this->logoText() . ' ';
} elseif( $left ) {
@@ -620,9 +751,9 @@
}
- function getCategoryLinks () {
+ function getCategoryLinks() {
global $wgOut, $wgTitle, $wgUseCategoryBrowser;
- global $wgContLang;
+ global $wgContLang, $wgUser;
if( count( $wgOut->mCategoryLinks ) == 0 ) return '';
@@ -634,15 +765,37 @@
$dir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
$embed = "";
$pop = ' ';
- $t = $embed . implode ( "{$pop} {$sep} {$embed}" , $wgOut->mCategoryLinks ) . $pop;
- $msg = wfMsgExt( 'pagecategories', array( 'parsemag', 'escape' ), count( $wgOut->mCategoryLinks ) );
- $s = $this->makeLinkObj( Title::newFromText( wfMsgForContent('pagecategorieslink') ), $msg )
- . ': ' . $t;
+ $allCats = $wgOut->getCategoryLinks();
+ $s = '';
+ $colon = wfMsgExt( 'colon-separator', 'escapenoentities' );
+ if ( !empty( $allCats['normal'] ) ) {
+ $t = $embed . implode ( "{$pop} {$sep} {$embed}" , $allCats['normal'] ) . $pop;
+
+ $msg = wfMsgExt( 'pagecategories', array( 'parsemag', 'escapenoentities' ), count( $allCats['normal'] ) );
+ $s .= '' .
+ $this->link( Title::newFromText( wfMsgForContent('pagecategorieslink') ), $msg )
+ . $colon . $t . '
';
+ }
+
+ # Hidden categories
+ if ( isset( $allCats['hidden'] ) ) {
+ if ( $wgUser->getBoolOption( 'showhiddencats' ) ) {
+ $class ='mw-hidden-cats-user-shown';
+ } elseif ( $wgTitle->getNamespace() == NS_CATEGORY ) {
+ $class = 'mw-hidden-cats-ns-shown';
+ } else {
+ $class = 'mw-hidden-cats-hidden';
+ }
+ $s .= "" .
+ wfMsgExt( 'hidden-categories', array( 'parsemag', 'escapenoentities' ), count( $allCats['hidden'] ) ) .
+ $colon . $embed . implode( "$pop $sep $embed", $allCats['hidden'] ) . $pop .
+ "
";
+ }
# optional 'dmoz-like' category browser. Will be shown under the list
# of categories an article belong to
- if($wgUseCategoryBrowser) {
+ if( $wgUseCategoryBrowser ){
$s .= ' ';
# get a big array of the parents tree
@@ -665,7 +818,7 @@
* @param &skin Object: skin passed by reference
* @return String separated by >, terminate with "\n"
*/
- function drawCategoryBrowser($tree, &$skin) {
+ function drawCategoryBrowser( $tree, &$skin ){
$return = '';
foreach ($tree as $element => $parent) {
if (empty($parent)) {
@@ -676,16 +829,24 @@
$return .= Skin::drawCategoryBrowser($parent, $skin) . ' > ';
}
# add our current element to the list
- $eltitle = Title::NewFromText($element);
- $return .= $skin->makeLinkObj( $eltitle, $eltitle->getText() ) ;
+ $eltitle = Title::newFromText($element);
+ $return .= $skin->link( $eltitle, $eltitle->getText() ) ;
}
return $return;
}
function getCategories() {
$catlinks=$this->getCategoryLinks();
- if(!empty($catlinks)) {
- return "{$catlinks}
";
+
+ $classes = 'catlinks';
+
+ if( strpos( $catlinks, '' ) === false &&
+ strpos( $catlinks, '
' ) !== false ) {
+ $classes .= ' catlinks-allhidden';
+ }
+
+ if( !empty( $catlinks ) ){
+ return "
{$catlinks}
";
}
}
@@ -694,8 +855,43 @@
}
/**
- * This gets called shortly before the \ tag.
- * @return String HTML to be put before \
+ * This runs a hook to allow extensions placing their stuff after content
+ * and article metadata (e.g. categories).
+ * Note: This function has nothing to do with afterContent().
+ *
+ * This hook is placed here in order to allow using the same hook for all
+ * skins, both the SkinTemplate based ones and the older ones, which directly
+ * use this class to get their data.
+ *
+ * The output of this function gets processed in SkinTemplate::outputPage() for
+ * the SkinTemplate based skins, all other skins should directly echo it.
+ *
+ * Returns an empty string by default, if not changed by any hook function.
+ */
+ protected function afterContentHook() {
+ $data = "";
+
+ if( wfRunHooks( 'SkinAfterContent', array( &$data ) ) ){
+ // adding just some spaces shouldn't toggle the output
+ // of the whole
, so we use trim() here
+ if( trim( $data ) != '' ){
+ // Doing this here instead of in the skins to
+ // ensure that the div has the same ID in all
+ // skins
+ $data = "
\n" .
+ "\t$data\n" .
+ "
\n";
+ }
+ } else {
+ wfDebug( "Hook SkinAfterContent changed output processing.\n" );
+ }
+
+ return $data;
+ }
+
+ /**
+ * This gets called shortly before the tag.
+ * @return String HTML to be put before
*/
function afterContent() {
$printfooter = "\n";
@@ -703,8 +899,8 @@
}
/**
- * This gets called shortly before the \