summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2007-05-16 20:58:53 +0000
committerPierre Schmitz <pierre@archlinux.de>2007-05-16 20:58:53 +0000
commitcecb985bee3bdd252e1b8dc0bd500b37cd52be01 (patch)
tree17266aa237742640aabee7856f0202317a45d540 /includes
parent0bac06c301f2a83edb0236e4c2434da16848d549 (diff)
Aktualisierung auf MediaWiki 1.10.0
Plugins angepasst und verbessert kleine Korrekturen am Design
Diffstat (limited to 'includes')
-rw-r--r--includes/AjaxDispatcher.php23
-rw-r--r--includes/AjaxFunctions.php95
-rw-r--r--includes/AjaxResponse.php70
-rw-r--r--includes/Article.php463
-rw-r--r--includes/AuthPlugin.php8
-rw-r--r--includes/AutoLoader.php49
-rw-r--r--includes/BagOStuff.php41
-rw-r--r--includes/Block.php100
-rw-r--r--includes/CacheDependency.php21
-rw-r--r--includes/CategoryPage.php35
-rw-r--r--includes/Categoryfinder.php73
-rw-r--r--includes/ChangesList.php78
-rw-r--r--includes/CoreParserFunctions.php23
-rw-r--r--includes/Credits.php1
-rw-r--r--includes/Database.php295
-rw-r--r--includes/DatabaseFunctions.php53
-rw-r--r--includes/DatabaseOracle.php788
-rw-r--r--includes/DatabasePostgres.php332
-rw-r--r--includes/DateFormatter.php10
-rw-r--r--includes/DefaultSettings.php198
-rw-r--r--includes/Defines.php1
-rw-r--r--includes/DifferenceEngine.php127
-rw-r--r--includes/DjVuImage.php134
-rw-r--r--includes/EditPage.php615
-rw-r--r--includes/Exception.php30
-rw-r--r--includes/Exif.php30
-rw-r--r--includes/Export.php38
-rw-r--r--includes/ExternalEdit.php4
-rw-r--r--includes/ExternalStore.php1
-rw-r--r--includes/ExternalStoreDB.php2
-rw-r--r--includes/ExternalStoreHttp.php1
-rw-r--r--includes/FakeTitle.php3
-rw-r--r--includes/Feed.php18
-rw-r--r--includes/FileStore.php16
-rw-r--r--includes/GlobalFunctions.php197
-rw-r--r--includes/HTMLCacheUpdate.php9
-rw-r--r--includes/HTMLFileCache.php4
-rw-r--r--includes/HTMLForm.php4
-rw-r--r--includes/HistoryBlob.php98
-rw-r--r--includes/Hooks.php3
-rw-r--r--includes/IP.php279
-rw-r--r--includes/Image.php1279
-rw-r--r--includes/ImageFunctions.php2
-rw-r--r--includes/ImageGallery.php137
-rw-r--r--includes/ImagePage.php256
-rw-r--r--includes/ImageQueryPage.php68
-rw-r--r--includes/JobQueue.php125
-rw-r--r--includes/Licenses.php12
-rw-r--r--includes/LinkBatch.php7
-rw-r--r--includes/LinkCache.php15
-rw-r--r--includes/LinkFilter.php46
-rw-r--r--includes/Linker.php322
-rw-r--r--includes/LinksUpdate.php16
-rw-r--r--includes/LoadBalancer.php13
-rw-r--r--includes/LogPage.php56
-rw-r--r--includes/MacBinary.php7
-rw-r--r--includes/MagicWord.php7
-rw-r--r--includes/Math.php8
-rw-r--r--includes/MediaTransformOutput.php166
-rw-r--r--includes/MemcachedSessions.php1
-rw-r--r--includes/MessageCache.php10
-rw-r--r--includes/Metadata.php5
-rw-r--r--includes/MimeMagic.php39
-rw-r--r--includes/Namespace.php16
-rw-r--r--includes/ObjectCache.php6
-rw-r--r--includes/OutputHandler.php64
-rw-r--r--includes/OutputPage.php122
-rw-r--r--includes/PageHistory.php59
-rw-r--r--includes/PageQueryPage.php26
-rw-r--r--includes/Pager.php57
-rw-r--r--includes/Parser.php522
-rw-r--r--includes/ParserCache.php28
-rw-r--r--includes/ParserOptions.php119
-rw-r--r--includes/ParserOutput.php133
-rw-r--r--includes/PatrolLog.php83
-rw-r--r--includes/Profiler.php11
-rw-r--r--includes/ProfilerSimple.php19
-rw-r--r--includes/ProfilerSimpleUDP.php11
-rw-r--r--includes/ProtectionForm.php183
-rw-r--r--includes/ProxyTools.php50
-rw-r--r--includes/QueryPage.php223
-rw-r--r--includes/RawPage.php35
-rw-r--r--includes/RecentChange.php12
-rw-r--r--includes/Revision.php85
-rw-r--r--includes/Sanitizer.php184
-rw-r--r--includes/SearchEngine.php48
-rw-r--r--includes/SearchMySQL.php13
-rw-r--r--includes/SearchMySQL4.php12
-rw-r--r--includes/SearchOracle.php235
-rw-r--r--includes/SearchPostgres.php176
-rw-r--r--includes/SearchTsearch2.php20
-rw-r--r--includes/SearchUpdate.php16
-rw-r--r--includes/Setup.php4
-rw-r--r--includes/SiteConfiguration.php10
-rw-r--r--includes/SiteStats.php53
-rw-r--r--includes/Skin.php111
-rw-r--r--includes/SkinTemplate.php114
-rw-r--r--includes/SpecialAllmessages.php9
-rw-r--r--includes/SpecialAllpages.php61
-rw-r--r--includes/SpecialAncientpages.php10
-rw-r--r--includes/SpecialBlockip.php293
-rw-r--r--includes/SpecialBlockme.php3
-rw-r--r--includes/SpecialBooksources.php9
-rw-r--r--includes/SpecialBrokenRedirects.php43
-rw-r--r--includes/SpecialCategories.php103
-rw-r--r--includes/SpecialConfirmemail.php16
-rw-r--r--includes/SpecialContributions.php568
-rw-r--r--includes/SpecialDeadendpages.php18
-rw-r--r--includes/SpecialDisambiguations.php129
-rw-r--r--includes/SpecialDoubleRedirects.php30
-rw-r--r--includes/SpecialEmailuser.php23
-rw-r--r--includes/SpecialExport.php71
-rw-r--r--includes/SpecialFewestrevisions.php65
-rw-r--r--includes/SpecialImagelist.php22
-rw-r--r--includes/SpecialImport.php123
-rw-r--r--includes/SpecialIpblocklist.php61
-rw-r--r--includes/SpecialListredirects.php10
-rw-r--r--includes/SpecialListusers.php301
-rw-r--r--includes/SpecialLockdb.php10
-rw-r--r--includes/SpecialLog.php173
-rw-r--r--includes/SpecialLonelypages.php11
-rw-r--r--includes/SpecialLongpages.php15
-rw-r--r--includes/SpecialMIMEsearch.php14
-rw-r--r--includes/SpecialMostcategories.php9
-rw-r--r--includes/SpecialMostimages.php29
-rw-r--r--includes/SpecialMostlinked.php13
-rw-r--r--includes/SpecialMostlinkedcategories.php10
-rw-r--r--includes/SpecialMostrevisions.php8
-rw-r--r--includes/SpecialMovepage.php35
-rw-r--r--includes/SpecialNewimages.php33
-rw-r--r--includes/SpecialNewpages.php31
-rw-r--r--includes/SpecialPage.php41
-rw-r--r--includes/SpecialPopularpages.php34
-rw-r--r--includes/SpecialPreferences.php49
-rw-r--r--includes/SpecialPrefixindex.php13
-rw-r--r--includes/SpecialProtectedpages.php260
-rw-r--r--includes/SpecialRandompage.php142
-rw-r--r--includes/SpecialRandomredirect.php47
-rw-r--r--includes/SpecialRecentchanges.php17
-rw-r--r--includes/SpecialRecentchangeslinked.php54
-rw-r--r--includes/SpecialResetpass.php9
-rw-r--r--includes/SpecialRevisiondelete.php9
-rw-r--r--includes/SpecialSearch.php22
-rw-r--r--includes/SpecialShortpages.php8
-rw-r--r--includes/SpecialSpecialpages.php5
-rw-r--r--includes/SpecialStatistics.php17
-rw-r--r--includes/SpecialUncategorizedcategories.php8
-rw-r--r--includes/SpecialUncategorizedimages.php26
-rw-r--r--includes/SpecialUncategorizedpages.php10
-rw-r--r--includes/SpecialUndelete.php204
-rw-r--r--includes/SpecialUnlockdb.php6
-rw-r--r--includes/SpecialUnusedcategories.php8
-rw-r--r--includes/SpecialUnusedimages.php39
-rw-r--r--includes/SpecialUnusedtemplates.php13
-rw-r--r--includes/SpecialUnwatchedpages.php14
-rw-r--r--includes/SpecialUpload.php151
-rw-r--r--includes/SpecialUploadMogile.php10
-rw-r--r--includes/SpecialUserlogin.php88
-rw-r--r--includes/SpecialUserlogout.php3
-rw-r--r--includes/SpecialUserrights.php89
-rw-r--r--includes/SpecialVersion.php28
-rw-r--r--includes/SpecialWantedcategories.php12
-rw-r--r--includes/SpecialWantedpages.php10
-rw-r--r--includes/SpecialWatchlist.php46
-rw-r--r--includes/SpecialWhatlinkshere.php170
-rw-r--r--includes/SpecialWithoutinterwiki.php56
-rw-r--r--includes/SquidUpdate.php6
-rw-r--r--includes/StringUtils.php4
-rw-r--r--includes/StubObject.php7
-rw-r--r--includes/Title.php829
-rw-r--r--includes/User.php249
-rw-r--r--includes/UserMailer.php42
-rw-r--r--includes/Utf8Case.php3
-rw-r--r--includes/WatchedItem.php29
-rw-r--r--includes/WebRequest.php46
-rw-r--r--includes/WebResponse.php14
-rw-r--r--includes/WebStart.php12
-rw-r--r--includes/Wiki.php107
-rw-r--r--includes/WikiError.php9
-rw-r--r--includes/Xml.php12
-rw-r--r--includes/XmlFunctions.php2
-rw-r--r--includes/ZhClient.php2
-rw-r--r--includes/ZhConversion.php1
-rw-r--r--includes/api/ApiBase.php9
-rw-r--r--includes/api/ApiFeedWatchlist.php6
-rw-r--r--includes/api/ApiFormatBase.php9
-rw-r--r--includes/api/ApiFormatJson.php8
-rw-r--r--includes/api/ApiFormatJson_json.php51
-rw-r--r--includes/api/ApiFormatPhp.php8
-rw-r--r--includes/api/ApiFormatWddx.php8
-rw-r--r--includes/api/ApiFormatXml.php8
-rw-r--r--includes/api/ApiFormatYaml.php8
-rw-r--r--includes/api/ApiFormatYaml_spyc.php13
-rw-r--r--includes/api/ApiHelp.php8
-rw-r--r--includes/api/ApiLogin.php6
-rw-r--r--includes/api/ApiMain.php9
-rw-r--r--includes/api/ApiOpenSearch.php8
-rw-r--r--includes/api/ApiPageSet.php16
-rw-r--r--includes/api/ApiQuery.php10
-rw-r--r--includes/api/ApiQueryAllpages.php8
-rw-r--r--includes/api/ApiQueryBacklinks.php10
-rw-r--r--includes/api/ApiQueryBase.php9
-rw-r--r--includes/api/ApiQueryInfo.php6
-rw-r--r--includes/api/ApiQueryLogEvents.php8
-rw-r--r--includes/api/ApiQueryRecentChanges.php10
-rw-r--r--includes/api/ApiQueryRevisions.php8
-rw-r--r--includes/api/ApiQuerySiteinfo.php8
-rw-r--r--includes/api/ApiQueryUserContributions.php8
-rw-r--r--includes/api/ApiQueryWatchlist.php6
-rw-r--r--includes/api/ApiResult.php8
-rw-r--r--includes/media/BMP.php31
-rw-r--r--includes/media/Bitmap.php236
-rw-r--r--includes/media/DjVu.php206
-rw-r--r--includes/media/Generic.php298
-rw-r--r--includes/media/SVG.php97
-rw-r--r--includes/memcached-client.php2
-rw-r--r--includes/mime.info5
-rw-r--r--includes/mime.types1
-rw-r--r--includes/normal/CleanUpTest.php36
-rw-r--r--includes/normal/Makefile36
-rw-r--r--includes/normal/README4
-rw-r--r--includes/normal/RandomTest.php4
-rw-r--r--includes/normal/Utf8Test.php2
-rw-r--r--includes/normal/UtfNormal.php39
-rw-r--r--includes/normal/UtfNormalBench.php10
-rw-r--r--includes/normal/UtfNormalData.inc9
-rw-r--r--includes/normal/UtfNormalDataK.inc3
-rw-r--r--includes/normal/UtfNormalGenerate.php4
-rw-r--r--includes/normal/UtfNormalTest.php2
-rw-r--r--includes/normal/UtfNormalUtil.php2
-rw-r--r--includes/proxy_check.php1
-rw-r--r--includes/templates/Userlogin.php9
-rw-r--r--includes/tidy.conf18
233 files changed, 10573 insertions, 6034 deletions
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php
index 39ec19f8..ca129029 100644
--- a/includes/AjaxDispatcher.php
+++ b/includes/AjaxDispatcher.php
@@ -1,7 +1,8 @@
<?php
-if( !defined( 'MEDIAWIKI' ) )
- die( 1 );
+if( !defined( 'MEDIAWIKI' ) ) {
+ die( 1 );
+}
if ( ! $wgUseAjax ) {
die( 1 );
@@ -9,12 +10,16 @@ if ( ! $wgUseAjax ) {
require_once( 'AjaxFunctions.php' );
+/**
+ * Object-Oriented Ajax functions.
+ * @addtogroup Ajax
+ */
class AjaxDispatcher {
var $mode;
var $func_name;
var $args;
- function AjaxDispatcher() {
+ function __construct() {
wfProfileIn( __METHOD__ );
$this->mode = "";
@@ -28,14 +33,14 @@ class AjaxDispatcher {
}
if ($this->mode == "get") {
- $this->func_name = $_GET["rs"];
+ $this->func_name = isset( $_GET["rs"] ) ? $_GET["rs"] : '';
if (! empty($_GET["rsargs"])) {
$this->args = $_GET["rsargs"];
} else {
$this->args = array();
}
} else {
- $this->func_name = $_POST["rs"];
+ $this->func_name = isset( $_POST["rs"] ) ? $_POST["rs"] : '';
if (! empty($_POST["rsargs"])) {
$this->args = $_POST["rsargs"];
} else {
@@ -47,7 +52,7 @@ class AjaxDispatcher {
function performAction() {
global $wgAjaxExportList, $wgOut;
-
+
if ( empty( $this->mode ) ) {
return;
}
@@ -59,7 +64,7 @@ class AjaxDispatcher {
} else {
try {
$result = call_user_func_array($this->func_name, $this->args);
-
+
if ( $result === false || $result === NULL ) {
wfHttpError( 500, 'Internal Error',
"{$this->func_name} returned no data" );
@@ -68,7 +73,7 @@ class AjaxDispatcher {
if ( is_string( $result ) ) {
$result= new AjaxResponse( $result );
}
-
+
$result->sendHeaders();
$result->printText();
}
@@ -82,7 +87,7 @@ class AjaxDispatcher {
}
}
}
-
+
wfProfileOut( __METHOD__ );
$wgOut = null;
}
diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php
index eee2a1a4..86f853db 100644
--- a/includes/AjaxFunctions.php
+++ b/includes/AjaxFunctions.php
@@ -1,7 +1,13 @@
<?php
-if( !defined( 'MEDIAWIKI' ) )
- die( 1 );
+/**
+ * @package MediaWiki
+ * @addtogroup Ajax
+ */
+
+if( !defined( 'MEDIAWIKI' ) ) {
+ die( 1 );
+}
/**
* Function converts an Javascript escaped string back into a string with
@@ -13,40 +19,39 @@ if( !defined( 'MEDIAWIKI' ) )
* @return string
*/
function js_unescape($source, $iconv_to = 'UTF-8') {
- $decodedStr = '';
- $pos = 0;
- $len = strlen ($source);
- while ($pos < $len) {
- $charAt = substr ($source, $pos, 1);
- if ($charAt == '%') {
- $pos++;
- $charAt = substr ($source, $pos, 1);
- if ($charAt == 'u') {
- // we got a unicode character
- $pos++;
- $unicodeHexVal = substr ($source, $pos, 4);
- $unicode = hexdec ($unicodeHexVal);
- $decodedStr .= code2utf($unicode);
- $pos += 4;
- }
- else {
- // we have an escaped ascii character
- $hexVal = substr ($source, $pos, 2);
- $decodedStr .= chr (hexdec ($hexVal));
- $pos += 2;
- }
- }
- else {
- $decodedStr .= $charAt;
- $pos++;
- }
- }
-
- if ($iconv_to != "UTF-8") {
- $decodedStr = iconv("UTF-8", $iconv_to, $decodedStr);
- }
-
- return $decodedStr;
+ $decodedStr = '';
+ $pos = 0;
+ $len = strlen ($source);
+
+ while ($pos < $len) {
+ $charAt = substr ($source, $pos, 1);
+ if ($charAt == '%') {
+ $pos++;
+ $charAt = substr ($source, $pos, 1);
+ if ($charAt == 'u') {
+ // we got a unicode character
+ $pos++;
+ $unicodeHexVal = substr ($source, $pos, 4);
+ $unicode = hexdec ($unicodeHexVal);
+ $decodedStr .= code2utf($unicode);
+ $pos += 4;
+ } else {
+ // we have an escaped ascii character
+ $hexVal = substr ($source, $pos, 2);
+ $decodedStr .= chr (hexdec ($hexVal));
+ $pos += 2;
+ }
+ } else {
+ $decodedStr .= $charAt;
+ $pos++;
+ }
+ }
+
+ if ($iconv_to != "UTF-8") {
+ $decodedStr = iconv("UTF-8", $iconv_to, $decodedStr);
+ }
+
+ return $decodedStr;
}
/**
@@ -71,7 +76,7 @@ function code2utf($num){
function wfSajaxSearch( $term ) {
global $wgContLang, $wgOut;
$limit = 16;
-
+
$l = new Linker;
$term = str_replace( ' ', '_', $wgContLang->ucfirst(
@@ -81,7 +86,7 @@ function wfSajaxSearch( $term ) {
if ( strlen( str_replace( '_', '', $term ) )<3 )
return;
- $db =& wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_SLAVE );
$res = $db->select( 'page', 'page_title',
array( 'page_namespace' => 0,
"page_title LIKE '". $db->strencode( $term) ."%'" ),
@@ -108,8 +113,8 @@ function wfSajaxSearch( $term ) {
$subtitlemsg = ( Title::newFromText($term) ? 'searchsubtitle' : 'searchsubtitleinvalid' );
$subtitle = $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($term) ) ); #FIXME: parser is missing mTitle !
- $term = htmlspecialchars( $term );
- $html = '<div style="float:right; border:solid 1px black;background:gainsboro;padding:2px;"><a onclick="Searching_Hide_Results();">'
+ $term = urlencode( $term );
+ $html = '<div style="float:right; border:solid 1px black;background:gainsboro;padding:2px;"><a onclick="Searching_Hide_Results();">'
. wfMsg( 'hideresults' ) . '</a></div>'
. '<h1 class="firstHeading">'.wfMsg('search')
. '</h1><div id="contentSub">'. $subtitle . '</div><ul><li>'
@@ -121,11 +126,11 @@ function wfSajaxSearch( $term ) {
"search=$term&go=Go" )
. "</li></ul><h2>" . wfMsg( 'articletitles', $term ) . "</h2>"
. '<ul>' .$r .'</ul>'.$more;
-
+
$response = new AjaxResponse( $html );
-
+
$response->setCacheDuration( 30*60 );
-
+
return $response;
}
@@ -152,14 +157,14 @@ function wfAjaxWatch($pageID = "", $watch = "") {
if($watch) {
if(!$watching) {
- $dbw =& wfGetDB(DB_MASTER);
+ $dbw = wfGetDB(DB_MASTER);
$dbw->begin();
$article->doWatch();
$dbw->commit();
}
} else {
if($watching) {
- $dbw =& wfGetDB(DB_MASTER);
+ $dbw = wfGetDB(DB_MASTER);
$dbw->begin();
$article->doUnwatch();
$dbw->commit();
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index a59c73bb..cb4af1b5 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -1,28 +1,32 @@
<?php
+if( !defined( 'MEDIAWIKI' ) ) {
+ die( 1 );
+}
-if( !defined( 'MEDIAWIKI' ) )
- die( 1 );
-
+/**
+ * @todo document
+ * @addtogroup Ajax
+ */
class AjaxResponse {
var $mCacheDuration;
var $mVary;
-
+
var $mDisabled;
var $mText;
var $mResponseCode;
var $mLastModified;
var $mContentType;
- function AjaxResponse( $text = NULL ) {
+ function __construct( $text = NULL ) {
$this->mCacheDuration = NULL;
$this->mVary = NULL;
-
+
$this->mDisabled = false;
$this->mText = '';
$this->mResponseCode = '200 OK';
$this->mLastModified = false;
$this->mContentType= 'text/html; charset=utf-8';
-
+
if ( $text ) {
$this->addText( $text );
}
@@ -39,15 +43,15 @@ class AjaxResponse {
function setResponseCode( $code ) {
$this->mResponseCode = $code;
}
-
+
function setContentType( $type ) {
$this->mContentType = $type;
}
-
+
function disable() {
$this->mDisabled = true;
}
-
+
function addText( $text ) {
if ( ! $this->mDisabled && $text ) {
$this->mText .= $text;
@@ -59,62 +63,62 @@ class AjaxResponse {
print $this->mText;
}
}
-
+
function sendHeaders() {
global $wgUseSquid, $wgUseESI;
-
+
if ( $this->mResponseCode ) {
$n = preg_replace( '/^ *(\d+)/', '\1', $this->mResponseCode );
header( "Status: " . $this->mResponseCode, true, (int)$n );
}
-
+
header ("Content-Type: " . $this->mContentType );
-
+
if ( $this->mLastModified ) {
header ("Last-Modified: " . $this->mLastModified );
}
else {
header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
}
-
+
if ( $this->mCacheDuration ) {
-
+
# If squid caches are configured, tell them to cache the response,
# and tell the client to always check with the squid. Otherwise,
# tell the client to use a cached copy, without a way to purge it.
-
+
if( $wgUseSquid ) {
-
+
# Expect explicite purge of the proxy cache, but require end user agents
# to revalidate against the proxy on each visit.
# Surrogate-Control controls our Squid, Cache-Control downstream caches
-
+
if ( $wgUseESI ) {
header( 'Surrogate-Control: max-age='.$this->mCacheDuration.', content="ESI/1.0"');
header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
} else {
header( 'Cache-Control: s-maxage='.$this->mCacheDuration.', must-revalidate, max-age=0' );
}
-
+
} else {
-
+
# Let the client do the caching. Cache is not purged.
header ("Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT");
header ("Cache-Control: s-max-age={$this->mCacheDuration},public,max-age={$this->mCacheDuration}");
}
-
+
} else {
# always expired, always modified
header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header ("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header ("Pragma: no-cache"); // HTTP/1.0
}
-
+
if ( $this->mVary ) {
header ( "Vary: " . $this->mVary );
}
}
-
+
/**
* checkLastModified tells the client to use the client-cached response if
* possible. If sucessful, the AjaxResponse is disabled so that
@@ -154,9 +158,9 @@ class AjaxResponse {
$this->setResponseCode( "304 Not Modified" );
$this->disable();
$this->mLastModified = $lastmod;
-
+
wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
-
+
return true;
} else {
wfDebug( "$fname: READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
@@ -167,11 +171,11 @@ class AjaxResponse {
$this->mLastModified = $lastmod;
}
}
-
+
function loadFromMemcached( $mckey, $touched ) {
global $wgMemc;
if ( !$touched ) return false;
-
+
$mcvalue = $wgMemc->get( $mckey );
if ( $mcvalue ) {
# Check to see if the value has been invalidated
@@ -183,20 +187,20 @@ class AjaxResponse {
wfDebug( "$mckey has expired\n" );
}
}
-
+
return false;
}
-
+
function storeInMemcached( $mckey, $expiry = 86400 ) {
global $wgMemc;
-
- $wgMemc->set( $mckey,
+
+ $wgMemc->set( $mckey,
array(
'timestamp' => wfTimestampNow(),
'value' => $this->mText
), $expiry
);
-
+
return true;
}
}
diff --git a/includes/Article.php b/includes/Article.php
index 6b4f5270..0130ceba 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -1,7 +1,6 @@
<?php
/**
* File for articles
- * @package MediaWiki
*/
/**
@@ -11,7 +10,6 @@
* Note: edit user interface and cache support functions have been
* moved to separate EditPage and HTMLFileCache classes.
*
- * @package MediaWiki
*/
class Article {
/**@{{
@@ -43,7 +41,7 @@ class Article {
* @param $title Reference to a Title object.
* @param $oldId Integer revision ID, null to fetch from request, zero for current
*/
- function Article( &$title, $oldId = null ) {
+ function __construct( &$title, $oldId = null ) {
$this->mTitle =& $title;
$this->mOldId = $oldId;
$this->clear();
@@ -57,14 +55,14 @@ class Article {
function setRedirectedFrom( $from ) {
$this->mRedirectedFrom = $from;
}
-
+
/**
* @return mixed false, Title of in-wiki target, or string with URL
*/
function followRedirect() {
$text = $this->getContent();
$rt = Title::newFromRedirect( $text );
-
+
# process if title object is valid and not special:userlogout
if( $rt ) {
if( $rt->getInterwiki() != '' ) {
@@ -73,7 +71,7 @@ class Article {
//
// This can be hard to reverse and may produce loops,
// so they may be disabled in the site configuration.
-
+
$source = $this->mTitle->getFullURL( 'redirect=no' );
return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
}
@@ -84,7 +82,7 @@ class Article {
// the rest of the page we're on.
//
// This can be hard to reverse, so they may be disabled.
-
+
if( $rt->isSpecial( 'Userlogout' ) ) {
// rolleyes
} else {
@@ -94,7 +92,7 @@ class Article {
return $rt;
}
}
-
+
// No or invalid redirect
return false;
}
@@ -247,7 +245,7 @@ class Article {
* @param array $conditions
* @private
*/
- function pageData( &$dbr, $conditions ) {
+ function pageData( $dbr, $conditions ) {
$fields = array(
'page_id',
'page_namespace',
@@ -273,7 +271,7 @@ class Article {
* @param Database $dbr
* @param Title $title
*/
- function pageDataFromTitle( &$dbr, $title ) {
+ function pageDataFromTitle( $dbr, $title ) {
return $this->pageData( $dbr, array(
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey() ) );
@@ -283,7 +281,7 @@ class Article {
* @param Database $dbr
* @param int $id
*/
- function pageDataFromId( &$dbr, $id ) {
+ function pageDataFromId( $dbr, $id ) {
return $this->pageData( $dbr, array( 'page_id' => $id ) );
}
@@ -296,17 +294,18 @@ class Article {
*/
function loadPageData( $data = 'fromdb' ) {
if ( $data === 'fromdb' ) {
- $dbr =& $this->getDB();
+ $dbr = $this->getDB();
$data = $this->pageDataFromId( $dbr, $this->getId() );
}
-
+
$lc =& LinkCache::singleton();
if ( $data ) {
$lc->addGoodLinkObj( $data->page_id, $this->mTitle );
$this->mTitle->mArticleID = $data->page_id;
+
+ # Old-fashioned restrictions.
$this->mTitle->loadRestrictions( $data->page_restrictions );
- $this->mTitle->mRestrictionsLoaded = true;
$this->mCounter = $data->page_counter;
$this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
@@ -333,7 +332,7 @@ class Article {
return $this->mContent;
}
- $dbr =& $this->getDB();
+ $dbr = $this->getDB();
# Pre-fill content with error message so that if something
# fails we'll have something telling us what we intended.
@@ -405,9 +404,8 @@ class Article {
*
* @return Database
*/
- function &getDB() {
- $ret =& wfGetDB( DB_MASTER );
- return $ret;
+ function getDB() {
+ return wfGetDB( DB_MASTER );
}
/**
@@ -455,7 +453,7 @@ class Article {
if ( $id == 0 ) {
$this->mCounter = 0;
} else {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$this->mCounter = $dbr->selectField( 'page', 'page_counter', array( 'page_id' => $id ),
'Article::getCount', $this->getSelectOptions() );
}
@@ -471,12 +469,12 @@ class Article {
* @return bool
*/
function isCountable( $text ) {
- global $wgUseCommaCount, $wgContentNamespaces;
+ global $wgUseCommaCount;
$token = $wgUseCommaCount ? ',' : '[[';
return
- array_search( $this->mTitle->getNamespace(), $wgContentNamespaces ) !== false
- && ! $this->isRedirect( $text )
+ $this->mTitle->isContentPage()
+ && !$this->isRedirect( $text )
&& in_string( $token, $text );
}
@@ -573,7 +571,7 @@ class Article {
# XXX: this is expensive; cache this info somewhere.
$contribs = array();
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$revTable = $dbr->tableName( 'revision' );
$userTable = $dbr->tableName( 'user' );
$user = $this->getUser();
@@ -613,7 +611,7 @@ class Article {
$parserCache =& ParserCache::singleton();
$ns = $this->mTitle->getNamespace(); # shortcut
-
+
# Get variables from query string
$oldid = $this->getOldID();
@@ -627,16 +625,21 @@ class Article {
$diff = $wgRequest->getVal( 'diff' );
$rcid = $wgRequest->getVal( 'rcid' );
$rdfrom = $wgRequest->getVal( 'rdfrom' );
+ $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
$wgOut->setArticleFlag( true );
- if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
+
+ # Discourage indexing of printable versions, but encourage following
+ if( $wgOut->isPrintable() ) {
+ $policy = 'noindex,follow';
+ } elseif( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
+ # Honour customised robot policies for this namespace
$policy = $wgNamespaceRobotPolicies[$ns];
} else {
- # The default policy. Dev note: make sure you change the documentation
- # in DefaultSettings.php before changing it.
+ # Default to encourage indexing and following links
$policy = 'index,follow';
}
- $wgOut->setRobotpolicy( $policy );
+ $wgOut->setRobotPolicy( $policy );
# If we got diff and oldid in the query, we want to see a
# diff page instead of the article.
@@ -647,8 +650,8 @@ class Article {
$de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid );
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
- $de->showDiffPage();
-
+ $de->showDiffPage( $diffOnly );
+
// Needed to get the page's current revision
$this->loadPageData();
if( $diff == 0 || $diff == $this->mLatest ) {
@@ -658,7 +661,7 @@ class Article {
wfProfileOut( __METHOD__ );
return;
}
-
+
if ( empty( $oldid ) && $this->checkTouched() ) {
$wgOut->setETag($parserCache->getETag($this, $wgUser));
@@ -713,11 +716,11 @@ class Article {
$wasRedirected = true;
}
}
-
+
$outputDone = false;
+ wfRunHooks( 'ArticleViewHeader', array( &$this ) );
if ( $pcache ) {
if ( $wgOut->tryParserCache( $this, $wgUser ) ) {
- wfRunHooks( 'ArticleViewHeader', array( &$this ) );
$outputDone = true;
}
}
@@ -764,17 +767,12 @@ class Article {
}
}
if( !$outputDone ) {
- /**
- * @fixme: this hook doesn't work most of the time, as it doesn't
- * trigger when the parser cache is used.
- */
- wfRunHooks( 'ArticleViewHeader', array( &$this ) ) ;
$wgOut->setRevisionId( $this->getRevIdFetched() );
# wrap user css and user js in pre and don't parse
# XXX: use $this->mTitle->usCssJsSubpage() when php is fixed/ a workaround is found
if (
$ns == NS_USER &&
- preg_match('/\\/[\\w]+\\.(css|js)$/', $this->mTitle->getDBkey())
+ preg_match('/\\/[\\w]+\\.(?:css|js)$/', $this->mTitle->getDBkey())
) {
$wgOut->addWikiText( wfMsg('clearyourcache'));
$wgOut->addHTML( '<pre>'.htmlspecialchars($this->mContent)."\n</pre>" );
@@ -795,7 +793,7 @@ class Article {
$wgOut->addParserOutputNoText( $parseout );
} else if ( $pcache ) {
# Display content and save to parser cache
- $wgOut->addPrimaryWikiText( $text, $this );
+ $this->outputWikiText( $text );
} else {
# Display content, don't attempt to save to parser cache
# Don't show section-edit links on old revisions... this way lies madness.
@@ -803,11 +801,21 @@ class Article {
$oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
}
# Display content and don't save to parser cache
- $wgOut->addPrimaryWikiText( $text, $this, false );
+ # With timing hack -- TS 2006-07-26
+ $time = -wfTime();
+ $this->outputWikiText( $text, false );
+ $time += wfTime();
+
+ # Timing hack
+ if ( $time > 3 ) {
+ wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
+ $this->mTitle->getPrefixedDBkey()));
+ }
if( !$this->isCurrent() ) {
$wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
}
+
}
}
/* title may have been set from the cache */
@@ -827,8 +835,9 @@ class Article {
if ( $wgUseRCPatrol && !is_null( $rcid ) && $rcid != 0 && $wgUser->isAllowed( 'patrol' ) ) {
$wgOut->addHTML(
"<div class='patrollink'>" .
- wfMsg ( 'markaspatrolledlink',
- $sk->makeKnownLinkObj( $this->mTitle, wfMsg('markaspatrolledtext'), "action=markpatrolled&rcid=$rcid" )
+ wfMsgHtml( 'markaspatrolledlink',
+ $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml('markaspatrolledtext'),
+ "action=markpatrolled&rcid=$rcid" )
) .
'</div>'
);
@@ -845,7 +854,7 @@ class Article {
function addTrackbacks() {
global $wgOut, $wgUser;
- $dbr =& wfGetDB(DB_SLAVE);
+ $dbr = wfGetDB(DB_SLAVE);
$tbs = $dbr->select(
/* FROM */ 'trackbacks',
/* SELECT */ array('tb_id', 'tb_title', 'tb_url', 'tb_ex', 'tb_name'),
@@ -891,7 +900,7 @@ class Article {
return;
}
- $db =& wfGetDB(DB_MASTER);
+ $db = wfGetDB(DB_MASTER);
$db->delete('trackbacks', array('tb_id' => $wgRequest->getInt('tbid')));
$wgTitle->invalidateCache();
$wgOut->addWikiText(wfMsg('trackbackdeleteok'));
@@ -910,7 +919,7 @@ class Article {
function purge() {
global $wgUser, $wgRequest, $wgOut;
- if ( $wgUser->isLoggedIn() || $wgRequest->wasPosted() ) {
+ if ( $wgUser->isAllowed( 'purge' ) || $wgRequest->wasPosted() ) {
if( wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
$this->doPurge();
}
@@ -928,7 +937,7 @@ class Article {
$wgOut->addHTML( $msg );
}
}
-
+
/**
* Perform the actions of a page purging
*/
@@ -957,11 +966,10 @@ class Article {
* Best if all done inside a transaction.
*
* @param Database $dbw
- * @param string $restrictions
* @return int The newly created page_id key
* @private
*/
- function insertOn( &$dbw, $restrictions = '' ) {
+ function insertOn( $dbw ) {
wfProfileIn( __METHOD__ );
$page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
@@ -970,7 +978,7 @@ class Article {
'page_namespace' => $this->mTitle->getNamespace(),
'page_title' => $this->mTitle->getDBkey(),
'page_counter' => 0,
- 'page_restrictions' => $restrictions,
+ 'page_restrictions' => '',
'page_is_redirect' => 0, # Will set this shortly...
'page_is_new' => 1,
'page_random' => wfRandom(),
@@ -996,7 +1004,7 @@ class Article {
* when different from the currently set value.
* Giving 0 indicates the new page flag should
* be set on.
- * @param bool $lastRevIsRedirect If given, will optimize adding and
+ * @param bool $lastRevIsRedirect If given, will optimize adding and
* removing rows in redirect table.
* @return bool true on success, false on failure
* @private
@@ -1006,7 +1014,7 @@ class Article {
$text = $revision->getText();
$rt = Title::newFromRedirect( $text );
-
+
$conditions = array( 'page_id' => $this->getId() );
if( !is_null( $lastRevision ) ) {
# An extra check against threads stepping on each other
@@ -1028,20 +1036,20 @@ class Article {
if ($result) {
// FIXME: Should the result from updateRedirectOn() be returned instead?
- $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
+ $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
}
-
+
wfProfileOut( __METHOD__ );
return $result;
}
/**
- * Add row to the redirect table if this is a redirect, remove otherwise.
+ * Add row to the redirect table if this is a redirect, remove otherwise.
*
* @param Database $dbw
* @param $redirectTitle a title object pointing to the redirect target,
- * or NULL if this is not a redirect
- * @param bool $lastRevIsRedirect If given, will optimize adding and
+ * or NULL if this is not a redirect
+ * @param bool $lastRevIsRedirect If given, will optimize adding and
* removing rows in redirect table.
* @return bool true on success, false on failure
* @private
@@ -1067,7 +1075,7 @@ class Article {
$dbw->replace( 'redirect', array( 'rd_from' ), $set, __METHOD__ );
} else {
- // This is not a redirect, remove row from redirect table
+ // This is not a redirect, remove row from redirect table
$where = array( 'rd_from' => $this->getId() );
$dbw->delete( 'redirect', $where, __METHOD__);
}
@@ -1075,7 +1083,7 @@ class Article {
wfProfileOut( __METHOD__ );
return ( $dbw->affectedRows() != 0 );
}
-
+
return true;
}
@@ -1119,14 +1127,14 @@ class Article {
*/
function replaceSection($section, $text, $summary = '', $edittime = NULL) {
wfProfileIn( __METHOD__ );
-
+
if( $section == '' ) {
// Whole-page edit; let the text through unmolested.
} else {
if( is_null( $edittime ) ) {
$rev = Revision::newFromTitle( $this->mTitle );
} else {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
}
if( is_null( $rev ) ) {
@@ -1166,10 +1174,10 @@ class Article {
if ( $comment && $summary != "" ) {
$text = "== {$summary} ==\n\n".$text;
}
-
+
$this->doEdit( $text, $summary, $flags );
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
if ($watchthis) {
if (!$this->mTitle->userIsWatching()) {
$dbw->begin();
@@ -1196,7 +1204,7 @@ class Article {
$good = $this->doEdit( $text, $summary, $flags );
if ( $good ) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
if ($watchthis) {
if (!$this->mTitle->userIsWatching()) {
$dbw->begin();
@@ -1219,7 +1227,7 @@ class Article {
/**
* Article::doEdit()
*
- * Change an existing article or create a new article. Updates RC and all necessary caches,
+ * Change an existing article or create a new article. Updates RC and all necessary caches,
* optionally via the deferred update array.
*
* $wgUser must be set before calling this function.
@@ -1241,9 +1249,9 @@ class Article {
* Defer some of the updates until the end of index.php
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
- *
- * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
- * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If
+ *
+ * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
+ * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If
* EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception
* to be thrown from the Database. These two conditions are also possible with auto-detection due
* to MediaWiki's performance-optimised locking strategy.
@@ -1267,7 +1275,7 @@ class Article {
if( !wfRunHooks( 'ArticleSave', array( &$this, &$wgUser, &$text,
&$summary, $flags & EDIT_MINOR,
- null, null, &$flags ) ) )
+ null, null, &$flags ) ) )
{
wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
wfProfileOut( __METHOD__ );
@@ -1288,9 +1296,9 @@ class Article {
$text = $this->preSaveTransform( $text );
$newsize = strlen( $text );
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$now = wfTimestampNow();
-
+
if ( $flags & EDIT_UPDATE ) {
# Update article, but only if changed.
@@ -1316,7 +1324,7 @@ class Article {
wfProfileOut( __METHOD__ );
return false;
}
-
+
$revision = new Revision( array(
'page' => $this->getId(),
'comment' => $summary,
@@ -1340,10 +1348,11 @@ class Article {
$rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary,
$lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
$revisionId );
-
+
# Mark as patrolled if the user can do so
- if( $wgUser->isAllowed( 'autopatrol' ) ) {
+ if( $GLOBALS['wgUseRCPatrol'] && $wgUser->isAllowed( 'autopatrol' ) ) {
RecentChange::markPatrolled( $rcid );
+ PatrolLog::record( $rcid, true );
}
}
$wgUser->incEditCount();
@@ -1362,19 +1371,19 @@ class Article {
}
if ( $good ) {
- # Invalidate cache of this article and all pages using this article
+ # Invalidate cache of this article and all pages using this article
# as a template. Partly deferred.
Article::onArticleEdit( $this->mTitle );
-
+
# Update links tables, site stats, etc.
$changed = ( strcmp( $oldtext, $text ) != 0 );
$this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
}
} else {
# Create new article
-
+
# Set statistics members
- # We work out if it's countable after PST to avoid counter drift
+ # We work out if it's countable after PST to avoid counter drift
# when articles are created with {{subst:}}
$this->mGoodAdjustment = (int)$this->isCountable( $text );
$this->mTotalAdjustment = 1;
@@ -1403,8 +1412,9 @@ class Article {
$rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot,
'', strlen( $text ), $revisionId );
# Mark as patrolled if the user can
- if( $wgUser->isAllowed( 'autopatrol' ) ) {
+ if( $GLOBALS['wgUseRCPatrol'] && $wgUser->isAllowed( 'autopatrol' ) ) {
RecentChange::markPatrolled( $rcid );
+ PatrolLog::record( $rcid, true );
}
}
$wgUser->incEditCount();
@@ -1429,7 +1439,7 @@ class Article {
array( &$this, &$wgUser, $text,
$summary, $flags & EDIT_MINOR,
null, null, &$flags ) );
-
+
wfProfileOut( __METHOD__ );
return $good;
}
@@ -1457,7 +1467,7 @@ class Article {
}
$wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor );
}
-
+
/**
* Mark this particular edit as patrolled
*/
@@ -1470,25 +1480,25 @@ class Article {
$wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
return;
}
-
+
# Check permissions
if( !$wgUser->isAllowed( 'patrol' ) ) {
$wgOut->permissionRequired( 'patrol' );
return;
}
-
+
# If we haven't been given an rc_id value, we can't do anything
$rcid = $wgRequest->getVal( 'rcid' );
if( !$rcid ) {
$wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
return;
}
-
+
# Handle the 'MarkPatrolled' hook
if( !wfRunHooks( 'MarkPatrolled', array( $rcid, &$wgUser, false ) ) ) {
return;
}
-
+
$return = SpecialPage::getTitleFor( 'Recentchanges' );
# If it's left up to us, check that the user is allowed to patrol this edit
# If the user has the "autopatrol" right, then we'll assume there are no
@@ -1507,11 +1517,12 @@ class Article {
return;
}
}
-
+
# Mark the edit as patrolled
RecentChange::markPatrolled( $rcid );
+ PatrolLog::record( $rcid );
wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) );
-
+
# Inform the user
$wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) );
$wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrolledtext' ) );
@@ -1534,7 +1545,7 @@ class Article {
$wgOut->readOnlyPage();
return;
}
-
+
if( $this->doWatch() ) {
$wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
@@ -1546,7 +1557,7 @@ class Article {
$wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
}
-
+
/**
* Add this page to $wgUser's watchlist
* @return bool true on successful watch operation
@@ -1556,13 +1567,13 @@ class Article {
if( $wgUser->isAnon() ) {
return false;
}
-
+
if (wfRunHooks('WatchArticle', array(&$wgUser, &$this))) {
$wgUser->addWatch( $this->mTitle );
return wfRunHooks('WatchArticleComplete', array(&$wgUser, &$this));
}
-
+
return false;
}
@@ -1581,7 +1592,7 @@ class Article {
$wgOut->readOnlyPage();
return;
}
-
+
if( $this->doUnwatch() ) {
$wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
@@ -1593,7 +1604,7 @@ class Article {
$wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
}
-
+
/**
* Stop watching a page
* @return bool true on successful unwatch
@@ -1609,7 +1620,7 @@ class Article {
return wfRunHooks('UnwatchArticleComplete', array(&$wgUser, &$this));
}
-
+
return false;
}
@@ -1618,7 +1629,7 @@ class Article {
*/
function protect() {
$form = new ProtectionForm( $this );
- $form->show();
+ $form->execute();
}
/**
@@ -1635,14 +1646,21 @@ class Article {
* @param string $reason
* @return bool true on success
*/
- function updateRestrictions( $limit = array(), $reason = '' ) {
+ function updateRestrictions( $limit = array(), $reason = '', $cascade = 0, $expiry = null ) {
global $wgUser, $wgRestrictionTypes, $wgContLang;
-
+
$id = $this->mTitle->getArticleID();
if( !$wgUser->isAllowed( 'protect' ) || wfReadOnly() || $id == 0 ) {
return false;
}
+ if (!$cascade) {
+ $cascade = false;
+ }
+
+ // Take this opportunity to purge out expired restrictions
+ Title::purgeExpiredRestrictions();
+
# FIXME: Same limitations as described in ProtectionForm.php (line 37);
# we expect a single selection, but the schema allows otherwise.
$current = array();
@@ -1651,48 +1669,89 @@ class Article {
$current = Article::flattenRestrictions( $current );
$updated = Article::flattenRestrictions( $limit );
-
+
$changed = ( $current != $updated );
+ $changed = $changed || ($this->mTitle->areRestrictionsCascading() != $cascade);
+ $changed = $changed || ($this->mTitle->mRestrictionsExpiry != $expiry);
$protect = ( $updated != '' );
-
+
# If nothing's changed, do nothing
if( $changed ) {
+ global $wgGroupPermissions;
if( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) {
- $dbw =& wfGetDB( DB_MASTER );
-
+ $dbw = wfGetDB( DB_MASTER );
+
+ $encodedExpiry = Block::encodeExpiry($expiry, $dbw );
+
+ $expiry_description = '';
+ if ( $encodedExpiry != 'infinity' ) {
+ $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ) ).')';
+ }
+
# Prepare a null revision to be added to the history
$comment = $wgContLang->ucfirst( wfMsgForContent( $protect ? 'protectedarticle' : 'unprotectedarticle', $this->mTitle->getPrefixedText() ) );
+
+ foreach( $limit as $action => $restrictions ) {
+ # Check if the group level required to edit also can protect pages
+ # Otherwise, people who cannot normally protect can "protect" pages via transclusion
+ $cascade = ( $cascade && isset($wgGroupPermissions[$restrictions]['protect']) && $wgGroupPermissions[$restrictions]['protect'] );
+ }
+
+ $cascade_description = '';
+ if ($cascade) {
+ $cascade_description = ' ['.wfMsg('protect-summary-cascade').']';
+ }
+
if( $reason )
$comment .= ": $reason";
if( $protect )
$comment .= " [$updated]";
+ if ( $expiry_description && $protect )
+ $comment .= "$expiry_description";
+ if ( $cascade )
+ $comment .= "$cascade_description";
+
$nullRevision = Revision::newNullRevision( $dbw, $id, $comment, true );
$nullRevId = $nullRevision->insertOn( $dbw );
-
+
+ # Update restrictions table
+ foreach( $limit as $action => $restrictions ) {
+ if ($restrictions != '' ) {
+ $dbw->replace( 'page_restrictions', array(array('pr_page', 'pr_type')),
+ array( 'pr_page' => $id, 'pr_type' => $action
+ , 'pr_level' => $restrictions, 'pr_cascade' => $cascade ? 1 : 0
+ , 'pr_expiry' => $encodedExpiry ), __METHOD__ );
+ } else {
+ $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
+ 'pr_type' => $action ), __METHOD__ );
+ }
+ }
+
# Update page record
$dbw->update( 'page',
array( /* SET */
'page_touched' => $dbw->timestamp(),
- 'page_restrictions' => $updated,
+ 'page_restrictions' => '',
'page_latest' => $nullRevId
), array( /* WHERE */
'page_id' => $id
), 'Article::protect'
);
wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) );
-
+
# Update the protection log
$log = new LogPage( 'protect' );
+
if( $protect ) {
- $log->addEntry( 'protect', $this->mTitle, trim( $reason . " [$updated]" ) );
+ $log->addEntry( 'protect', $this->mTitle, trim( $reason . " [$updated]$cascade_description$expiry_description" ) );
} else {
$log->addEntry( 'unprotect', $this->mTitle, $reason );
}
-
+
} # End hook
} # End "changed" check
-
+
return true;
}
@@ -1745,9 +1804,9 @@ class Article {
}
$wgOut->setPagetitle( wfMsg( 'confirmdelete' ) );
-
+
# Better double-check that it hasn't been deleted yet!
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$conds = $this->mTitle->pageCond();
$latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
if ( $latest === false ) {
@@ -1769,7 +1828,7 @@ class Article {
# and insert a warning if it does
$maxRevisions = 20;
$authors = $this->getLastNAuthors( $maxRevisions, $latest );
-
+
if( count( $authors ) > 1 && !$confirm ) {
$skin=$wgUser->getSkin();
$wgOut->addHTML( '<strong>' . wfMsg( 'historywarning' ) . ' ' . $skin->historyLink() . '</strong>' );
@@ -1813,7 +1872,7 @@ class Article {
$reason = wfMsgForContent( 'exblank' );
}
- if( $length < 500 && $reason === '' ) {
+ if( $reason === '' ) {
# comment field=255, let's grep the first 150 to have some user
# space left
global $wgContLang;
@@ -1849,7 +1908,7 @@ class Article {
// First try the slave
// If that doesn't have the latest revision, try the master
$continue = 2;
- $db =& wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_SLAVE );
do {
$res = $db->select( array( 'page', 'revision' ),
array( 'rev_id', 'rev_user_text' ),
@@ -1868,7 +1927,7 @@ class Article {
}
$row = $db->fetchObject( $res );
if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
- $db =& wfGetDB( DB_MASTER );
+ $db = wfGetDB( DB_MASTER );
$continue--;
} else {
$continue = 0;
@@ -1882,7 +1941,7 @@ class Article {
wfProfileOut( __METHOD__ );
return $authors;
}
-
+
/**
* Output deletion confirmation dialog
*/
@@ -1929,6 +1988,23 @@ class Article {
</form>\n" );
$wgOut->returnToMain( false );
+
+ $this->showLogExtract( $wgOut );
+ }
+
+
+ /**
+ * Fetch deletion log
+ */
+ function showLogExtract( &$out ) {
+ # Show relevant lines from the deletion log:
+ $out->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
+ $logViewer = new LogViewer(
+ new LogReader(
+ new FauxRequest(
+ array( 'page' => $this->mTitle->getPrefixedText(),
+ 'type' => 'delete' ) ) ) );
+ $logViewer->showList( $out );
}
@@ -1969,7 +2045,7 @@ class Article {
wfDebug( __METHOD__."\n" );
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$ns = $this->mTitle->getNamespace();
$t = $this->mTitle->getDBkey();
$id = $this->mTitle->getArticleID();
@@ -2004,12 +2080,16 @@ class Article {
'ar_text_id' => 'rev_text_id',
'ar_text' => '\'\'', // Be explicit to appease
'ar_flags' => '\'\'', // MySQL's "strict mode"...
+ 'ar_len' => 'rev_len'
), array(
'page_id' => $id,
'page_id = rev_page'
), __METHOD__
);
+ # Delete restrictions for it
+ $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
+
# Now that it's safely backed up, delete it
$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__);
@@ -2078,7 +2158,7 @@ class Article {
$wgOut->addWikiText( wfMsg( 'sessionfailure' ) );
return;
}
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
# Enhanced rollback, marks edits rc_bot=1
$bot = $wgRequest->getBool( 'bot' );
@@ -2103,7 +2183,7 @@ class Article {
if( $current->getComment() != '') {
$wgOut->addHTML(
wfMsg( 'editcomment',
- htmlspecialchars( $current->getComment() ) ) );
+ $wgUser->getSkin()->formatComment( $current->getComment() ) ) );
}
return;
}
@@ -2189,7 +2269,7 @@ class Article {
* Do standard deferred updates after page edit.
* Update links tables, site stats, search index and message cache.
* Every 1000th edit, prune the recent changes table.
- *
+ *
* @private
* @param $text New text of the article
* @param $summary Edit summary
@@ -2222,7 +2302,7 @@ class Article {
# Periodically flush old entries from the recentchanges table.
global $wgRCMaxAge;
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
$recentchanges = $dbw->tableName( 'recentchanges' );
$sql = "DELETE FROM $recentchanges WHERE rc_timestamp < '{$cutoff}'";
@@ -2269,13 +2349,13 @@ class Article {
wfProfileOut( __METHOD__ );
}
-
+
/**
* Perform article updates on a special page creation.
*
* @param Revision $rev
*
- * @fixme This is a shitty interface function. Kill it and replace the
+ * @todo This is a shitty interface function. Kill it and replace the
* other shitty functions like editUpdates and such so it's not needed
* anymore.
*/
@@ -2299,8 +2379,8 @@ class Article {
global $wgLang, $wgOut, $wgUser;
if ( !wfRunHooks( 'DisplayOldSubtitle', array(&$this, &$oldid) ) ) {
- return;
- }
+ return;
+ }
$revision = Revision::newFromId( $oldid );
@@ -2326,10 +2406,10 @@ class Article {
$nextdiff = $current
? wfMsg( 'diff' )
: $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid );
-
+
$userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() )
. $sk->userToolLinks( $revision->getUser(), $revision->getUserText() );
-
+
$r = "\n\t\t\t\t<div id=\"mw-revision-info\">" . wfMsg( 'revision-info', $td, $userlinks ) . "</div>\n" .
"\n\t\t\t\t<div id=\"mw-revision-nav\">" . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
$wgOut->setSubtitle( $r );
@@ -2381,25 +2461,40 @@ class Article {
* @return bool
*/
function isFileCacheable() {
- global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest;
+ global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest, $wgLang, $wgContLang;
$action = $wgRequest->getVal( 'action' );
$oldid = $wgRequest->getVal( 'oldid' );
$diff = $wgRequest->getVal( 'diff' );
$redirect = $wgRequest->getVal( 'redirect' );
$printable = $wgRequest->getVal( 'printable' );
+ $page = $wgRequest->getVal( 'page' );
+
+ //check for non-standard user language; this covers uselang,
+ //and extensions for auto-detecting user language.
+ $ulang = $wgLang->getCode();
+ $clang = $wgContLang->getCode();
- return $wgUseFileCache
- and (!$wgShowIPinHeader)
- and ($this->getID() != 0)
- and ($wgUser->isAnon())
- and (!$wgUser->getNewtalk())
- and ($this->mTitle->getNamespace() != NS_SPECIAL )
- and (empty( $action ) || $action == 'view')
- and (!isset($oldid))
- and (!isset($diff))
- and (!isset($redirect))
- and (!isset($printable))
- and (!$this->mRedirectedFrom);
+ $cacheable = $wgUseFileCache
+ && (!$wgShowIPinHeader)
+ && ($this->getID() != 0)
+ && ($wgUser->isAnon())
+ && (!$wgUser->getNewtalk())
+ && ($this->mTitle->getNamespace() != NS_SPECIAL )
+ && (empty( $action ) || $action == 'view')
+ && (!isset($oldid))
+ && (!isset($diff))
+ && (!isset($redirect))
+ && (!isset($printable))
+ && !isset($page)
+ && (!$this->mRedirectedFrom)
+ && ($ulang === $clang);
+
+ if ( $cacheable ) {
+ //extension may have reason to disable file caching on some pages.
+ $cacheable = wfRunHooks( 'IsFileCacheable', array( $this ) );
+ }
+
+ return $cacheable;
}
/**
@@ -2446,7 +2541,7 @@ class Article {
function quickEdit( $text, $comment = '', $minor = 0 ) {
wfProfileIn( __METHOD__ );
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$dbw->begin();
$revision = new Revision( array(
'page' => $this->getId(),
@@ -2471,7 +2566,7 @@ class Article {
$id = intval( $id );
global $wgHitcounterUpdateFreq, $wgDBtype;
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$pageTable = $dbw->tableName( 'page' );
$hitcounterTable = $dbw->tableName( 'hitcounter' );
$acchitsTable = $dbw->tableName( 'acchits' );
@@ -2555,7 +2650,7 @@ class Article {
$title->touchLinks();
$title->purgeSquid();
-
+
# File cache
if ( $wgUseFileCache ) {
$cm = new HTMLFileCache( $title );
@@ -2617,7 +2712,7 @@ class Article {
$wgOut->addHTML(wfMsg( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon' ) );
}
} else {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$wl_clause = array(
'wl_title' => $page->getDBkey(),
'wl_namespace' => $page->getNamespace() );
@@ -2659,7 +2754,7 @@ class Article {
return false;
}
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$rev_clause = array( 'rev_page' => $id );
@@ -2693,7 +2788,7 @@ class Article {
return array();
}
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( array( 'templatelinks' ),
array( 'tl_namespace', 'tl_title' ),
array( 'tl_from' => $id ),
@@ -2708,7 +2803,7 @@ class Article {
$dbr->freeResult( $res );
return $result;
}
-
+
/**
* Return an auto-generated summary if the text provided is a redirect.
*
@@ -2785,6 +2880,84 @@ class Article {
return $summary;
}
+
+ /**
+ * Add the primary page-view wikitext to the output buffer
+ * Saves the text into the parser cache if possible.
+ * Updates templatelinks if it is out of date.
+ *
+ * @param string $text
+ * @param bool $cache
+ */
+ public function outputWikiText( $text, $cache = true ) {
+ global $wgParser, $wgUser, $wgOut;
+
+ $popts = $wgOut->parserOptions();
+ $popts->setTidy(true);
+ $parserOutput = $wgParser->parse( $text, $this->mTitle,
+ $popts, true, true, $this->getRevIdFetched() );
+ $popts->setTidy(false);
+ if ( $cache && $this && $parserOutput->getCacheTime() != -1 ) {
+ $parserCache =& ParserCache::singleton();
+ $parserCache->save( $parserOutput, $this, $wgUser );
+ }
+
+ if ( !wfReadOnly() && $this->mTitle->areRestrictionsCascading() ) {
+ // templatelinks table may have become out of sync,
+ // especially if using variable-based transclusions.
+ // For paranoia, check if things have changed and if
+ // so apply updates to the database. This will ensure
+ // that cascaded protections apply as soon as the changes
+ // are visible.
+
+ # Get templates from templatelinks
+ $id = $this->mTitle->getArticleID();
+
+ $tlTemplates = array();
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( array( 'templatelinks' ),
+ array( 'tl_namespace', 'tl_title' ),
+ array( 'tl_from' => $id ),
+ 'Article:getUsedTemplates' );
+
+ global $wgContLang;
+
+ if ( false !== $res ) {
+ if ( $dbr->numRows( $res ) ) {
+ while ( $row = $dbr->fetchObject( $res ) ) {
+ $tlTemplates[] = $wgContLang->getNsText( $row->tl_namespace ) . ':' . $row->tl_title ;
+ }
+ }
+ }
+
+ # Get templates from parser output.
+ $poTemplates_allns = $parserOutput->getTemplates();
+
+ $poTemplates = array ();
+ foreach ( $poTemplates_allns as $ns_templates ) {
+ $poTemplates = array_merge( $poTemplates, $ns_templates );
+ }
+
+ # Get the diff
+ $templates_diff = array_diff( $poTemplates, $tlTemplates );
+
+ if ( count( $templates_diff ) > 0 ) {
+ # Whee, link updates time.
+ $u = new LinksUpdate( $this->mTitle, $parserOutput );
+
+ $dbw = wfGetDb( DB_MASTER );
+ $dbw->begin();
+
+ $u->doUpdate();
+
+ $dbw->commit();
+ }
+ }
+
+ $wgOut->addParserOutput( $parserOutput );
+ }
+
}
?>
diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php
index e33ef1bf..9395032f 100644
--- a/includes/AuthPlugin.php
+++ b/includes/AuthPlugin.php
@@ -1,6 +1,5 @@
<?php
/**
- * @package MediaWiki
*/
# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
# http://www.mediawiki.org/
@@ -33,7 +32,6 @@
* This interface is new, and might change a bit before 1.4.0 final is
* done...
*
- * @package MediaWiki
*/
class AuthPlugin {
/**
@@ -187,12 +185,14 @@ class AuthPlugin {
* Add a user to the external authentication database.
* Return true if successful.
*
- * @param User $user
+ * @param User $user - only the name should be assumed valid at this point
* @param string $password
+ * @param string $email
+ * @param string $realname
* @return bool
* @public
*/
- function addUser( $user, $password ) {
+ function addUser( $user, $password, $email='', $realname='' ) {
return true;
}
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 8de5608f..72a71c71 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -8,9 +8,11 @@ function __autoload($className) {
global $wgAutoloadClasses;
static $localClasses = array(
+ # Includes
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxCachePolicy' => 'includes/AjaxFunctions.php',
'AjaxResponse' => 'includes/AjaxResponse.php',
+ 'AlphabeticPager' => 'includes/Pager.php',
'Article' => 'includes/Article.php',
'AuthPlugin' => 'includes/AuthPlugin.php',
'BagOStuff' => 'includes/BagOStuff.php',
@@ -39,9 +41,8 @@ function __autoload($className) {
'Database' => 'includes/Database.php',
'DatabaseMysql' => 'includes/Database.php',
'ResultWrapper' => 'includes/Database.php',
- 'OracleBlob' => 'includes/DatabaseOracle.php',
- 'DatabaseOracle' => 'includes/DatabaseOracle.php',
'DatabasePostgres' => 'includes/DatabasePostgres.php',
+ 'DatabaseOracle' => 'includes/DatabaseOracle.php',
'DateFormatter' => 'includes/DateFormatter.php',
'DifferenceEngine' => 'includes/DifferenceEngine.php',
'_DiffOp' => 'includes/DifferenceEngine.php',
@@ -95,6 +96,7 @@ function __autoload($className) {
'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php',
'Http' => 'includes/HttpFunctions.php',
'Image' => 'includes/Image.php',
+ 'ArchivedFile' => 'includes/Image.php',
'IP' => 'includes/IP.php',
'ThumbnailImage' => 'includes/Image.php',
'ImageGallery' => 'includes/ImageGallery.php',
@@ -114,6 +116,10 @@ function __autoload($className) {
'MacBinary' => 'includes/MacBinary.php',
'MagicWord' => 'includes/MagicWord.php',
'MathRenderer' => 'includes/Math.php',
+ 'MediaTransformOutput' => 'includes/MediaTransformOutput.php',
+ 'ThumbnailImage' => 'includes/MediaTransformOutput.php',
+ 'MediaTransformError' => 'includes/MediaTransformOutput.php',
+ 'TransformParameterError' => 'includes/MediaTransformOutput.php',
'MessageCache' => 'includes/MessageCache.php',
'MimeMagic' => 'includes/MimeMagic.php',
'Namespace' => 'includes/Namespace.php',
@@ -124,16 +130,18 @@ function __autoload($className) {
'ReverseChronologicalPager' => 'includes/Pager.php',
'TablePager' => 'includes/Pager.php',
'Parser' => 'includes/Parser.php',
- 'ParserOutput' => 'includes/Parser.php',
- 'ParserOptions' => 'includes/Parser.php',
+ 'ParserOutput' => 'includes/ParserOutput.php',
+ 'ParserOptions' => 'includes/ParserOptions.php',
'ParserCache' => 'includes/ParserCache.php',
+ 'PatrolLog' => 'includes/PatrolLog.php',
'ProfilerSimple' => 'includes/ProfilerSimple.php',
'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php',
'Profiler' => 'includes/Profiler.php',
'ProxyTools' => 'includes/ProxyTools.php',
'ProtectionForm' => 'includes/ProtectionForm.php',
'QueryPage' => 'includes/QueryPage.php',
- 'PageQueryPage' => 'includes/QueryPage.php',
+ 'PageQueryPage' => 'includes/PageQueryPage.php',
+ 'ImageQueryPage' => 'includes/ImageQueryPage.php',
'RawPage' => 'includes/RawPage.php',
'RecentChange' => 'includes/RecentChange.php',
'Revision' => 'includes/Revision.php',
@@ -148,6 +156,7 @@ function __autoload($className) {
'SearchPostgres' => 'includes/SearchPostgres.php',
'SearchUpdate' => 'includes/SearchUpdate.php',
'SearchUpdateMyISAM' => 'includes/SearchUpdate.php',
+ 'SearchOracle' => 'includes/SearchOracle.php',
'SiteConfiguration' => 'includes/SiteConfiguration.php',
'SiteStats' => 'includes/SiteStats.php',
'SiteStatsUpdate' => 'includes/SiteStats.php',
@@ -160,7 +169,6 @@ function __autoload($className) {
'IPBlockForm' => 'includes/SpecialBlockip.php',
'SpecialBookSources' => 'includes/SpecialBooksources.php',
'BrokenRedirectsPage' => 'includes/SpecialBrokenRedirects.php',
- 'CategoriesPage' => 'includes/SpecialCategories.php',
'EmailConfirmation' => 'includes/SpecialConfirmemail.php',
'ContributionsPage' => 'includes/SpecialContributions.php',
'DeadendPagesPage' => 'includes/SpecialDeadendpages.php',
@@ -173,7 +181,6 @@ function __autoload($className) {
'ImportStreamSource' => 'includes/SpecialImport.php',
'IPUnblockForm' => 'includes/SpecialIpblocklist.php',
'ListredirectsPage' => 'includes/SpecialListredirects.php',
- 'ListUsersPage' => 'includes/SpecialListusers.php',
'DBLockForm' => 'includes/SpecialLockdb.php',
'LogReader' => 'includes/SpecialLog.php',
'LogViewer' => 'includes/SpecialLog.php',
@@ -185,6 +192,7 @@ function __autoload($className) {
'MostlinkedPage' => 'includes/SpecialMostlinked.php',
'MostlinkedCategoriesPage' => 'includes/SpecialMostlinkedcategories.php',
'MostrevisionsPage' => 'includes/SpecialMostrevisions.php',
+ 'FewestrevisionsPage' => 'includes/SpecialFewestrevisions.php',
'MovePageForm' => 'includes/SpecialMovepage.php',
'NewbieContributionsPage' => 'includes/SpecialNewbieContributions.php',
'NewPagesPage' => 'includes/SpecialNewpages.php',
@@ -194,6 +202,7 @@ function __autoload($className) {
'PopularPagesPage' => 'includes/SpecialPopularpages.php',
'PreferencesForm' => 'includes/SpecialPreferences.php',
'SpecialPrefixindex' => 'includes/SpecialPrefixindex.php',
+ 'PasswordResetForm' => 'includes/SpecialResetpass.php',
'RevisionDeleteForm' => 'includes/SpecialRevisiondelete.php',
'RevisionDeleter' => 'includes/SpecialRevisiondelete.php',
'SpecialSearch' => 'includes/SpecialSearch.php',
@@ -215,6 +224,7 @@ function __autoload($className) {
'WantedCategoriesPage' => 'includes/SpecialWantedcategories.php',
'WantedPagesPage' => 'includes/SpecialWantedpages.php',
'WhatLinksHerePage' => 'includes/SpecialWhatlinkshere.php',
+ 'WithoutInterwikiPage' => 'includes/SpecialWithoutinterwiki.php',
'SquidUpdate' => 'includes/SquidUpdate.php',
'ReplacementArray' => 'includes/StringUtils.php',
'Replacer' => 'includes/StringUtils.php',
@@ -237,13 +247,27 @@ function __autoload($className) {
'Xml' => 'includes/Xml.php',
'ZhClient' => 'includes/ZhClient.php',
'memcached' => 'includes/memcached-client.php',
+
+ # Media
+ 'BitmapHandler' => 'includes/media/Bitmap.php',
+ 'BmpHandler' => 'includes/media/BMP.php',
+ 'DjVuHandler' => 'includes/media/DjVu.php',
+ 'MediaHandler' => 'includes/media/Generic.php',
+ 'ImageHandler' => 'includes/media/Generic.php',
+ 'SvgHandler' => 'includes/media/SVG.php',
+
+ # Normal
'UtfNormal' => 'includes/normal/UtfNormal.php',
+
+ # Templates
'UsercreateTemplate' => 'includes/templates/Userlogin.php',
'UserloginTemplate' => 'includes/templates/Userlogin.php',
+
+ # Languages
'Language' => 'languages/Language.php',
- 'PasswordResetForm' => 'includes/SpecialResetpass.php',
+ 'RandomPage' => 'includes/SpecialRandompage.php',
- // API classes
+ # API
'ApiBase' => 'includes/api/ApiBase.php',
'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php',
@@ -274,6 +298,7 @@ function __autoload($className) {
'ApiResult' => 'includes/api/ApiResult.php',
);
+ wfProfileIn( __METHOD__ );
if ( isset( $localClasses[$className] ) ) {
$filename = $localClasses[$className];
} elseif ( isset( $wgAutoloadClasses[$className] ) ) {
@@ -290,6 +315,7 @@ function __autoload($className) {
}
if ( !$filename ) {
# Give up
+ wfProfileOut( __METHOD__ );
return;
}
}
@@ -300,6 +326,7 @@ function __autoload($className) {
$filename = "$IP/$filename";
}
require( $filename );
+ wfProfileOut( __METHOD__ );
}
function wfLoadAllExtensions() {
@@ -311,10 +338,10 @@ function wfLoadAllExtensions() {
# guaranteed by entering special pages via SpecialPage members such as
# executePath(), but here we have to take a more explicit measure.
- require_once( 'SpecialPage.php' );
+ require_once( dirname(__FILE__) . '/SpecialPage.php' );
foreach( $wgAutoloadClasses as $class => $file ) {
- if ( ! class_exists( $class ) ) {
+ if( !( class_exists( $class ) || interface_exists( $class ) ) ) {
require( $file );
}
}
diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php
index c720807d..2a04b9dd 100644
--- a/includes/BagOStuff.php
+++ b/includes/BagOStuff.php
@@ -19,7 +19,6 @@
# http://www.gnu.org/copyleft/gpl.html
/**
*
- * @package MediaWiki
*/
/**
@@ -29,15 +28,16 @@
* the PHP memcached client.
*
* backends for local hash array and SQL table included:
- * $bag = new HashBagOStuff();
- * $bag = new MysqlBagOStuff($tablename); # connect to db first
+ * <code>
+ * $bag = new HashBagOStuff();
+ * $bag = new MysqlBagOStuff($tablename); # connect to db first
+ * </code>
*
- * @package MediaWiki
*/
class BagOStuff {
var $debugmode;
- function BagOStuff() {
+ function __construct() {
$this->set_debug( false );
}
@@ -163,7 +163,6 @@ class BagOStuff {
/**
* Functional versions!
* @todo document
- * @package MediaWiki
*/
class HashBagOStuff extends BagOStuff {
/*
@@ -218,7 +217,6 @@ CREATE TABLE objectcache (
/**
* @todo document
* @abstract
- * @package MediaWiki
*/
abstract class SqlBagOStuff extends BagOStuff {
var $table;
@@ -386,34 +384,32 @@ abstract class SqlBagOStuff extends BagOStuff {
/**
* @todo document
- * @package MediaWiki
*/
class MediaWikiBagOStuff extends SqlBagOStuff {
var $tableInitialised = false;
function _doquery($sql) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
return $dbw->query($sql, 'MediaWikiBagOStuff::_doquery');
}
function _doinsert($t, $v) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert',
array( 'IGNORE' ) );
}
function _fetchobject($result) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
return $dbw->fetchObject($result);
}
function _freeresult($result) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
return $dbw->freeResult($result);
}
function _dberror($result) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
return $dbw->lastError();
}
function _maxdatetime() {
- $dbw =& wfGetDB(DB_MASTER);
if ( time() > 0x7fffffff ) {
return $this->_fromunixtime( 1<<62 );
} else {
@@ -421,24 +417,24 @@ class MediaWikiBagOStuff extends SqlBagOStuff {
}
}
function _fromunixtime($ts) {
- $dbw =& wfGetDB(DB_MASTER);
+ $dbw = wfGetDB(DB_MASTER);
return $dbw->timestamp($ts);
}
function _strencode($s) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
return $dbw->strencode($s);
}
function _blobencode($s) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
return $dbw->encodeBlob($s);
}
function _blobdecode($s) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
return $dbw->decodeBlob($s);
}
function getTableName() {
if ( !$this->tableInitialised ) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
/* This is actually a hack, we should be able
to use Language classes here... or not */
if (!$dbw)
@@ -463,7 +459,6 @@ class MediaWikiBagOStuff extends SqlBagOStuff {
* that Turck's serializer is faster, so a possible future extension would be
* to use it for arrays but not for objects.
*
- * @package MediaWiki
*/
class TurckBagOStuff extends BagOStuff {
function get($key) {
@@ -498,9 +493,7 @@ class TurckBagOStuff extends BagOStuff {
/**
* This is a wrapper for APC's shared memory functions
*
- * @package MediaWiki
*/
-
class APCBagOStuff extends BagOStuff {
function get($key) {
$val = apc_fetch($key);
@@ -528,7 +521,6 @@ class APCBagOStuff extends BagOStuff {
* This is basically identical to the Turck MMCache version,
* mostly because eAccelerator is based on Turck MMCache.
*
- * @package MediaWiki
*/
class eAccelBagOStuff extends BagOStuff {
function get($key) {
@@ -560,6 +552,9 @@ class eAccelBagOStuff extends BagOStuff {
}
}
+/**
+ * @todo document
+ */
class DBABagOStuff extends BagOStuff {
var $mHandler, $mFile, $mReader, $mWriter, $mDisabled;
diff --git a/includes/Block.php b/includes/Block.php
index ff813ba3..94bfa5b4 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -1,7 +1,6 @@
<?php
/**
* Blocks and bans object
- * @package MediaWiki
*/
/**
@@ -12,22 +11,24 @@
* Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
*
* @todo This could be used everywhere, but it isn't.
- * @package MediaWiki
*/
class Block
{
/* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
- $mRangeStart, $mRangeEnd, $mAnonOnly, $mEnableAutoblock;
+ $mRangeStart, $mRangeEnd, $mAnonOnly, $mEnableAutoblock, $mHideName;
/* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
const EB_KEEP_EXPIRED = 1;
const EB_FOR_UPDATE = 2;
const EB_RANGE_ONLY = 4;
- function Block( $address = '', $user = 0, $by = 0, $reason = '',
- $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0 )
+ function __construct( $address = '', $user = 0, $by = 0, $reason = '',
+ $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0,
+ $hideName = 0 )
{
$this->mId = 0;
+ # Expand valid IPv6 addresses
+ $address = IP::sanitizeIP( $address );
$this->mAddress = $address;
$this->mUser = $user;
$this->mBy = $by;
@@ -38,6 +39,7 @@ class Block
$this->mCreateAccount = $createAccount;
$this->mExpiry = self::decodeExpiry( $expiry );
$this->mEnableAutoblock = $enableAutoblock;
+ $this->mHideName = $hideName;
$this->mForUpdate = false;
$this->mFromMaster = false;
@@ -58,7 +60,7 @@ class Block
static function newFromID( $id )
{
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
array( 'ipb_id' => $id ), __METHOD__ ) );
$block = new Block;
@@ -74,7 +76,7 @@ class Block
$this->mAddress = $this->mReason = $this->mTimestamp = '';
$this->mId = $this->mAnonOnly = $this->mCreateAccount =
$this->mEnableAutoblock = $this->mAuto = $this->mUser =
- $this->mBy = 0;
+ $this->mBy = $this->mHideName = 0;
$this->mByName = false;
}
@@ -85,14 +87,14 @@ class Block
{
global $wgAntiLockFlags;
if ( $this->mForUpdate || $this->mFromMaster ) {
- $db =& wfGetDB( DB_MASTER );
+ $db = wfGetDB( DB_MASTER );
if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) {
$options = array();
} else {
$options = array( 'FOR UPDATE' );
}
} else {
- $db =& wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_SLAVE );
$options = array();
}
return $db;
@@ -147,7 +149,7 @@ class Block
}
# Try range block
- if ( $this->loadRange( $address, $killExpired, $user == 0 ) ) {
+ if ( $this->loadRange( $address, $killExpired, $user ) ) {
if ( $user && $this->mAnonOnly ) {
$this->clear();
return false;
@@ -176,7 +178,8 @@ class Block
/**
* Fill in member variables from a result wrapper
*/
- function loadFromResult( ResultWrapper $res, $killExpired = true ) {
+ function loadFromResult( ResultWrapper $res, $killExpired = true )
+ {
$ret = false;
if ( 0 != $res->numRows() ) {
# Get first block
@@ -211,7 +214,7 @@ class Block
* Search the database for any range blocks matching the given address, and
* load the row if one is found.
*/
- function loadRange( $address, $killExpired = true )
+ function loadRange( $address, $killExpired = true, $user = 0 )
{
$iaddr = IP::toHex( $address );
if ( $iaddr === false ) {
@@ -230,6 +233,10 @@ class Block
"ipb_range_start <= '$iaddr'",
"ipb_range_end >= '$iaddr'"
);
+
+ if ( $user ) {
+ $conds['ipb_anon_only'] = 0;
+ }
$res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
$success = $this->loadFromResult( $res, $killExpired );
@@ -255,6 +262,7 @@ class Block
$this->mAnonOnly = $row->ipb_anon_only;
$this->mCreateAccount = $row->ipb_create_account;
$this->mEnableAutoblock = $row->ipb_enable_autoblock;
+ $this->mHideName = $row->ipb_deleted;
$this->mId = $row->ipb_id;
$this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
if ( isset( $row->user_name ) ) {
@@ -286,7 +294,7 @@ class Block
$block = new Block();
if ( $flags & Block::EB_FOR_UPDATE ) {
- $db =& wfGetDB( DB_MASTER );
+ $db = wfGetDB( DB_MASTER );
if ( $wgAntiLockFlags & ALF_NO_BLOCK_LOCK ) {
$options = '';
} else {
@@ -294,7 +302,7 @@ class Block
}
$block->forUpdate( true );
} else {
- $db =& wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_SLAVE );
$options = '';
}
if ( $flags & Block::EB_RANGE_ONLY ) {
@@ -341,7 +349,7 @@ class Block
throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
}
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
return $dbw->affectedRows() > 0;
}
@@ -353,8 +361,7 @@ class Block
function insert()
{
wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
- $dbw =& wfGetDB( DB_MASTER );
- $dbw->begin();
+ $dbw = wfGetDB( DB_MASTER );
# Unset ipb_anon_only for user blocks, makes no sense
if ( $this->mUser ) {
@@ -385,6 +392,7 @@ class Block
'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
'ipb_range_start' => $this->mRangeStart,
'ipb_range_end' => $this->mRangeEnd,
+ 'ipb_deleted' => $this->mHideName
), 'Block::insert', array( 'IGNORE' )
);
$affected = $dbw->affectedRows();
@@ -418,20 +426,20 @@ class Block
} else {
#Limit is 1, so no loop needed.
$retroblockip = $row->rc_ip;
- return $this->doAutoblock($retroblockip);
+ return $this->doAutoblock( $retroblockip, true );
}
}
}
/**
* Autoblocks the given IP, referring to this Block.
- * @param $autoblockip The IP to autoblock.
+ * @param string $autoblockip The IP to autoblock.
+ * @param bool $justInserted The main block was just inserted
* @return bool Whether or not an autoblock was inserted.
*/
- function doAutoblock( $autoblockip ) {
+ function doAutoblock( $autoblockip, $justInserted = false ) {
# Check if this IP address is already blocked
- $dbw =& wfGetDB( DB_MASTER );
- $dbw->begin();
+ $dbw = wfGetDB( DB_MASTER );
# If autoblocks are disabled, go away.
if ( !$this->mEnableAutoblock ) {
@@ -480,7 +488,9 @@ class Block
return;
}
# Just update the timestamp
- $ipblock->updateTimestamp();
+ if ( !$justInserted ) {
+ $ipblock->updateTimestamp();
+ }
return;
} else {
$ipblock = new Block;
@@ -495,6 +505,8 @@ class Block
$ipblock->mTimestamp = wfTimestampNow();
$ipblock->mAuto = 1;
$ipblock->mCreateAccount = $this->mCreateAccount;
+ # Continue suppressing the name if needed
+ $ipblock->mHideName = $this->mHideName;
# If the user is already blocked with an expiry date, we don't
# want to pile on top of that!
@@ -544,7 +556,7 @@ class Block
$this->mTimestamp = wfTimestamp();
$this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$dbw->update( 'ipblocks',
array( /* SET */
'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
@@ -628,16 +640,36 @@ class Block
global $wgAutoblockExpiry;
return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
}
-
- static function normaliseRange( $range )
- {
+
+ /**
+ * Gets rid of uneeded numbers in quad-dotted/octet IP strings
+ * For example, 127.111.113.151/24 -> 127.111.113.0/24
+ */
+ static function normaliseRange( $range ) {
$parts = explode( '/', $range );
if ( count( $parts ) == 2 ) {
- $shift = 32 - $parts[1];
- $ipint = IP::toUnsigned( $parts[0] );
- $ipint = $ipint >> $shift << $shift;
- $newip = long2ip( $ipint );
- $range = "$newip/{$parts[1]}";
+ // IPv6
+ if ( IP::isIPv6($range) && $parts[1] >= 64 && $parts[1] <= 128 ) {
+ $bits = $parts[1];
+ $ipint = IP::toUnsigned6( $parts[0] );
+ # Native 32 bit functions WONT work here!!!
+ # Convert to a padded binary number
+ $network = wfBaseConvert( $ipint, 10, 2, 128 );
+ # Truncate the last (128-$bits) bits and replace them with zeros
+ $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
+ # Convert back to an integer
+ $network = wfBaseConvert( $network, 2, 10 );
+ # Reform octet address
+ $newip = IP::toOctet( $network );
+ $range = "$newip/{$parts[1]}";
+ } // IPv4
+ else if ( IP::isIPv4($range) && $parts[1] >= 16 && $parts[1] <= 32 ) {
+ $shift = 32 - $parts[1];
+ $ipint = IP::toUnsigned( $parts[0] );
+ $ipint = $ipint >> $shift << $shift;
+ $newip = long2ip( $ipint );
+ $range = "$newip/{$parts[1]}";
+ }
}
return $range;
}
@@ -646,7 +678,7 @@ class Block
* Purge expired blocks from the ipblocks table
*/
static function purgeExpired() {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
}
@@ -658,7 +690,7 @@ class Block
/*
static $infinity;
if ( !isset( $infinity ) ) {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$infinity = $dbr->bigTimestamp();
}
return $infinity;
diff --git a/includes/CacheDependency.php b/includes/CacheDependency.php
index 4bb3d328..bb5c5437 100644
--- a/includes/CacheDependency.php
+++ b/includes/CacheDependency.php
@@ -4,6 +4,7 @@
* This class stores an arbitrary value along with its dependencies.
* Users should typically only use DependencyWrapper::getFromCache(), rather
* than instantiating one of these objects directly.
+ * @addtogroup Cache
*/
class DependencyWrapper {
var $value;
@@ -95,6 +96,9 @@ class DependencyWrapper {
}
}
+/**
+ * @addtogroup Cache
+ */
abstract class CacheDependency {
/**
* Returns true if the dependency is expired, false otherwise
@@ -107,6 +111,9 @@ abstract class CacheDependency {
function loadDependencyValues() {}
}
+/**
+ * @addtogroup Cache
+ */
class FileDependency extends CacheDependency {
var $filename, $timestamp;
@@ -163,6 +170,9 @@ class FileDependency extends CacheDependency {
}
}
+/**
+ * @addtogroup Cache
+ */
class TitleDependency extends CacheDependency {
var $titleObj;
var $ns, $dbk;
@@ -219,6 +229,9 @@ class TitleDependency extends CacheDependency {
}
}
+/**
+ * @addtogroup Cache
+ */
class TitleListDependency extends CacheDependency {
var $linkBatch;
var $timestamps;
@@ -244,7 +257,7 @@ class TitleListDependency extends CacheDependency {
# Do the query
if ( count( $timestamps ) ) {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$where = $this->getLinkBatch()->constructSet( 'page', $dbr );
$res = $dbr->select( 'page',
array( 'page_namespace', 'page_title', 'page_touched' ),
@@ -299,6 +312,9 @@ class TitleListDependency extends CacheDependency {
}
}
+/**
+ * @addtogroup Cache
+ */
class GlobalDependency extends CacheDependency {
var $name, $value;
@@ -312,6 +328,9 @@ class GlobalDependency extends CacheDependency {
}
}
+/**
+ * @addtogroup Cache
+ */
class ConstantDependency extends CacheDependency {
var $name, $value;
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index 0086a2f9..356f9ea2 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -3,17 +3,23 @@
* Special handling for category description pages
* Modelled after ImagePage.php
*
- * @package MediaWiki
*/
if( !defined( 'MEDIAWIKI' ) )
die( 1 );
/**
- * @package MediaWiki
*/
class CategoryPage extends Article {
function view() {
+ global $wgRequest, $wgUser;
+
+ $diff = $wgRequest->getVal( 'diff' );
+ $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
+
+ if ( isset( $diff ) && $diffOnly )
+ return Article::view();
+
if(!wfRunHooks('CategoryPageView', array(&$this))) return;
if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
@@ -175,7 +181,7 @@ class CategoryViewer {
}
function doCategoryQuery() {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
if( $this->from != '' ) {
$pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes( $this->from );
$this->flip = false;
@@ -196,6 +202,7 @@ class CategoryViewer {
#+ $pageCondition,
__METHOD__,
array( 'ORDER BY' => $this->flip ? 'cl_sortkey DESC' : 'cl_sortkey',
+ 'USE INDEX' => 'cl_sortkey',
'LIMIT' => $this->limit + 1 ) );
$count = 0;
@@ -234,11 +241,12 @@ class CategoryViewer {
function getSubcategorySection() {
# Don't show subcategories section if there are none.
$r = '';
- if( count( $this->children ) > 0 ) {
+ $c = count( $this->children );
+ if( $c > 0 ) {
# Showing subcategories
$r .= "<div id=\"mw-subcategories\">\n";
$r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n";
- $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), count( $this->children) );
+ $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), $c );
$r .= $this->formatList( $this->children, $this->children_start_char );
$r .= "\n</div>";
}
@@ -247,11 +255,16 @@ class CategoryViewer {
function getPagesSection() {
$ti = htmlspecialchars( $this->title->getText() );
- $r = "<div id=\"mw-pages\">\n";
- $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
- $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), count( $this->articles) );
- $r .= $this->formatList( $this->articles, $this->articles_start_char );
- $r .= "\n</div>";
+ # Don't show articles section if there are none.
+ $r = '';
+ $c = count( $this->articles );
+ if( $c > 0 ) {
+ $r = "<div id=\"mw-pages\">\n";
+ $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
+ $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), $c );
+ $r .= $this->formatList( $this->articles, $this->articles_start_char );
+ $r .= "\n</div>";
+ }
return $r;
}
@@ -391,7 +404,7 @@ class CategoryViewer {
*/
function pagingLinks( $title, $first, $last, $limit, $query = array() ) {
global $wgUser, $wgLang;
- $sk =& $this->getSkin();
+ $sk = $this->getSkin();
$limitText = $wgLang->formatNum( $limit );
$prevLink = htmlspecialchars( wfMsg( 'prevn', $limitText ) );
diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php
index a8cdf3ce..7faae935 100644
--- a/includes/Categoryfinder.php
+++ b/includes/Categoryfinder.php
@@ -1,26 +1,27 @@
<?php
-/*
-The "Categoryfinder" class takes a list of articles, creates an internal representation of all their parent
-categories (as well as parents of parents etc.). From this representation, it determines which of these articles
-are in one or all of a given subset of categories.
-
-Example use :
-
- # Determines wether the article with the page_id 12345 is in both
- # "Category 1" and "Category 2" or their subcategories, respectively
-
- $cf = new Categoryfinder ;
- $cf->seed (
- array ( 12345 ) ,
- array ( "Category 1","Category 2" ) ,
- "AND"
- ) ;
- $a = $cf->run() ;
- print implode ( "," , $a ) ;
-
-*/
-
+/**
+ * The "Categoryfinder" class takes a list of articles, creates an internal
+ * representation of all their parent categories (as well as parents of
+ * parents etc.). From this representation, it determines which of these
+ * articles are in one or all of a given subset of categories.
+ *
+ * Example use :
+ * <code>
+ * # Determines whether the article with the page_id 12345 is in both
+ * # "Category 1" and "Category 2" or their subcategories, respectively
+ *
+ * $cf = new Categoryfinder ;
+ * $cf->seed (
+ * array ( 12345 ) ,
+ * array ( "Category 1","Category 2" ) ,
+ * "AND"
+ * ) ;
+ * $a = $cf->run() ;
+ * print implode ( "," , $a ) ;
+ * </code>
+ *
+ */
class Categoryfinder {
var $articles = array () ; # The original article IDs passed to the seed function
@@ -34,8 +35,8 @@ class Categoryfinder {
/**
* Constructor (currently empty).
- */
- function Categoryfinder () {
+ */
+ function __construct() {
}
/**
@@ -61,10 +62,10 @@ class Categoryfinder {
/**
* Iterates through the parent tree starting with the seed values,
* then checks the articles if they match the conditions
- @return array of page_ids (those given to seed() that match the conditions)
- */
+ * @return array of page_ids (those given to seed() that match the conditions)
+ */
function run () {
- $this->dbr =& wfGetDB( DB_SLAVE );
+ $this->dbr = wfGetDB( DB_SLAVE );
while ( count ( $this->next ) > 0 ) {
$this->scan_next_layer () ;
}
@@ -83,20 +84,20 @@ class Categoryfinder {
/**
* This functions recurses through the parent representation, trying to match the conditions
- @param $id The article/category to check
- @param $conds The array of categories to match
- @return bool Does this match the conditions?
- */
+ * @param $id The article/category to check
+ * @param $conds The array of categories to match
+ * @return bool Does this match the conditions?
+ */
function check ( $id , &$conds ) {
# Shortcut (runtime paranoia): No contitions=all matched
if ( count ( $conds ) == 0 ) return true ;
-
+
if ( !isset ( $this->parents[$id] ) ) return false ;
# iterate through the parents
foreach ( $this->parents[$id] AS $p ) {
$pname = $p->cl_to ;
-
+
# Is this a condition?
if ( isset ( $conds[$pname] ) ) {
# This key is in the category list!
@@ -113,7 +114,7 @@ class Categoryfinder {
}
}
}
-
+
# Not done yet, try sub-parents
if ( !isset ( $this->name2id[$pname] ) ) {
# No sub-parent
@@ -130,10 +131,10 @@ class Categoryfinder {
/**
* Scans a "parent layer" of the articles/categories in $this->next
- */
+ */
function scan_next_layer () {
$fname = "Categoryfinder::scan_next_layer" ;
-
+
# Find all parents of the article currently in $this->next
$layer = array () ;
$res = $this->dbr->select(
@@ -161,7 +162,7 @@ class Categoryfinder {
$this->dbr->freeResult( $res ) ;
$this->next = array() ;
-
+
# Find the IDs of all category pages in $layer, if they exist
if ( count ( $layer ) > 0 ) {
$res = $this->dbr->select(
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
index a2c1a265..bc141579 100644
--- a/includes/ChangesList.php
+++ b/includes/ChangesList.php
@@ -1,15 +1,7 @@
<?php
-/**
- * @package MediaWiki
- * Contain class to show various lists of change:
- * - what's link here
- * - related changes
- * - recent changes
- */
/**
* @todo document
- * @package MediaWiki
*/
class RCCacheEntry extends RecentChange
{
@@ -17,8 +9,7 @@ class RCCacheEntry extends RecentChange
var $curlink , $difflink, $lastlink , $usertalklink , $versionlink ;
var $userlink, $timestamp, $watched;
- function newFromParent( $rc )
- {
+ function newFromParent( $rc ) {
$rc2 = new RCCacheEntry;
$rc2->mAttribs = $rc->mAttribs;
$rc2->mExtra = $rc->mExtra;
@@ -27,14 +18,17 @@ class RCCacheEntry extends RecentChange
} ;
/**
- * @package MediaWiki
+ * Class to show various lists of changes:
+ * - what links here
+ * - related changes
+ * - recent changes
*/
class ChangesList {
# Called by history lists and recent changes
#
/** @todo document */
- function ChangesList( &$skin ) {
+ function __construct( &$skin ) {
$this->skin =& $skin;
$this->preCacheMessages();
}
@@ -47,7 +41,7 @@ class ChangesList {
* @return ChangesList derivative
*/
public static function newFromUser( &$user ) {
- $sk =& $user->getSkin();
+ $sk = $user->getSkin();
$list = NULL;
if( wfRunHooks( 'FetchChangesList', array( &$user, &$sk, &$list ) ) ) {
return $user->getOption( 'usenewrc' ) ? new EnhancedChangesList( $sk ) : new OldChangesList( $sk );
@@ -64,7 +58,7 @@ class ChangesList {
// Precache various messages
if( !isset( $this->message ) ) {
foreach( explode(' ', 'cur diff hist minoreditletter newpageletter last '.
- 'blocklink changes history boteditletter' ) as $msg ) {
+ 'blocklink history boteditletter' ) as $msg ) {
$this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
}
}
@@ -212,6 +206,23 @@ class ChangesList {
global $wgUseRCPatrol, $wgUser;
return( $wgUseRCPatrol && $wgUser->isAllowed( 'patrol' ) );
}
+
+ /**
+ * Returns the string which indicates the number of watching users
+ */
+ function numberofWatchingusers( $count ) {
+ global $wgLang;
+ static $cache = array();
+ if ( $count > 0 ) {
+ if ( !isset( $cache[$count] ) ) {
+ $cache[$count] = wfMsgExt('number_of_watching_users_RCview',
+ array('parsemag', 'escape'), $wgLang->formatNum($count));
+ }
+ return $cache[$count];
+ } else {
+ return '';
+ }
+ }
}
@@ -229,6 +240,7 @@ class OldChangesList extends ChangesList {
wfProfileIn( $fname );
# Extract DB fields into local scope
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( $rc->mAttribs );
# Should patrol-related stuff be shown?
@@ -273,9 +285,7 @@ class OldChangesList extends ChangesList {
$this->insertUserRelatedLinks($s,$rc);
$this->insertComment($s, $rc);
- if($rc->numberofWatchingusers > 0) {
- $s .= ' ' . wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($rc->numberofWatchingusers));
- }
+ $s .= rtrim(' ' . $this->numberofWatchingusers($rc->numberofWatchingusers));
$s .= "</li>\n";
@@ -301,6 +311,7 @@ class EnhancedChangesList extends ChangesList {
$rc = RCCacheEntry::newFromParent( $baseRC );
# Extract fields from DB into the function scope (rc_xxxx variables)
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( $rc->mAttribs );
$curIdEq = 'curid=' . $rc_cur_id;
@@ -405,7 +416,7 @@ class EnhancedChangesList extends ChangesList {
* Enhanced RC group
*/
function recentChangesBlockGroup( $block ) {
- global $wgContLang, $wgRCShowChangedSize;
+ global $wgLang, $wgContLang, $wgRCShowChangedSize;
$r = '';
# Collate list of users
@@ -467,22 +478,32 @@ class EnhancedChangesList extends ChangesList {
$currentRevision = $block[0]->mAttribs['rc_this_oldid'];
if( $block[0]->mAttribs['rc_type'] != RC_LOG ) {
# Changes
- $r .= ' ('.count($block).' ';
+
+ $n = count($block);
+ static $nchanges = array();
+ if ( !isset( $nchanges[$n] ) ) {
+ $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape'),
+ $wgLang->formatNum( $n ) );
+ }
+
+ $r .= ' (';
if( $isnew ) {
- $r .= $this->message['changes'];
+ $r .= $nchanges[$n];
} else {
$r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(),
- $this->message['changes'], $curIdEq."&diff=$currentRevision&oldid=$oldid" );
+ $nchanges[$n], $curIdEq."&diff=$currentRevision&oldid=$oldid" );
}
+ $r .= ') . . ';
+
# Character difference
$chardiff = $rcObj->getCharacterDifference( $block[ count( $block ) - 1 ]->mAttribs['rc_old_len'],
$block[0]->mAttribs['rc_new_len'] );
if( $chardiff == '' ) {
- $r .= '; ';
+ $r .= ' (';
} else {
- $r .= '; ' . $chardiff . ' ';
+ $r .= ' ' . $chardiff. ' . . (';
}
@@ -494,16 +515,14 @@ class EnhancedChangesList extends ChangesList {
$r .= $users;
- if($block[0]->numberofWatchingusers > 0) {
- global $wgContLang;
- $r .= wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($block[0]->numberofWatchingusers));
- }
+ $r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers);
$r .= "<br />\n";
# Sub-entries
$r .= '<div id="'.$rci.'" style="display:none">';
foreach( $block as $rcObj ) {
# Get rc_xxxx variables
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( $rcObj->mAttribs );
$r .= $this->spacerArrow();
@@ -607,6 +626,7 @@ class EnhancedChangesList extends ChangesList {
global $wgContLang, $wgRCShowChangedSize;
# Get rc_xxxx variables
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( $rcObj->mAttribs );
$curIdEq = 'curid='.$rc_cur_id;
@@ -647,9 +667,7 @@ class EnhancedChangesList extends ChangesList {
$r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
}
- if( $rcObj->numberofWatchingusers > 0 ) {
- $r .= wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($rcObj->numberofWatchingusers));
- }
+ $r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers);
$r .= "<br />\n";
return $r;
diff --git a/includes/CoreParserFunctions.php b/includes/CoreParserFunctions.php
index 402a3ba9..72ceb45f 100644
--- a/includes/CoreParserFunctions.php
+++ b/includes/CoreParserFunctions.php
@@ -2,8 +2,8 @@
/**
* Various core parser functions, registered in Parser::firstCallInit()
+ * @addtogroup Parser
*/
-
class CoreParserFunctions {
static function intFunction( $parser, $part1 = '' /*, ... */ ) {
if ( strval( $part1 ) !== '' ) {
@@ -87,7 +87,7 @@ class CoreParserFunctions {
static function formatNum( $parser, $num = '' ) {
return $parser->getFunctionLang()->formatNum( $num );
}
-
+
static function grammar( $parser, $case = '', $word = '' ) {
return $parser->getFunctionLang()->convertGrammar( $word, $case );
}
@@ -135,6 +135,7 @@ class CoreParserFunctions {
static function numberofarticles( $parser, $raw = null ) { return self::statisticsFunction( 'articles', $raw ); }
static function numberoffiles( $parser, $raw = null ) { return self::statisticsFunction( 'images', $raw ); }
static function numberofadmins( $parser, $raw = null ) { return self::statisticsFunction( 'admins', $raw ); }
+ static function numberofedits( $parser, $raw = null ) { return self::statisticsFunction( 'edits', $raw ); }
static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
$count = SiteStats::pagesInNs( intval( $namespace ) );
@@ -151,7 +152,7 @@ class CoreParserFunctions {
$lang = $wgContLang->getLanguageName( strtolower( $arg ) );
return $lang != '' ? $lang : $arg;
}
-
+
static function pad( $string = '', $length = 0, $char = 0, $direction = STR_PAD_RIGHT ) {
$length = min( max( $length, 0 ), 500 );
$char = substr( $char, 0, 1 );
@@ -159,17 +160,21 @@ class CoreParserFunctions {
? str_pad( $string, $length, (string)$char, $direction )
: $string;
}
-
+
static function padleft( $parser, $string = '', $length = 0, $char = 0 ) {
return self::pad( $string, $length, $char, STR_PAD_LEFT );
}
-
+
static function padright( $parser, $string = '', $length = 0, $char = 0 ) {
return self::pad( $string, $length, $char );
}
-
+
static function anchorencode( $parser, $text ) {
- return strtr( urlencode( $text ) , array( '%' => '.' , '+' => '_' ) );
+ $a = urlencode( $text );
+ $a = strtr( $a, array( '%' => '.', '+' => '_' ) );
+ # leave colons alone, however
+ $a = str_replace( '.3A', ':', $a );
+ return $a;
}
static function special( $parser, $text ) {
@@ -180,14 +185,12 @@ class CoreParserFunctions {
return wfMsgForContent( 'nosuchspecialpage' );
}
}
-
+
public static function defaultsort( $parser, $text ) {
$text = trim( $text );
if( strlen( $text ) > 0 )
$parser->setDefaultSort( $text );
return '';
}
-
}
-
?>
diff --git a/includes/Credits.php b/includes/Credits.php
index 62f0b256..87382a86 100644
--- a/includes/Credits.php
+++ b/includes/Credits.php
@@ -18,7 +18,6 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @author <evan@wikitravel.org>
- * @package MediaWiki
*/
/**
diff --git a/includes/Database.php b/includes/Database.php
index eb1ee135..3fd6ad16 100644
--- a/includes/Database.php
+++ b/includes/Database.php
@@ -2,7 +2,6 @@
/**
* This file deals with MySQL interface functions
* and query specifics/optimisations
- * @package MediaWiki
*/
/** Number of times to re-try an operation in case of deadlock */
@@ -16,6 +15,10 @@ define( 'DEADLOCK_DELAY_MAX', 1500000 );
* Utility classes
*****************************************************************************/
+/**
+ * Utility class.
+ * @addtogroup Database
+ */
class DBObject {
public $mData;
@@ -32,12 +35,66 @@ class DBObject {
}
};
+/**
+ * Utility class.
+ * @addtogroup Database
+ */
+class MySQLField {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_key, $type;
+ function __construct ($info) {
+ $this->name = $info->name;
+ $this->tablename = $info->table;
+ $this->default = $info->def;
+ $this->max_length = $info->max_length;
+ $this->nullable = !$info->not_null;
+ $this->is_pk = $info->primary_key;
+ $this->is_unique = $info->unique_key;
+ $this->is_multiple = $info->multiple_key;
+ $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
+ $this->type = $info->type;
+ }
+
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function maxLength() {
+ return $this->max_length;
+ }
+
+ function nullable() {
+ return $this->nullable;
+ }
+
+ function isKey() {
+ return $this->is_key;
+ }
+
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+
+ function type() {
+ return $this->type;
+ }
+}
+
/******************************************************************************
* Error classes
*****************************************************************************/
/**
* Database error base class
+ * @addtogroup Database
*/
class DBError extends MWException {
public $db;
@@ -53,6 +110,9 @@ class DBError extends MWException {
}
}
+/**
+ * @addtogroup Database
+ */
class DBConnectionError extends DBError {
public $error;
@@ -154,6 +214,7 @@ border=\"0\" ALT=\"Google\"></A>
$cache = new HTMLFileCache( $t );
if( $cache->isFileCached() ) {
+ // FIXME: $msg is not defined on the next line.
$msg = '<p style="color: red"><b>'.$msg."<br />\n" .
$cachederror . "</b></p>\n";
@@ -169,6 +230,9 @@ border=\"0\" ALT=\"Google\"></A>
}
}
+/**
+ * @addtogroup Database
+ */
class DBQueryError extends DBError {
public $error, $errno, $sql, $fname;
@@ -222,13 +286,16 @@ class DBQueryError extends DBError {
}
}
+/**
+ * @addtogroup Database
+ */
class DBUnexpectedError extends DBError {}
/******************************************************************************/
/**
* Database abstraction object
- * @package MediaWiki
+ * @addtogroup Database
*/
class Database {
@@ -247,9 +314,6 @@ class Database {
protected $mTrxLevel = 0;
protected $mErrorCount = 0;
protected $mLBInfo = array();
- protected $mCascadingDeletes = false;
- protected $mCleanupTriggers = false;
- protected $mStrictIPs = false;
#------------------------------------------------------------------------------
# Accessors
@@ -344,14 +408,14 @@ class Database {
* Returns true if this database supports (and uses) cascading deletes
*/
function cascadingDeletes() {
- return $this->mCascadingDeletes;
+ return false;
}
/**
* Returns true if this database supports (and uses) triggers (e.g. on the page table)
*/
function cleanupTriggers() {
- return $this->mCleanupTriggers;
+ return false;
}
/**
@@ -359,7 +423,7 @@ class Database {
* Specifically, it uses a NULL value instead of an empty string.
*/
function strictIPs() {
- return $this->mStrictIPs;
+ return false;
}
/**
@@ -376,6 +440,14 @@ class Database {
return true;
}
+ /**
+ * Returns true if this database can do a native search on IP columns
+ * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
+ */
+ function searchableIPs() {
+ return false;
+ }
+
/**#@+
* Get function
*/
@@ -407,13 +479,11 @@ class Database {
#------------------------------------------------------------------------------
/**@{{
+ * Constructor.
* @param string $server database server host
* @param string $user database user name
* @param string $password database user password
* @param string $dbname database name
- */
-
- /**
* @param failFunction
* @param $flags
* @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php
@@ -463,8 +533,7 @@ class Database {
* @param failFunction
* @param $flags
*/
- static function newFromParams( $server, $user, $password, $dbName,
- $failFunction = false, $flags = 0 )
+ static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
{
return new Database( $server, $user, $password, $dbName, $failFunction, $flags );
}
@@ -514,7 +583,7 @@ class Database {
}
if ($this->mConn === false) {
$iplus = $i + 1;
- wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
+ #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
}
}
@@ -541,12 +610,19 @@ class Database {
}
if ( $success ) {
- global $wgDBmysql5;
- if( $wgDBmysql5 ) {
+ $version = $this->getServerVersion();
+ if ( version_compare( $version, '4.1' ) >= 0 ) {
// Tell the server we're communicating with it in UTF-8.
// This may engage various charset conversions.
- $this->query( 'SET NAMES utf8' );
+ global $wgDBmysql5;
+ if( $wgDBmysql5 ) {
+ $this->query( 'SET NAMES utf8', __METHOD__ );
+ }
+ // Turn off strict mode
+ $this->query( "SET sql_mode = ''", __METHOD__ );
}
+
+ // Turn off strict mode if it is on
} else {
$this->reportConnectionError();
}
@@ -599,10 +675,15 @@ class Database {
}
/**
- * Usually aborts on failure
- * If errors are explicitly ignored, returns success
+ * Usually aborts on failure. If errors are explicitly ignored, returns success.
+ *
+ * @param $sql String: SQL query
+ * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST comment (you can use __METHOD__ or add some extra info)
+ * @param $tempIgnore Bool: Whether to avoid throwing an exception on errors... maybe best to catch the exception instead?
+ * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure if $tempIgnore set
+ * @throws DBQueryError Thrown when the database returns an error of any kind
*/
- function query( $sql, $fname = '', $tempIgnore = false ) {
+ public function query( $sql, $fname = '', $tempIgnore = false ) {
global $wgProfiling;
if ( $wgProfiling ) {
@@ -626,11 +707,21 @@ class Database {
$this->mLastQuery = $sql;
# Add a comment for easy SHOW PROCESSLIST interpretation
- if ( $fname ) {
- $commentedSql = preg_replace('/\s/', " /* $fname */ ", $sql, 1);
- } else {
- $commentedSql = $sql;
- }
+ #if ( $fname ) {
+ global $wgUser;
+ if ( is_object( $wgUser ) && !($wgUser instanceof StubObject) ) {
+ $userName = $wgUser->getName();
+ if ( strlen( $userName ) > 15 ) {
+ $userName = substr( $userName, 0, 15 ) . '...';
+ }
+ $userName = str_replace( '/', '', $userName );
+ } else {
+ $userName = '';
+ }
+ $commentedSql = preg_replace('/\s/', " /* $fname $userName */ ", $sql, 1);
+ #} else {
+ # $commentedSql = $sql;
+ #}
# If DBO_TRX is set, start a transaction
if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
@@ -655,6 +746,11 @@ class Database {
wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->ping() ) {
wfDebug( "Reconnected\n" );
+ $sqlx = substr( $commentedSql, 0, 500 );
+ $sqlx = strtr( $sqlx, "\t\n", ' ' );
+ global $wgRequestTime;
+ $elapsed = round( microtime(true) - $wgRequestTime, 3 );
+ wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
$ret = $this->doQuery( $commentedSql );
} else {
wfDebug( "Failed\n" );
@@ -674,9 +770,11 @@ class Database {
/**
* The DBMS-dependent part of query()
- * @param string $sql SQL query.
+ * @param $sql String: SQL query.
+ * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
+ * @access private
*/
- function doQuery( $sql ) {
+ /*private*/ function doQuery( $sql ) {
if( $this->bufferResults() ) {
$ret = mysql_query( $sql, $this->mConn );
} else {
@@ -817,7 +915,13 @@ class Database {
}
/**
- * Fetch the next row from the given result object, in object form
+ * Fetch the next row from the given result object, in object form.
+ * Fields can be retrieved with $row->fieldname, with fields acting like
+ * member variables.
+ *
+ * @param $res SQL result object as returned from Database::query(), etc.
+ * @return MySQL row object
+ * @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchObject( $res ) {
@/**/$row = mysql_fetch_object( $res );
@@ -828,8 +932,12 @@ class Database {
}
/**
- * Fetch the next row from the given result object
- * Returns an array
+ * Fetch the next row from the given result object, in associative array
+ * form. Fields are retrieved with $row['fieldname'].
+ *
+ * @param $res SQL result object as returned from Database::query(), etc.
+ * @return MySQL row object
+ * @throws DBUnexpectedError Thrown if the database returns an error
*/
function fetchRow( $res ) {
@/**/$row = mysql_fetch_array( $res );
@@ -972,7 +1080,7 @@ class Database {
* @return array
*/
function makeSelectOptions( $options ) {
- $tailOpts = '';
+ $preLimitTail = $postLimitTail = '';
$startOpts = '';
$noKeyOptions = array();
@@ -982,16 +1090,17 @@ class Database {
}
}
- if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}";
- if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}";
+ if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
+ if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
- if (isset($options['LIMIT'])) {
- $tailOpts .= $this->limitResult('', $options['LIMIT'],
- isset($options['OFFSET']) ? $options['OFFSET'] : false);
- }
-
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
+ //if (isset($options['LIMIT'])) {
+ // $tailOpts .= $this->limitResult('', $options['LIMIT'],
+ // isset($options['OFFSET']) ? $options['OFFSET']
+ // : false);
+ //}
+
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
+ if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
# Various MySQL extensions
@@ -1010,7 +1119,7 @@ class Database {
$useIndex = '';
}
- return array( $startOpts, $useIndex, $tailOpts );
+ return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
/**
@@ -1038,20 +1147,33 @@ class Database {
else
$from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
} elseif ($table!='') {
- $from = ' FROM ' . $this->tableName( $table );
+ if ($table{0}==' ') {
+ $from = ' FROM ' . $table;
+ } else {
+ $from = ' FROM ' . $this->tableName( $table );
+ }
} else {
$from = '';
}
- list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $options );
+ list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
if( !empty( $conds ) ) {
if ( is_array( $conds ) ) {
$conds = $this->makeList( $conds, LIST_AND );
}
- $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $tailOpts";
+ $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
} else {
- $sql = "SELECT $startOpts $vars $from $useIndex $tailOpts";
+ $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
+ }
+
+ if (isset($options['LIMIT']))
+ $sql = $this->limitResult($sql, $options['LIMIT'],
+ isset($options['OFFSET']) ? $options['OFFSET'] : false);
+ $sql = "$sql $postLimitTail";
+
+ if (isset($options['EXPLAIN'])) {
+ $sql = 'EXPLAIN ' . $sql;
}
return $this->query( $sql, $fname );
@@ -1085,6 +1207,33 @@ class Database {
return $obj;
}
+
+ /**
+ * Estimate rows in dataset
+ * Returns estimated count, based on EXPLAIN output
+ * Takes same arguments as Database::select()
+ */
+
+ function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
+ $options['EXPLAIN']=true;
+ $res = $this->select ($table, $vars, $conds, $fname, $options );
+ if ( $res === false )
+ return false;
+ if (!$this->numRows($res)) {
+ $this->freeResult($res);
+ return 0;
+ }
+
+ $rows=1;
+
+ while( $plan = $this->fetchObject( $res ) ) {
+ $rows *= ($plan->rows > 0)?$plan->rows:1; // avoid resetting to zero
+ }
+
+ $this->freeResult($res);
+ return $rows;
+ }
+
/**
* Removes most variables from an SQL query and replaces them with X or N for numbers.
@@ -1207,7 +1356,7 @@ class Database {
for( $i = 0; $i < $n; $i++ ) {
$meta = mysql_fetch_field( $res, $i );
if( $field == $meta->name ) {
- return $meta;
+ return new MySQLField($meta);
}
}
return false;
@@ -1417,7 +1566,7 @@ class Database {
}
/**
- * @desc: Fetch a number of table names into an zero-indexed numerical array
+ * Fetch a number of table names into an zero-indexed numerical array
* This is handy when you need to construct SQL for joins
*
* Example:
@@ -1952,20 +2101,50 @@ class Database {
}
/**
+ * Override database's default connection timeout.
+ * May be useful for very long batch queries such as
+ * full-wiki dumps, where a single query reads out
+ * over hours or days.
+ * @param int $timeout in seconds
+ */
+ public function setTimeout( $timeout ) {
+ $this->query( "SET net_read_timeout=$timeout" );
+ $this->query( "SET net_write_timeout=$timeout" );
+ }
+
+ /**
* Read and execute SQL commands from a file.
* Returns true on success, error string on failure
+ * @param string $filename File name to open
+ * @param callback $lineCallback Optional function called before reading each line
+ * @param callback $resultCallback Optional function called for each MySQL result
*/
- function sourceFile( $filename ) {
+ function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
$fp = fopen( $filename, 'r' );
if ( false === $fp ) {
return "Could not open \"{$filename}\".\n";
}
+ $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
+ fclose( $fp );
+ return $error;
+ }
+ /**
+ * Read and execute commands from an open file handle
+ * Returns true on success, error string on failure
+ * @param string $fp File handle
+ * @param callback $lineCallback Optional function called before reading each line
+ * @param callback $resultCallback Optional function called for each MySQL result
+ */
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
$cmd = "";
$done = false;
$dollarquote = false;
while ( ! feof( $fp ) ) {
+ if ( $lineCallback ) {
+ call_user_func( $lineCallback );
+ }
$line = trim( fgets( $fp, 1024 ) );
$sl = strlen( $line ) - 1;
@@ -1995,7 +2174,10 @@ class Database {
if ( $done ) {
$cmd = str_replace(';;', ";", $cmd);
$cmd = $this->replaceVars( $cmd );
- $res = $this->query( $cmd, 'dbsource', true );
+ $res = $this->query( $cmd, __METHOD__, true );
+ if ( $resultCallback ) {
+ call_user_func( $resultCallback, $this->resultObject( $res ) );
+ }
if ( false === $res ) {
$err = $this->lastError();
@@ -2006,10 +2188,10 @@ class Database {
$done = false;
}
}
- fclose( $fp );
return true;
}
+
/**
* Replace variables in sourced SQL
*/
@@ -2017,7 +2199,7 @@ class Database {
$varnames = array(
'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser',
'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword',
- 'wgDBadminuser', 'wgDBadminpassword',
+ 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions',
);
// Ordinary variables
@@ -2050,7 +2232,7 @@ class Database {
* Database abstraction object for mySQL
* Inherit all methods and properties of Database::Database()
*
- * @package MediaWiki
+ * @addtogroup Database
* @see Database
*/
class DatabaseMysql extends Database {
@@ -2060,8 +2242,7 @@ class DatabaseMysql extends Database {
/**
* Result wrapper for grabbing data queried by someone else
- *
- * @package MediaWiki
+ * @addtogroup Database
*/
class ResultWrapper {
var $db, $result;
@@ -2107,6 +2288,12 @@ class ResultWrapper {
function seek( $row ) {
$this->db->dataSeek( $this->result, $row );
}
+
+ function rewind() {
+ if ($this->numRows()) {
+ $this->db->dataSeek($this->result, 0);
+ }
+ }
}
diff --git a/includes/DatabaseFunctions.php b/includes/DatabaseFunctions.php
index ca83b9e5..4b31b4f0 100644
--- a/includes/DatabaseFunctions.php
+++ b/includes/DatabaseFunctions.php
@@ -3,7 +3,6 @@
* Legacy database functions, for compatibility with pre-1.3 code
* NOTE: this file is no longer loaded by default.
*
- * @package MediaWiki
*/
/**
@@ -18,7 +17,7 @@ function wfQuery( $sql, $db, $fname = '' ) {
# Someone has tried to call this the old way
throw new FatalError( wfMsgNoDB( 'wrong_wfQuery_params', $db, $sql ) );
}
- $c =& wfGetDB( $db );
+ $c = wfGetDB( $db );
if ( $c !== false ) {
return $c->query( $sql, $fname );
} else {
@@ -34,7 +33,7 @@ function wfQuery( $sql, $db, $fname = '' ) {
* @return Array: first row from the database
*/
function wfSingleQuery( $sql, $dbi, $fname = '' ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
$res = $db->query($sql, $fname );
$row = $db->fetchRow( $res );
$ret = $row[0];
@@ -54,7 +53,7 @@ function wfSingleQuery( $sql, $dbi, $fname = '' ) {
* @return Returns the previous state.
*/
function wfIgnoreSQLErrors( $newstate, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->ignoreErrors( $newstate );
} else {
@@ -73,7 +72,7 @@ function wfIgnoreSQLErrors( $newstate, $dbi = DB_LAST ) {
*/
function wfFreeResult( $res, $dbi = DB_LAST )
{
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
$db->freeResult( $res );
return true;
@@ -87,7 +86,7 @@ function wfFreeResult( $res, $dbi = DB_LAST )
* @return object|false object we requested
*/
function wfFetchObject( $res, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->fetchObject( $res, $dbi = DB_LAST );
} else {
@@ -100,7 +99,7 @@ function wfFetchObject( $res, $dbi = DB_LAST ) {
* @return object|false row we requested
*/
function wfFetchRow( $res, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->fetchRow ( $res, $dbi = DB_LAST );
} else {
@@ -113,7 +112,7 @@ function wfFetchRow( $res, $dbi = DB_LAST ) {
* @return integer|false number of rows
*/
function wfNumRows( $res, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->numRows( $res, $dbi = DB_LAST );
} else {
@@ -126,7 +125,7 @@ function wfNumRows( $res, $dbi = DB_LAST ) {
* @return integer|false number of fields
*/
function wfNumFields( $res, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->numFields( $res );
} else {
@@ -143,7 +142,7 @@ function wfNumFields( $res, $dbi = DB_LAST ) {
*/
function wfFieldName( $res, $n, $dbi = DB_LAST )
{
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->fieldName( $res, $n, $dbi = DB_LAST );
} else {
@@ -156,7 +155,7 @@ function wfFieldName( $res, $n, $dbi = DB_LAST )
* @todo document function
*/
function wfInsertId( $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->insertId();
} else {
@@ -168,7 +167,7 @@ function wfInsertId( $dbi = DB_LAST ) {
* @todo document function
*/
function wfDataSeek( $res, $row, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->dataSeek( $res, $row );
} else {
@@ -180,7 +179,7 @@ function wfDataSeek( $res, $row, $dbi = DB_LAST ) {
* @todo document function
*/
function wfLastErrno( $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->lastErrno();
} else {
@@ -192,7 +191,7 @@ function wfLastErrno( $dbi = DB_LAST ) {
* @todo document function
*/
function wfLastError( $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->lastError();
} else {
@@ -204,7 +203,7 @@ function wfLastError( $dbi = DB_LAST ) {
* @todo document function
*/
function wfAffectedRows( $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->affectedRows();
} else {
@@ -216,7 +215,7 @@ function wfAffectedRows( $dbi = DB_LAST ) {
* @todo document function
*/
function wfLastDBquery( $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->lastQuery();
} else {
@@ -235,7 +234,7 @@ function wfLastDBquery( $dbi = DB_LAST ) {
*/
function wfSetSQL( $table, $var, $value, $cond, $dbi = DB_MASTER )
{
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->set( $table, $var, $value, $cond );
} else {
@@ -254,7 +253,7 @@ function wfSetSQL( $table, $var, $value, $cond, $dbi = DB_MASTER )
*/
function wfGetSQL( $table, $var, $cond='', $dbi = DB_LAST )
{
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->selectField( $table, $var, $cond );
} else {
@@ -271,7 +270,7 @@ function wfGetSQL( $table, $var, $cond='', $dbi = DB_LAST )
* @return Result of Database::fieldExists() or false.
*/
function wfFieldExists( $table, $field, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->fieldExists( $table, $field );
} else {
@@ -288,7 +287,7 @@ function wfFieldExists( $table, $field, $dbi = DB_LAST ) {
* @return Result of Database::indexExists() or false.
*/
function wfIndexExists( $table, $index, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->indexExists( $table, $index );
} else {
@@ -306,7 +305,7 @@ function wfIndexExists( $table, $index, $dbi = DB_LAST ) {
* @return result of Database::insert() or false.
*/
function wfInsertArray( $table, $array, $fname = 'wfInsertArray', $dbi = DB_MASTER ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->insert( $table, $array, $fname );
} else {
@@ -325,7 +324,7 @@ function wfInsertArray( $table, $array, $fname = 'wfInsertArray', $dbi = DB_MAST
* @return result of Database::getArray() or false.
*/
function wfGetArray( $table, $vars, $conds, $fname = 'wfGetArray', $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->getArray( $table, $vars, $conds, $fname );
} else {
@@ -344,7 +343,7 @@ function wfGetArray( $table, $vars, $conds, $fname = 'wfGetArray', $dbi = DB_LAS
* @todo document function
*/
function wfUpdateArray( $table, $values, $conds, $fname = 'wfUpdateArray', $dbi = DB_MASTER ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
$db->update( $table, $values, $conds, $fname );
return true;
@@ -357,7 +356,7 @@ function wfUpdateArray( $table, $values, $conds, $fname = 'wfUpdateArray', $dbi
* @todo document function
*/
function wfTableName( $name, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->tableName( $name );
} else {
@@ -369,7 +368,7 @@ function wfTableName( $name, $dbi = DB_LAST ) {
* @todo document function
*/
function wfStrencode( $s, $dbi = DB_LAST ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->strencode( $s );
} else {
@@ -381,7 +380,7 @@ function wfStrencode( $s, $dbi = DB_LAST ) {
* @todo document function
*/
function wfNextSequenceValue( $seqName, $dbi = DB_MASTER ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->nextSequenceValue( $seqName );
} else {
@@ -393,7 +392,7 @@ function wfNextSequenceValue( $seqName, $dbi = DB_MASTER ) {
* @todo document function
*/
function wfUseIndexClause( $index, $dbi = DB_SLAVE ) {
- $db =& wfGetDB( $dbi );
+ $db = wfGetDB( $dbi );
if ( $db !== false ) {
return $db->useIndexClause( $index );
} else {
diff --git a/includes/DatabaseOracle.php b/includes/DatabaseOracle.php
index 1a6f62f2..2b720df7 100644
--- a/includes/DatabaseOracle.php
+++ b/includes/DatabaseOracle.php
@@ -1,44 +1,141 @@
<?php
/**
- * Oracle.
- *
- * @package MediaWiki
+ * This is the Oracle database abstraction layer.
+ * @addtogroup Database
*/
+class ORABlob {
+ var $mData;
-class OracleBlob extends DBObject {
- function isLOB() {
- return true;
+ function __construct($data) {
+ $this->mData = $data;
}
- function data() {
+
+ function getData() {
return $this->mData;
}
-};
+}
+
+/**
+ * The oci8 extension is fairly weak and doesn't support oci_num_rows, among
+ * other things. We use a wrapper class to handle that and other
+ * Oracle-specific bits, like converting column names back to lowercase.
+ * @addtogroup Database
+ */
+class ORAResult {
+ private $rows;
+ private $cursor;
+ private $stmt;
+ private $nrows;
+ private $db;
+
+ function __construct(&$db, $stmt) {
+ $this->db =& $db;
+ if (($this->nrows = oci_fetch_all($stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM)) === false) {
+ $e = oci_error($stmt);
+ $db->reportQueryError($e['message'], $e['code'], '', __FUNCTION__);
+ return;
+ }
+
+ $this->cursor = 0;
+ $this->stmt = $stmt;
+ }
+
+ function free() {
+ oci_free_statement($this->stmt);
+ }
+
+ function seek($row) {
+ $this->cursor = min($row, $this->nrows);
+ }
+
+ function numRows() {
+ return $this->nrows;
+ }
+
+ function numFields() {
+ return oci_num_fields($this->stmt);
+ }
+
+ function fetchObject() {
+ if ($this->cursor >= $this->nrows)
+ return false;
+
+ $row = $this->rows[$this->cursor++];
+ $ret = new stdClass();
+ foreach ($row as $k => $v) {
+ $lc = strtolower(oci_field_name($this->stmt, $k + 1));
+ $ret->$lc = $v;
+ }
+
+ return $ret;
+ }
+
+ function fetchAssoc() {
+ if ($this->cursor >= $this->nrows)
+ return false;
+
+ $row = $this->rows[$this->cursor++];
+ $ret = array();
+ foreach ($row as $k => $v) {
+ $lc = strtolower(oci_field_name($this->stmt, $k + 1));
+ $ret[$lc] = $v;
+ $ret[$k] = $v;
+ }
+ return $ret;
+ }
+}
/**
- *
- * @package MediaWiki
+ * @addtogroup Database
*/
class DatabaseOracle extends Database {
var $mInsertId = NULL;
var $mLastResult = NULL;
- var $mFetchCache = array();
- var $mFetchID = array();
- var $mNcols = array();
- var $mFieldNames = array(), $mFieldTypes = array();
- var $mAffectedRows = array();
- var $mErr;
+ var $numeric_version = NULL;
+ var $lastResult = null;
+ var $cursor = 0;
+ var $mAffectedRows;
function DatabaseOracle($server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
+ $failFunction = false, $flags = 0 )
{
- Database::Database( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix );
+
+ global $wgOut;
+ # Can't get a reference if it hasn't been set yet
+ if ( !isset( $wgOut ) ) {
+ $wgOut = NULL;
+ }
+ $this->mOut =& $wgOut;
+ $this->mFailFunction = $failFunction;
+ $this->mFlags = $flags;
+ $this->open( $server, $user, $password, $dbName);
+
+ }
+
+ function cascadingDeletes() {
+ return true;
+ }
+ function cleanupTriggers() {
+ return true;
+ }
+ function strictIPs() {
+ return true;
+ }
+ function realTimestamps() {
+ return true;
+ }
+ function implicitGroupby() {
+ return false;
+ }
+ function searchableIPs() {
+ return true;
}
- /* static */ function newFromParams( $server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
+ static function newFromParams( $server = false, $user = false, $password = false, $dbName = false,
+ $failFunction = false, $flags = 0)
{
- return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix );
+ return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags );
}
/**
@@ -47,23 +144,33 @@ class DatabaseOracle extends Database {
*/
function open( $server, $user, $password, $dbName ) {
if ( !function_exists( 'oci_connect' ) ) {
- throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n" );
+ throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
}
+
+ # Needed for proper UTF-8 functionality
+ putenv("NLS_LANG=AMERICAN_AMERICA.AL32UTF8");
+
$this->close();
$this->mServer = $server;
$this->mUser = $user;
$this->mPassword = $password;
$this->mDBname = $dbName;
- $this->mConn = oci_new_connect($user, $password, $dbName, "AL32UTF8");
- if ( $this->mConn === false ) {
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: "
- . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError()."\n" );
- } else {
- $this->mOpened = true;
+ if (!strlen($user)) { ## e.g. the class is being loaded
+ return;
+ }
+
+ error_reporting( E_ALL );
+ $this->mConn = oci_connect($user, $password, $dbName);
+
+ if ($this->mConn == false) {
+ wfDebug("DB connection error\n");
+ wfDebug("Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n");
+ wfDebug($this->lastError()."\n");
+ return false;
}
+
+ $this->mOpened = true;
return $this->mConn;
}
@@ -73,116 +180,67 @@ class DatabaseOracle extends Database {
*/
function close() {
$this->mOpened = false;
- if ($this->mConn) {
- return oci_close($this->mConn);
+ if ( $this->mConn ) {
+ return oci_close( $this->mConn );
} else {
return true;
}
}
- function parseStatement($sql) {
- $this->mErr = $this->mLastResult = false;
- if (($stmt = oci_parse($this->mConn, $sql)) === false) {
- $this->lastError();
- return $this->mLastResult = false;
- }
- $this->mAffectedRows[$stmt] = 0;
- return $this->mLastResult = $stmt;
+ function execFlags() {
+ return $this->mTrxLevel ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS;
}
function doQuery($sql) {
- if (($stmt = $this->parseStatement($sql)) === false)
- return false;
- return $this->executeStatement($stmt);
- }
+ wfDebug("SQL: [$sql]\n");
+ if (!mb_check_encoding($sql)) {
+ throw new MWException("SQL encoding is invalid");
+ }
- function executeStatement($stmt) {
- if (!oci_execute($stmt, OCI_DEFAULT)) {
- $this->lastError();
- oci_free_statement($stmt);
- return false;
+ if (($this->mLastResult = $stmt = oci_parse($this->mConn, $sql)) === false) {
+ $e = oci_error($this->mConn);
+ $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__);
}
- $this->mAffectedRows[$stmt] = oci_num_rows($stmt);
- $this->mFetchCache[$stmt] = array();
- $this->mFetchID[$stmt] = 0;
- $this->mNcols[$stmt] = oci_num_fields($stmt);
- if ($this->mNcols[$stmt] == 0)
- return $this->mLastResult;
- for ($i = 1; $i <= $this->mNcols[$stmt]; $i++) {
- $this->mFieldNames[$stmt][$i] = oci_field_name($stmt, $i);
- $this->mFieldTypes[$stmt][$i] = oci_field_type($stmt, $i);
+
+ if (oci_execute($stmt, $this->execFlags()) == false) {
+ $e = oci_error($stmt);
+ $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__);
}
- while (($o = oci_fetch_array($stmt)) !== false) {
- foreach ($o as $key => $value) {
- if (is_object($value)) {
- $o[$key] = $value->load();
- }
- }
- $this->mFetchCache[$stmt][] = $o;
+ if (oci_statement_type($stmt) == "SELECT")
+ return new ORAResult($this, $stmt);
+ else {
+ $this->mAffectedRows = oci_num_rows($stmt);
+ return true;
}
- return $this->mLastResult;
}
- function queryIgnore( $sql, $fname = '' ) {
- return $this->query( $sql, $fname, true );
+ function queryIgnore($sql, $fname = '') {
+ return $this->query($sql, $fname, true);
}
- function freeResult( $res ) {
- if (!oci_free_statement($res)) {
- throw new DBUnexpectedError( $this, "Unable to free Oracle result\n" );
- }
- unset($this->mFetchID[$res]);
- unset($this->mFetchCache[$res]);
- unset($this->mNcols[$res]);
- unset($this->mFieldNames[$res]);
- unset($this->mFieldTypes[$res]);
+ function freeResult($res) {
+ $res->free();
}
- function fetchAssoc($res) {
- if ($this->mFetchID[$res] >= count($this->mFetchCache[$res]))
- return false;
-
- for ($i = 1; $i <= $this->mNcols[$res]; $i++) {
- $name = $this->mFieldNames[$res][$i];
- if (isset($this->mFetchCache[$res][$this->mFetchID[$res]][$name]))
- $value = $this->mFetchCache[$res][$this->mFetchID[$res]][$name];
- else $value = NULL;
- $key = strtolower($name);
- wfdebug("'$key' => '$value'\n");
- $ret[$key] = $value;
- }
- $this->mFetchID[$res]++;
- return $ret;
+ function fetchObject($res) {
+ return $res->fetchObject();
}
function fetchRow($res) {
- $r = $this->fetchAssoc($res);
- if (!$r)
- return false;
- $i = 0;
- $ret = array();
- foreach ($r as $value) {
- wfdebug("ret[$i]=[$value]\n");
- $ret[$i++] = $value;
- }
- return $ret;
+ return $res->fetchAssoc();
}
- function fetchObject($res) {
- $row = $this->fetchAssoc($res);
- if (!$row)
- return false;
- $ret = new stdClass;
- foreach ($row as $key => $value)
- $ret->$key = $value;
- return $ret;
+ function numRows($res) {
+ return $res->numRows();
}
- function numRows($res) {
- return count($this->mFetchCache[$res]);
+ function numFields($res) {
+ return $res->numFields();
+ }
+
+ function fieldName($stmt, $n) {
+ return pg_field_name($stmt, $n);
}
- function numFields( $res ) { return pg_num_fields( $res ); }
- function fieldName( $res, $n ) { return pg_field_name( $res, $n ); }
/**
* This must be called after nextSequenceVal
@@ -192,139 +250,153 @@ class DatabaseOracle extends Database {
}
function dataSeek($res, $row) {
- $this->mFetchID[$res] = $row;
+ $res->seek($row);
}
function lastError() {
- if ($this->mErr === false) {
- if ($this->mLastResult !== false) {
- $what = $this->mLastResult;
- } else if ($this->mConn !== false) {
- $what = $this->mConn;
- } else {
- $what = false;
- }
- $err = ($what !== false) ? oci_error($what) : oci_error();
- if ($err === false) {
- $this->mErr = 'no error';
- } else {
- $this->mErr = $err['message'];
- }
- }
- return str_replace("\n", '<br />', $this->mErr);
+ if ($this->mConn === false)
+ $e = oci_error();
+ else
+ $e = oci_error($this->mConn);
+ return $e['message'];
}
+
function lastErrno() {
- return 0;
+ if ($this->mConn === false)
+ $e = oci_error();
+ else
+ $e = oci_error($this->mConn);
+ return $e['code'];
}
function affectedRows() {
- return $this->mAffectedRows[$this->mLastResult];
+ return $this->mAffectedRows;
}
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
*/
- function indexInfo ($table, $index, $fname = 'Database::indexInfo' ) {
- $table = $this->tableName($table, true);
- if ($index == 'PRIMARY')
- $index = "${table}_pk";
- $sql = "SELECT uniqueness FROM all_indexes WHERE table_name='" .
- $table . "' AND index_name='" .
- $this->strencode(strtoupper($index)) . "'";
- $res = $this->query($sql, $fname);
- if (!$res)
- return NULL;
- if (($row = $this->fetchObject($res)) == NULL)
- return false;
- $this->freeResult($res);
- $row->Non_unique = !$row->uniqueness;
- return $row;
-
- // BUG: !!!! This code needs to be synced up with database.php
-
+ function indexInfo( $table, $index, $fname = 'Database::indexExists' ) {
+ return false;
}
- function indexUnique ($table, $index, $fname = 'indexUnique') {
- if (!($i = $this->indexInfo($table, $index, $fname)))
- return $i;
- return $i->uniqueness == 'UNIQUE';
+ function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
+ return false;
}
- function fieldInfo( $table, $field ) {
- $o = new stdClass;
- $o->multiple_key = true; /* XXX */
- return $o;
- }
+ function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
+ if (!is_array($options))
+ $options = array($options);
- function getColumnInformation($table, $field) {
- $table = $this->tableName($table, true);
- $field = strtoupper($field);
+ #if (in_array('IGNORE', $options))
+ # $oldIgnore = $this->ignoreErrors(true);
- $res = $this->doQuery("SELECT * FROM all_tab_columns " .
- "WHERE table_name='".$table."' " .
- "AND column_name='".$field."'");
- if (!$res)
- return false;
- $o = $this->fetchObject($res);
- $this->freeResult($res);
- return $o;
- }
+ # IGNORE is performed using single-row inserts, ignoring errors in each
+ # FIXME: need some way to distiguish between key collision and other types of error
+ //$oldIgnore = $this->ignoreErrors(true);
+ if (!is_array(reset($a))) {
+ $a = array($a);
+ }
+ foreach ($a as $row) {
+ $this->insertOneRow($table, $row, $fname);
+ }
+ //$this->ignoreErrors($oldIgnore);
+ $retVal = true;
- function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
- $column = $this->getColumnInformation($table, $field);
- if (!$column)
- return false;
- return true;
+ //if (in_array('IGNORE', $options))
+ // $this->ignoreErrors($oldIgnore);
+
+ return $retVal;
}
- function tableName($name, $forddl = false) {
- # First run any transformations from the parent object
- $name = parent::tableName( $name );
+ function insertOneRow($table, $row, $fname) {
+ // "INSERT INTO tables (a, b, c)"
+ $sql = "INSERT INTO " . $this->tableName($table) . " (" . join(',', array_keys($row)) . ')';
+ $sql .= " VALUES (";
+
+ // for each value, append ":key"
+ $first = true;
+ $returning = '';
+ foreach ($row as $col => $val) {
+ if (is_object($val)) {
+ $what = "EMPTY_BLOB()";
+ assert($returning === '');
+ $returning = " RETURNING $col INTO :bval";
+ $blobcol = $col;
+ } else
+ $what = ":$col";
+
+ if ($first)
+ $sql .= "$what";
+ else
+ $sql.= ", $what";
+ $first = false;
+ }
+ $sql .= ") $returning";
+
+ $stmt = oci_parse($this->mConn, $sql);
+ foreach ($row as $col => $val) {
+ if (!is_object($val)) {
+ if (oci_bind_by_name($stmt, ":$col", $row[$col]) === false)
+ $this->reportQueryError($this->lastErrno(), $this->lastError(), $sql, __METHOD__);
+ }
+ }
+
+ if (($bval = oci_new_descriptor($this->mConn, OCI_D_LOB)) === false) {
+ $e = oci_error($stmt);
+ throw new DBUnexpectedError($this, "Cannot create LOB descriptor: " . $e['message']);
+ }
+
+ if (strlen($returning))
+ oci_bind_by_name($stmt, ":bval", $bval, -1, SQLT_BLOB);
- # Replace backticks into empty
- # Note: "foo" and foo are not the same in Oracle!
- $name = str_replace('`', '', $name);
+ if (oci_execute($stmt, OCI_DEFAULT) === false) {
+ $e = oci_error($stmt);
+ $this->reportQueryError($e['message'], $e['code'], $sql, __METHOD__);
+ }
+ if (strlen($returning)) {
+ $bval->save($row[$blobcol]->getData());
+ $bval->free();
+ }
+ if (!$this->mTrxLevel)
+ oci_commit($this->mConn);
- # Now quote Oracle reserved keywords
+ oci_free_statement($stmt);
+ }
+
+ function tableName( $name ) {
+ # Replace reserved words with better ones
switch( $name ) {
case 'user':
- case 'group':
- case 'validate':
- if ($forddl)
- return $name;
- else
- return '"' . $name . '"';
-
+ return 'mwuser';
+ case 'text':
+ return 'pagecontent';
default:
- return strtoupper($name);
+ return $name;
}
}
- function strencode( $s ) {
- return str_replace("'", "''", $s);
- }
-
/**
* Return the next in a sequence, save the value for retrieval via insertId()
*/
- function nextSequenceValue( $seqName ) {
- $r = $this->doQuery("SELECT $seqName.nextval AS val FROM dual");
- $o = $this->fetchObject($r);
- $this->freeResult($r);
- return $this->mInsertId = (int)$o->val;
+ function nextSequenceValue($seqName) {
+ $res = $this->query("SELECT $seqName.nextval FROM dual");
+ $row = $this->fetchRow($res);
+ $this->mInsertId = $row[0];
+ $this->freeResult($res);
+ return $this->mInsertId;
}
/**
- * USE INDEX clause
- * PostgreSQL doesn't have them and returns ""
+ * Oracle does not have a "USE INDEX" clause, so return an empty string
*/
- function useIndexClause( $index ) {
+ function useIndexClause($index) {
return '';
}
# REPLACE query wrapper
- # PostgreSQL simulates this with a DELETE followed by INSERT
+ # Oracle simulates this with a DELETE followed by INSERT
# $row is the row to insert, an associative array
# $uniqueIndexes is an array of indexes. Each element may be either a
# field name or an array of field names
@@ -333,15 +405,15 @@ class DatabaseOracle extends Database {
# However if you do this, you run the risk of encountering errors which wouldn't have
# occurred in MySQL
function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
- $table = $this->tableName( $table );
+ $table = $this->tableName($table);
if (count($rows)==0) {
return;
}
# Single row case
- if ( !is_array( reset( $rows ) ) ) {
- $rows = array( $rows );
+ if (!is_array(reset($rows))) {
+ $rows = array($rows);
}
foreach( $rows as $row ) {
@@ -377,14 +449,14 @@ class DatabaseOracle extends Database {
# Now insert the row
$sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
$this->makeList( $row, LIST_COMMA ) . ')';
- $this->query( $sql, $fname );
+ $this->query($sql, $fname);
}
}
# DELETE where the condition is a join
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
@@ -421,17 +493,14 @@ class DatabaseOracle extends Database {
}
function limitResult($sql, $limit, $offset) {
- $ret = "SELECT * FROM ($sql) WHERE ROWNUM < " . ((int)$limit + (int)($offset+1));
- if (is_numeric($offset))
- $ret .= " AND ROWNUM >= " . (int)$offset;
- return $ret;
- }
- function limitResultForUpdate($sql, $limit) {
- return $sql;
+ if ($offset === false)
+ $offset = 0;
+ return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < 1 + $limit + $offset";
}
+
/**
* Returns an SQL expression for a simple conditional.
- * Uses CASE on PostgreSQL.
+ * Uses CASE on Oracle
*
* @param string $cond SQL expression which will result in a boolean value
* @param string $trueVal SQL expression to return if true
@@ -442,15 +511,12 @@ class DatabaseOracle extends Database {
return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
- # FIXME: actually detecting deadlocks might be nice
function wasDeadlock() {
- return false;
+ return $this->lastErrno() == 'OCI-00060';
}
- # Return DB-style timestamp used for MySQL schema
function timestamp($ts = 0) {
- return $this->strencode(wfTimestamp(TS_ORACLE, $ts));
-# return "TO_TIMESTAMP('" . $this->strencode(wfTimestamp(TS_DB, $ts)) . "', 'RRRR-MM-DD HH24:MI:SS')";
+ return wfTimestamp(TS_ORACLE, $ts);
}
/**
@@ -460,13 +526,25 @@ class DatabaseOracle extends Database {
return $valuedata;
}
+ function reportQueryError($error, $errno, $sql, $fname, $tempIgnore = false) {
+ # Ignore errors during error handling to avoid infinite
+ # recursion
+ $ignore = $this->ignoreErrors(true);
+ ++$this->mErrorCount;
- function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- $message = "A database error has occurred\n" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
- throw new DBUnexpectedError($this, $message);
+ if ($ignore || $tempIgnore) {
+echo "error ignored! query = [$sql]\n";
+ wfDebug("SQL ERROR (ignored): $error\n");
+ $this->ignoreErrors( $ignore );
+ }
+ else {
+echo "error!\n";
+ $message = "A database error has occurred\n" .
+ "Query: $sql\n" .
+ "Function: $fname\n" .
+ "Error: $errno $error\n";
+ throw new DBUnexpectedError($this, $message);
+ }
}
/**
@@ -483,209 +561,125 @@ class DatabaseOracle extends Database {
return oci_server_version($this->mConn);
}
- function setSchema($schema=false) {
- $schemas=$this->mSchemas;
- if ($schema) { array_unshift($schemas,$schema); }
- $searchpath=$this->makeList($schemas,LIST_NAMES);
- $this->query("SET search_path = $searchpath");
+ /**
+ * Query whether a given table exists (in the given schema, or the default mw one if not given)
+ */
+ function tableExists($table) {
+ $etable= $this->addQuotes($table);
+ $SQL = "SELECT 1 FROM user_tables WHERE table_name='$etable'";
+ $res = $this->query($SQL);
+ $count = $res ? oci_num_rows($res) : 0;
+ if ($res)
+ $this->freeResult($res);
+ return $count;
}
- function begin() {
+ /**
+ * Query whether a given column exists in the mediawiki schema
+ */
+ function fieldExists( $table, $field ) {
+ return true; // XXX
}
- function immediateCommit( $fname = 'Database::immediateCommit' ) {
- oci_commit($this->mConn);
- $this->mTrxLevel = 0;
+ function fieldInfo( $table, $field ) {
+ return false; // XXX
}
- function rollback( $fname = 'Database::rollback' ) {
- oci_rollback($this->mConn);
- $this->mTrxLevel = 0;
+
+ function begin( $fname = '' ) {
+ $this->mTrxLevel = 1;
}
- function getLag() {
- return false;
+ function immediateCommit( $fname = '' ) {
+ return true;
}
- function getStatus($which=null) {
- $result = array('Threads_running' => 0, 'Threads_connected' => 0);
- return $result;
+ function commit( $fname = '' ) {
+ oci_commit($this->mConn);
+ $this->mTrxLevel = 0;
}
- /**
- * Returns an optional USE INDEX clause to go after the table, and a
- * string to go at the end of the query
- *
- * @access private
- *
- * @param array $options an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return array
- */
- function makeSelectOptions($options) {
- $tailOpts = '';
-
- if (isset( $options['ORDER BY'])) {
- $tailOpts .= " ORDER BY {$options['ORDER BY']}";
- }
+ /* Not even sure why this is used in the main codebase... */
+ function limitResultForUpdate($sql, $num) {
+ return $sql;
+ }
- return array('', $tailOpts);
+ function strencode($s) {
+ return str_replace("'", "''", $s);
}
- function maxListLen() {
- return 1000;
+ function encodeBlob($b) {
+ return new ORABlob($b);
+ }
+ function decodeBlob($b) {
+ return $b; //return $b->load();
}
- /**
- * Query whether a given table exists
- */
- function tableExists( $table ) {
- $table = $this->tableName($table, true);
- $res = $this->query( "SELECT COUNT(*) as NUM FROM user_tables WHERE table_name='"
- . $table . "'" );
- if (!$res)
- return false;
- $row = $this->fetchObject($res);
- $this->freeResult($res);
- return $row->num >= 1;
+ function addQuotes( $s ) {
+ global $wgLang;
+ $s = $wgLang->checkTitleEncoding($s);
+ return "'" . $this->strencode($s) . "'";
}
- /**
- * UPDATE wrapper, takes a condition array and a SET array
- */
- function update( $table, $values, $conds, $fname = 'Database::update' ) {
- $table = $this->tableName( $table );
+ function quote_ident( $s ) {
+ return $s;
+ }
- $sql = "UPDATE $table SET ";
- $first = true;
- foreach ($values as $field => $v) {
- if ($first)
- $first = false;
- else
- $sql .= ", ";
- $sql .= "$field = :n$field ";
- }
- if ( $conds != '*' ) {
- $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
- }
- $stmt = $this->parseStatement($sql);
- if ($stmt === false) {
- $this->reportQueryError( $this->lastError(), $this->lastErrno(), $stmt );
- return false;
- }
- if ($this->debug())
- wfDebug("SQL: $sql\n");
- $s = '';
- foreach ($values as $field => $v) {
- oci_bind_by_name($stmt, ":n$field", $values[$field]);
- if ($this->debug())
- $s .= " [$field] = [$v]\n";
- }
- if ($this->debug())
- wfdebug(" PH: $s\n");
- $ret = $this->executeStatement($stmt);
- return $ret;
+ /* For now, does nothing */
+ function selectDB( $db ) {
+ return true;
}
/**
- * INSERT wrapper, inserts an array into a table
+ * Returns an optional USE INDEX clause to go after the table, and a
+ * string to go at the end of the query
*
- * $a may be a single associative array, or an array of these with numeric keys, for
- * multi-row insert.
+ * @private
*
- * Usually aborts on failure
- * If errors are explicitly ignored, returns success
+ * @param array $options an associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
+ * @return array
*/
- function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
- # No rows to insert, easy just return now
- if ( !count( $a ) ) {
- return true;
- }
-
- $table = $this->tableName( $table );
- if (!is_array($options))
- $options = array($options);
-
- $oldIgnore = false;
- if (in_array('IGNORE', $options))
- $oldIgnore = $this->ignoreErrors( true );
-
- if ( isset( $a[0] ) && is_array( $a[0] ) ) {
- $multi = true;
- $keys = array_keys( $a[0] );
- } else {
- $multi = false;
- $keys = array_keys( $a );
+ function makeSelectOptions( $options ) {
+ $preLimitTail = $postLimitTail = '';
+ $startOpts = '';
+
+ $noKeyOptions = array();
+ foreach ( $options as $key => $option ) {
+ if ( is_numeric( $key ) ) {
+ $noKeyOptions[$option] = true;
+ }
}
- $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES (';
- $return = '';
- $first = true;
- foreach ($a as $key => $value) {
- if ($first)
- $first = false;
- else
- $sql .= ", ";
- if (is_object($value) && $value->isLOB()) {
- $sql .= "EMPTY_BLOB()";
- $return = "RETURNING $key INTO :bobj";
- } else
- $sql .= ":$key";
+ if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
+ if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
+
+ if (isset($options['LIMIT'])) {
+ // $tailOpts .= $this->limitResult('', $options['LIMIT'],
+ // isset($options['OFFSET']) ? $options['OFFSET']
+ // : false);
}
- $sql .= ") $return";
- if ($this->debug()) {
- wfDebug("SQL: $sql\n");
- }
+ #if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
+ #if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
+ if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
- if (($stmt = $this->parseStatement($sql)) === false) {
- $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname);
- $this->ignoreErrors($oldIgnore);
- return false;
+ if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
+ $useIndex = $this->useIndexClause( $options['USE INDEX'] );
+ } else {
+ $useIndex = '';
}
+
+ return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
+ }
- /*
- * If we're inserting multiple rows, parse the statement once and
- * execute it for each set of values. Otherwise, convert it into an
- * array and pretend.
- */
- if (!$multi)
- $a = array($a);
-
- foreach ($a as $key => $row) {
- $blob = false;
- $bdata = false;
- $s = '';
- foreach ($row as $k => $value) {
- if (is_object($value) && $value->isLOB()) {
- $blob = oci_new_descriptor($this->mConn, OCI_D_LOB);
- $bdata = $value->data();
- oci_bind_by_name($stmt, ":bobj", $blob, -1, OCI_B_BLOB);
- } else
- oci_bind_by_name($stmt, ":$k", $a[$key][$k], -1);
- if ($this->debug())
- $s .= " [$k] = {$row[$k]}";
- }
- if ($this->debug())
- wfDebug(" PH: $s\n");
- if (($s = $this->executeStatement($stmt)) === false) {
- $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname);
- $this->ignoreErrors($oldIgnore);
- return false;
- }
-
- if ($blob) {
- $blob->save($bdata);
- }
- }
- $this->ignoreErrors($oldIgnore);
- return $this->mLastResult = $s;
+ public function setTimeout( $timeout ) {
+ // @todo fixme no-op
}
function ping() {
+ wfDebug( "Function ping() not written for DatabasePostgres.php yet");
return true;
}
- function encodeBlob($b) {
- return new OracleBlob($b);
- }
-}
+
+} // end DatabaseOracle class
?>
diff --git a/includes/DatabasePostgres.php b/includes/DatabasePostgres.php
index 803c0e26..7158e2d1 100644
--- a/includes/DatabasePostgres.php
+++ b/includes/DatabasePostgres.php
@@ -7,12 +7,69 @@
* than MySQL ones, some of them should be moved to parent
* Database class.
*
- * @package MediaWiki
+ * @addtogroup Database
*/
+class PostgresField {
+ private $name, $tablename, $type, $nullable, $max_length;
+
+ static function fromText($db, $table, $field) {
+ global $wgDBmwschema;
+
+ $q = <<<END
+SELECT typname, attnotnull, attlen
+FROM pg_class, pg_namespace, pg_attribute, pg_type
+WHERE relnamespace=pg_namespace.oid
+AND relkind='r'
+AND attrelid=pg_class.oid
+AND atttypid=pg_type.oid
+AND nspname=%s
+AND relname=%s
+AND attname=%s;
+END;
+ $res = $db->query(sprintf($q,
+ $db->addQuotes($wgDBmwschema),
+ $db->addQuotes($table),
+ $db->addQuotes($field)));
+ $row = $db->fetchObject($res);
+ if (!$row)
+ return null;
+ $n = new PostgresField;
+ $n->type = $row->typname;
+ $n->nullable = ($row->attnotnull == 'f');
+ $n->name = $field;
+ $n->tablename = $table;
+ $n->max_length = $row->attlen;
+ return $n;
+ }
+
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tablename;
+ }
+
+ function type() {
+ return $this->type;
+ }
+
+ function nullable() {
+ return $this->nullable;
+ }
+ function maxLength() {
+ return $this->max_length;
+ }
+}
+
+/**
+ * @addtogroup Database
+ */
class DatabasePostgres extends Database {
var $mInsertId = NULL;
var $mLastResult = NULL;
+ var $numeric_version = NULL;
function DatabasePostgres($server = false, $user = false, $password = false, $dbName = false,
$failFunction = false, $flags = 0 )
@@ -25,24 +82,31 @@ class DatabasePostgres extends Database {
}
$this->mOut =& $wgOut;
$this->mFailFunction = $failFunction;
- $this->mCascadingDeletes = true;
- $this->mCleanupTriggers = true;
- $this->mStrictIPs = true;
$this->mFlags = $flags;
$this->open( $server, $user, $password, $dbName);
}
+ function cascadingDeletes() {
+ return true;
+ }
+ function cleanupTriggers() {
+ return true;
+ }
+ function strictIPs() {
+ return true;
+ }
function realTimestamps() {
return true;
}
-
function implicitGroupby() {
return false;
}
+ function searchableIPs() {
+ return true;
+ }
- static function newFromParams( $server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0)
+ static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0)
{
return new DatabasePostgres( $server, $user, $password, $dbName, $failFunction, $flags );
}
@@ -57,9 +121,12 @@ class DatabasePostgres extends Database {
throw new DBConnectionError( $this, "Postgres functions missing, have you compiled PHP with the --with-pgsql option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
}
-
global $wgDBport;
+ if (!strlen($user)) { ## e.g. the class is being loaded
+ return;
+ }
+
$this->close();
$this->mServer = $server;
$port = $wgDBport;
@@ -75,9 +142,6 @@ class DatabasePostgres extends Database {
$hstring .= "port=$port ";
}
- if (!strlen($user)) { ## e.g. the class is being loaded
- return;
- }
error_reporting( E_ALL );
@$this->mConn = pg_connect("$hstring dbname=$dbName user=$user password=$password");
@@ -94,21 +158,15 @@ class DatabasePostgres extends Database {
if (defined('MEDIAWIKI_INSTALL')) {
global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema,
$wgDBts2schema;
- print "OK</li>\n";
print "<li>Checking the version of Postgres...";
- $version = pg_fetch_result($this->doQuery("SELECT version()"),0,0);
- $thisver = array();
- if (!preg_match('/PostgreSQL (\d+\.\d+)(\S+)/', $version, $thisver)) {
- print "<b>FAILED</b> (could not determine the version)</li>\n";
- dieout("</ul>");
- }
+ $version = $this->getServerVersion();
$PGMINVER = "8.1";
- if ($thisver[1] < $PGMINVER) {
- print "<b>FAILED</b>. Required version is $PGMINVER. You have $thisver[1]$thisver[2]</li>\n";
+ if ($this->numeric_version < $PGMINVER) {
+ print "<b>FAILED</b>. Required version is $PGMINVER. You have $this->numeric_version ($version)</li>\n";
dieout("</ul>");
}
- print "version $thisver[1]$thisver[2] is OK.</li>\n";
+ print "version $this->numeric_version is OK.</li>\n";
$safeuser = $this->quote_ident($wgDBuser);
## Are we connecting as a superuser for the first time?
@@ -232,7 +290,8 @@ class DatabasePostgres extends Database {
$wgDBsuperuser = '';
return true; ## Reconnect as regular user
- }
+
+ } ## end superuser
if (!defined('POSTGRES_SEARCHPATH')) {
@@ -249,13 +308,24 @@ class DatabasePostgres extends Database {
## Does this user have the rights to the tsearch2 tables?
$ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0);
print "<li>Checking tsearch2 permissions...";
+ ## Let's check all four, just to be safe
+ error_reporting( 0 );
+ $ts2tables = array('cfg','cfgmap','dict','parser');
+ foreach ( $ts2tables AS $tname ) {
+ $SQL = "SELECT count(*) FROM $wgDBts2schema.pg_ts_$tname";
+ $res = $this->doQuery($SQL);
+ if (!$res) {
+ print "<b>FAILED</b> to access pg_ts_$tname. Make sure that the user ".
+ "\"$wgDBuser\" has SELECT access to all four tsearch2 tables</li>\n";
+ dieout("</ul>");
+ }
+ }
$SQL = "SELECT ts_name FROM $wgDBts2schema.pg_ts_cfg WHERE locale = '$ctype'";
$SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END";
- error_reporting( 0 );
$res = $this->doQuery($SQL);
error_reporting( E_ALL );
if (!$res) {
- print "<b>FAILED</b>. Make sure that the user \"$wgDBuser\" has SELECT access to the tsearch2 tables</li>\n";
+ print "<b>FAILED</b>. Could not determine the tsearch2 locale information</li>\n";
dieout("</ul>");
}
print "OK</li>";
@@ -282,7 +352,7 @@ class DatabasePostgres extends Database {
$res = $this->doQuery($SQL);
if (!$res) {
print "<b>FAILED</b>. ";
- print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"ctype\"</li>\n";
+ print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"$ctype\"</li>\n";
dieout("</ul>");
}
print "OK</li>";
@@ -325,9 +395,13 @@ class DatabasePostgres extends Database {
$result = $this->schemaExists($wgDBmwschema);
if (!$result) {
print "<li>Creating schema <b>$wgDBmwschema</b> ...";
+ error_reporting( 0 );
$result = $this->doQuery("CREATE SCHEMA $wgDBmwschema");
+ error_reporting( E_ALL );
if (!$result) {
- print "<b>FAILED</b>.</li>\n";
+ print "<b>FAILED</b>. The user \"$wgDBuser\" must be able to access the schema. ".
+ "You can try making them the owner of the database, or try creating the schema with a ".
+ "different user, and then grant access to the \"$wgDBuser\" user.</li>\n";
dieout("</ul>");
}
print "OK</li>\n";
@@ -339,6 +413,39 @@ class DatabasePostgres extends Database {
print "<li>Schema \"$wgDBmwschema\" exists and is owned by \"$user\". Excellent.</li>\n";
}
+ ## Always return GMT time to accomodate the existing integer-based timestamp assumption
+ print "<li>Setting the timezone to GMT for user \"$user\" ...";
+ $SQL = "ALTER USER $safeuser SET timezone = 'GMT'";
+ $result = pg_query($this->mConn, $SQL);
+ if (!$result) {
+ print "<b>FAILED</b>.</li>\n";
+ dieout("</ul>");
+ }
+ print "OK</li>\n";
+ ## Set for the rest of this session
+ $SQL = "SET timezone = 'GMT'";
+ $result = pg_query($this->mConn, $SQL);
+ if (!$result) {
+ print "<li>Failed to set timezone</li>\n";
+ dieout("</ul>");
+ }
+
+ print "<li>Setting the datestyle to ISO, YMD for user \"$user\" ...";
+ $SQL = "ALTER USER $safeuser SET datestyle = 'ISO, YMD'";
+ $result = pg_query($this->mConn, $SQL);
+ if (!$result) {
+ print "<b>FAILED</b>.</li>\n";
+ dieout("</ul>");
+ }
+ print "OK</li>\n";
+ ## Set for the rest of this session
+ $SQL = "SET datestyle = 'ISO, YMD'";
+ $result = pg_query($this->mConn, $SQL);
+ if (!$result) {
+ print "<li>Failed to set datestyle</li>\n";
+ dieout("</ul>");
+ }
+
## Fix up the search paths if needed
print "<li>Setting the search path for user \"$user\" ...";
$path = $this->quote_ident($wgDBmwschema);
@@ -455,6 +562,30 @@ class DatabasePostgres extends Database {
}
/**
+ * Estimate rows in dataset
+ * Returns estimated count, based on EXPLAIN output
+ * This is not necessarily an accurate estimate, so use sparingly
+ * Returns -1 if count cannot be found
+ * Takes same arguments as Database::select()
+ */
+
+ function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
+ $options['EXPLAIN'] = true;
+ $res = $this->select( $table, $vars, $conds, $fname, $options );
+ $rows = -1;
+ if ( $res ) {
+ $row = $this->fetchRow( $res );
+ $count = array();
+ if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
+ $rows = $count[1];
+ }
+ $this->freeResult($res);
+ }
+ return $rows;
+ }
+
+
+ /**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
*/
@@ -645,7 +776,7 @@ class DatabasePostgres extends Database {
return '';
}
- function limitResult($sql, $limit,$offset) {
+ function limitResult($sql, $limit,$offset=false) {
return "$sql LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":"");
}
@@ -707,26 +838,31 @@ class DatabasePostgres extends Database {
* @return string Version information from the database
*/
function getServerVersion() {
- $res = $this->query( "SELECT version()" );
- $row = $this->fetchRow( $res );
- $version = $row[0];
- $this->freeResult( $res );
+ $version = pg_fetch_result($this->doQuery("SELECT version()"),0,0);
+ $thisver = array();
+ if (!preg_match('/PostgreSQL (\d+\.\d+)(\S+)/', $version, $thisver)) {
+ die("Could not determine the numeric version from $version!");
+ }
+ $this->numeric_version = $thisver[1];
return $version;
}
/**
- * Query whether a given table exists (in the given schema, or the default mw one if not given)
+ * Query whether a given relation exists (in the given schema, or the
+ * default mw one if not given)
*/
- function tableExists( $table, $schema = false ) {
+ function relationExists( $table, $types, $schema = false ) {
global $wgDBmwschema;
+ if (!is_array($types))
+ $types = array($types);
if (! $schema )
$schema = $wgDBmwschema;
- $etable = preg_replace("/'/", "''", $table);
- $eschema = preg_replace("/'/", "''", $schema);
+ $etable = $this->addQuotes($table);
+ $eschema = $this->addQuotes($schema);
$SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
- . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' "
- . "AND c.relkind IN ('r','v')";
+ . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
+ . "AND c.relkind IN ('" . implode("','", $types) . "')";
$res = $this->query( $SQL );
$count = $res ? pg_num_rows($res) : 0;
if ($res)
@@ -734,6 +870,61 @@ class DatabasePostgres extends Database {
return $count;
}
+ /*
+ * For backward compatibility, this function checks both tables and
+ * views.
+ */
+ function tableExists ($table, $schema = false) {
+ return $this->relationExists($table, array('r', 'v'), $schema);
+ }
+
+ function sequenceExists ($sequence, $schema = false) {
+ return $this->relationExists($sequence, 'S', $schema);
+ }
+
+ function triggerExists($table, $trigger) {
+ global $wgDBmwschema;
+
+ $q = <<<END
+ SELECT 1 FROM pg_class, pg_namespace, pg_trigger
+ WHERE relnamespace=pg_namespace.oid AND relkind='r'
+ AND tgrelid=pg_class.oid
+ AND nspname=%s AND relname=%s AND tgname=%s
+END;
+ $res = $this->query(sprintf($q,
+ $this->addQuotes($wgDBmwschema),
+ $this->addQuotes($table),
+ $this->addQuotes($trigger)));
+ if (!$res)
+ return NULL;
+ $rows = pg_num_rows($res);
+ $this->freeResult($res);
+ return $rows;
+ }
+
+ function ruleExists($table, $rule) {
+ global $wgDBmwschema;
+ $exists = $this->selectField("pg_rules", "rulename",
+ array( "rulename" => $rule,
+ "tablename" => $table,
+ "schemaname" => $wgDBmwschema));
+ return $exists === $rule;
+ }
+
+ function constraintExists($table, $constraint) {
+ global $wgDBmwschema;
+ $SQL = sprintf("SELECT 1 FROM information_schema.table_constraints ".
+ "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
+ $this->addQuotes($wgDBmwschema),
+ $this->addQuotes($table),
+ $this->addQuotes($constraint));
+ $res = $this->query($SQL);
+ if (!$res)
+ return NULL;
+ $rows = pg_num_rows($res);
+ $this->freeResult($res);
+ return $rows;
+ }
/**
* Query whether a given schema exists. Returns the name of the owner
@@ -752,7 +943,7 @@ class DatabasePostgres extends Database {
/**
* Query whether a given column exists in the mediawiki schema
*/
- function fieldExists( $table, $field ) {
+ function fieldExists( $table, $field, $fname = 'DatabasePostgres::fieldExists' ) {
global $wgDBmwschema;
$etable = preg_replace("/'/", "''", $table);
$eschema = preg_replace("/'/", "''", $wgDBmwschema);
@@ -760,7 +951,7 @@ class DatabasePostgres extends Database {
$SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a "
. "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' "
. "AND a.attrelid = c.oid AND a.attname = '$ecol'";
- $res = $this->query( $SQL );
+ $res = $this->query( $SQL, $fname );
$count = $res ? pg_num_rows($res) : 0;
if ($res)
$this->freeResult( $res );
@@ -768,12 +959,10 @@ class DatabasePostgres extends Database {
}
function fieldInfo( $table, $field ) {
- $res = $this->query( "SELECT $field FROM $table LIMIT 1" );
- $type = pg_field_type( $res, 0 );
- return $type;
+ return PostgresField::fromText($this, $table, $field);
}
- function begin( $fname = 'DatabasePostgrs::begin' ) {
+ function begin( $fname = 'DatabasePostgres::begin' ) {
$this->query( 'BEGIN', $fname );
$this->mTrxLevel = 1;
}
@@ -791,10 +980,36 @@ class DatabasePostgres extends Database {
}
function setup_database() {
- global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport;
+ global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport, $wgDBuser;
+
+ ## Make sure that we can write to the correct schema
+ ## If not, Postgres will happily and silently go to the next search_path item
+ $ctest = "mw_test_table";
+ if ($this->tableExists($ctest, $wgDBmwschema)) {
+ $this->doQuery("DROP TABLE $wgDBmwschema.$ctest");
+ }
+ $SQL = "CREATE TABLE $wgDBmwschema.$ctest(a int)";
+ error_reporting( 0 );
+ $res = $this->doQuery($SQL);
+ error_reporting( E_ALL );
+ if (!$res) {
+ print "<b>FAILED</b>. Make sure that the user \"$wgDBuser\" can write to the schema \"$wgDBmwschema\"</li>\n";
+ dieout("</ul>");
+ }
+ $this->doQuery("DROP TABLE $wgDBmwschema.mw_test_table");
dbsource( "../maintenance/postgres/tables.sql", $this);
+ ## Version-specific stuff
+ if ($this->numeric_version == 8.1) {
+ $this->doQuery("CREATE INDEX ts2_page_text ON pagecontent USING gist(textvector)");
+ $this->doQuery("CREATE INDEX ts2_page_title ON page USING gist(titlevector)");
+ }
+ else {
+ $this->doQuery("CREATE INDEX ts2_page_text ON pagecontent USING gin(textvector)");
+ $this->doQuery("CREATE INDEX ts2_page_title ON page USING gin(titlevector)");
+ }
+
## Update version information
$mwv = $this->addQuotes($wgVersion);
$pgv = $this->addQuotes($this->getServerVersion());
@@ -827,6 +1042,8 @@ class DatabasePostgres extends Database {
$this->query("$SQL $matches[1],$matches[2])");
}
print " (table interwiki successfully populated)...\n";
+
+ $this->doQuery("COMMIT");
}
function encodeBlob($b) {
@@ -870,7 +1087,7 @@ class DatabasePostgres extends Database {
* @return array
*/
function makeSelectOptions( $options ) {
- $tailOpts = '';
+ $preLimitTail = $postLimitTail = '';
$startOpts = '';
$noKeyOptions = array();
@@ -880,16 +1097,17 @@ class DatabasePostgres extends Database {
}
}
- if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}";
- if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}";
+ if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY " . $options['GROUP BY'];
+ if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY " . $options['ORDER BY'];
- if (isset($options['LIMIT'])) {
- $tailOpts .= $this->limitResult('', $options['LIMIT'],
- isset($options['OFFSET']) ? $options['OFFSET'] : false);
- }
-
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
+ //if (isset($options['LIMIT'])) {
+ // $tailOpts .= $this->limitResult('', $options['LIMIT'],
+ // isset($options['OFFSET']) ? $options['OFFSET']
+ // : false);
+ //}
+
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
+ if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
@@ -898,7 +1116,11 @@ class DatabasePostgres extends Database {
$useIndex = '';
}
- return array( $startOpts, $useIndex, $tailOpts );
+ return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
+ }
+
+ public function setTimeout( $timeout ) {
+ // @todo fixme no-op
}
function ping() {
diff --git a/includes/DateFormatter.php b/includes/DateFormatter.php
index c795618a..88a64453 100644
--- a/includes/DateFormatter.php
+++ b/includes/DateFormatter.php
@@ -1,15 +1,9 @@
<?php
-/**
- * Date formatter, recognises dates in plain text and formats them accoding to user preferences.
- *
- * @package MediaWiki
- * @subpackage Parser
- */
/**
+ * Date formatter, recognises dates in plain text and formats them accoding to user preferences.
* @todo preferences, OutputPage
- * @package MediaWiki
- * @subpackage Parser
+ * @addtogroup Parser
*/
class DateFormatter
{
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 0692401d..169d67c9 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -15,7 +15,6 @@
* Documentation is in the source and on:
* http://www.mediawiki.org/wiki/Help:Configuration_settings
*
- * @package MediaWiki
*/
# This is not a valid entry point, perform no further processing unless MEDIAWIKI is defined
@@ -32,7 +31,7 @@ require_once( 'includes/SiteConfiguration.php' );
$wgConf = new SiteConfiguration;
/** MediaWiki version number */
-$wgVersion = '1.9.3';
+$wgVersion = '1.10.0';
/** Name of the site. It must be changed in LocalSettings.php */
$wgSitename = 'MediaWiki';
@@ -163,7 +162,6 @@ $wgTmpDirectory = false; /// defaults to "{$wgUploadDirectory}/tmp"
$wgUploadBaseUrl = "";
/**#@-*/
-
/**
* By default deleted files are simply discarded; to save them and
* make it possible to undelete images, create a directory which
@@ -192,6 +190,7 @@ $wgFileStore['deleted']['hash'] = 3; // 3-level subdirectory split
*
* Problematic punctuation:
* []{}|# Are needed for link syntax, never enable these
+ * <> Causes problems with HTML escaping, don't use
* % Enabled by default, minor problems with path to query rewrite rules, see below
* + Enabled by default, but doesn't work with path to query rewrite rules, corrupted by apache
* ? Enabled by default, but doesn't work with path to PATH_INFO rewrites
@@ -307,8 +306,8 @@ $wgVerifyMimeType= true;
/** Sets the mime type definition file to use by MimeMagic.php.
* @global string $wgMimeTypeFile
*/
-#$wgMimeTypeFile= "/etc/mime.types";
$wgMimeTypeFile= "includes/mime.types";
+#$wgMimeTypeFile= "/etc/mime.types";
#$wgMimeTypeFile= NULL; #use built-in defaults only.
/** Sets the mime type info file to use by MimeMagic.php.
@@ -372,7 +371,11 @@ $wgSharedUploadDBprefix = '';
$wgCacheSharedUploads = true;
/** Allow for upload to be copied from an URL. Requires Special:Upload?source=web */
$wgAllowCopyUploads = false;
-/** Max size for uploads, in bytes */
+/**
+ * Max size for uploads, in bytes. Currently only works for uploads from URL
+ * via CURL (see $wgAllowCopyUploads). The only way to impose limits on
+ * normal uploads is currently to edit php.ini.
+ */
$wgMaxUploadSize = 1024*1024*100; # 100MB
/**
@@ -502,8 +505,12 @@ $wgDBtype = "mysql";
$wgSearchType = null;
/** Table name prefix */
$wgDBprefix = '';
+/** MySQL table options to use during installation or update */
+$wgDBTableOptions = 'TYPE=InnoDB';
+
/**#@-*/
+
/** Live high performance sites should disable this - some checks acquire giant mysql locks */
$wgCheckDBSchema = true;
@@ -964,6 +971,7 @@ $wgGroupPermissions['user' ]['upload'] = true;
$wgGroupPermissions['user' ]['reupload'] = true;
$wgGroupPermissions['user' ]['reupload-shared'] = true;
$wgGroupPermissions['user' ]['minoredit'] = true;
+$wgGroupPermissions['user' ]['purge'] = true; // can use ?action=purge without clicking "ok"
// Implicit group for accounts that pass $wgAutoConfirmAge
$wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true;
@@ -977,6 +985,7 @@ $wgGroupPermissions['emailconfirmed']['emailconfirmed'] = true;
$wgGroupPermissions['bot' ]['bot'] = true;
$wgGroupPermissions['bot' ]['autoconfirmed'] = true;
$wgGroupPermissions['bot' ]['nominornewtalk'] = true;
+$wgGroupPermissions['bot' ]['autopatrol'] = true;
// Most extra permission abilities go to this group
$wgGroupPermissions['sysop']['block'] = true;
@@ -988,7 +997,7 @@ $wgGroupPermissions['sysop']['import'] = true;
$wgGroupPermissions['sysop']['importupload'] = true;
$wgGroupPermissions['sysop']['move'] = true;
$wgGroupPermissions['sysop']['patrol'] = true;
-$wgGroupPermissions['sysop']['autopatrol'] = true;
+$wgGroupPermissions['sysop']['autopatrol'] = true;
$wgGroupPermissions['sysop']['protect'] = true;
$wgGroupPermissions['sysop']['proxyunbannable'] = true;
$wgGroupPermissions['sysop']['rollback'] = true;
@@ -1029,6 +1038,21 @@ $wgRestrictionTypes = array( 'edit', 'move' );
*/
$wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' );
+/**
+ * Set the minimum permissions required to edit pages in each
+ * namespace. If you list more than one permission, a user must
+ * have all of them to edit pages in that namespace.
+ */
+$wgNamespaceProtection = array();
+$wgNamespaceProtection[ NS_MEDIAWIKI ] = array( 'editinterface' );
+
+/**
+* Pages in namespaces in this array can not be used as templates.
+* Elements must be numeric namespace ids.
+* Among other things, this may be useful to enforce read-restrictions
+* which may otherwise be bypassed by using the template machanism.
+*/
+$wgNonincludableNamespaces = array();
/**
* Number of seconds an account is required to age before
@@ -1045,6 +1069,11 @@ $wgAutoConfirmAge = 0;
//$wgAutoConfirmAge = 600; // ten minutes
//$wgAutoConfirmAge = 3600*24; // one day
+# Number of edits an account requires before it is autoconfirmed
+# Passing both this AND the time requirement is needed
+$wgAutoConfirmCount = 0;
+//$wgAutoConfirmCount = 50;
+
# Proxy scanner settings
@@ -1096,7 +1125,7 @@ $wgCacheEpoch = '20030516000000';
* to ensure that client-side caches don't keep obsolete copies of global
* styles.
*/
-$wgStyleVersion = '42b';
+$wgStyleVersion = '63';
# Server-side caching:
@@ -1145,6 +1174,11 @@ $wgEnotifRevealEditorAddress = false; # UPO; reply-to address may be filled with
$wgEnotifMinorEdits = true; # UPO; false: "minor edits" on pages do not trigger notification mails.
# # Attention: _every_ change on a user_talk page trigger a notification mail (if the user is not yet notified)
+/**
+ * Array of usernames who will be sent a notification email for every change which occurs on a wiki
+ */
+$wgUsersNotifedOnAllChanges = array();
+
/** Show watching users in recent changes, watchlist and page history views */
$wgRCShowWatchingUsers = false; # UPO
/** Show watching users in Page views */
@@ -1419,8 +1453,19 @@ $wgSiteNotice = '';
# Images settings
#
-/** dynamic server side image resizing ("Thumbnails") */
-$wgUseImageResize = false;
+/**
+ * Plugins for media file type handling.
+ * Each entry in the array maps a MIME type to a class name
+ */
+$wgMediaHandlers = array(
+ 'image/jpeg' => 'BitmapHandler',
+ 'image/png' => 'BitmapHandler',
+ 'image/gif' => 'BitmapHandler',
+ 'image/x-ms-bmp' => 'BmpHandler',
+ 'image/svg+xml' => 'SvgHandler',
+ 'image/vnd.djvu' => 'DjVuHandler',
+);
+
/**
* Resizing can be done using PHP's internal image libraries or using
@@ -1434,6 +1479,12 @@ $wgUseImageMagick = false;
/** The convert command shipped with ImageMagick */
$wgImageMagickConvertCommand = '/usr/bin/convert';
+/** Sharpening parameter to ImageMagick */
+$wgSharpenParameter = '0x0.4';
+
+/** Reduction in linear dimensions below which sharpening will be enabled */
+$wgSharpenReductionThreshold = 0.85;
+
/**
* Use another resizing converter, e.g. GraphicMagick
* %s will be replaced with the source path, %d with the destination
@@ -1451,7 +1502,7 @@ $wgCustomConvertCommand = false;
#
# An external program is required to perform this conversion:
$wgSVGConverters = array(
- 'ImageMagick' => '$path/convert -background white -geometry $width $input $output',
+ 'ImageMagick' => '$path/convert -background white -geometry $width $input PNG:$output',
'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output',
'inkscape' => '$path/inkscape -z -w $width -f $input -e $output',
'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input',
@@ -1499,11 +1550,17 @@ $wgIgnoreImageErrors = false;
*/
$wgGenerateThumbnailOnParse = true;
+/** Obsolete, always true, kept for compatibility with extensions */
+$wgUseImageResize = true;
+
+
/** Set $wgCommandLineMode if it's not set already, to avoid notices */
if( !isset( $wgCommandLineMode ) ) {
$wgCommandLineMode = false;
}
+/** For colorized maintenance script output, is your terminal background dark ? */
+$wgCommandLineDarkBg = false;
#
# Recent changes settings
@@ -1613,18 +1670,22 @@ $wgExportAllowListContributors = false ;
/** Text matching this regular expression will be recognised as spam
* See http://en.wikipedia.org/wiki/Regular_expression */
$wgSpamRegex = false;
-/** Similarly if this function returns true */
+/** Similarly you can get a function to do the job. The function will be given
+ * the following args:
+ * - a Title object for the article the edit is made on
+ * - the text submitted in the textarea (wpTextbox1)
+ * - the section number.
+ * The return should be boolean indicating whether the edit matched some evilness:
+ * - true : block it
+ * - false : let it through
+ *
+ * For a complete example, have a look at the SpamBlacklist extension.
+ */
$wgFilterCallback = false;
/** Go button goes straight to the edit screen if the article doesn't exist. */
$wgGoToEdit = false;
-/** Allow limited user-specified HTML in wiki pages?
- * It will be run through a whitelist for security. Set this to false if you
- * want wiki pages to consist only of wiki markup. Note that replacements do not
- * yet exist for all HTML constructs.*/
-$wgUserHtml = true;
-
/** Allow raw, unchecked HTML in <html>...</html> sections.
* THIS IS VERY DANGEROUS on a publically editable site, so USE wgGroupPermissions
* TO RESTRICT EDITING to only those that you trust
@@ -1633,8 +1694,7 @@ $wgRawHtml = false;
/**
* $wgUseTidy: use tidy to make sure HTML output is sane.
- * This should only be enabled if $wgUserHtml is true.
- * tidy is a free tool that fixes broken HTML.
+ * Tidy is a free tool that fixes broken HTML.
* See http://www.w3.org/People/Raggett/tidy/
* $wgTidyBin should be set to the path of the binary and
* $wgTidyConf to the path of the configuration file.
@@ -1649,7 +1709,7 @@ $wgRawHtml = false;
$wgUseTidy = false;
$wgAlwaysUseTidy = false;
$wgTidyBin = 'tidy';
-$wgTidyConf = $IP.'/extensions/tidy/tidy.conf';
+$wgTidyConf = $IP.'/includes/tidy.conf';
$wgTidyOpts = '';
$wgTidyInternal = function_exists( 'tidy_load_config' );
@@ -1660,7 +1720,7 @@ $wgDefaultSkin = 'monobook';
* Settings added to this array will override the default globals for the user
* preferences used by anonymous visitors and newly created accounts.
* For instance, to disable section editing links:
- *  $wgDefaultUserOptions ['editsection'] = 0;
+ * $wgDefaultUserOptions ['editsection'] = 0;
*
*/
$wgDefaultUserOptions = array(
@@ -1831,9 +1891,28 @@ $wgFeedDiffCutoff = 32768;
$wgExtraNamespaces = NULL;
/**
+ * Namespace aliases
+ * These are alternate names for the primary localised namespace names, which
+ * are defined by $wgExtraNamespaces and the language file. If a page is
+ * requested with such a prefix, the request will be redirected to the primary
+ * name.
+ *
+ * Set this to a map from namespace names to IDs.
+ * Example:
+ * $wgNamespaceAliases = array(
+ * 'Wikipedian' => NS_USER,
+ * 'Help' => 100,
+ * );
+ */
+$wgNamespaceAliases = array();
+
+/**
* Limit images on image description pages to a user-selectable limit. In order
- * to reduce disk usage, limits can only be selected from a list. This is the
- * list of settings the user can choose from:
+ * to reduce disk usage, limits can only be selected from a list.
+ * The user preference is saved as an array offset in the database, by default
+ * the offset is set with $wgDefaultUserOptions['imagesize']. Make sure you
+ * change it if you alter the array (see bug 8858).
+ * This is the list of settings the user can choose from:
*/
$wgImageLimits = array (
array(320,240),
@@ -1883,9 +1962,9 @@ $wgBrowserBlackList = array(
*
* Reference: http://www.psychedelix.com/agents/index.shtml
*/
- '/^Mozilla\/2\.[^ ]+ .*?\((?!compatible).*; [UIN]/',
- '/^Mozilla\/3\.[^ ]+ .*?\((?!compatible).*; [UIN]/',
- '/^Mozilla\/4\.[^ ]+ .*?\((?!compatible).*; [UIN]/',
+ '/^Mozilla\/2\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
+ '/^Mozilla\/3\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
+ '/^Mozilla\/4\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/',
/**
* MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, Þ to <THORN> and Ð to <ETH>
@@ -1985,7 +2064,9 @@ $wgLogTypes = array( '',
'delete',
'upload',
'move',
- 'import' );
+ 'import',
+ 'patrol',
+);
/**
* Lists the message key string for each log type. The localized messages
@@ -2001,7 +2082,9 @@ $wgLogNames = array(
'delete' => 'dellogpage',
'upload' => 'uploadlogpage',
'move' => 'movelogpage',
- 'import' => 'importlogpage' );
+ 'import' => 'importlogpage',
+ 'patrol' => 'patrol-log-page',
+);
/**
* Lists the message key string for descriptive text to be shown at the
@@ -2017,7 +2100,9 @@ $wgLogHeaders = array(
'delete' => 'dellogpagetext',
'upload' => 'uploadlogpagetext',
'move' => 'movelogpagetext',
- 'import' => 'importlogpagetext', );
+ 'import' => 'importlogpagetext',
+ 'patrol' => 'patrol-log-header',
+);
/**
* Lists the message key string for formatting individual events of each
@@ -2039,7 +2124,8 @@ $wgLogActions = array(
'move/move' => '1movedto2',
'move/move_redir' => '1movedto2_redir',
'import/upload' => 'import-logentry-upload',
- 'import/interwiki' => 'import-logentry-interwiki' );
+ 'import/interwiki' => 'import-logentry-interwiki',
+);
/**
* Experimental preview feature to fetch rendered text
@@ -2166,6 +2252,9 @@ $wgRateLimits = array(
'mailpassword' => array(
'anon' => NULL,
),
+ 'emailuser' => array(
+ 'user' => null,
+ ),
);
/**
@@ -2235,7 +2324,7 @@ $wgTrustedMediaFormats= array(
MEDIATYPE_BITMAP, //all bitmap formats
MEDIATYPE_AUDIO, //all audio formats
MEDIATYPE_VIDEO, //all plain video formats
- "image/svg", //svg (only needed if inline rendering of svg is not supported)
+ "image/svg+xml", //svg (only needed if inline rendering of svg is not supported)
"application/pdf", //PDF files
#"application/x-shockwave-flash", //flash/shockwave movie
);
@@ -2330,7 +2419,7 @@ $wgAllowDisplayTitle = false ;
$wgReservedUsernames = array(
'MediaWiki default', // Default 'Main Page' and MediaWiki: message pages
'Conversion script', // Used for the old Wikipedia software upgrade
- 'Maintenance script', // ... maintenance/edit.php uses this?
+ 'Maintenance script', // Maintenance scripts which perform editing, image import script
'Template namespace initialisation script', // Used in 1.2->1.3 upgrade
);
@@ -2338,7 +2427,7 @@ $wgReservedUsernames = array(
* MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't
* perform basic stuff like MIME detection and which are vulnerable to further idiots uploading
* crap files as images. When this directive is on, <title> will be allowed in files with
- * an "image/svg" MIME type. You should leave this disabled if your web server is misconfigured
+ * an "image/svg+xml" MIME type. You should leave this disabled if your web server is misconfigured
* and doesn't send appropriate MIME types for SVG images.
*/
$wgAllowTitlesInSVG = false;
@@ -2364,25 +2453,42 @@ $wgMaxShellFileSize = 102400;
/**
* DJVU settings
- * Path of the djvutoxml executable
+ * Path of the djvudump executable
* Enable this and $wgDjvuRenderer to enable djvu rendering
*/
-# $wgDjvuToXML = 'djvutoxml';
-$wgDjvuToXML = null;
+# $wgDjvuDump = 'djvudump';
+$wgDjvuDump = null;
/**
* Path of the ddjvu DJVU renderer
- * Enable this and $wgDjvuToXML to enable djvu rendering
+ * Enable this and $wgDjvuDump to enable djvu rendering
*/
# $wgDjvuRenderer = 'ddjvu';
$wgDjvuRenderer = null;
/**
- * Path of the DJVU post processor
- * May include command line options
- * Default: ppmtojpeg, since ddjvu generates ppm output
+ * Path of the djvutoxml executable
+ * This works like djvudump except much, much slower as of version 3.5.
+ *
+ * For now I recommend you use djvudump instead. The djvuxml output is
+ * probably more stable, so we'll switch back to it as soon as they fix
+ * the efficiency problem.
+ * http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
+ */
+# $wgDjvuToXML = 'djvutoxml';
+$wgDjvuToXML = null;
+
+
+/**
+ * Shell command for the DJVU post processor
+ * Default: pnmtopng, since ddjvu generates ppm output
+ * Set this to false to output the ppm file directly.
+ */
+$wgDjvuPostProcessor = 'pnmtojpeg';
+/**
+ * File extension for the DJVU post processor output
*/
-$wgDjvuPostProcessor = 'ppmtojpeg';
+$wgDjvuOutputExtension = 'jpg';
/**
* Enable direct access to the data API
@@ -2416,4 +2522,14 @@ $wgBreakFrames = false;
*/
$wgDisableQueryPageUpdate = false;
+/**
+ * Set this to false to disable cascading protection
+ */
+$wgEnableCascadingProtection = true;
+
+/**
+ * Disable output compression (enabled by default if zlib is available)
+ */
+$wgDisableOutputCompression = false;
+
?>
diff --git a/includes/Defines.php b/includes/Defines.php
index 84bc4495..98e76277 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -1,7 +1,6 @@
<?php
/**
* A few constants that might be needed during LocalSettings.php
- * @package MediaWiki
*/
/**
diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php
index a72f0153..af65ce3a 100644
--- a/includes/DifferenceEngine.php
+++ b/includes/DifferenceEngine.php
@@ -1,15 +1,14 @@
<?php
/**
* See diff.doc
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @todo indicate where diff.doc can be found.
+ * @addtogroup DifferenceEngine
*/
/**
* @todo document
* @public
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class DifferenceEngine {
/**#@+
@@ -63,8 +62,8 @@ class DifferenceEngine {
$this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer
}
- function showDiffPage() {
- global $wgUser, $wgOut, $wgContLang, $wgUseExternalEditor, $wgUseRCPatrol;
+ function showDiffPage( $diffOnly = false ) {
+ global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
$fname = 'DifferenceEngine::showDiffPage';
wfProfileIn( $fname );
@@ -118,6 +117,7 @@ CONTROL;
# is the first version of that article. In that case, V' does not exist.
if ( $this->mOldid === false ) {
$this->showFirstRevision();
+ $this->renderNewRevision(); // should we respect $diffOnly here or not?
wfProfileOut( $fname );
return;
}
@@ -178,15 +178,34 @@ CONTROL;
$oldHeader = "<strong>{$this->mOldtitle}</strong><br />" .
$sk->revUserTools( $this->mOldRev ) . "<br />" .
- $oldminor . $sk->revComment( $this->mOldRev, true ) . "<br />" .
+ $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly ) . "<br />" .
$prevlink;
$newHeader = "<strong>{$this->mNewtitle}</strong><br />" .
$sk->revUserTools( $this->mNewRev ) . " $rollback<br />" .
- $newminor . $sk->revComment( $this->mNewRev, true ) . "<br />" .
+ $newminor . $sk->revComment( $this->mNewRev, !$diffOnly ) . "<br />" .
$nextlink . $patrol;
$this->showDiff( $oldHeader, $newHeader );
+
+ if ( !$diffOnly )
+ $this->renderNewRevision();
+
+ wfProfileOut( $fname );
+ }
+
+ /**
+ * Show the new revision of the page.
+ */
+ function renderNewRevision() {
+ global $wgOut;
+ $fname = 'DifferenceEngine::renderNewRevision';
+ wfProfileIn( $fname );
+
$wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
+ #add deleted rev tag if needed
+ if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
+ }
if( !$this->mNewRev->isCurrent() ) {
$oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
@@ -196,7 +215,8 @@ CONTROL;
if( is_object( $this->mNewRev ) ) {
$wgOut->setRevisionId( $this->mNewRev->getId() );
}
- $wgOut->addSecondaryWikiText( $this->mNewtext );
+
+ $wgOut->addWikiTextTidy( $this->mNewtext );
if( !$this->mNewRev->isCurrent() ) {
$wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
@@ -254,15 +274,6 @@ CONTROL;
$wgOut->setSubtitle( wfMsg( 'difference' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
-
- # Show current revision
- #
- $wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
- if( is_object( $this->mNewRev ) ) {
- $wgOut->setRevisionId( $this->mNewRev->getId() );
- }
- $wgOut->addSecondaryWikiText( $this->mNewtext );
-
wfProfileOut( $fname );
}
@@ -322,9 +333,14 @@ CONTROL;
}
}
+ #loadtext is permission safe, this just clears out the diff
if ( !$this->loadText() ) {
wfProfileOut( $fname );
return false;
+ } else if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
+ return '';
+ } else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
+ return '';
}
$difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
@@ -463,6 +479,14 @@ CONTROL;
* Add the header to a diff body
*/
function addHeader( $diff, $otitle, $ntitle, $multi = '' ) {
+ global $wgOut;
+
+ if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
+ $otitle = '<span class="history-deleted">'.$otitle.'</span>';
+ }
+ if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+ $ntitle = '<span class="history-deleted">'.$ntitle.'</span>';
+ }
$header = "
<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>
<tr>
@@ -523,21 +547,17 @@ CONTROL;
$newLink = $this->mNewPage->escapeLocalUrl();
$this->mPagetitle = htmlspecialchars( wfMsg( 'currentrev' ) );
$newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit' );
- $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undo=' . $this->mNewid );
$this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)"
- . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"
- . " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
+ . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
} else {
$newLink = $this->mNewPage->escapeLocalUrl( 'oldid=' . $this->mNewid );
$newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mNewid );
- $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undo=' . $this->mNewid );
$this->mPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $timestamp ) );
$this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>"
- . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"
- . " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
+ . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
}
// Load the old revision object
@@ -568,6 +588,9 @@ CONTROL;
$oldEdit = $this->mOldPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mOldid );
$this->mOldtitle = "<a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) )
. "</a> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
+ //now that we considered old rev, we can make undo link (bug 8133, multi-edit undo)
+ $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undoafter=' . $this->mOldid . '&undo=' . $this->mNewid);
+ $this->mNewtitle .= " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
}
return true;
@@ -589,13 +612,13 @@ CONTROL;
}
if ( $this->mOldRev ) {
// FIXME: permission tests
- $this->mOldtext = $this->mOldRev->getText();
+ $this->mOldtext = $this->mOldRev->revText();
if ( $this->mOldtext === false ) {
return false;
}
}
if ( $this->mNewRev ) {
- $this->mNewtext = $this->mNewRev->getText();
+ $this->mNewtext = $this->mNewRev->revText();
if ( $this->mNewtext === false ) {
return false;
}
@@ -633,8 +656,7 @@ define('USE_ASSERTS', function_exists('assert'));
/**
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class _DiffOp {
var $type;
@@ -657,8 +679,7 @@ class _DiffOp {
/**
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class _DiffOp_Copy extends _DiffOp {
var $type = 'copy';
@@ -678,8 +699,7 @@ class _DiffOp_Copy extends _DiffOp {
/**
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class _DiffOp_Delete extends _DiffOp {
var $type = 'delete';
@@ -697,8 +717,7 @@ class _DiffOp_Delete extends _DiffOp {
/**
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class _DiffOp_Add extends _DiffOp {
var $type = 'add';
@@ -716,8 +735,7 @@ class _DiffOp_Add extends _DiffOp {
/**
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class _DiffOp_Change extends _DiffOp {
var $type = 'change';
@@ -754,8 +772,7 @@ class _DiffOp_Change extends _DiffOp {
*
* @author Geoffrey T. Dairiki, Tim Starling
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class _DiffEngine
{
@@ -1176,8 +1193,7 @@ class _DiffEngine
* Class representing a 'diff' between two sequences of strings.
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class Diff
{
@@ -1315,11 +1331,9 @@ class Diff
}
/**
- * FIXME: bad name.
- * @todo document
+ * @todo document, bad name.
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class MappedDiff extends Diff
{
@@ -1382,8 +1396,7 @@ class MappedDiff extends Diff
* to obtain fancier outputs.
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class DiffFormatter
{
@@ -1549,8 +1562,7 @@ define('NBSP', '&#160;'); // iso-8859-x non-breaking space.
/**
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class _HWLDF_WordAccumulator {
function _HWLDF_WordAccumulator () {
@@ -1562,9 +1574,12 @@ class _HWLDF_WordAccumulator {
function _flushGroup ($new_tag) {
if ($this->_group !== '') {
- if ($this->_tag == 'mark')
- $this->_line .= '<span class="diffchange">' .
- htmlspecialchars ( $this->_group ) . '</span>';
+ if ($this->_tag == 'ins')
+ $this->_line .= '<ins class="diffchange">' .
+ htmlspecialchars ( $this->_group ) . '</ins>';
+ elseif ($this->_tag == 'del')
+ $this->_line .= '<del class="diffchange">' .
+ htmlspecialchars ( $this->_group ) . '</del>';
else
$this->_line .= htmlspecialchars ( $this->_group );
}
@@ -1608,8 +1623,7 @@ class _HWLDF_WordAccumulator {
/**
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class WordLevelDiff extends MappedDiff
{
@@ -1669,7 +1683,7 @@ class WordLevelDiff extends MappedDiff
if ($edit->type == 'copy')
$orig->addWords($edit->orig);
elseif ($edit->orig)
- $orig->addWords($edit->orig, 'mark');
+ $orig->addWords($edit->orig, 'del');
}
$lines = $orig->getLines();
wfProfileOut( $fname );
@@ -1685,7 +1699,7 @@ class WordLevelDiff extends MappedDiff
if ($edit->type == 'copy')
$closing->addWords($edit->closing);
elseif ($edit->closing)
- $closing->addWords($edit->closing, 'mark');
+ $closing->addWords($edit->closing, 'ins');
}
$lines = $closing->getLines();
wfProfileOut( $fname );
@@ -1697,8 +1711,7 @@ class WordLevelDiff extends MappedDiff
* Wikipedia Table style diff formatter.
* @todo document
* @private
- * @package MediaWiki
- * @subpackage DifferenceEngine
+ * @addtogroup DifferenceEngine
*/
class TableDiffFormatter extends DiffFormatter
{
diff --git a/includes/DjVuImage.php b/includes/DjVuImage.php
index 3b8a68ba..1e423565 100644
--- a/includes/DjVuImage.php
+++ b/includes/DjVuImage.php
@@ -1,11 +1,6 @@
<?php
+
/**
- * Support for detecting/validating DjVu image files and getting
- * some basic file metadata (resolution etc)
- *
- * File format docs are available in source package for DjVuLibre:
- * http://djvulibre.djvuzone.org/
- *
*
* Copyright (C) 2006 Brion Vibber <brion@pobox.com>
* http://www.mediawiki.org/
@@ -25,9 +20,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @package MediaWiki
*/
+/**
+ * Support for detecting/validating DjVu image files and getting
+ * some basic file metadata (resolution etc)
+ *
+ * File format docs are available in source package for DjVuLibre:
+ * http://djvulibre.djvuzone.org/
+ *
+ * @addtogroup Media
+ */
class DjVuImage {
function __construct( $filename ) {
$this->mFilename = $filename;
@@ -68,6 +71,7 @@ class DjVuImage {
function dump() {
$file = fopen( $this->mFilename, 'rb' );
$header = fread( $file, 12 );
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4chunk/NchunkLength', $header ) );
echo "$chunk $chunkLength\n";
$this->dumpForm( $file, $chunkLength, 1 );
@@ -83,6 +87,7 @@ class DjVuImage {
if( $chunkHeader == '' ) {
break;
}
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) );
echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n";
@@ -111,6 +116,7 @@ class DjVuImage {
if( strlen( $header ) < 16 ) {
wfDebug( __METHOD__ . ": too short file header\n" );
} else {
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) );
if( $magic != 'AT&T' ) {
@@ -134,6 +140,7 @@ class DjVuImage {
if( strlen( $header ) < 8 ) {
return array( false, 0 );
} else {
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4chunk/Nlength', $header ) );
return array( $chunk, $length );
}
@@ -192,6 +199,7 @@ class DjVuImage {
return false;
}
+ // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack(
'nwidth/' .
'nheight/' .
@@ -214,17 +222,121 @@ class DjVuImage {
* @return string
*/
function retrieveMetaData() {
- global $wgDjvuToXML;
- if ( isset( $wgDjvuToXML ) ) {
- $cmd = $wgDjvuToXML . ' --without-anno --without-text ' .
+ global $wgDjvuToXML, $wgDjvuDump;
+ if ( isset( $wgDjvuDump ) ) {
+ # djvudump is faster as of version 3.5
+ # http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
+ wfProfileIn( 'djvudump' );
+ $cmd = wfEscapeShellArg( $wgDjvuDump ) . ' ' . wfEscapeShellArg( $this->mFilename );
+ $dump = wfShellExec( $cmd );
+ $xml = $this->convertDumpToXML( $dump );
+ wfProfileOut( 'djvudump' );
+ } elseif ( isset( $wgDjvuToXML ) ) {
+ wfProfileIn( 'djvutoxml' );
+ $cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' .
wfEscapeShellArg( $this->mFilename );
$xml = wfShellExec( $cmd );
+ wfProfileOut( 'djvutoxml' );
} else {
$xml = null;
}
return $xml;
}
-
+
+ /**
+ * Hack to temporarily work around djvutoxml bug
+ */
+ function convertDumpToXML( $dump ) {
+ if ( strval( $dump ) == '' ) {
+ return false;
+ }
+
+ $xml = <<<EOT
+<?xml version="1.0" ?>
+<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
+<DjVuXML>
+<HEAD></HEAD>
+<BODY>
+EOT;
+
+ $dump = str_replace( "\r", '', $dump );
+ $line = strtok( $dump, "\n" );
+ $m = false;
+ $good = false;
+ if ( preg_match( '/^( *)FORM:DJVU/', $line, $m ) ) {
+ # Single-page
+ if ( $this->parseFormDjvu( $line, $xml ) ) {
+ $good = true;
+ } else {
+ return false;
+ }
+ } elseif ( preg_match( '/^( *)FORM:DJVM/', $line, $m ) ) {
+ # Multi-page
+ $parentLevel = strlen( $m[1] );
+ # Find DIRM
+ $line = strtok( "\n" );
+ while ( $line !== false ) {
+ $childLevel = strspn( $line, ' ' );
+ if ( $childLevel <= $parentLevel ) {
+ # End of chunk
+ break;
+ }
+
+ if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) {
+ wfDebug( "Indirect multi-page DjVu document, bad for server!\n" );
+ return false;
+ }
+ if ( preg_match( '/^ *FORM:DJVU/', $line ) ) {
+ # Found page
+ if ( $this->parseFormDjvu( $line, $xml ) ) {
+ $good = true;
+ } else {
+ return false;
+ }
+ }
+ $line = strtok( "\n" );
+ }
+ }
+ if ( !$good ) {
+ return false;
+ }
+
+ $xml .= "</BODY>\n</DjVuXML>\n";
+ return $xml;
+ }
+
+ function parseFormDjvu( $line, &$xml ) {
+ $parentLevel = strspn( $line, ' ' );
+ $line = strtok( "\n" );
+
+ # Find INFO
+ while ( $line !== false ) {
+ $childLevel = strspn( $line, ' ' );
+ if ( $childLevel <= $parentLevel ) {
+ # End of chunk
+ break;
+ }
+
+ if ( preg_match( '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/', $line, $m ) ) {
+ $xml .= Xml::tags( 'OBJECT',
+ array(
+ #'data' => '',
+ #'type' => 'image/x.djvu',
+ 'height' => $m[2],
+ 'width' => $m[1],
+ #'usemap' => '',
+ ),
+ "\n" .
+ Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" .
+ Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n"
+ ) . "\n";
+ return true;
+ }
+ $line = strtok( "\n" );
+ }
+ # Not found
+ return false;
+ }
}
diff --git a/includes/EditPage.php b/includes/EditPage.php
index 7688a64a..bec6e300 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -1,18 +1,14 @@
<?php
/**
- * Contain the EditPage class
- * @package MediaWiki
+ * Contains the EditPage class
*/
/**
- * Splitting edit page/HTML interface from Article...
+ * The edit page/HTML interface (split from Article)
* The actual database and text munging is still in Article,
* but it should get easier to call those from alternate
* interfaces.
- *
- * @package MediaWiki
*/
-
class EditPage {
var $mArticle;
var $mTitle;
@@ -69,22 +65,26 @@ class EditPage {
/**
* Fetch initial editing page content.
*/
- private function getContent() {
+ private function getContent( $def_text = '' ) {
global $wgOut, $wgRequest, $wgParser;
# Get variables from query string :P
$section = $wgRequest->getVal( 'section' );
$preload = $wgRequest->getVal( 'preload' );
+ $undoafter = $wgRequest->getVal( 'undoafter' );
$undo = $wgRequest->getVal( 'undo' );
wfProfileIn( __METHOD__ );
$text = '';
if( !$this->mTitle->exists() ) {
-
- # If requested, preload some text.
- $text = $this->getPreloadedText( $preload );
-
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ # If this is a system message, get the default text.
+ $text = wfMsgWeirdKey ( $this->mTitle->getText() ) ;
+ } else {
+ # If requested, preload some text.
+ $text = $this->getPreloadedText( $preload );
+ }
# We used to put MediaWiki:Newarticletext here if
# $text was empty at this point.
# This is now shown above the edit box instead.
@@ -94,51 +94,63 @@ class EditPage {
// fetch the page record from the high-priority server,
// which is needed to guarantee we don't pick up lagged
// information.
-
+
$text = $this->mArticle->getContent();
- if ( $undo > 0 ) {
- #Undoing a specific edit overrides section editing; section-editing
+ if ( $undo > 0 && $undo > $undoafter ) {
+ # Undoing a specific edit overrides section editing; section-editing
# doesn't work with undoing.
- $undorev = Revision::newFromId($undo);
+ if ( $undoafter ) {
+ $undorev = Revision::newFromId($undo);
+ $oldrev = Revision::newFromId($undoafter);
+ } else {
+ $undorev = Revision::newFromId($undo);
+ $oldrev = $undorev ? $undorev->getPrevious() : null;
+ }
#Sanity check, make sure it's the right page.
# Otherwise, $text will be left as-is.
- if (!is_null($undorev) && $undorev->getPage() == $this->mArticle->getID()) {
- $oldrev = $undorev->getPrevious();
+ if ( !is_null($undorev) && !is_null($oldrev) && $undorev->getPage()==$oldrev->getPage() && $undorev->getPage()==$this->mArticle->getID() ) {
$undorev_text = $undorev->getText();
$oldrev_text = $oldrev->getText();
$currev_text = $text;
#No use doing a merge if it's just a straight revert.
- if ($currev_text != $undorev_text) {
+ if ( $currev_text != $undorev_text ) {
$result = wfMerge($undorev_text, $oldrev_text, $currev_text, $text);
} else {
$text = $oldrev_text;
$result = true;
}
-
- if( $result ) {
- # Inform the user of our success and set an automatic edit summary
- $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-success' ) );
- $this->summary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
- $this->formtype = 'diff';
- } else {
- # Warn the user that something went wrong
- $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-failure' ) );
+ } else {
+ // Failed basic sanity checks.
+ // Older revisions may have been removed since the link
+ // was created, or we may simply have got bogus input.
+ $result = false;
+ }
+
+ if( $result ) {
+ # Inform the user of our success and set an automatic edit summary
+ $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-success' ) );
+ $firstrev = $oldrev->getNext();
+ # If we just undid one rev, use an autosummary
+ if ( $firstrev->mId == $undo ) {
+ $this->summary = wfMsgForContent('undo-summary', $undo, $undorev->getUserText());
}
-
+ $this->formtype = 'diff';
+ } else {
+ # Warn the user that something went wrong
+ $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-failure' ) );
}
- }
- else if( $section != '' ) {
+ } else if( $section != '' ) {
if( $section == 'new' ) {
$text = $this->getPreloadedText( $preload );
} else {
- $text = $wgParser->getSection( $text, $section );
+ $text = $wgParser->getSection( $text, $section, $def_text );
}
}
}
-
+
wfProfileOut( __METHOD__ );
return $text;
}
@@ -282,7 +294,7 @@ class EditPage {
global $wgOut, $wgUser, $wgRequest, $wgTitle;
global $wgEmailConfirmToEdit;
- if ( ! wfRunHooks( 'AlternateEdit', array( &$this ) ) )
+ if ( ! wfRunHooks( 'AlternateEdit', array( &$this ) ) )
return;
$fname = 'EditPage::edit';
@@ -301,7 +313,7 @@ class EditPage {
return;
}
- if ( ! $this->mTitle->userCanEdit() ) {
+ if ( ! $this->mTitle->userCan( 'edit' ) ) {
wfDebug( "$fname: user can't edit\n" );
$wgOut->readOnlyPage( $this->getContent(), true );
wfProfileOut( $fname );
@@ -335,7 +347,7 @@ class EditPage {
wfProfileOut($fname);
return;
}
- if ( !$this->mTitle->userCanCreate() && !$this->mTitle->exists() ) {
+ if ( !$this->mTitle->userCan( 'create' ) && !$this->mTitle->exists() ) {
wfDebug( "$fname: no create permission\n" );
$this->noCreatePermission();
wfProfileOut( $fname );
@@ -421,7 +433,12 @@ class EditPage {
# First time through: get contents, set time for conflict
# checking, etc.
if ( 'initial' == $this->formtype || $this->firsttime ) {
- $this->initialiseForm();
+ if ($this->initialiseForm() === false) {
+ $this->noSuchSectionPage();
+ wfProfileOut( "$fname-business-end" );
+ wfProfileOut( $fname );
+ return;
+ }
if( !$this->mTitle->getArticleId() )
wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
}
@@ -482,7 +499,7 @@ class EditPage {
// Remember whether a save was requested, so we can indicate
// if we forced preview due to session failure.
$this->mTriedSave = !$this->preview;
-
+
if ( $this->tokenOk( $request ) ) {
# Some browsers will not report any submit button
# if the user hits enter in the comment box.
@@ -519,8 +536,8 @@ class EditPage {
} else {
$this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' );
}
-
- $this->autoSumm = $request->getText( 'wpAutoSummary' );
+
+ $this->autoSumm = $request->getText( 'wpAutoSummary' );
} else {
# Not a posted form? Start with nothing.
wfDebug( "$fname: Not a posted form.\n" );
@@ -652,7 +669,7 @@ class EditPage {
wfProfileOut( $fname );
return true;
}
-
+
if ( !$wgUser->isAllowed('edit') ) {
if ( $wgUser->isAnon() ) {
$this->userNotLoggedInPage();
@@ -696,7 +713,7 @@ class EditPage {
if ( 0 == $aid ) {
// Late check for create permission, just in case *PARANOIA*
- if ( !$this->mTitle->userCanCreate() ) {
+ if ( !$this->mTitle->userCan( 'create' ) ) {
wfDebug( "$fname: no create permission\n" );
$this->noCreatePermission();
wfProfileOut( $fname );
@@ -723,6 +740,8 @@ class EditPage {
$this->mArticle->clear(); # Force reload of dates, etc.
$this->mArticle->forUpdate( true ); # Lock the article
+ wfDebug("timestamp: {$this->mArticle->getTimestamp()}, edittime: {$this->edittime}\n");
+
if( $this->mArticle->getTimestamp() != $this->edittime ) {
$this->isConflict = true;
if( $this->section == 'new' ) {
@@ -794,7 +813,7 @@ class EditPage {
}
#And a similar thing for new sections
- if( $this->section == 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) {
+ if( $this->section == 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) {
if (trim($this->summary) == '') {
$this->missingSummary = true;
wfProfileOut( $fname );
@@ -860,10 +879,13 @@ class EditPage {
function initialiseForm() {
$this->edittime = $this->mArticle->getTimestamp();
$this->summary = '';
- $this->textbox1 = $this->getContent();
+ $this->textbox1 = $this->getContent(false);
+ if ($this->textbox1 === false) return false;
+
if ( !$this->mArticle->exists() && $this->mArticle->mTitle->getNamespace() == NS_MEDIAWIKI )
- $this->textbox1 = wfMsgWeirdKey( $this->mArticle->mTitle->getText() ) ;
+ $this->textbox1 = wfMsgWeirdKey( $this->mArticle->mTitle->getText() );
wfProxyCheck();
+ return true;
}
/**
@@ -878,7 +900,7 @@ class EditPage {
$fname = 'EditPage::showEditForm';
wfProfileIn( $fname );
- $sk =& $wgUser->getSkin();
+ $sk = $wgUser->getSkin();
wfRunHooks( 'EditPage::showEditForm:initial', array( &$this ) ) ;
@@ -920,15 +942,15 @@ class EditPage {
if ( $this->missingComment ) {
$wgOut->addWikiText( wfMsg( 'missingcommenttext' ) );
}
-
+
if( $this->missingSummary && $this->section != 'new' ) {
$wgOut->addWikiText( wfMsg( 'missingsummary' ) );
}
- if( $this->missingSummary && $this->section == 'new' ) {
- $wgOut->addWikiText( wfMsg( 'missingcommentheader' ) );
- }
-
+ if( $this->missingSummary && $this->section == 'new' ) {
+ $wgOut->addWikiText( wfMsg( 'missingcommentheader' ) );
+ }
+
if( !$this->hookError == '' ) {
$wgOut->addWikiText( $this->hookError );
}
@@ -936,11 +958,15 @@ class EditPage {
if ( !$this->checkUnicodeCompliantBrowser() ) {
$wgOut->addWikiText( wfMsg( 'nonunicodebrowser') );
}
- if ( isset( $this->mArticle )
- && isset( $this->mArticle->mRevision )
- && !$this->mArticle->mRevision->isCurrent() ) {
- $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() );
- $wgOut->addWikiText( wfMsg( 'editingold' ) );
+ if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) ) {
+ // Let sysop know that this will make private content public if saved
+ if( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
+ }
+ if( !$this->mArticle->mRevision->isCurrent() ) {
+ $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() );
+ $wgOut->addWikiText( wfMsg( 'editingold' ) );
+ }
}
}
@@ -958,24 +984,33 @@ class EditPage {
}
}
}
-
- if( $this->mTitle->isProtected( 'edit' ) ) {
- # Is the protection due to the namespace, e.g. interface text?
- if( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- # Yes; remind the user
- $notice = wfMsg( 'editinginterface' );
- } elseif( $this->mTitle->isSemiProtected() ) {
- # No; semi protected
+
+ if( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ # Show a warning if editing an interface message
+ $wgOut->addWikiText( wfMsg( 'editinginterface' ) );
+ } elseif( $this->mTitle->isProtected( 'edit' ) ) {
+ # Is the title semi-protected?
+ if( $this->mTitle->isSemiProtected() ) {
$notice = wfMsg( 'semiprotectedpagewarning' );
- if( wfEmptyMsg( 'semiprotectedpagewarning', $notice ) || $notice == '-' ) {
+ if( wfEmptyMsg( 'semiprotectedpagewarning', $notice ) || $notice == '-' )
$notice = '';
- }
} else {
- # No; regular protection
+ # Then it must be protected based on static groups (regular)
$notice = wfMsg( 'protectedpagewarning' );
}
$wgOut->addWikiText( $notice );
}
+ if ( $this->mTitle->isCascadeProtected() ) {
+ # Is this page under cascading protection from some source pages?
+ list($cascadeSources, $restrictions) = $this->mTitle->getCascadeProtectionSources();
+ if ( count($cascadeSources) > 0 ) {
+ # Explain, and list the titles responsible
+ $notice = wfMsgExt( 'cascadeprotectedwarning', array('parsemag'), count($cascadeSources) ) . "\n";
+ foreach( $cascadeSources as $id => $page )
+ $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
+ }
+ $wgOut->addWikiText( $notice );
+ }
if ( $this->kblength === false ) {
$this->kblength = (int)(strlen( $this->textbox1 ) / 1024);
@@ -1005,8 +1040,6 @@ class EditPage {
$summary = wfMsg('summary');
$subject = wfMsg('subject');
- $minor = wfMsgExt('minoredit', array('parseinline'));
- $watchthis = wfMsgExt('watchthis', array('parseinline'));
$cancel = $sk->makeKnownLink( $this->mTitle->getPrefixedText(),
wfMsgExt('cancel', array('parseinline')) );
@@ -1041,31 +1074,10 @@ class EditPage {
# Already watched
$this->watchthis = true;
}
-
- if( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true;
- }
-
- $minoredithtml = '';
-
- if ( $wgUser->isAllowed('minoredit') ) {
- $minoredithtml =
- "<input tabindex='3' type='checkbox' value='1' name='wpMinoredit'".($this->minoredit?" checked='checked'":"").
- " accesskey='".wfMsg('accesskey-minoredit')."' id='wpMinoredit' />\n".
- "<label for='wpMinoredit' title='".wfMsg('tooltip-minoredit')."'>{$minor}</label>\n";
- }
- $watchhtml = '';
-
- if ( $wgUser->isLoggedIn() ) {
- $watchhtml = "<input tabindex='4' type='checkbox' name='wpWatchthis'".
- ($this->watchthis?" checked='checked'":"").
- " accesskey=\"".htmlspecialchars(wfMsg('accesskey-watch'))."\" id='wpWatchthis' />\n".
- "<label for='wpWatchthis' title=\"" .
- htmlspecialchars(wfMsg('tooltip-watch'))."\">{$watchthis}</label>\n";
+ if( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true;
}
- $checkboxhtml = $minoredithtml . $watchhtml;
-
$wgOut->addHTML( $this->editFormPageTop );
if ( $wgUser->getOption( 'previewontop' ) ) {
@@ -1132,68 +1144,18 @@ class EditPage {
}
}
- $temp = array(
- 'id' => 'wpSave',
- 'name' => 'wpSave',
- 'type' => 'submit',
- 'tabindex' => '5',
- 'value' => wfMsg('savearticle'),
- 'accesskey' => wfMsg('accesskey-save'),
- 'title' => wfMsg('tooltip-save'),
- );
- $buttons['save'] = wfElement('input', $temp, '');
- $temp = array(
- 'id' => 'wpDiff',
- 'name' => 'wpDiff',
- 'type' => 'submit',
- 'tabindex' => '7',
- 'value' => wfMsg('showdiff'),
- 'accesskey' => wfMsg('accesskey-diff'),
- 'title' => wfMsg('tooltip-diff'),
- );
- $buttons['diff'] = wfElement('input', $temp, '');
+ $tabindex = 2;
- global $wgLivePreview;
- if ( $wgLivePreview && $wgUser->getOption( 'uselivepreview' ) ) {
- $temp = array(
- 'id' => 'wpPreview',
- 'name' => 'wpPreview',
- 'type' => 'submit',
- 'tabindex' => '6',
- 'value' => wfMsg('showpreview'),
- 'accesskey' => '',
- 'title' => wfMsg('tooltip-preview'),
- 'style' => 'display: none;',
- );
- $buttons['preview'] = wfElement('input', $temp, '');
- $temp = array(
- 'id' => 'wpLivePreview',
- 'name' => 'wpLivePreview',
- 'type' => 'submit',
- 'tabindex' => '6',
- 'value' => wfMsg('showlivepreview'),
- 'accesskey' => wfMsg('accesskey-preview'),
- 'title' => '',
- 'onclick' => $this->doLivePreviewScript(),
- );
- $buttons['live'] = wfElement('input', $temp, '');
- } else {
- $temp = array(
- 'id' => 'wpPreview',
- 'name' => 'wpPreview',
- 'type' => 'submit',
- 'tabindex' => '6',
- 'value' => wfMsg('showpreview'),
- 'accesskey' => wfMsg('accesskey-preview'),
- 'title' => wfMsg('tooltip-preview'),
- );
- $buttons['preview'] = wfElement('input', $temp, '');
- $buttons['live'] = '';
- }
+ $checkboxes = self::getCheckboxes( $tabindex, $sk,
+ array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
+
+ $checkboxhtml = implode( $checkboxes, "\n" );
+
+ $buttons = $this->getEditButtons( $tabindex );
+ $buttonshtml = implode( $buttons, "\n" );
$safemodehtml = $this->checkUnicodeCompliantBrowser()
- ? ""
- : "<input type='hidden' name=\"safemode\" value='1' />\n";
+ ? '' : Xml::hidden( 'safemode', '1' );
$wgOut->addHTML( <<<END
{$toolbar}
@@ -1205,6 +1167,8 @@ END
call_user_func_array( $formCallback, array( &$wgOut ) );
}
+ wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
+
// Put these up at the top to ensure they aren't lost on early form submission
$wgOut->addHTML( "
<input type='hidden' value=\"" . htmlspecialchars( $this->section ) . "\" name=\"wpSection\" />
@@ -1236,10 +1200,7 @@ END
$wgOut->addHTML(
"<div class='editButtons'>
- {$buttons['save']}
- {$buttons['preview']}
- {$buttons['live']}
- {$buttons['diff']}
+{$buttonshtml}
<span class='editHelp'>{$cancel} | {$edithelp}</span>
</div><!-- editButtons -->
</div><!-- editOptions -->");
@@ -1282,7 +1243,7 @@ END
if( $this->missingSummary ) {
$wgOut->addHTML( "<input type=\"hidden\" name=\"wpIgnoreBlankSummary\" value=\"1\" />\n" );
}
-
+
# For a bit more sophisticated detection of blank summaries, hash the
# automatic one and pass that in a hidden field.
$autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
@@ -1308,7 +1269,7 @@ END
} else {
$wgOut->addHTML( '<div id="wikiPreview"></div>' );
}
-
+
if ( $this->formtype == 'diff') {
$wgOut->addHTML( $this->getDiff() );
}
@@ -1361,7 +1322,7 @@ END
}
function getLastDelete() {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$fname = 'EditPage::getLastDelete';
$res = $dbr->select(
array( 'logging', 'user' ),
@@ -1425,7 +1386,7 @@ END
# don't parse user css/js, show message about preview
# XXX: stupid php bug won't let us use $wgTitle->isCssJsSubpage() here
-
+
if ( $this->isCssJsSubpage ) {
if(preg_match("/\\.css$/", $wgTitle->getText() ) ) {
$previewtext = wfMsg('usercsspreview');
@@ -1469,16 +1430,16 @@ END
function blockedPage() {
global $wgOut, $wgUser;
$wgOut->blockedPage( false ); # Standard block notice on the top, don't 'return'
-
+
# If the user made changes, preserve them when showing the markup
- # (This happens when a user is blocked during edit, for instance)
+ # (This happens when a user is blocked during edit, for instance)
$first = $this->firsttime || ( !$this->save && $this->textbox1 == '' );
if( $first ) {
$source = $this->mTitle->exists() ? $this->getContent() : false;
} else {
$source = $this->textbox1;
}
-
+
# Spit out the source or the user's modified version
if( $source !== false ) {
$rows = $wgUser->getOption( 'rows' );
@@ -1496,14 +1457,14 @@ END
function userNotLoggedInPage() {
global $wgUser, $wgOut;
$skin = $wgUser->getSkin();
-
+
$loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
$loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $this->mTitle->getPrefixedUrl() );
-
+
$wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
-
+
$wgOut->addHtml( wfMsgWikiHtml( 'whitelistedittext', $loginLink ) );
$wgOut->returnToMain( false, $this->mTitle->getPrefixedUrl() );
}
@@ -1519,12 +1480,27 @@ END
$wgOut->setPageTitle( wfMsg( 'confirmedittitle' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
-
+
$wgOut->addWikiText( wfMsg( 'confirmedittext' ) );
$wgOut->returnToMain( false );
}
/**
+ * Creates a basic error page which informs the user that
+ * they have attempted to edit a nonexistant section.
+ */
+ function noSuchSectionPage() {
+ global $wgOut;
+
+ $wgOut->setPageTitle( wfMsg( 'nosuchsectiontitle' ) );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
+ $wgOut->setArticleRelated( false );
+
+ $wgOut->addWikiText( wfMsg( 'nosuchsectiontext', $this->section ) );
+ $wgOut->returnToMain( false );
+ }
+
+ /**
* Produce the stock "your edit contains spam" page
*
* @param $match Text which triggered one or more filters
@@ -1539,7 +1515,7 @@ END
$wgOut->addWikiText( wfMsg( 'spamprotectiontext' ) );
if ( $match )
$wgOut->addWikiText( wfMsg( 'spamprotectionmatch', "<nowiki>{$match}</nowiki>" ) );
-
+
$wgOut->returnToMain( false );
}
@@ -1551,7 +1527,7 @@ END
$fname = 'EditPage::mergeChangesInto';
wfProfileIn( $fname );
- $db =& wfGetDB( DB_MASTER );
+ $db = wfGetDB( DB_MASTER );
// This is the revision the editor started from
$baseRevision = Revision::loadFromTimestamp(
@@ -1645,90 +1621,102 @@ END
* can figure out a way to make them work in IE. However, we should make
* sure these keys are not defined on the edit page.
*/
- $toolarray=array(
- array( 'image'=>'button_bold.png',
- 'open' => '\\\'\\\'\\\'',
- 'close' => '\\\'\\\'\\\'',
- 'sample'=> wfMsg('bold_sample'),
- 'tip' => wfMsg('bold_tip'),
- 'key' => 'B'
- ),
- array( 'image'=>'button_italic.png',
- 'open' => '\\\'\\\'',
- 'close' => '\\\'\\\'',
- 'sample'=> wfMsg('italic_sample'),
- 'tip' => wfMsg('italic_tip'),
- 'key' => 'I'
- ),
- array( 'image'=>'button_link.png',
- 'open' => '[[',
- 'close' => ']]',
- 'sample'=> wfMsg('link_sample'),
- 'tip' => wfMsg('link_tip'),
- 'key' => 'L'
- ),
- array( 'image'=>'button_extlink.png',
- 'open' => '[',
- 'close' => ']',
- 'sample'=> wfMsg('extlink_sample'),
- 'tip' => wfMsg('extlink_tip'),
- 'key' => 'X'
- ),
- array( 'image'=>'button_headline.png',
- 'open' => "\\n== ",
- 'close' => " ==\\n",
- 'sample'=> wfMsg('headline_sample'),
- 'tip' => wfMsg('headline_tip'),
- 'key' => 'H'
- ),
- array( 'image'=>'button_image.png',
- 'open' => '[['.$wgContLang->getNsText(NS_IMAGE).":",
- 'close' => ']]',
- 'sample'=> wfMsg('image_sample'),
- 'tip' => wfMsg('image_tip'),
- 'key' => 'D'
- ),
- array( 'image' =>'button_media.png',
- 'open' => '[['.$wgContLang->getNsText(NS_MEDIA).':',
- 'close' => ']]',
- 'sample'=> wfMsg('media_sample'),
- 'tip' => wfMsg('media_tip'),
- 'key' => 'M'
- ),
- array( 'image' =>'button_math.png',
- 'open' => "<math>",
- 'close' => "<\\/math>",
- 'sample'=> wfMsg('math_sample'),
- 'tip' => wfMsg('math_tip'),
- 'key' => 'C'
- ),
- array( 'image' =>'button_nowiki.png',
- 'open' => "<nowiki>",
- 'close' => "<\\/nowiki>",
- 'sample'=> wfMsg('nowiki_sample'),
- 'tip' => wfMsg('nowiki_tip'),
- 'key' => 'N'
- ),
- array( 'image' =>'button_sig.png',
- 'open' => '--~~~~',
- 'close' => '',
- 'sample'=> '',
- 'tip' => wfMsg('sig_tip'),
- 'key' => 'Y'
- ),
- array( 'image' =>'button_hr.png',
- 'open' => "\\n----\\n",
- 'close' => '',
- 'sample'=> '',
- 'tip' => wfMsg('hr_tip'),
- 'key' => 'R'
- )
+ $toolarray = array(
+ array( 'image' => 'button_bold.png',
+ 'id' => 'mw-editbutton-bold',
+ 'open' => '\\\'\\\'\\\'',
+ 'close' => '\\\'\\\'\\\'',
+ 'sample'=> wfMsg('bold_sample'),
+ 'tip' => wfMsg('bold_tip'),
+ 'key' => 'B'
+ ),
+ array( 'image' => 'button_italic.png',
+ 'id' => 'mw-editbutton-italic',
+ 'open' => '\\\'\\\'',
+ 'close' => '\\\'\\\'',
+ 'sample'=> wfMsg('italic_sample'),
+ 'tip' => wfMsg('italic_tip'),
+ 'key' => 'I'
+ ),
+ array( 'image' => 'button_link.png',
+ 'id' => 'mw-editbutton-link',
+ 'open' => '[[',
+ 'close' => ']]',
+ 'sample'=> wfMsg('link_sample'),
+ 'tip' => wfMsg('link_tip'),
+ 'key' => 'L'
+ ),
+ array( 'image' => 'button_extlink.png',
+ 'id' => 'mw-editbutton-extlink',
+ 'open' => '[',
+ 'close' => ']',
+ 'sample'=> wfMsg('extlink_sample'),
+ 'tip' => wfMsg('extlink_tip'),
+ 'key' => 'X'
+ ),
+ array( 'image' => 'button_headline.png',
+ 'id' => 'mw-editbutton-headline',
+ 'open' => "\\n== ",
+ 'close' => " ==\\n",
+ 'sample'=> wfMsg('headline_sample'),
+ 'tip' => wfMsg('headline_tip'),
+ 'key' => 'H'
+ ),
+ array( 'image' => 'button_image.png',
+ 'id' => 'mw-editbutton-image',
+ 'open' => '[['.$wgContLang->getNsText(NS_IMAGE).":",
+ 'close' => ']]',
+ 'sample'=> wfMsg('image_sample'),
+ 'tip' => wfMsg('image_tip'),
+ 'key' => 'D'
+ ),
+ array( 'image' => 'button_media.png',
+ 'id' => 'mw-editbutton-media',
+ 'open' => '[['.$wgContLang->getNsText(NS_MEDIA).':',
+ 'close' => ']]',
+ 'sample'=> wfMsg('media_sample'),
+ 'tip' => wfMsg('media_tip'),
+ 'key' => 'M'
+ ),
+ array( 'image' => 'button_math.png',
+ 'id' => 'mw-editbutton-math',
+ 'open' => "<math>",
+ 'close' => "<\\/math>",
+ 'sample'=> wfMsg('math_sample'),
+ 'tip' => wfMsg('math_tip'),
+ 'key' => 'C'
+ ),
+ array( 'image' => 'button_nowiki.png',
+ 'id' => 'mw-editbutton-nowiki',
+ 'open' => "<nowiki>",
+ 'close' => "<\\/nowiki>",
+ 'sample'=> wfMsg('nowiki_sample'),
+ 'tip' => wfMsg('nowiki_tip'),
+ 'key' => 'N'
+ ),
+ array( 'image' => 'button_sig.png',
+ 'id' => 'mw-editbutton-signature',
+ 'open' => '--~~~~',
+ 'close' => '',
+ 'sample'=> '',
+ 'tip' => wfMsg('sig_tip'),
+ 'key' => 'Y'
+ ),
+ array( 'image' => 'button_hr.png',
+ 'id' => 'mw-editbutton-hr',
+ 'open' => "\\n----\\n",
+ 'close' => '',
+ 'sample'=> '',
+ 'tip' => wfMsg('hr_tip'),
+ 'key' => 'R'
+ )
);
$toolbar = "<div id='toolbar'>\n";
$toolbar.="<script type='$wgJsMimeType'>\n/*<![CDATA[*/\n";
foreach($toolarray as $tool) {
+ $cssId = $tool['id'];
$image=$wgStylePath.'/common/images/'.$tool['image'];
$open=$tool['open'];
$close=$tool['close'];
@@ -1742,7 +1730,7 @@ END
#$key = $tool["key"];
- $toolbar.="addButton('$image','$tip','$open','$close','$sample');\n";
+ $toolbar.="addButton('$image','$tip','$open','$close','$sample','$cssId');\n";
}
$toolbar.="/*]]>*/\n</script>";
@@ -1751,6 +1739,127 @@ END
}
/**
+ * Returns an array of html code of the following checkboxes:
+ * minor and watch
+ *
+ * @param $tabindex Current tabindex
+ * @param $skin Skin object
+ * @param $checked Array of checkbox => bool, where bool indicates the checked
+ * status of the checkbox
+ *
+ * @return array
+ */
+ public static function getCheckboxes( &$tabindex, $skin, $checked ) {
+ global $wgUser;
+
+ $checkboxes = array();
+
+ $checkboxes['minor'] = '';
+ $minorLabel = wfMsgExt('minoredit', array('parseinline'));
+ if ( $wgUser->isAllowed('minoredit') ) {
+ $attribs = array(
+ 'tabindex' => ++$tabindex,
+ 'accesskey' => wfMsg( 'accesskey-minoredit' ),
+ 'id' => 'wpMinoredit',
+ );
+ $checkboxes['minor'] =
+ Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
+ "&nbsp;<label for='wpMinoredit'".$skin->tooltipAndAccesskey('minoredit').">{$minorLabel}</label>";
+ }
+
+ $watchLabel = wfMsgExt('watchthis', array('parseinline'));
+ $checkboxes['watch'] = '';
+ if ( $wgUser->isLoggedIn() ) {
+ $attribs = array(
+ 'tabindex' => ++$tabindex,
+ 'accesskey' => wfMsg( 'accesskey-watch' ),
+ 'id' => 'wpWatchthis',
+ );
+ $checkboxes['watch'] =
+ Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
+ "&nbsp;<label for='wpWatchthis'".$skin->tooltipAndAccesskey('watch').">{$watchLabel}</label>";
+ }
+ return $checkboxes;
+ }
+
+ /**
+ * Returns an array of html code of the following buttons:
+ * save, diff, preview and live
+ *
+ * @param $tabindex Current tabindex
+ *
+ * @return array
+ */
+ public function getEditButtons(&$tabindex) {
+ global $wgLivePreview, $wgUser;
+
+ $buttons = array();
+
+ $temp = array(
+ 'id' => 'wpSave',
+ 'name' => 'wpSave',
+ 'type' => 'submit',
+ 'tabindex' => ++$tabindex,
+ 'value' => wfMsg('savearticle'),
+ 'accesskey' => wfMsg('accesskey-save'),
+ 'title' => wfMsg( 'tooltip-save' ).' ['.wfMsg( 'accesskey-save' ).']',
+ );
+ $buttons['save'] = wfElement('input', $temp, '');
+
+ ++$tabindex; // use the same for preview and live preview
+ if ( $wgLivePreview && $wgUser->getOption( 'uselivepreview' ) ) {
+ $temp = array(
+ 'id' => 'wpPreview',
+ 'name' => 'wpPreview',
+ 'type' => 'submit',
+ 'tabindex' => $tabindex,
+ 'value' => wfMsg('showpreview'),
+ 'accesskey' => '',
+ 'title' => wfMsg( 'tooltip-preview' ).' ['.wfMsg( 'accesskey-preview' ).']',
+ 'style' => 'display: none;',
+ );
+ $buttons['preview'] = wfElement('input', $temp, '');
+
+ $temp = array(
+ 'id' => 'wpLivePreview',
+ 'name' => 'wpLivePreview',
+ 'type' => 'submit',
+ 'tabindex' => $tabindex,
+ 'value' => wfMsg('showlivepreview'),
+ 'accesskey' => wfMsg('accesskey-preview'),
+ 'title' => '',
+ 'onclick' => $this->doLivePreviewScript(),
+ );
+ $buttons['live'] = wfElement('input', $temp, '');
+ } else {
+ $temp = array(
+ 'id' => 'wpPreview',
+ 'name' => 'wpPreview',
+ 'type' => 'submit',
+ 'tabindex' => $tabindex,
+ 'value' => wfMsg('showpreview'),
+ 'accesskey' => wfMsg('accesskey-preview'),
+ 'title' => wfMsg( 'tooltip-preview' ).' ['.wfMsg( 'accesskey-preview' ).']',
+ );
+ $buttons['preview'] = wfElement('input', $temp, '');
+ $buttons['live'] = '';
+ }
+
+ $temp = array(
+ 'id' => 'wpDiff',
+ 'name' => 'wpDiff',
+ 'type' => 'submit',
+ 'tabindex' => ++$tabindex,
+ 'value' => wfMsg('showdiff'),
+ 'accesskey' => wfMsg('accesskey-diff'),
+ 'title' => wfMsg( 'tooltip-diff' ).' ['.wfMsg( 'accesskey-diff' ).']',
+ );
+ $buttons['diff'] = wfElement('input', $temp, '');
+
+ return $buttons;
+ }
+
+ /**
* Output preview text only. This can be sucked into the edit page
* via JavaScript, and saves the server time rendering the skin as
* well as theoretically being more robust on the client (doesn't
@@ -1758,8 +1867,8 @@ END
* failure, etc).
*
* @todo This doesn't include category or interlanguage links.
- * Would need to enhance it a bit, maybe wrap them in XML
- * or something... that might also require more skin
+ * Would need to enhance it a bit, <s>maybe wrap them in XML
+ * or something...</s> that might also require more skin
* initialization, so check whether that's a problem.
*/
function livePreview() {
@@ -1767,10 +1876,14 @@ END
$wgOut->disable();
header( 'Content-type: text/xml; charset=utf-8' );
header( 'Cache-control: no-cache' );
- # FIXME
- echo $this->getPreviewText( );
- /* To not shake screen up and down between preview and live-preview */
- echo "<br style=\"clear:both;\" />\n";
+
+ $s =
+ '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
+ Xml::openElement( 'livepreview' ) .
+ Xml::element( 'preview', null, $this->getPreviewText() ) .
+ Xml::element( 'br', array( 'style' => 'clear: both;' ) ) .
+ Xml::closeElement( 'livepreview' );
+ echo $s;
}
diff --git a/includes/Exception.php b/includes/Exception.php
index ad7ec14a..4cf0b7ba 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * MediaWiki exception
+ * @addtogroup Exception
+ */
class MWException extends Exception
{
function useOutputPage() {
@@ -12,6 +16,7 @@ class MWException extends Exception
return is_object( $wgLang );
}
+ /** Get a message from i18n */
function msg( $key, $fallback /*[, params...] */ ) {
$args = array_slice( func_get_args(), 2 );
if ( $this->useMessageCache() ) {
@@ -21,6 +26,7 @@ class MWException extends Exception
}
}
+ /* If wgShowExceptionDetails, return a HTML message with a backtrace to the error. */
function getHTML() {
global $wgShowExceptionDetails;
if( $wgShowExceptionDetails ) {
@@ -33,6 +39,7 @@ class MWException extends Exception
}
}
+ /* If wgShowExceptionDetails, return a text message with a backtrace to the error */
function getText() {
global $wgShowExceptionDetails;
if( $wgShowExceptionDetails ) {
@@ -43,7 +50,8 @@ class MWException extends Exception
"in LocalSettings.php to show detailed debugging information.</p>";
}
}
-
+
+ /* Return titles of this error page */
function getPageTitle() {
if ( $this->useMessageCache() ) {
return wfMsg( 'internalerror' );
@@ -52,7 +60,10 @@ class MWException extends Exception
return "$wgSitename error";
}
}
-
+
+ /** Return the requested URL and point to file and line number from which the
+ * exception occured
+ */
function getLogMessage() {
global $wgRequest;
$file = $this->getFile();
@@ -60,7 +71,8 @@ class MWException extends Exception
$message = $this->getMessage();
return $wgRequest->getRequestURL() . " Exception from line $line of $file: $message";
}
-
+
+ /** Output the exception report using HTML */
function reportHTML() {
global $wgOut;
if ( $this->useOutputPage() ) {
@@ -78,11 +90,15 @@ class MWException extends Exception
echo $this->htmlFooter();
}
}
-
+
+ /** Print the exception report using text */
function reportText() {
echo $this->getText();
}
+ /* Output a report about the exception and takes care of formatting.
+ * It will be either HTML or plain text based on $wgCommandLineMode.
+ */
function report() {
global $wgCommandLineMode;
if ( $wgCommandLineMode ) {
@@ -125,6 +141,7 @@ class MWException extends Exception
/**
* Exception class which takes an HTML error message, and does not
* produce a backtrace. Replacement for OutputPage::fatalError().
+ * @addtogroup Exception
*/
class FatalError extends MWException {
function getHTML() {
@@ -136,6 +153,9 @@ class FatalError extends MWException {
}
}
+/**
+ * @addtogroup Exception
+ */
class ErrorPageError extends MWException {
public $title, $msg;
@@ -203,7 +223,7 @@ function wfReportException( Exception $e ) {
function wfExceptionHandler( $e ) {
global $wgFullyInitialised;
wfReportException( $e );
-
+
// Final cleanup, similar to wfErrorExit()
if ( $wgFullyInitialised ) {
try {
diff --git a/includes/Exif.php b/includes/Exif.php
index 0860d5f7..3a06ca1b 100644
--- a/includes/Exif.php
+++ b/includes/Exif.php
@@ -1,7 +1,6 @@
<?php
/**
- * @package MediaWiki
- * @subpackage Metadata
+ * @addtogroup Media
*
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
* @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
@@ -22,13 +21,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification
- * @bug 1555, 1947
+ * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
*/
/**
- * @package MediaWiki
- * @subpackage Metadata
+ * @todo document (e.g. one-sentence class-overview description)
+ * @addtogroup Media
*/
class Exif {
//@{
@@ -95,9 +93,9 @@ class Exif {
var $basename;
/**
- * The private log to log to
+ * The private log to log to, e.g. 'exif'
*/
- var $log = 'exif';
+ var $log = false;
//@}
@@ -106,7 +104,7 @@ class Exif {
*
* @param $file String: filename.
*/
- function Exif( $file ) {
+ function __construct( $file ) {
/**
* Page numbers here refer to pages in the EXIF 2.2 standard
*
@@ -563,7 +561,10 @@ class Exif {
* @param $fname String:
* @param $action Mixed: , default NULL.
*/
- function debug( $in, $fname, $action = NULL ) {
+ function debug( $in, $fname, $action = NULL ) {
+ if ( !$this->log ) {
+ return;
+ }
$type = gettype( $in );
$class = ucfirst( __CLASS__ );
if ( $type === 'array' )
@@ -588,6 +589,9 @@ class Exif {
* @param $io Boolean: Specify whether we're beginning or ending
*/
function debugFile( $fname, $io ) {
+ if ( !$this->log ) {
+ return;
+ }
$class = ucfirst( __CLASS__ );
if ( $io ) {
wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
@@ -599,8 +603,8 @@ class Exif {
}
/**
- * @package MediaWiki
- * @subpackage Metadata
+ * @todo document (e.g. one-sentence class-overview description)
+ * @addtogroup Media
*/
class FormatExif {
/**
@@ -733,7 +737,7 @@ class FormatExif {
case 'DateTimeDigitized':
if( $val == '0000:00:00 00:00:00' ) {
$tags[$tag] = wfMsg('exif-unknowndate');
- } elseif( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/', $val ) ) {
+ } elseif( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) {
$tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) );
}
break;
diff --git a/includes/Export.php b/includes/Export.php
index b7e0f9a1..9307795d 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -17,16 +17,15 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# http://www.gnu.org/copyleft/gpl.html
+
/**
*
- * @package MediaWiki
- * @subpackage SpecialPage
+ * @addtogroup SpecialPage
*/
-
class WikiExporter {
var $list_authors = false ; # Return distinct author list (when not returning full history)
var $author_list = "" ;
-
+
const FULL = 0;
const CURRENT = 1;
@@ -44,14 +43,14 @@ class WikiExporter {
* main query is still running.
*
* @param Database $db
- * @param mixed $history one of WikiExporter::FULL or WikiExporter::CURRENT, or an
+ * @param mixed $history one of WikiExporter::FULL or WikiExporter::CURRENT, or an
* associative array:
* offset: non-inclusive offset at which to start the query
* limit: maximum number of rows to return
* dir: "asc" or "desc" timestamp order
* @param int $buffer one of WikiExporter::BUFFER or WikiExporter::STREAM
*/
- function WikiExporter( &$db, $history = WikiExporter::CURRENT,
+ function __construct( &$db, $history = WikiExporter::CURRENT,
$buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT ) {
$this->db =& $db;
$this->history = $history;
@@ -140,7 +139,10 @@ class WikiExporter {
$fname = "do_list_authors" ;
wfProfileIn( $fname );
$this->author_list = "<contributors>";
- $sql = "SELECT DISTINCT rev_user_text,rev_user FROM {$page},{$revision} WHERE page_id=rev_page AND " . $cond ;
+ //rev_deleted
+ $nothidden = '(rev_deleted & '.Revision::DELETED_USER.') = 0';
+
+ $sql = "SELECT DISTINCT rev_user_text,rev_user FROM {$page},{$revision} WHERE page_id=rev_page AND $nothidden AND " . $cond ;
$result = $this->db->query( $sql, $fname );
$resultset = $this->db->resultObject( $result );
while( $row = $resultset->fetchObject() ) {
@@ -164,10 +166,10 @@ class WikiExporter {
$page = $this->db->tableName( 'page' );
$revision = $this->db->tableName( 'revision' );
$text = $this->db->tableName( 'text' );
-
+
$order = 'ORDER BY page_id';
$limit = '';
-
+
if( $this->history == WikiExporter::FULL ) {
$join = 'page_id=rev_page';
} elseif( $this->history == WikiExporter::CURRENT ) {
@@ -185,7 +187,7 @@ class WikiExporter {
$order .= ', rev_timestamp DESC';
}
if ( !empty( $this->history['offset'] ) ) {
- $join .= " AND rev_timestamp $op " . $this->db->addQuotes(
+ $join .= " AND rev_timestamp $op " . $this->db->addQuotes(
$this->db->timestamp( $this->history['offset'] ) );
}
if ( !empty( $this->history['limit'] ) ) {
@@ -229,7 +231,7 @@ class WikiExporter {
$result = $this->db->query( $sql, $fname );
$wrapper = $this->db->resultObject( $result );
$this->outputStream( $wrapper );
-
+
if ( $this->list_authors ) {
$this->outputStream( $wrapper );
}
@@ -279,6 +281,9 @@ class WikiExporter {
}
}
+/**
+ * @addtogroup Dump
+ */
class XmlDumpWriter {
/**
@@ -461,6 +466,7 @@ class XmlDumpWriter {
/**
* Base class for output stream; prints to stdout or buffer or whereever.
+ * @addtogroup Dump
*/
class DumpOutput {
function writeOpenStream( $string ) {
@@ -494,6 +500,7 @@ class DumpOutput {
/**
* Stream outputter to send data to a file.
+ * @addtogroup Dump
*/
class DumpFileOutput extends DumpOutput {
var $handle;
@@ -511,6 +518,7 @@ class DumpFileOutput extends DumpOutput {
* Stream outputter to send data to a file via some filter program.
* Even if compression is available in a library, using a separate
* program can allow us to make use of a multi-processor system.
+ * @addtogroup Dump
*/
class DumpPipeOutput extends DumpFileOutput {
function DumpPipeOutput( $command, $file = null ) {
@@ -523,6 +531,7 @@ class DumpPipeOutput extends DumpFileOutput {
/**
* Sends dump output via the gzip compressor.
+ * @addtogroup Dump
*/
class DumpGZipOutput extends DumpPipeOutput {
function DumpGZipOutput( $file ) {
@@ -532,6 +541,7 @@ class DumpGZipOutput extends DumpPipeOutput {
/**
* Sends dump output via the bgzip2 compressor.
+ * @addtogroup Dump
*/
class DumpBZip2Output extends DumpPipeOutput {
function DumpBZip2Output( $file ) {
@@ -541,6 +551,7 @@ class DumpBZip2Output extends DumpPipeOutput {
/**
* Sends dump output via the p7zip compressor.
+ * @addtogroup Dump
*/
class Dump7ZipOutput extends DumpPipeOutput {
function Dump7ZipOutput( $file ) {
@@ -558,6 +569,7 @@ class Dump7ZipOutput extends DumpPipeOutput {
* Dump output filter class.
* This just does output filtering and streaming; XML formatting is done
* higher up, so be careful in what you do.
+ * @addtogroup Dump
*/
class DumpFilter {
function DumpFilter( &$sink ) {
@@ -603,6 +615,7 @@ class DumpFilter {
/**
* Simple dump output filter to exclude all talk pages.
+ * @addtogroup Dump
*/
class DumpNotalkFilter extends DumpFilter {
function pass( $page ) {
@@ -612,6 +625,7 @@ class DumpNotalkFilter extends DumpFilter {
/**
* Dump output filter to include or exclude pages in a given set of namespaces.
+ * @addtogroup Dump
*/
class DumpNamespaceFilter extends DumpFilter {
var $invert = false;
@@ -666,6 +680,7 @@ class DumpNamespaceFilter extends DumpFilter {
/**
* Dump output filter to include only the last revision in each page sequence.
+ * @addtogroup Dump
*/
class DumpLatestFilter extends DumpFilter {
var $page, $pageString, $rev, $revString;
@@ -697,6 +712,7 @@ class DumpLatestFilter extends DumpFilter {
/**
* Base class for output stream; prints to stdout or buffer or whereever.
+ * @addtogroup Dump
*/
class DumpMultiWriter {
function DumpMultiWriter( $sinks ) {
diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php
index 14b55fdb..c8ed8bde 100644
--- a/includes/ExternalEdit.php
+++ b/includes/ExternalEdit.php
@@ -3,12 +3,10 @@
* License: Public domain
*
* @author Erik Moeller <moeller@scireview.de>
- * @package MediaWiki
*/
/**
*
- * @package MediaWiki
*
* Support for external editors to modify both text and files
* in external applications. It works as follows: MediaWiki
@@ -22,7 +20,7 @@
class ExternalEdit {
- function ExternalEdit ( $article, $mode ) {
+ function __construct( $article, $mode ) {
global $wgInputEncoding;
$this->mArticle =& $article;
$this->mTitle =& $article->mTitle;
diff --git a/includes/ExternalStore.php b/includes/ExternalStore.php
index 79f1a528..fb66b652 100644
--- a/includes/ExternalStore.php
+++ b/includes/ExternalStore.php
@@ -1,7 +1,6 @@
<?php
/**
*
- * @package MediaWiki
*
* Constructor class for data kept in external repositories
*
diff --git a/includes/ExternalStoreDB.php b/includes/ExternalStoreDB.php
index 861a9939..7b4ffc2f 100644
--- a/includes/ExternalStoreDB.php
+++ b/includes/ExternalStoreDB.php
@@ -1,14 +1,12 @@
<?php
/**
*
- * @package MediaWiki
*
* DB accessable external objects
*
*/
-/** @package MediaWiki */
/**
* External database storage will use one (or more) separate connection pools
diff --git a/includes/ExternalStoreHttp.php b/includes/ExternalStoreHttp.php
index daf62cc4..e6656986 100644
--- a/includes/ExternalStoreHttp.php
+++ b/includes/ExternalStoreHttp.php
@@ -1,7 +1,6 @@
<?php
/**
*
- * @package MediaWiki
*
* Example class for HTTP accessable external objects
*
diff --git a/includes/FakeTitle.php b/includes/FakeTitle.php
index ae05385a..293bdaf0 100644
--- a/includes/FakeTitle.php
+++ b/includes/FakeTitle.php
@@ -14,7 +14,6 @@ class FakeTitle {
function getInterwikiCached() { $this->error(); }
function isLocal() { $this->error(); }
function isTrans() { $this->error(); }
- function touchArray( $titles, $timestamp = '' ) { $this->error(); }
function getText() { $this->error(); }
function getPartialURL() { $this->error(); }
function getDBkey() { $this->error(); }
@@ -41,6 +40,7 @@ class FakeTitle {
function isProtected() { $this->error(); }
function userIsWatching() { $this->error(); }
function userCan() { $this->error(); }
+ function userCanCreate() { $this->error(); }
function userCanEdit() { $this->error(); }
function userCanMove() { $this->error(); }
function isMovable() { $this->error(); }
@@ -71,7 +71,6 @@ class FakeTitle {
function moveOverExistingRedirect() { $this->error(); }
function moveToNewTitle() { $this->error(); }
function isValidMoveTarget() { $this->error(); }
- function createRedirect() { $this->error(); }
function getParentCategories() { $this->error(); }
function getParentCategoryTree() { $this->error(); }
function pageCond() { $this->error(); }
diff --git a/includes/Feed.php b/includes/Feed.php
index 5c14865d..ed4343c3 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -1,6 +1,5 @@
<?php
-# Basic support for outputting syndication feeds in RSS, other formats
-#
+
# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
# http://www.mediawiki.org/
#
@@ -20,15 +19,13 @@
# http://www.gnu.org/copyleft/gpl.html
/**
+ * Basic support for outputting syndication feeds in RSS, other formats.
* Contain a feed class as well as classes to build rss / atom ... feeds
* Available feeds are defined in Defines.php
- * @package MediaWiki
*/
-
/**
- * @todo document
- * @package MediaWiki
+ * A base class for basic support for outputting syndication feeds in RSS and other formats.
*/
class FeedItem {
/**#@+
@@ -45,7 +42,7 @@ class FeedItem {
/**#@+
* @todo document
*/
- function FeedItem( $Title, $Description, $Url, $Date = '', $Author = '', $Comments = '' ) {
+ function __construct( $Title, $Description, $Url, $Date = '', $Author = '', $Comments = '' ) {
$this->Title = $Title;
$this->Description = $Description;
$this->Url = $Url;
@@ -77,8 +74,7 @@ class FeedItem {
}
/**
- * @todo document
- * @package MediaWiki
+ * @todo document (needs one-sentence top-level class description).
*/
class ChannelFeed extends FeedItem {
/**#@+
@@ -160,8 +156,6 @@ class ChannelFeed extends FeedItem {
/**
* Generate a RSS feed
- * @todo document
- * @package MediaWiki
*/
class RSSFeed extends ChannelFeed {
@@ -221,8 +215,6 @@ class RSSFeed extends ChannelFeed {
/**
* Generate an Atom feed
- * @todo document
- * @package MediaWiki
*/
class AtomFeed extends ChannelFeed {
/**
diff --git a/includes/FileStore.php b/includes/FileStore.php
index 1fd35b01..dcec71c5 100644
--- a/includes/FileStore.php
+++ b/includes/FileStore.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @todo document (needs one-sentence top-level class description).
+ */
class FileStore {
const DELETE_ORIGINAL = 1;
@@ -33,7 +36,7 @@ class FileStore {
* suffer an uncaught error the lock will be released when the
* connection is closed.
*
- * @fixme Probably only works on MySQL. Abstract to the Database class?
+ * @todo Probably only works on MySQL. Abstract to the Database class?
*/
static function lock() {
global $wgDBtype;
@@ -106,7 +109,7 @@ class FileStore {
private function copyFile( $sourcePath, $destPath, $flags=0 ) {
if( !file_exists( $sourcePath ) ) {
// Abort! Abort!
- throw new FSException( "missing source file '$sourcePath'\n" );
+ throw new FSException( "missing source file '$sourcePath'" );
}
$transaction = new FSTransaction();
@@ -125,7 +128,7 @@ class FileStore {
if( !$ok ) {
throw new FSException(
- "failed to create directory for '$destPath'\n" );
+ "failed to create directory for '$destPath'" );
}
}
@@ -138,7 +141,7 @@ class FileStore {
$transaction->addRollback( FSTransaction::DELETE_FILE, $destPath );
} else {
throw new FSException(
- __METHOD__." failed to copy '$sourcePath' to '$destPath'\n" );
+ __METHOD__." failed to copy '$sourcePath' to '$destPath'" );
}
}
@@ -175,7 +178,7 @@ class FileStore {
* @throws FSException if file can't be deleted
* @return FSTransaction
*
- * @fixme Might be worth preliminary permissions check
+ * @todo Might be worth preliminary permissions check
*/
static function deleteFile( $path ) {
if( file_exists( $path ) ) {
@@ -368,6 +371,9 @@ class FSTransaction {
}
}
+/**
+ * @addtogroup Exception
+ */
class FSException extends MWException { }
?>
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index de07b321..1ffde741 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -1,8 +1,11 @@
<?php
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( "This file is part of MediaWiki, it is not a valid entry point" );
+}
+
/**
* Global functions used everywhere
- * @package MediaWiki
*/
/**
@@ -19,9 +22,9 @@ $wgTotalViews = -1;
$wgTotalEdits = -1;
-require_once( 'LogPage.php' );
-require_once( 'normal/UtfNormalUtil.php' );
-require_once( 'XmlFunctions.php' );
+require_once dirname(__FILE__) . '/LogPage.php';
+require_once dirname(__FILE__) . '/normal/UtfNormalUtil.php';
+require_once dirname(__FILE__) . '/XmlFunctions.php';
/**
* Compatibility functions
@@ -57,6 +60,30 @@ if ( !function_exists( 'mb_substr' ) ) {
}
}
+if ( !function_exists( 'mb_strlen' ) ) {
+ /**
+ * Fallback implementation of mb_strlen, hardcoded to UTF-8.
+ * @param string $str
+ * @param string $enc optional encoding; ignored
+ * @return int
+ */
+ function mb_strlen( $str, $enc="" ) {
+ $counts = count_chars( $str );
+ $total = 0;
+
+ // Count ASCII bytes
+ for( $i = 0; $i < 0x80; $i++ ) {
+ $total += $counts[$i];
+ }
+
+ // Count multibyte sequence heads
+ for( $i = 0xc0; $i < 0xff; $i++ ) {
+ $total += $counts[$i];
+ }
+ return $total;
+ }
+}
+
if ( !function_exists( 'array_diff_key' ) ) {
/**
* Exists in PHP 5.1.0+
@@ -168,7 +195,7 @@ function wfDebug( $text, $logonly = false ) {
# Strip unprintables; they can switch terminal modes when binary data
# gets dumped, which is pretty annoying.
$text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
- @error_log( $text, 3, $wgDebugLogFile );
+ wfErrorLog( $text, $wgDebugLogFile );
}
}
@@ -187,7 +214,7 @@ function wfDebugLog( $logGroup, $text, $public = true ) {
if( isset( $wgDebugLogGroups[$logGroup] ) ) {
$time = wfTimestamp( TS_DB );
$wiki = wfWikiID();
- @error_log( "$time $wiki: $text", 3, $wgDebugLogGroups[$logGroup] );
+ wfErrorLog( "$time $wiki: $text", $wgDebugLogGroups[$logGroup] );
} else if ( $public === true ) {
wfDebug( $text, true );
}
@@ -198,15 +225,28 @@ function wfDebugLog( $logGroup, $text, $public = true ) {
* @param $text String: database error message.
*/
function wfLogDBError( $text ) {
- global $wgDBerrorLog;
+ global $wgDBerrorLog, $wgDBname;
if ( $wgDBerrorLog ) {
$host = trim(`hostname`);
- $text = date('D M j G:i:s T Y') . "\t$host\t".$text;
- error_log( $text, 3, $wgDBerrorLog );
+ $text = date('D M j G:i:s T Y') . "\t$host\t$wgDBname\t$text";
+ wfErrorLog( $text, $wgDBerrorLog );
}
}
/**
+ * Log to a file without getting "file size exceeded" signals
+ */
+function wfErrorLog( $text, $file ) {
+ wfSuppressWarnings();
+ $exists = file_exists( $file );
+ $size = $exists ? filesize( $file ) : false;
+ if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
+ error_log( $text, 3, $file );
+ }
+ wfRestoreWarnings();
+}
+
+/**
* @todo document
*/
function wfLogProfilingData() {
@@ -232,7 +272,7 @@ function wfLogProfilingData() {
gmdate( 'YmdHis' ), $elapsed,
urldecode( $wgRequest->getRequestURL() . $forward ) );
if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) {
- error_log( $log . $prof, 3, $wgDebugLogFile );
+ wfErrorLog( $log . $prof, $wgDebugLogFile );
}
}
}
@@ -376,8 +416,11 @@ function wfMsgNoDBForContent( $key ) {
* @return String: the requested message.
*/
function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform = true ) {
+ $fname = 'wfMsgReal';
+ wfProfileIn( $fname );
$message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
$message = wfMsgReplaceArgs( $message, $args );
+ wfProfileOut( $fname );
return $message;
}
@@ -515,11 +558,11 @@ function wfMsgWikiHtml( $key ) {
* Returns message in the requested format
* @param string $key Key of the message
* @param array $options Processing rules:
- * <i>parse<i>: parses wikitext to html
- * <i>parseinline<i>: parses wikitext to html and removes the surrounding p's added by parser or tidy
- * <i>escape<i>: filters message trough htmlspecialchars
- * <i>replaceafter<i>: parameters are substituted after parsing or escaping
- * <i>parsemag<i>: ??
+ * <i>parse</i>: parses wikitext to html
+ * <i>parseinline</i>: parses wikitext to html and removes the surrounding p's added by parser or tidy
+ * <i>escape</i>: filters message trough htmlspecialchars
+ * <i>replaceafter</i>: parameters are substituted after parsing or escaping
+ * <i>parsemag</i>: transform the message using magic phrases
*/
function wfMsgExt( $key, $options ) {
global $wgOut, $wgParser;
@@ -569,7 +612,7 @@ function wfMsgExt( $key, $options ) {
* Just like exit() but makes a note of it.
* Commits open transactions except if the error parameter is set
*
- * @obsolete Please return control to the caller or throw an exception
+ * @deprecated Please return control to the caller or throw an exception
*/
function wfAbruptExit( $error = false ){
global $wgLoadBalancer;
@@ -599,7 +642,7 @@ function wfAbruptExit( $error = false ){
}
/**
- * @obsolete Please return control the caller or throw an exception
+ * @deprecated Please return control the caller or throw an exception
*/
function wfErrorExit() {
wfAbruptExit( true );
@@ -736,7 +779,7 @@ function wfBacktrace() {
*/
function wfShowingResults( $offset, $limit ) {
global $wgLang;
- return wfMsg( 'showingresults', $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) );
+ return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) );
}
/**
@@ -744,7 +787,7 @@ function wfShowingResults( $offset, $limit ) {
*/
function wfShowingResultsNum( $offset, $limit, $num ) {
global $wgLang;
- return wfMsg( 'showingresultsnum', $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
+ return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
}
/**
@@ -941,6 +984,26 @@ function wfArrayToCGI( $array1, $array2 = NULL )
}
/**
+ * Append a query string to an existing URL, which may or may not already
+ * have query string parameters already. If so, they will be combined.
+ *
+ * @param string $url
+ * @param string $query
+ * @return string
+ */
+function wfAppendQuery( $url, $query ) {
+ if( $query != '' ) {
+ if( false === strpos( $url, '?' ) ) {
+ $url .= '?';
+ } else {
+ $url .= '&';
+ }
+ $url .= $query;
+ }
+ return $url;
+}
+
+/**
* This is obsolete, use SquidUpdate::purge()
* @deprecated
*/
@@ -1104,9 +1167,15 @@ function wfHttpError( $code, $label, $desc ) {
* Note that some PHP configuration options may add output buffer
* layers which cannot be removed; these are left in place.
*
- * @parameter bool $resetGzipEncoding
+ * @param bool $resetGzipEncoding
*/
function wfResetOutputBuffers( $resetGzipEncoding=true ) {
+ if( $resetGzipEncoding ) {
+ // Suppress Content-Encoding and Content-Length
+ // headers from 1.10+s wfOutputHandler
+ global $wgDisableOutputCompression;
+ $wgDisableOutputCompression = true;
+ }
while( $status = ob_get_status() ) {
if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
// Probably from zlib.output_compression or other
@@ -1332,7 +1401,7 @@ define('TS_ISO_8601', 4);
/**
* An Exif timestamp (YYYY:MM:DD HH:MM:SS)
*
- * @url http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
+ * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
* DateTime tag and page 36 for the DateTimeOriginal and
* DateTimeDigitized tags.
*/
@@ -1624,6 +1693,7 @@ function wfMkdirParents( $fullDir, $mode = 0777 ) {
foreach ( $createList as $dir ) {
# use chmod to override the umask, as suggested by the PHP manual
if ( !mkdir( $dir, $mode ) || !chmod( $dir, $mode ) ) {
+ wfDebugLog( 'mkdir', "Unable to create directory $dir\n" );
return false;
}
}
@@ -1750,14 +1820,14 @@ function wfShellExec( $cmd, &$retval=null ) {
}
if ( php_uname( 's' ) == 'Linux' ) {
- $time = ini_get( 'max_execution_time' );
+ $time = intval( ini_get( 'max_execution_time' ) );
$mem = intval( $wgMaxShellMemory );
$filesize = intval( $wgMaxShellFileSize );
if ( $time > 0 && $mem > 0 ) {
- $script = "$IP/bin/ulimit-tvf.sh";
+ $script = "$IP/bin/ulimit4.sh";
if ( is_executable( $script ) ) {
- $cmd = escapeshellarg( $script ) . " $time $mem $filesize $cmd";
+ $cmd = escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
}
}
} elseif ( php_uname( 's' ) == 'Windows NT' ) {
@@ -1844,23 +1914,83 @@ function wfBaseName( $path ) {
}
/**
+ * Generate a relative path name to the given file.
+ * May explode on non-matching case-insensitive paths,
+ * funky symlinks, etc.
+ *
+ * @param string $path Absolute destination path including target filename
+ * @param string $from Absolute source path, directory only
+ * @return string
+ */
+function wfRelativePath( $path, $from ) {
+ // Normalize mixed input on Windows...
+ $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
+ $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
+
+ $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
+ $against = explode( DIRECTORY_SEPARATOR, $from );
+
+ // Trim off common prefix
+ while( count( $pieces ) && count( $against )
+ && $pieces[0] == $against[0] ) {
+ array_shift( $pieces );
+ array_shift( $against );
+ }
+
+ // relative dots to bump us to the parent
+ while( count( $against ) ) {
+ array_unshift( $pieces, '..' );
+ array_shift( $against );
+ }
+
+ array_push( $pieces, wfBaseName( $path ) );
+
+ return implode( DIRECTORY_SEPARATOR, $pieces );
+}
+
+/**
* Make a URL index, appropriate for the el_index field of externallinks.
*/
function wfMakeUrlIndex( $url ) {
- wfSuppressWarnings();
+ global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
$bits = parse_url( $url );
+ wfSuppressWarnings();
wfRestoreWarnings();
- if ( !$bits || $bits['scheme'] !== 'http' ) {
+ if ( !$bits ) {
return false;
}
+ // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
+ $delimiter = '';
+ if ( in_array( $bits['scheme'] . '://' , $wgUrlProtocols ) ) {
+ $delimiter = '://';
+ } elseif ( in_array( $bits['scheme'] .':' , $wgUrlProtocols ) ) {
+ $delimiter = ':';
+ // parse_url detects for news: and mailto: the host part of an url as path
+ // We have to correct this wrong detection
+ if ( isset ( $bits['path'] ) ) {
+ $bits['host'] = $bits['path'];
+ $bits['path'] = '';
+ }
+ } else {
+ return false;
+ }
+
// Reverse the labels in the hostname, convert to lower case
- $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
+ // For emails reverse domainpart only
+ if ( $bits['scheme'] == 'mailto' ) {
+ $mailparts = explode( '@', $bits['host'] );
+ $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
+ $reversedHost = $domainpart . '@' . $mailparts[0];
+ } else {
+ $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
+ }
// Add an extra dot to the end
if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
$reversedHost .= '.';
}
// Reconstruct the pseudo-URL
- $index = "http://$reversedHost";
+ $prot = $bits['scheme'];
+ $index = "$prot$delimiter$reversedHost";
// Leave out user and password. Add the port, path, query and fragment
if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port'];
if ( isset( $bits['path'] ) ) {
@@ -1908,9 +2038,11 @@ function wfExplodeMarkup( $separator, $text ) {
* @param $sourceBase int 2-36
* @param $destBase int 2-36
* @param $pad int 1 or greater
+ * @param $lowercase bool
* @return string or false on invalid input
*/
-function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1 ) {
+function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true ) {
+ $input = strval( $input );
if( $sourceBase < 2 ||
$sourceBase > 36 ||
$destBase < 2 ||
@@ -1923,8 +2055,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1 ) {
$input == '' ) {
return false;
}
-
- $digitChars = '0123456789abcdefghijklmnopqrstuvwxyz';
+ $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$inDigits = array();
$outChars = '';
@@ -2024,7 +2155,7 @@ function wfIsLocalURL( $url ) {
* Initialise php session
*/
function wfSetupSession() {
- global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
+ global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain, $wgCookieSecure;
if( $wgSessionsInMemcached ) {
require_once( 'MemcachedSessions.php' );
} elseif( 'files' != ini_get( 'session.save_handler' ) ) {
@@ -2032,7 +2163,7 @@ function wfSetupSession() {
# application, it will end up failing. Try to recover.
ini_set ( 'session.save_handler', 'files' );
}
- session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure);
session_cache_limiter( 'private, must-revalidate' );
@session_start();
}
diff --git a/includes/HTMLCacheUpdate.php b/includes/HTMLCacheUpdate.php
index bda4720d..9a0b6a08 100644
--- a/includes/HTMLCacheUpdate.php
+++ b/includes/HTMLCacheUpdate.php
@@ -38,7 +38,7 @@ class HTMLCacheUpdate
function doUpdate() {
# Fetch the IDs
$cond = $this->getToCondition();
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( $this->mTable, $this->getFromField(), $cond, __METHOD__ );
$resWrap = new ResultWrapper( $dbr, $res );
if ( $dbr->numRows( $res ) != 0 ) {
@@ -136,7 +136,7 @@ class HTMLCacheUpdate
return;
}
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$timestamp = $dbw->timestamp();
$done = false;
@@ -184,6 +184,9 @@ class HTMLCacheUpdate
}
}
+/**
+ * @todo document (e.g. one-sentence top-level class description).
+ */
class HTMLCacheUpdateJob extends Job {
var $table, $start, $end;
@@ -218,7 +221,7 @@ class HTMLCacheUpdateJob extends Job {
$conds[] = "$fromField <= {$this->end}";
}
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( $this->table, $fromField, $conds, __METHOD__ );
$update->invalidateIDs( new ResultWrapper( $dbr, $res ) );
$dbr->freeResult( $res );
diff --git a/includes/HTMLFileCache.php b/includes/HTMLFileCache.php
index d85a4411..1d3778b2 100644
--- a/includes/HTMLFileCache.php
+++ b/includes/HTMLFileCache.php
@@ -1,8 +1,7 @@
<?php
/**
* Contain the HTMLFileCache class
- * @package MediaWiki
- * @subpackage Cache
+ * @addtogroup Cache
*/
/**
@@ -16,7 +15,6 @@
* $wgUseFileCache
* $wgFileCacheDirectory
* $wgUseGzip
- * @package MediaWiki
*/
class HTMLFileCache {
var $mTitle, $mFileCache;
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
index 189e5c79..715c8c88 100644
--- a/includes/HTMLForm.php
+++ b/includes/HTMLForm.php
@@ -2,13 +2,11 @@
/**
* This file contain a class to easily build HTML forms as well as custom
* functions used by SpecialUserrights.php
- * @package MediaWiki
*/
/**
* Class to build various forms
*
- * @package MediaWiki
* @author jeluf, hashar
*/
class HTMLForm {
@@ -125,6 +123,7 @@ class HTMLForm {
function HTMLSelectGroups($selectname, $selectmsg, $selected=array(), $multiple=false, $size=6, $reverse=false) {
$groups = User::getAllGroups();
$out = htmlspecialchars( wfMsg( $selectmsg ) );
+ $out .= "<br />";
if( $multiple ) {
$attribs = array(
@@ -134,6 +133,7 @@ function HTMLSelectGroups($selectname, $selectmsg, $selected=array(), $multiple=
} else {
$attribs = array( 'name' => $selectname );
}
+ $attribs['style'] = 'width: 100%';
$out .= wfElement( 'select', $attribs, null );
foreach( $groups as $group ) {
diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php
index a06b620d..9dfd6d61 100644
--- a/includes/HistoryBlob.php
+++ b/includes/HistoryBlob.php
@@ -1,82 +1,86 @@
<?php
/**
*
- * @package MediaWiki
*/
/**
* Pure virtual parent
- * @package MediaWiki
+ * @todo document (needs a one-sentence top-level class description, that answers the question: "what is a HistoryBlob?")
*/
-class HistoryBlob
+interface HistoryBlob
{
/**
* setMeta and getMeta currently aren't used for anything, I just thought
* they might be useful in the future.
* @param $meta String: a single string.
*/
- function setMeta( $meta ) {}
+ public function setMeta( $meta );
/**
* setMeta and getMeta currently aren't used for anything, I just thought
* they might be useful in the future.
* Gets the meta-value
*/
- function getMeta() {}
+ public function getMeta();
/**
* Adds an item of text, returns a stub object which points to the item.
* You must call setLocation() on the stub object before storing it to the
* database
*/
- function addItem() {}
+ public function addItem( $text );
/**
* Get item by hash
*/
- function getItem( $hash ) {}
+ public function getItem( $hash );
# Set the "default text"
# This concept is an odd property of the current DB schema, whereby each text item has a revision
# associated with it. The default text is the text of the associated revision. There may, however,
# be other revisions in the same object
- function setText() {}
+ public function setText( $text );
/**
* Get default text. This is called from Revision::getRevisionText()
*/
- function getText() {}
+ function getText();
}
/**
* The real object
- * @package MediaWiki
+ * @todo document (needs one-sentence top-level class description + function descriptions).
*/
-class ConcatenatedGzipHistoryBlob extends HistoryBlob
+class ConcatenatedGzipHistoryBlob implements HistoryBlob
{
- /* private */ var $mVersion = 0, $mCompressed = false, $mItems = array(), $mDefaultHash = '';
- /* private */ var $mFast = 0, $mSize = 0;
+ public $mVersion = 0, $mCompressed = false, $mItems = array(), $mDefaultHash = '';
+ public $mFast = 0, $mSize = 0;
- function ConcatenatedGzipHistoryBlob() {
+ /** Constructor */
+ public function ConcatenatedGzipHistoryBlob() {
if ( !function_exists( 'gzdeflate' ) ) {
throw new MWException( "Need zlib support to read or write this kind of history object (ConcatenatedGzipHistoryBlob)\n" );
}
}
+ #
+ # HistoryBlob implementation:
+ #
+
/** @todo document */
- function setMeta( $metaData ) {
+ public function setMeta( $metaData ) {
$this->uncompress();
$this->mItems['meta'] = $metaData;
}
/** @todo document */
- function getMeta() {
+ public function getMeta() {
$this->uncompress();
return $this->mItems['meta'];
}
/** @todo document */
- function addItem( $text ) {
+ public function addItem( $text ) {
$this->uncompress();
$hash = md5( $text );
$this->mItems[$hash] = $text;
@@ -87,7 +91,7 @@ class ConcatenatedGzipHistoryBlob extends HistoryBlob
}
/** @todo document */
- function getItem( $hash ) {
+ public function getItem( $hash ) {
$this->uncompress();
if ( array_key_exists( $hash, $this->mItems ) ) {
return $this->mItems[$hash];
@@ -97,13 +101,29 @@ class ConcatenatedGzipHistoryBlob extends HistoryBlob
}
/** @todo document */
- function removeItem( $hash ) {
+ public function setText( $text ) {
+ $this->uncompress();
+ $stub = $this->addItem( $text );
+ $this->mDefaultHash = $stub->mHash;
+ }
+
+ /** @todo document */
+ public function getText() {
+ $this->uncompress();
+ return $this->getItem( $this->mDefaultHash );
+ }
+
+ # HistoryBlob implemented.
+
+
+ /** @todo document */
+ public function removeItem( $hash ) {
$this->mSize -= strlen( $this->mItems[$hash] );
unset( $this->mItems[$hash] );
}
/** @todo document */
- function compress() {
+ public function compress() {
if ( !$this->mCompressed ) {
$this->mItems = gzdeflate( serialize( $this->mItems ) );
$this->mCompressed = true;
@@ -111,25 +131,13 @@ class ConcatenatedGzipHistoryBlob extends HistoryBlob
}
/** @todo document */
- function uncompress() {
+ public function uncompress() {
if ( $this->mCompressed ) {
$this->mItems = unserialize( gzinflate( $this->mItems ) );
$this->mCompressed = false;
}
}
- /** @todo document */
- function getText() {
- $this->uncompress();
- return $this->getItem( $this->mDefaultHash );
- }
-
- /** @todo document */
- function setText( $text ) {
- $this->uncompress();
- $stub = $this->addItem( $text );
- $this->mDefaultHash = $stub->mHash;
- }
/** @todo document */
function __sleep() {
@@ -145,7 +153,7 @@ class ConcatenatedGzipHistoryBlob extends HistoryBlob
/**
* Determines if this object is happy
*/
- function isHappy( $maxFactor, $factorThreshold ) {
+ public function isHappy( $maxFactor, $factorThreshold ) {
if ( count( $this->mItems ) == 0 ) {
return true;
}
@@ -179,7 +187,7 @@ $wgBlobCache = array();
/**
- * @package MediaWiki
+ * @todo document (needs one-sentence top-level class description + some function descriptions).
*/
class HistoryBlobStub {
var $mOldId, $mHash, $mRef;
@@ -197,28 +205,28 @@ class HistoryBlobStub {
$this->mOldId = $id;
}
- /**
- * Sets the location (old_id) of the referring object
- */
+ /**
+ * Sets the location (old_id) of the referring object
+ */
function setReferrer( $id ) {
$this->mRef = $id;
}
- /**
- * Gets the location of the referring object
- */
+ /**
+ * Gets the location of the referring object
+ */
function getReferrer() {
return $this->mRef;
}
/** @todo document */
function getText() {
- $fname = 'HistoryBlob::getText';
+ $fname = 'HistoryBlobStub::getText';
global $wgBlobCache;
if( isset( $wgBlobCache[$this->mOldId] ) ) {
$obj = $wgBlobCache[$this->mOldId];
} else {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'text', array( 'old_flags', 'old_text' ), array( 'old_id' => $this->mOldId ) );
if( !$row ) {
return false;
@@ -273,8 +281,6 @@ class HistoryBlobStub {
*
* Serialized HistoryBlobCurStub objects will be inserted into the text table
* on conversion if $wgFastSchemaUpgrades is set to true.
- *
- * @package MediaWiki
*/
class HistoryBlobCurStub {
var $mCurId;
@@ -294,7 +300,7 @@ class HistoryBlobCurStub {
/** @todo document */
function getText() {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'cur', array( 'cur_text' ), array( 'cur_id' => $this->mCurId ) );
if( !$row ) {
return false;
diff --git a/includes/Hooks.php b/includes/Hooks.php
index 2eecfd72..b428b08d 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -18,7 +18,6 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @author Evan Prodromou <evan@wikitravel.org>
- * @package MediaWiki
* @see hooks.txt
*/
@@ -103,7 +102,7 @@ function wfRunHooks($event, $args = null) {
if ( isset( $object ) ) {
$func = get_class( $object ) . '::' . $method;
$callback = array( $object, $method );
- } elseif ( false !== ( $pos = strpos( '::', $func ) ) ) {
+ } elseif ( false !== ( $pos = strpos( $func, '::' ) ) ) {
$callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) );
} else {
$callback = $func;
diff --git a/includes/IP.php b/includes/IP.php
index edf4af7a..8a2756c9 100644
--- a/includes/IP.php
+++ b/includes/IP.php
@@ -1,8 +1,5 @@
<?php
/*
- * Collection of public static functions to play with IP address
- * and IP blocks.
- *
* @Author "Ashar Voultoiz" <hashar@altern.org>
* @License GPL v2 or later
*/
@@ -12,22 +9,231 @@
// An IP is made of 4 bytes from x00 to xFF which is d0 to d255
define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])');
define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
-// An IP block is an IP address and a prefix (d1 to d32)
+// An IPv4 block is an IP address and a prefix (d1 to d32)
define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)');
define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX);
// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
+// An IPv6 block is an IP address and a prefix (d1 to d128)
+define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)');
+// An IPv6 IP is made up of 8 octets. However abbreviations like "::" can be used. This is lax!
+define( 'RE_IPV6_ADD', '(:(:' . RE_IPV6_WORD . '){1,7}|' . RE_IPV6_WORD . '(:{1,2}' . RE_IPV6_WORD . '|::$){1,7})' );
+define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
+// This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network
+define( 'IP_ADDRESS_STRING', RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)|' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)');
+/**
+ * A collection of public static functions to play with IP address
+ * and IP blocks.
+ */
class IP {
+ /**
+ * Given a string, determine if it as valid IP
+ * Unlike isValid(), this looks for networks too
+ * @param $ip IP address.
+ * @return string
+ */
+ public static function isIPAddress( $ip ) {
+ if ( !$ip ) return false;
+ if ( is_array( $ip ) ) {
+ throw new MWException( "invalid value passed to " . __METHOD__ );
+ }
+ // IPv6 IPs with two "::" strings are ambiguous and thus invalid
+ return preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip) && ( substr_count($ip, '::') < 2 );
+ }
+
+ public static function isIPv6( $ip ) {
+ if ( !$ip ) return false;
+ if( is_array( $ip ) ) {
+ throw new MWException( "invalid value passed to " . __METHOD__ );
+ }
+ // IPv6 IPs with two "::" strings are ambiguous and thus invalid
+ return preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip) && ( substr_count($ip, '::') < 2);
+ }
+
+ public static function isIPv4( $ip ) {
+ if ( !$ip ) return false;
+ return preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip);
+ }
+
+ /**
+ * Given an IP address in dotted-quad notation, returns an IPv6 octet.
+ * See http://www.answers.com/topic/ipv4-compatible-address
+ * IPs with the first 92 bits as zeros are reserved from IPv6
+ * @param $ip quad-dotted IP address.
+ * @return string
+ */
+ public static function IPv4toIPv6( $ip ) {
+ if ( !$ip ) return null;
+ // Convert only if needed
+ if ( self::isIPv6( $ip ) ) return $ip;
+ // IPv4 CIDRs
+ if ( strpos( $ip, '/' ) !== false ) {
+ $parts = explode( '/', $ip, 2 );
+ if ( count( $parts ) != 2 ) {
+ return false;
+ }
+ $network = self::toUnsigned( $parts[0] );
+ if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
+ $bits = $parts[1] + 96;
+ return self::toOctet( $network ) . "/$bits";
+ } else {
+ return false;
+ }
+ }
+ return self::toOctet( self::toUnsigned( $ip ) );
+ }
/**
+ * Given an IPv6 address in octet notation, returns an unsigned integer.
+ * @param $ip octet ipv6 IP address.
+ * @return string
+ */
+ public static function toUnsigned6( $ip ) {
+ if ( !$ip ) return null;
+ $ip = explode(':', self::sanitizeIP( $ip ) );
+ $r_ip = '';
+ foreach ($ip as $v) {
+ $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
+ }
+ $r_ip = wfBaseConvert( $r_ip, 16, 10 );
+ return $r_ip;
+ }
+
+ /**
+ * Given an IPv6 address in octet notation, returns the expanded octet.
+ * IPv4 IPs will be trimmed, thats it...
+ * @param $ip octet ipv6 IP address.
+ * @return string
+ */
+ public static function sanitizeIP( $ip ) {
+ if ( !$ip ) return null;
+ // Trim and return IPv4 addresses
+ if ( self::isIPv4($ip) ) return trim($ip);
+ // Only IPv6 addresses can be expanded
+ if ( !self::isIPv6($ip) ) return $ip;
+ // Remove any whitespaces, convert to upper case
+ $ip = strtoupper( trim($ip) );
+ // Expand zero abbreviations
+ if ( strpos( $ip, '::' ) !== false ) {
+ $ip = str_replace('::', str_repeat(':0', 8 - substr_count($ip, ':')) . ':', $ip);
+ }
+ // For IPs that start with "::", correct the final IP so that it starts with '0' and not ':'
+ if ( $ip[0] == ':' ) $ip = "0$ip";
+ // Remove leading zereos from each bloc as needed
+ $ip = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip );
+ return $ip;
+ }
+
+ /**
+ * Given an unsigned integer, returns an IPv6 address in octet notation
+ * @param $ip integer IP address.
+ * @return string
+ */
+ public static function toOctet( $ip_int ) {
+ // Convert to padded uppercase hex
+ $ip_hex = wfBaseConvert($ip_int, 10, 16, 32, false);
+ // Seperate into 8 octets
+ $ip_oct = substr( $ip_hex, 0, 4 );
+ for ($n=1; $n < 8; $n++) {
+ $ip_oct .= ':' . substr($ip_hex, 4*$n, 4);
+ }
+ // NO leading zeroes
+ $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct );
+ return $ip_oct;
+ }
+
+ /**
+ * Convert a network specification in IPv6 CIDR notation to an integer network and a number of bits
+ * @return array(string, int)
+ */
+ public static function parseCIDR6( $range ) {
+ # Expand any IPv6 IP
+ $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
+ if ( count( $parts ) != 2 ) {
+ return array( false, false );
+ }
+ $network = self::toUnsigned6( $parts[0] );
+ if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 128 ) {
+ $bits = $parts[1];
+ if ( $bits == 0 ) {
+ $network = 0;
+ } else {
+ # Native 32 bit functions WONT work here!!!
+ # Convert to a padded binary number
+ $network = wfBaseConvert( $network, 10, 2, 128 );
+ # Truncate the last (128-$bits) bits and replace them with zeros
+ $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
+ # Convert back to an integer
+ $network = wfBaseConvert( $network, 2, 10 );
+ }
+ } else {
+ $network = false;
+ $bits = false;
+ }
+ return array( $network, $bits );
+ }
+
+ /**
+ * Given a string range in a number of formats, return the start and end of
+ * the range in hexadecimal. For IPv6.
+ *
+ * Formats are:
+ * 2001:0db8:85a3::7344/96 CIDR
+ * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
+ * 2001:0db8:85a3::7344/96 Single IP
+ * @return array(string, int)
+ */
+ public static function parseRange6( $range ) {
+ # Expand any IPv6 IP
+ $range = IP::sanitizeIP( $range );
+ if ( strpos( $range, '/' ) !== false ) {
+ # CIDR
+ list( $network, $bits ) = self::parseCIDR6( $range );
+ if ( $network === false ) {
+ $start = $end = false;
+ } else {
+ $start = wfBaseConvert( $network, 10, 16, 32, false );
+ # Turn network to binary (again)
+ $end = wfBaseConvert( $network, 10, 2, 128 );
+ # Truncate the last (128-$bits) bits and replace them with ones
+ $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
+ # Convert to hex
+ $end = wfBaseConvert( $end, 2, 16, 32, false );
+ # see toHex() comment
+ $start = "v6-$start"; $end = "v6-$end";
+ }
+ } elseif ( strpos( $range, '-' ) !== false ) {
+ # Explicit range
+ list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+ $start = self::toUnsigned6( $start ); $end = self::toUnsigned6( $end );
+ if ( $start > $end ) {
+ $start = $end = false;
+ } else {
+ $start = wfBaseConvert( $start, 10, 16, 32, false );
+ $end = wfBaseConvert( $end, 10, 16, 32, false );
+ }
+ # see toHex() comment
+ $start = "v6-$start"; $end = "v6-$end";
+ } else {
+ # Single IP
+ $start = $end = self::toHex( $range );
+ }
+ if ( $start === false || $end === false ) {
+ return array( false, false );
+ } else {
+ return array( $start, $end );
+ }
+ }
+
+ /**
* Validate an IP address.
* @return boolean True if it is valid.
*/
public static function isValid( $ip ) {
- return preg_match( '/^' . RE_IP_ADD . '$/', $ip) ;
+ return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip) || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip) );
}
/**
@@ -44,7 +250,7 @@ class IP {
* Comes from ProxyTools.php
*/
public static function isPublic( $ip ) {
- $n = IP::toUnsigned( $ip );
+ $n = self::toUnsigned( $ip );
if ( !$n ) {
return false;
}
@@ -67,8 +273,8 @@ class IP {
}
foreach ( $privateRanges as $r ) {
- $start = IP::toUnsigned( $r[0] );
- $end = IP::toUnsigned( $r[1] );
+ $start = self::toUnsigned( $r[0] );
+ $end = self::toUnsigned( $r[1] );
if ( $n >= $start && $n <= $end ) {
return false;
}
@@ -80,15 +286,17 @@ class IP {
* Split out an IP block as an array of 4 bytes and a mask,
* return false if it can't be determined
*
- * @parameter $ip string A quad dotted IP address
+ * @param $ip string A quad dotted/octet IP address
* @return array
*/
public static function toArray( $ipblock ) {
$matches = array();
- if(! preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
- return false;
- } else {
+ if( preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
+ return $matches;
+ } else if ( preg_match( '/^' . RE_IPV6_ADD . '(?:\/(?:'.RE_IPV6_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
return $matches;
+ } else {
+ return false;
}
}
@@ -100,23 +308,29 @@ class IP {
* function for an IPv6 address will be prefixed with "v6-", a non-
* hexadecimal string which sorts after the IPv4 addresses.
*
- * @param $ip Quad dotted IP address.
+ * @param $ip Quad dotted/octet IP address.
+ * @return hexidecimal
*/
public static function toHex( $ip ) {
$n = self::toUnsigned( $ip );
if ( $n !== false ) {
- $n = sprintf( '%08X', $n );
+ $n = ( self::isIPv6($ip) ) ? "v6-" . wfBaseConvert( $n, 10, 16, 32, false ) : wfBaseConvert( $n, 10, 16, 8, false );
}
return $n;
}
/**
- * Given an IP address in dotted-quad notation, returns an unsigned integer.
+ * Given an IP address in dotted-quad/octet notation, returns an unsigned integer.
* Like ip2long() except that it actually works and has a consistent error return value.
* Comes from ProxyTools.php
* @param $ip Quad dotted IP address.
+ * @return integer
*/
public static function toUnsigned( $ip ) {
+ // Use IPv6 functions if needed
+ if ( self::isIPv6( $ip ) ) {
+ return self::toUnsigned6( $ip );
+ }
if ( $ip == '255.255.255.255' ) {
$n = -1;
} else {
@@ -149,13 +363,14 @@ class IP {
/**
* Convert a network specification in CIDR notation to an integer network and a number of bits
+ * @return array(string, int)
*/
public static function parseCIDR( $range ) {
$parts = explode( '/', $range, 2 );
if ( count( $parts ) != 2 ) {
return array( false, false );
}
- $network = IP::toSigned( $parts[0] );
+ $network = self::toSigned( $parts[0] );
if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
$bits = $parts[1];
if ( $bits == 0 ) {
@@ -182,11 +397,20 @@ class IP {
* 1.2.3.4/24 CIDR
* 1.2.3.4 - 1.2.3.5 Explicit range
* 1.2.3.4 Single IP
+ *
+ * 2001:0db8:85a3::7344/96 CIDR
+ * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
+ * 2001:0db8:85a3::7344 Single IP
+ * @return array(string, int)
*/
public static function parseRange( $range ) {
+ // Use IPv6 functions if needed
+ if ( self::isIPv6( $range ) ) {
+ return self::parseRange6( $range );
+ }
if ( strpos( $range, '/' ) !== false ) {
# CIDR
- list( $network, $bits ) = IP::parseCIDR( $range );
+ list( $network, $bits ) = self::parseCIDR( $range );
if ( $network === false ) {
$start = $end = false;
} else {
@@ -196,15 +420,16 @@ class IP {
} elseif ( strpos( $range, '-' ) !== false ) {
# Explicit range
list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+ $start = self::toUnsigned( $start ); $end = self::toUnsigned( $end );
if ( $start > $end ) {
$start = $end = false;
} else {
- $start = IP::toHex( $start );
- $end = IP::toHex( $end );
+ $start = sprintf( '%08X', $start );
+ $end = sprintf( '%08X', $end );
}
} else {
# Single IP
- $start = $end = IP::toHex( $range );
+ $start = $end = self::toHex( $range );
}
if ( $start === false || $end === false ) {
return array( false, false );
@@ -214,18 +439,15 @@ class IP {
}
/**
- * Determine if a given integer IPv4 address is in a given CIDR network
+ * Determine if a given IPv4/IPv6 address is in a given CIDR network
* @param $addr The address to check against the given range.
* @param $range The range to check the given address against.
* @return bool Whether or not the given address is in the given range.
*/
public static function isInRange( $addr, $range ) {
- $unsignedIP = IP::toUnsigned($addr);
- list( $start, $end ) = IP::parseRange($range);
-
- $start = hexdec($start);
- $end = hexdec($end);
-
+ // Convert to IPv6 if needed
+ $unsignedIP = self::toHex( $addr );
+ list( $start, $end ) = self::parseRange( $range );
return (($unsignedIP >= $start) && ($unsignedIP <= $end));
}
@@ -240,10 +462,11 @@ class IP {
* @return valid dotted quad IPv4 address or null
*/
public static function canonicalize( $addr ) {
- if ( IP::isValid( $addr ) )
+ if ( self::isValid( $addr ) )
return $addr;
// IPv6 loopback address
+ $m = array();
if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) )
return '127.0.0.1';
diff --git a/includes/Image.php b/includes/Image.php
index 7a6442c3..09c2286e 100644
--- a/includes/Image.php
+++ b/includes/Image.php
@@ -1,6 +1,5 @@
<?php
/**
- * @package MediaWiki
*/
/**
@@ -15,17 +14,24 @@
/**
* Bump this number when serialized cache records may be incompatible.
*/
-define( 'MW_IMAGE_VERSION', 1 );
+define( 'MW_IMAGE_VERSION', 2 );
/**
* Class to represent an image
*
* Provides methods to retrieve paths (physical, logical, URL),
* to generate thumbnails or for uploading.
- * @package MediaWiki
+ *
+ * @addtogroup Media
*/
class Image
{
+ const DELETED_FILE = 1;
+ const DELETED_COMMENT = 2;
+ const DELETED_USER = 4;
+ const DELETED_RESTRICTED = 8;
+ const RENDER_NOW = 1;
+
/**#@+
* @private
*/
@@ -43,6 +49,7 @@ class Image
$attr, # /
$type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
$mime, # MIME type, determined by MimeMagic::guessMimeType
+ $extension, # The file extension (constructor)
$size, # Size in bytes (loadFromXxx)
$metadata, # Metadata
$dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
@@ -81,18 +88,16 @@ class Image
}
$this->title =& $title;
$this->name = $title->getDBkey();
- $this->metadata = serialize ( array() ) ;
+ $this->metadata = '';
$n = strrpos( $this->name, '.' );
$this->extension = Image::normalizeExtension( $n ?
substr( $this->name, $n + 1 ) : '' );
$this->historyLine = 0;
- $this->page = 1;
$this->dataLoaded = false;
}
-
/**
* Normalize a file extension to the common form, and ensure it's clean.
* Extensions with non-alphanumeric characters will be discarded.
@@ -115,7 +120,7 @@ class Image
return '';
}
}
-
+
/**
* Get the memcached keys
* Returns an array, first element is the local cache key, second is the shared cache key, if there is one
@@ -264,32 +269,26 @@ class Image
$this->mime = $magic->guessMimeType($this->imagePath,true);
$this->type = $magic->getMediaType($this->imagePath,$this->mime);
+ $handler = MediaHandler::getHandler( $this->mime );
# Get size in bytes
$this->size = filesize( $this->imagePath );
- $magic=& MimeMagic::singleton();
-
- # Height and width
- wfSuppressWarnings();
- if( $this->mime == 'image/svg' ) {
- $gis = wfGetSVGsize( $this->imagePath );
- } elseif( $this->mime == 'image/vnd.djvu' ) {
- $deja = new DjVuImage( $this->imagePath );
- $gis = $deja->getImageSize();
- } elseif ( !$magic->isPHPImageType( $this->mime ) ) {
- # Don't try to get the width and height of sound and video files, that's bad for performance
- $gis = false;
+ # Height, width and metadata
+ if ( $handler ) {
+ $gis = $handler->getImageSize( $this, $this->imagePath );
+ $this->metadata = $handler->getMetadata( $this, $this->imagePath );
} else {
- $gis = getimagesize( $this->imagePath );
+ $gis = false;
+ $this->metadata = '';
}
- wfRestoreWarnings();
wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n");
}
else {
$this->mime = NULL;
$this->type = MEDIATYPE_UNKNOWN;
+ $this->metadata = '';
wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n");
}
@@ -308,13 +307,6 @@ class Image
# as ther's only one thread of execution, this should be safe anyway.
$this->dataLoaded = true;
-
- if ( $this->mime == 'image/vnd.djvu' ) {
- $this->metadata = $deja->retrieveMetaData();
- } else {
- $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) );
- }
-
if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
else $this->bits = 0;
@@ -328,7 +320,7 @@ class Image
global $wgUseSharedUploads, $wgSharedUploadDBname, $wgSharedUploadDBprefix, $wgContLang;
wfProfileIn( __METHOD__ );
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$this->checkDBSchema($dbr);
$row = $dbr->selectRow( 'image',
@@ -341,15 +333,13 @@ class Image
$this->loadFromRow( $row );
$this->imagePath = $this->getFullPath();
// Check for rows from a previous schema, quietly upgrade them
- if ( is_null($this->type) ) {
- $this->upgradeRow();
- }
+ $this->maybeUpgradeRow();
} elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) {
# In case we're on a wgCapitalLinks=false wiki, we
# capitalize the first letter of the filename before
# looking it up in the shared repository.
$name = $wgContLang->ucfirst($this->name);
- $dbc =& wfGetDB( DB_SLAVE, 'commons' );
+ $dbc = Image::getCommonsDB();
$row = $dbc->selectRow( "`$wgSharedUploadDBname`.{$wgSharedUploadDBprefix}image",
array(
@@ -364,9 +354,7 @@ class Image
$this->loadFromRow( $row );
// Check for rows from a previous schema, quietly upgrade them
- if ( is_null($this->type) ) {
- $this->upgradeRow();
- }
+ $this->maybeUpgradeRow();
}
}
@@ -378,7 +366,7 @@ class Image
$this->type = 0;
$this->fileExists = false;
$this->fromSharedDirectory = false;
- $this->metadata = serialize ( array() ) ;
+ $this->metadata = '';
$this->mime = false;
}
@@ -405,9 +393,7 @@ class Image
if (!$minor) $minor= "unknown";
$this->mime = $major.'/'.$minor;
}
-
$this->metadata = $row->img_metadata;
- if ( $this->metadata == "" ) $this->metadata = serialize ( array() ) ;
$this->dataLoaded = true;
}
@@ -434,8 +420,21 @@ class Image
}
/**
- * Metadata was loaded from the database, but the row had a marker indicating it needs to be
- * upgraded from the 1.4 schema, which had no width, height, bits or type. Upgrade the row.
+ * Upgrade a row if it needs it
+ */
+ function maybeUpgradeRow() {
+ if ( is_null($this->type) || $this->mime == 'image/svg' ) {
+ $this->upgradeRow();
+ } else {
+ $handler = $this->getHandler();
+ if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
+ $this->upgradeRow();
+ }
+ }
+ }
+
+ /**
+ * Fix assorted version-related problems with the image row by reloading it from the file
*/
function upgradeRow() {
global $wgDBname, $wgSharedUploadDBname;
@@ -451,17 +450,16 @@ class Image
// Write to the other DB using selectDB, not database selectors
// This avoids breaking replication in MySQL
- $dbw =& wfGetDB( DB_MASTER, 'commons' );
- $dbw->selectDB( $wgSharedUploadDBname );
+ $dbw = Image::getCommonsDB();
} else {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
}
$this->checkDBSchema($dbw);
list( $major, $minor ) = self::splitMime( $this->mime );
- wfDebug(__METHOD__.': upgrading '.$this->name." to 1.5 schema\n");
+ wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n");
$dbw->update( 'image',
array(
@@ -479,7 +477,7 @@ class Image
}
wfProfileOut( __METHOD__ );
}
-
+
/**
* Split an internet media type into its two components; if not
* a two-part name, set the minor type to 'unknown'.
@@ -554,23 +552,49 @@ class Image
/**
* Return the width of the image
*
- * Returns -1 if the file specified is not a known image type
+ * Returns false on error
* @public
*/
- function getWidth() {
+ function getWidth( $page = 1 ) {
$this->load();
- return $this->width;
+ if ( $this->isMultipage() ) {
+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
+ if ( $dim ) {
+ return $dim['width'];
+ } else {
+ return false;
+ }
+ } else {
+ return $this->width;
+ }
}
/**
* Return the height of the image
*
- * Returns -1 if the file specified is not a known image type
+ * Returns false on error
* @public
*/
- function getHeight() {
+ function getHeight( $page = 1 ) {
$this->load();
- return $this->height;
+ if ( $this->isMultipage() ) {
+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
+ if ( $dim ) {
+ return $dim['height'];
+ } else {
+ return false;
+ }
+ } else {
+ return $this->height;
+ }
+ }
+
+ /**
+ * Get handler-specific metadata
+ */
+ function getMetadata() {
+ $this->load();
+ return $this->metadata;
}
/**
@@ -610,58 +634,10 @@ class Image
* @todo remember the result of this check.
*/
function canRender() {
- global $wgUseImageMagick, $wgDjvuRenderer;
-
- if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false;
-
- $mime= $this->getMimeType();
-
- if (!$mime || $mime==='unknown' || $mime==='unknown/unknown') return false;
-
- #if it's SVG, check if there's a converter enabled
- if ($mime === 'image/svg') {
- global $wgSVGConverters, $wgSVGConverter;
-
- if ($wgSVGConverter && isset( $wgSVGConverters[$wgSVGConverter])) {
- wfDebug( "Image::canRender: SVG is ready!\n" );
- return true;
- } else {
- wfDebug( "Image::canRender: SVG renderer missing\n" );
- }
- }
-
- #image formats available on ALL browsers
- if ( $mime === 'image/gif'
- || $mime === 'image/png'
- || $mime === 'image/jpeg' ) return true;
-
- #image formats that can be converted to the above formats
- if ($wgUseImageMagick) {
- #convertable by ImageMagick (there are more...)
- if ( $mime === 'image/vnd.wap.wbmp'
- || $mime === 'image/x-xbitmap'
- || $mime === 'image/x-xpixmap'
- #|| $mime === 'image/x-icon' #file may be split into multiple parts
- || $mime === 'image/x-portable-anymap'
- || $mime === 'image/x-portable-bitmap'
- || $mime === 'image/x-portable-graymap'
- || $mime === 'image/x-portable-pixmap'
- #|| $mime === 'image/x-photoshop' #this takes a lot of CPU and RAM!
- || $mime === 'image/x-rgb'
- || $mime === 'image/x-bmp'
- || $mime === 'image/tiff' ) return true;
- }
- else {
- #convertable by the PHP GD image lib
- if ( $mime === 'image/vnd.wap.wbmp'
- || $mime === 'image/x-xbitmap' ) return true;
- }
- if ( $mime === 'image/vnd.djvu' && isset( $wgDjvuRenderer ) && $wgDjvuRenderer ) return true;
-
- return false;
+ $handler = $this->getHandler();
+ return $handler && $handler->canRender();
}
-
/**
* Return true if the file is of a type that can't be directly
* rendered by typical browsers and needs to be re-rasterized.
@@ -673,13 +649,8 @@ class Image
* @return bool
*/
function mustRender() {
- $mime= $this->getMimeType();
-
- if ( $mime === "image/gif"
- || $mime === "image/png"
- || $mime === "image/jpeg" ) return false;
-
- return true;
+ $handler = $this->getHandler();
+ return $handler && $handler->mustRender();
}
/**
@@ -745,15 +716,7 @@ class Image
* @public
*/
function getEscapeLocalURL( $query=false) {
- $this->getTitle();
- if ( $query === false ) {
- if ( $this->page != 1 ) {
- $query = 'page=' . $this->page;
- } else {
- $query = '';
- }
- }
- return $this->title->escapeLocalURL( $query );
+ return $this->getTitle()->escapeLocalURL( $query );
}
/**
@@ -801,74 +764,83 @@ class Image
* @todo document
* @private
*/
- function thumbUrl( $width, $subdir='thumb') {
+ function thumbUrlFromName( $thumbName, $subdir = 'thumb' ) {
global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath;
- global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
+ if($this->fromSharedDirectory) {
+ $base = '';
+ $path = $wgSharedUploadPath;
+ } else {
+ $base = $wgUploadBaseUrl;
+ $path = $wgUploadPath;
+ }
+ if ( Image::isHashed( $this->fromSharedDirectory ) ) {
+ $hashdir = wfGetHashPath($this->name, $this->fromSharedDirectory) .
+ wfUrlencode( $this->name );
+ } else {
+ $hashdir = '';
+ }
+ $url = "{$base}{$path}/{$subdir}{$hashdir}/" . wfUrlencode( $thumbName );
+ return $url;
+ }
- // Generate thumb.php URL if possible
- $script = false;
- $url = false;
+ /**
+ * @deprecated Use $image->transform()->getUrl() or thumbUrlFromName()
+ */
+ function thumbUrl( $width, $subdir = 'thumb' ) {
+ $name = $this->thumbName( array( 'width' => $width ) );
+ if ( strval( $name ) !== '' ) {
+ return array( false, $this->thumbUrlFromName( $name, $subdir ) );
+ } else {
+ return array( false, false );
+ }
+ }
+ function getTransformScript() {
+ global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
if ( $this->fromSharedDirectory ) {
- if ( $wgSharedThumbnailScriptPath ) {
- $script = $wgSharedThumbnailScriptPath;
- }
+ $script = $wgSharedThumbnailScriptPath;
} else {
- if ( $wgThumbnailScriptPath ) {
- $script = $wgThumbnailScriptPath;
- }
+ $script = $wgThumbnailScriptPath;
}
if ( $script ) {
- $url = $script . '?f=' . urlencode( $this->name ) . '&w=' . urlencode( $width );
- if( $this->mustRender() ) {
- $url.= '&r=1';
- }
+ return "$script?f=" . urlencode( $this->name );
} else {
- $name = $this->thumbName( $width );
- if($this->fromSharedDirectory) {
- $base = '';
- $path = $wgSharedUploadPath;
- } else {
- $base = $wgUploadBaseUrl;
- $path = $wgUploadPath;
- }
- if ( Image::isHashed( $this->fromSharedDirectory ) ) {
- $url = "{$base}{$path}/{$subdir}" .
- wfGetHashPath($this->name, $this->fromSharedDirectory)
- . $this->name.'/'.$name;
- $url = wfUrlencode( $url );
- } else {
- $url = "{$base}{$path}/{$subdir}/{$name}";
- }
+ return false;
}
- return array( $script !== false, $url );
}
/**
- * Return the file name of a thumbnail of the specified width
+ * Get a ThumbnailImage which is the same size as the source
+ */
+ function getUnscaledThumb( $page = false ) {
+ if ( $page ) {
+ $params = array(
+ 'page' => $page,
+ 'width' => $this->getWidth( $page )
+ );
+ } else {
+ $params = array( 'width' => $this->getWidth() );
+ }
+ return $this->transform( $params );
+ }
+
+ /**
+ * Return the file name of a thumbnail with the specified parameters
*
- * @param integer $width Width of the thumbnail image
- * @param boolean $shared Does the thumbnail come from the shared repository?
+ * @param array $params Handler-specific parameters
* @private
*/
- function thumbName( $width ) {
- $thumb = $width."px-".$this->name;
- if ( $this->page != 1 ) {
- $thumb = "page{$this->page}-$thumb";
+ function thumbName( $params ) {
+ $handler = $this->getHandler();
+ if ( !$handler ) {
+ return null;
}
-
- if( $this->mustRender() ) {
- if( $this->canRender() ) {
- # Rasterize to PNG (for SVG vector images, etc)
- $thumb .= '.png';
- }
- else {
- #should we use iconThumb here to get a symbolic thumbnail?
- #or should we fail with an internal error?
- return NULL; //can't make bitmap
- }
+ list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
+ $thumbName = $handler->makeParamString( $params ) . '-' . $this->name;
+ if ( $thumbExt != $this->extension ) {
+ $thumbName .= ".$thumbExt";
}
- return $thumb;
+ return $thumbName;
}
/**
@@ -887,9 +859,13 @@ class Image
* @param integer $height maximum height of the image (optional)
* @public
*/
- function createThumb( $width, $height=-1 ) {
- $thumb = $this->getThumbnail( $width, $height );
- if( is_null( $thumb ) ) return '';
+ function createThumb( $width, $height = -1 ) {
+ $params = array( 'width' => $width );
+ if ( $height != -1 ) {
+ $params['height'] = $height;
+ }
+ $thumb = $this->transform( $params );
+ if( is_null( $thumb ) || $thumb->isError() ) return '';
return $thumb->getUrl();
}
@@ -907,149 +883,90 @@ class Image
*
* @return ThumbnailImage or null on failure
* @public
+ *
+ * @deprecated use transform()
*/
function getThumbnail( $width, $height=-1, $render = true ) {
- wfProfileIn( __METHOD__ );
- if ($this->canRender()) {
- if ( $height > 0 ) {
- $this->load();
- if ( $width > $this->width * $height / $this->height ) {
- $width = wfFitBoxWidth( $this->width, $this->height, $height );
- }
- }
- if ( $render ) {
- $thumb = $this->renderThumb( $width );
- } else {
- // Don't render, just return the URL
- if ( $this->validateThumbParams( $width, $height ) ) {
- if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
- $url = $this->getURL();
- } else {
- list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width );
- }
- $thumb = new ThumbnailImage( $url, $width, $height );
- } else {
- $thumb = null;
- }
- }
- } else {
- // not a bitmap or renderable image, don't try.
- $thumb = $this->iconThumb();
+ $params = array( 'width' => $width );
+ if ( $height != -1 ) {
+ $params['height'] = $height;
}
- wfProfileOut( __METHOD__ );
- return $thumb;
+ $flags = $render ? self::RENDER_NOW : 0;
+ return $this->transform( $params, $flags );
}
-
+
/**
- * @return ThumbnailImage
+ * Transform a media file
+ *
+ * @param array $params An associative array of handler-specific parameters. Typical
+ * keys are width, height and page.
+ * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
+ * @return MediaTransformOutput
*/
- function iconThumb() {
- global $wgStylePath, $wgStyleDirectory;
+ function transform( $params, $flags = 0 ) {
+ global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors;
- $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
- foreach( $try as $icon ) {
- $path = '/common/images/icons/' . $icon;
- $filepath = $wgStyleDirectory . $path;
- if( file_exists( $filepath ) ) {
- return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
+ wfProfileIn( __METHOD__ );
+ do {
+ $handler = $this->getHandler();
+ if ( !$handler || !$handler->canRender() ) {
+ // not a bitmap or renderable image, don't try.
+ $thumb = $this->iconThumb();
+ break;
}
- }
- return null;
- }
- /**
- * Validate thumbnail parameters and fill in the correct height
- *
- * @param integer &$width Specified width (input/output)
- * @param integer &$height Height (output only)
- * @return false to indicate that an error should be returned to the user.
- */
- function validateThumbParams( &$width, &$height ) {
- global $wgSVGMaxSize, $wgMaxImageArea;
-
- $this->load();
+ $script = $this->getTransformScript();
+ if ( $script && !($flags & self::RENDER_NOW) ) {
+ // Use a script to transform on client request
+ $thumb = $handler->getScriptedTransform( $this, $script, $params );
+ break;
+ }
- if ( ! $this->exists() )
- {
- # If there is no image, there will be no thumbnail
- return false;
- }
-
- $width = intval( $width );
-
- # Sanity check $width
- if( $width <= 0 || $this->width <= 0) {
- # BZZZT
- return false;
- }
+ $normalisedParams = $params;
+ $handler->normaliseParams( $this, $normalisedParams );
+ list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
+ $thumbName = $this->thumbName( $normalisedParams );
+ $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) . "/$thumbName";
+ $thumbUrl = $this->thumbUrlFromName( $thumbName );
- # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
- # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
- # an exception for it.
- if ( $this->getMediaType() == MEDIATYPE_BITMAP &&
- $this->getMimeType() !== 'image/jpeg' &&
- $this->width * $this->height > $wgMaxImageArea )
- {
- return false;
- }
+ $this->migrateThumbFile( $thumbName );
- # Don't make an image bigger than the source, or wgMaxSVGSize for SVGs
- if ( $this->mustRender() ) {
- $width = min( $width, $wgSVGMaxSize );
- } elseif ( $width > $this->width - 1 ) {
- $width = $this->width;
- $height = $this->height;
- return true;
- }
+ if ( file_exists( $thumbPath ) ) {
+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ break;
+ }
- $height = round( $this->height * $width / $this->width );
- return true;
+ if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) {
+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ break;
+ }
+ $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
+
+ // Ignore errors if requested
+ if ( !$thumb ) {
+ $thumb = null;
+ } elseif ( $thumb->isError() ) {
+ $this->lastError = $thumb->toText();
+ if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ }
+ }
+
+ if ( $wgUseSquid ) {
+ wfPurgeSquidServers( array( $thumbUrl ) );
+ }
+ } while (false);
+
+ wfProfileOut( __METHOD__ );
+ return $thumb;
}
-
+
/**
- * Create a thumbnail of the image having the specified width.
- * The thumbnail will not be created if the width is larger than the
- * image's width. Let the browser do the scaling in this case.
- * The thumbnail is stored on disk and is only computed if the thumbnail
- * file does not exist OR if it is older than the image.
- * Returns an object which can return the pathname, URL, and physical
- * pixel size of the thumbnail -- or null on failure.
- *
- * @return ThumbnailImage or null on failure
- * @private
+ * Fix thumbnail files from 1.4 or before, with extreme prejudice
*/
- function renderThumb( $width, $useScript = true ) {
- global $wgUseSquid, $wgThumbnailEpoch;
-
- wfProfileIn( __METHOD__ );
-
- $this->load();
- $height = -1;
- if ( !$this->validateThumbParams( $width, $height ) ) {
- # Validation error
- wfProfileOut( __METHOD__ );
- return null;
- }
-
- if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
- # validateThumbParams (or the user) wants us to return the unscaled image
- $thumb = new ThumbnailImage( $this->getURL(), $width, $height );
- wfProfileOut( __METHOD__ );
- return $thumb;
- }
-
- list( $isScriptUrl, $url ) = $this->thumbUrl( $width );
- if ( $isScriptUrl && $useScript ) {
- // Use thumb.php to render the image
- $thumb = new ThumbnailImage( $url, $width, $height );
- wfProfileOut( __METHOD__ );
- return $thumb;
- }
-
- $thumbName = $this->thumbName( $width, $this->fromSharedDirectory );
+ function migrateThumbFile( $thumbName ) {
$thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory );
- $thumbPath = $thumbDir.'/'.$thumbName;
-
+ $thumbPath = "$thumbDir/$thumbName";
if ( is_dir( $thumbPath ) ) {
// Directory where file should be
// This happened occasionally due to broken migration code in 1.5
@@ -1062,254 +979,50 @@ class Image
break;
}
}
- // Code below will ask if it exists, and the answer is now no
+ // Doesn't exist anymore
clearstatcache();
}
-
- $done = true;
- if ( !file_exists( $thumbPath ) ||
- filemtime( $thumbPath ) < wfTimestamp( TS_UNIX, $wgThumbnailEpoch ) )
- {
- // Create the directory if it doesn't exist
- if ( is_file( $thumbDir ) ) {
- // File where thumb directory should be, destroy if possible
- @unlink( $thumbDir );
- }
- wfMkdirParents( $thumbDir );
-
- $oldThumbPath = wfDeprecatedThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ).
- '/'.$thumbName;
- $done = false;
-
- // Migration from old directory structure
- if ( is_file( $oldThumbPath ) ) {
- if ( filemtime($oldThumbPath) >= filemtime($this->imagePath) ) {
- if ( file_exists( $thumbPath ) ) {
- if ( !is_dir( $thumbPath ) ) {
- // Old image in the way of rename
- unlink( $thumbPath );
- } else {
- // This should have been dealt with already
- throw new MWException( "Directory where image should be: $thumbPath" );
- }
- }
- // Rename the old image into the new location
- rename( $oldThumbPath, $thumbPath );
- $done = true;
- } else {
- unlink( $oldThumbPath );
- }
- }
- if ( !$done ) {
- $this->lastError = $this->reallyRenderThumb( $thumbPath, $width, $height );
- if ( $this->lastError === true ) {
- $done = true;
- } elseif( $GLOBALS['wgIgnoreImageErrors'] ) {
- // Log the error but output anyway.
- // With luck it's a transitory error...
- $done = true;
- }
-
- # Purge squid
- # This has to be done after the image is updated and present for all machines on NFS,
- # or else the old version might be stored into the squid again
- if ( $wgUseSquid ) {
- $urlArr = array( $url );
- wfPurgeSquidServers($urlArr);
- }
- }
- }
-
- if ( $done ) {
- $thumb = new ThumbnailImage( $url, $width, $height, $thumbPath );
- } else {
- $thumb = null;
+ if ( is_file( $thumbDir ) ) {
+ // File where directory should be
+ unlink( $thumbDir );
+ // Doesn't exist anymore
+ clearstatcache();
}
- wfProfileOut( __METHOD__ );
- return $thumb;
- } // END OF function renderThumb
+ }
/**
- * Really render a thumbnail
- * Call this only for images for which canRender() returns true.
- *
- * @param string $thumbPath Path to thumbnail
- * @param int $width Desired width in pixels
- * @param int $height Desired height in pixels
- * @return bool True on error, false or error string on failure.
- * @private
+ * Get a MediaHandler instance for this image
*/
- function reallyRenderThumb( $thumbPath, $width, $height ) {
- global $wgSVGConverters, $wgSVGConverter;
- global $wgUseImageMagick, $wgImageMagickConvertCommand;
- global $wgCustomConvertCommand;
- global $wgDjvuRenderer, $wgDjvuPostProcessor;
-
- $this->load();
-
- $err = false;
- $cmd = "";
- $retval = 0;
-
- if( $this->mime === "image/svg" ) {
- #Right now we have only SVG
-
- global $wgSVGConverters, $wgSVGConverter;
- if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
- global $wgSVGConverterPath;
- $cmd = str_replace(
- array( '$path/', '$width', '$height', '$input', '$output' ),
- array( $wgSVGConverterPath ? "$wgSVGConverterPath/" : "",
- intval( $width ),
- intval( $height ),
- wfEscapeShellArg( $this->imagePath ),
- wfEscapeShellArg( $thumbPath ) ),
- $wgSVGConverters[$wgSVGConverter] );
- wfProfileIn( 'rsvg' );
- wfDebug( "reallyRenderThumb SVG: $cmd\n" );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'rsvg' );
- }
- } else {
- if ( $this->mime === "image/vnd.djvu" && $wgDjvuRenderer ) {
- // DJVU image
- // The file contains several images. First, extract the
- // page in hi-res, if it doesn't yet exist. Then, thumbnail
- // it.
-
- $cmd = "{$wgDjvuRenderer} -page={$this->page} -size=${width}x${height} " .
- wfEscapeShellArg( $this->imagePath ) .
- " | {$wgDjvuPostProcessor} > " . wfEscapeShellArg($thumbPath);
- wfProfileIn( 'ddjvu' );
- wfDebug( "reallyRenderThumb DJVU: $cmd\n" );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'ddjvu' );
-
- } elseif ( $wgUseImageMagick ) {
- # use ImageMagick
-
- if ( $this->mime == 'image/jpeg' ) {
- $quality = "-quality 80"; // 80%
- } elseif ( $this->mime == 'image/png' ) {
- $quality = "-quality 95"; // zlib 9, adaptive filtering
- } else {
- $quality = ''; // default
- }
-
- # Specify white background color, will be used for transparent images
- # in Internet Explorer/Windows instead of default black.
-
- # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}".
- # It seems that ImageMagick has a bug wherein it produces thumbnails of
- # the wrong size in the second case.
-
- $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) .
- " {$quality} -background white -size {$width} ".
- wfEscapeShellArg($this->imagePath) .
- // Coalesce is needed to scale animated GIFs properly (bug 1017).
- ' -coalesce ' .
- // For the -resize option a "!" is needed to force exact size,
- // or ImageMagick may decide your ratio is wrong and slice off
- // a pixel.
- " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) .
- " -depth 8 " .
- wfEscapeShellArg($thumbPath) . " 2>&1";
- wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n");
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } elseif( $wgCustomConvertCommand ) {
- # Use a custom convert command
- # Variables: %s %d %w %h
- $src = wfEscapeShellArg( $this->imagePath );
- $dst = wfEscapeShellArg( $thumbPath );
- $cmd = $wgCustomConvertCommand;
- $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
- $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size
- wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" );
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } else {
- # Use PHP's builtin GD library functions.
- #
- # First find out what kind of file this is, and select the correct
- # input routine for this.
-
- $typemap = array(
- 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
- 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( &$this, 'imageJpegWrapper' ) ),
- 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
- 'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
- 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
- );
- if( !isset( $typemap[$this->mime] ) ) {
- $err = 'Image type not supported';
- wfDebug( "$err\n" );
- return $err;
- }
- list( $loader, $colorStyle, $saveType ) = $typemap[$this->mime];
-
- if( !function_exists( $loader ) ) {
- $err = "Incomplete GD library configuration: missing function $loader";
- wfDebug( "$err\n" );
- return $err;
- }
- if( $colorStyle == 'palette' ) {
- $truecolor = false;
- } elseif( $colorStyle == 'truecolor' ) {
- $truecolor = true;
- } elseif( $colorStyle == 'bits' ) {
- $truecolor = ( $this->bits > 8 );
- }
+ function getHandler() {
+ return MediaHandler::getHandler( $this->getMimeType() );
+ }
- $src_image = call_user_func( $loader, $this->imagePath );
- if ( $truecolor ) {
- $dst_image = imagecreatetruecolor( $width, $height );
- } else {
- $dst_image = imagecreate( $width, $height );
- }
- imagecopyresampled( $dst_image, $src_image,
- 0,0,0,0,
- $width, $height, $this->width, $this->height );
- call_user_func( $saveType, $dst_image, $thumbPath );
- imagedestroy( $dst_image );
- imagedestroy( $src_image );
- }
- }
+ /**
+ * Get a ThumbnailImage representing a file type icon
+ * @return ThumbnailImage
+ */
+ function iconThumb() {
+ global $wgStylePath, $wgStyleDirectory;
- #
- # Check for zero-sized thumbnails. Those can be generated when
- # no disk space is available or some other error occurs
- #
- if( file_exists( $thumbPath ) ) {
- $thumbstat = stat( $thumbPath );
- if( $thumbstat['size'] == 0 || $retval != 0 ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'Removing bad %d-byte thumbnail "%s"',
- $thumbstat['size'], $thumbPath ) );
- unlink( $thumbPath );
+ $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
+ foreach( $try as $icon ) {
+ $path = '/common/images/icons/' . $icon;
+ $filepath = $wgStyleDirectory . $path;
+ if( file_exists( $filepath ) ) {
+ return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
}
}
- if ( $retval != 0 ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim($err), $cmd ) );
- return wfMsg( 'thumbnail_error', $err );
- } else {
- return true;
- }
+ return null;
}
+ /**
+ * Get last thumbnailing error.
+ * Largely obsolete.
+ */
function getLastError() {
return $this->lastError;
}
- function imageJpegWrapper( $dst_image, $thumbPath ) {
- imageinterlace( $dst_image );
- imagejpeg( $dst_image, $thumbPath, 95 );
- }
-
/**
* Get all thumbnail names previously generated for this image
*/
@@ -1319,16 +1032,17 @@ class Image
$files = array();
$dir = wfImageThumbDir( $this->name, $shared );
- // This generates an error on failure, hence the @
- $handle = @opendir( $dir );
+ if ( is_dir( $dir ) ) {
+ $handle = opendir( $dir );
- if ( $handle ) {
- while ( false !== ( $file = readdir($handle) ) ) {
- if ( $file{0} != '.' ) {
- $files[] = $file;
+ if ( $handle ) {
+ while ( false !== ( $file = readdir($handle) ) ) {
+ if ( $file{0} != '.' ) {
+ $files[] = $file;
+ }
}
+ closedir( $handle );
}
- closedir( $handle );
}
} else {
$files = array();
@@ -1361,8 +1075,10 @@ class Image
$urls = array();
foreach ( $files as $file ) {
$m = array();
- if ( preg_match( '/^(\d+)px/', $file, $m ) ) {
- list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] );
+ # Check that the base image name is part of the thumb name
+ # This is a basic sanity check to avoid erasing unrelated directories
+ if ( strpos( $file, $this->name ) !== false ) {
+ $url = $this->thumbUrlFromName( $file );
$urls[] = $url;
@unlink( "$dir/$file" );
}
@@ -1377,7 +1093,7 @@ class Image
wfPurgeSquidServers( $urls );
}
}
-
+
/**
* Purge the image description page, but don't go after
* pages using the image. Use when modifying file history
@@ -1388,7 +1104,7 @@ class Image
$page->invalidateCache();
$page->purgeSquid();
}
-
+
/**
* Purge metadata and all affected pages when the image is created,
* deleted, or majorly updated. A set of additional URLs may be
@@ -1399,12 +1115,15 @@ class Image
// Delete thumbnails and refresh image metadata cache
$this->purgeCache();
$this->purgeDescription();
-
+
// Purge cache of all pages using this image
$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
$update->doUpdate();
}
+ /**
+ * Check the image table schema on the given connection for subtle problems
+ */
function checkDBSchema(&$db) {
static $checkDone = false;
global $wgCheckDBSchema;
@@ -1445,7 +1164,7 @@ class Image
* @public
*/
function nextHistoryLine() {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$this->checkDBSchema($dbr);
@@ -1541,7 +1260,7 @@ class Image
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
global $wgUser, $wgUseCopyrightUpload;
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$this->checkDBSchema($dbw);
@@ -1670,6 +1389,9 @@ class Image
$article->insertNewArticle( $textdesc, $desc, $minor, $watch, $suppressRC );
}
+ # Hooks, hooks, the magic of hooks...
+ wfRunHooks( 'FileUpload', array( $this ) );
+
# Add the log entry
$log = new LogPage( 'upload' );
$log->addEntry( 'upload', $descTitle, $desc );
@@ -1697,9 +1419,9 @@ class Image
wfProfileIn( __METHOD__ );
if ( $options ) {
- $db =& wfGetDB( DB_MASTER );
+ $db = wfGetDB( DB_MASTER );
} else {
- $db =& wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_SLAVE );
}
$linkCache =& LinkCache::singleton();
@@ -1721,75 +1443,24 @@ class Image
wfProfileOut( __METHOD__ );
return $retVal;
}
-
- /**
- * Retrive Exif data from the file and prune unrecognized tags
- * and/or tags with invalid contents
- *
- * @param $filename
- * @return array
- */
- private function retrieveExifData( $filename ) {
- global $wgShowEXIF;
-
- /*
- if ( $this->getMimeType() !== "image/jpeg" )
- return array();
- */
-
- if( $wgShowEXIF && file_exists( $filename ) ) {
- $exif = new Exif( $filename );
- return $exif->getFilteredData();
- }
-
- return array();
- }
function getExifData() {
global $wgRequest;
- if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' )
+ $handler = $this->getHandler();
+ if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) {
return array();
-
- $purge = $wgRequest->getVal( 'action' ) == 'purge';
- $ret = unserialize( $this->metadata );
-
- $oldver = isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) ? $ret['MEDIAWIKI_EXIF_VERSION'] : 0;
- $newver = Exif::version();
-
- if ( !count( $ret ) || $purge || $oldver != $newver ) {
- $this->purgeMetadataCache();
- $this->updateExifData( $newver );
}
- if ( isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) )
- unset( $ret['MEDIAWIKI_EXIF_VERSION'] );
- $format = new FormatExif( $ret );
-
- return $format->getFormattedData();
- }
-
- function updateExifData( $version ) {
- if ( $this->getImagePath() === false ) # Not a local image
- return;
-
- # Get EXIF data from image
- $exif = $this->retrieveExifData( $this->imagePath );
- if ( count( $exif ) ) {
- $exif['MEDIAWIKI_EXIF_VERSION'] = $version;
- $this->metadata = serialize( $exif );
- } else {
- $this->metadata = '0';
+ if ( !$this->metadata ) {
+ return array();
}
+ $exif = unserialize( $this->metadata );
+ if ( !$exif ) {
+ return array();
+ }
+ unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
+ $format = new FormatExif( $exif );
- # Update EXIF data in database
- $dbw =& wfGetDB( DB_MASTER );
-
- $this->checkDBSchema($dbw);
-
- $dbw->update( 'image',
- array( 'img_metadata' => $this->metadata ),
- array( 'img_name' => $this->name ),
- __METHOD__
- );
+ return $format->getFormattedData();
}
/**
@@ -1801,7 +1472,7 @@ class Image
function isLocal() {
return !$this->fromSharedDirectory;
}
-
+
/**
* Was this image ever deleted from the wiki?
*
@@ -1811,7 +1482,7 @@ class Image
$title = Title::makeTitle( NS_IMAGE, $this->name );
return ( $title->isDeleted() > 0 );
}
-
+
/**
* Delete all versions of the image.
*
@@ -1823,37 +1494,37 @@ class Image
* @param $reason
* @return true on success, false on some kind of failure
*/
- function delete( $reason ) {
+ function delete( $reason, $suppress=false ) {
$transaction = new FSTransaction();
$urlArr = array( $this->getURL() );
-
+
if( !FileStore::lock() ) {
wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
return false;
}
-
+
try {
$dbw = wfGetDB( DB_MASTER );
$dbw->begin();
-
+
// Delete old versions
$result = $dbw->select( 'oldimage',
array( 'oi_archive_name' ),
array( 'oi_name' => $this->name ) );
-
+
while( $row = $dbw->fetchObject( $result ) ) {
$oldName = $row->oi_archive_name;
-
- $transaction->add( $this->prepareDeleteOld( $oldName, $reason ) );
-
+
+ $transaction->add( $this->prepareDeleteOld( $oldName, $reason, $suppress ) );
+
// We'll need to purge this URL from caches...
$urlArr[] = wfImageArchiveUrl( $oldName );
}
$dbw->freeResult( $result );
-
+
// And the current version...
- $transaction->add( $this->prepareDeleteCurrent( $reason ) );
-
+ $transaction->add( $this->prepareDeleteCurrent( $reason, $suppress ) );
+
$dbw->immediateCommit();
} catch( MWException $e ) {
wfDebug( __METHOD__.": db error, rolling back file transactions\n" );
@@ -1861,22 +1532,22 @@ class Image
FileStore::unlock();
throw $e;
}
-
+
wfDebug( __METHOD__.": deleted db items, applying file transactions\n" );
$transaction->commit();
FileStore::unlock();
-
+
// Update site_stats
$site_stats = $dbw->tableName( 'site_stats' );
$dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ );
-
+
$this->purgeEverything( $urlArr );
-
+
return true;
}
-
-
+
+
/**
* Delete an old version of the image.
*
@@ -1889,20 +1560,20 @@ class Image
* @throws MWException or FSException on database or filestore failure
* @return true on success, false on some kind of failure
*/
- function deleteOld( $archiveName, $reason ) {
+ function deleteOld( $archiveName, $reason, $suppress=false ) {
$transaction = new FSTransaction();
$urlArr = array();
-
+
if( !FileStore::lock() ) {
wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
return false;
}
-
+
$transaction = new FSTransaction();
try {
$dbw = wfGetDB( DB_MASTER );
$dbw->begin();
- $transaction->add( $this->prepareDeleteOld( $archiveName, $reason ) );
+ $transaction->add( $this->prepareDeleteOld( $archiveName, $reason, $suppress ) );
$dbw->immediateCommit();
} catch( MWException $e ) {
wfDebug( __METHOD__.": db error, rolling back file transaction\n" );
@@ -1910,11 +1581,11 @@ class Image
FileStore::unlock();
throw $e;
}
-
+
wfDebug( __METHOD__.": deleted db items, applying file transaction\n" );
$transaction->commit();
FileStore::unlock();
-
+
$this->purgeDescription();
// Squid purging
@@ -1927,13 +1598,13 @@ class Image
}
return true;
}
-
+
/**
* Delete the current version of a file.
* May throw a database error.
* @return true on success, false on failure
*/
- private function prepareDeleteCurrent( $reason ) {
+ private function prepareDeleteCurrent( $reason, $suppress=false ) {
return $this->prepareDeleteVersion(
$this->getFullPath(),
$reason,
@@ -1954,6 +1625,7 @@ class Image
'fa_user_text' => 'img_user_text',
'fa_timestamp' => 'img_timestamp' ),
array( 'img_name' => $this->name ),
+ $suppress,
__METHOD__ );
}
@@ -1962,7 +1634,7 @@ class Image
* May throw a database error.
* @return true on success, false on failure
*/
- private function prepareDeleteOld( $archiveName, $reason ) {
+ private function prepareDeleteOld( $archiveName, $reason, $suppress=false ) {
$oldpath = wfImageArchiveDir( $this->name ) .
DIRECTORY_SEPARATOR . $archiveName;
return $this->prepareDeleteVersion(
@@ -1987,6 +1659,7 @@ class Image
array(
'oi_name' => $this->name,
'oi_archive_name' => $archiveName ),
+ $suppress,
__METHOD__ );
}
@@ -1999,14 +1672,14 @@ class Image
*
* @return FSTransaction
*/
- private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $fname ) {
+ private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $suppress=false, $fname ) {
global $wgUser, $wgSaveDeletedFiles;
-
+
// Dupe the file into the file store
if( file_exists( $path ) ) {
if( $wgSaveDeletedFiles ) {
$group = 'deleted';
-
+
$store = FileStore::get( $group );
$key = FileStore::calculateKey( $path, $this->extension );
$transaction = $store->insert( $key, $path,
@@ -2022,7 +1695,7 @@ class Image
$key = null;
$transaction = new FSTransaction(); // empty
}
-
+
if( $transaction === false ) {
// Fail to restore?
wfDebug( __METHOD__.": import to file store failed, aborting\n" );
@@ -2030,16 +1703,28 @@ class Image
return false;
}
+ // Bitfields to further supress the image content
+ // Note that currently, live images are stored elsewhere
+ // and cannot be partially deleted
+ $bitfield = 0;
+ if ( $suppress ) {
+ $bitfield |= self::DELETED_FILE;
+ $bitfield |= self::DELETED_COMMENT;
+ $bitfield |= self::DELETED_USER;
+ $bitfield |= self::DELETED_RESTRICTED;
+ }
+
$dbw = wfGetDB( DB_MASTER );
$storageMap = array(
'fa_storage_group' => $dbw->addQuotes( $group ),
'fa_storage_key' => $dbw->addQuotes( $key ),
-
+
'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ),
'fa_deleted_timestamp' => $dbw->timestamp(),
- 'fa_deleted_reason' => $dbw->addQuotes( $reason ) );
+ 'fa_deleted_reason' => $dbw->addQuotes( $reason ),
+ 'fa_deleted' => $bitfield);
$allFields = array_merge( $storageMap, $fieldMap );
-
+
try {
if( $wgSaveDeletedFiles ) {
$dbw->insertSelect( 'filearchive', $table, $allFields, $where, $fname );
@@ -2052,10 +1737,10 @@ class Image
$transaction->rollback();
throw $e;
}
-
+
return $transaction;
}
-
+
/**
* Restore all or specified deleted revisions to the given file.
* Permissions and logging are left to the caller.
@@ -2067,36 +1752,38 @@ class Image
* @return the number of file revisions restored if successful,
* or false on failure
*/
- function restore( $versions=array() ) {
+ function restore( $versions=array(), $Unsuppress=false ) {
+ global $wgUser;
+
if( !FileStore::lock() ) {
wfDebug( __METHOD__." could not acquire filestore lock\n" );
return false;
}
-
+
$transaction = new FSTransaction();
try {
$dbw = wfGetDB( DB_MASTER );
$dbw->begin();
-
+
// Re-confirm whether this image presently exists;
// if no we'll need to create an image record for the
// first item we restore.
$exists = $dbw->selectField( 'image', '1',
array( 'img_name' => $this->name ),
__METHOD__ );
-
+
// Fetch all or selected archived revisions for the file,
// sorted from the most recent to the oldest.
$conditions = array( 'fa_name' => $this->name );
if( $versions ) {
$conditions['fa_id'] = $versions;
}
-
+
$result = $dbw->select( 'filearchive', '*',
$conditions,
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
-
+
if( $dbw->numRows( $result ) < count( $versions ) ) {
// There's some kind of conflict or confusion;
// we can't restore everything we were asked to.
@@ -2113,40 +1800,51 @@ class Image
FileStore::unlock();
return true;
}
-
+
$revisions = 0;
while( $row = $dbw->fetchObject( $result ) ) {
+ if ( $Unsuppress ) {
+ // Currently, fa_deleted flags fall off upon restore, lets be careful about this
+ } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) {
+ // Skip restoring file revisions that the user cannot restore
+ continue;
+ }
$revisions++;
$store = FileStore::get( $row->fa_storage_group );
if( !$store ) {
wfDebug( __METHOD__.": skipping row with no file.\n" );
continue;
}
-
+
if( $revisions == 1 && !$exists ) {
$destDir = wfImageDir( $row->fa_name );
if ( !is_dir( $destDir ) ) {
wfMkdirParents( $destDir );
}
$destPath = $destDir . DIRECTORY_SEPARATOR . $row->fa_name;
-
+
// We may have to fill in data if this was originally
// an archived file revision.
if( is_null( $row->fa_metadata ) ) {
$tempFile = $store->filePath( $row->fa_storage_key );
- $metadata = serialize( $this->retrieveExifData( $tempFile ) );
-
+
$magic = MimeMagic::singleton();
$mime = $magic->guessMimeType( $tempFile, true );
$media_type = $magic->getMediaType( $tempFile, $mime );
list( $major_mime, $minor_mime ) = self::splitMime( $mime );
+ $handler = MediaHandler::getHandler( $mime );
+ if ( $handler ) {
+ $metadata = $handler->getMetadata( false, $tempFile );
+ } else {
+ $metadata = '';
+ }
} else {
$metadata = $row->fa_metadata;
$major_mime = $row->fa_major_mime;
$minor_mime = $row->fa_minor_mime;
$media_type = $row->fa_media_type;
}
-
+
$table = 'image';
$fields = array(
'img_name' => $row->fa_name,
@@ -2177,7 +1875,7 @@ class Image
wfMkdirParents( $destDir );
}
$destPath = $destDir . DIRECTORY_SEPARATOR . $archiveName;
-
+
$table = 'oldimage';
$fields = array(
'oi_name' => $row->fa_name,
@@ -2191,13 +1889,13 @@ class Image
'oi_user_text' => $row->fa_user_text,
'oi_timestamp' => $row->fa_timestamp );
}
-
+
$dbw->insert( $table, $fields, __METHOD__ );
- /// @fixme this delete is not totally safe, potentially
+ // @todo this delete is not totally safe, potentially
$dbw->delete( 'filearchive',
array( 'fa_id' => $row->fa_id ),
__METHOD__ );
-
+
// Check if any other stored revisions use this file;
// if so, we shouldn't remove the file from the deletion
// archives so they will still work.
@@ -2213,161 +1911,230 @@ class Image
} else {
$flags = 0;
}
-
+
$transaction->add( $store->export( $row->fa_storage_key,
$destPath, $flags ) );
}
-
+
$dbw->immediateCommit();
} catch( MWException $e ) {
wfDebug( __METHOD__." caught error, aborting\n" );
$transaction->rollback();
throw $e;
}
-
+
$transaction->commit();
FileStore::unlock();
-
+
if( $revisions > 0 ) {
if( !$exists ) {
wfDebug( __METHOD__." restored $revisions items, creating a new current\n" );
-
+
// Update site_stats
$site_stats = $dbw->tableName( 'site_stats' );
$dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
-
+
$this->purgeEverything();
} else {
wfDebug( __METHOD__." restored $revisions as archived versions\n" );
$this->purgeDescription();
}
}
-
+
return $revisions;
}
/**
- * Select a page from a multipage document. Determines the page used for
- * rendering thumbnails.
+ * Returns 'true' if this image is a multipage document, e.g. a DJVU
+ * document.
*
- * @param $page Integer: page number, starting with 1
+ * @return Bool
*/
- function selectPage( $page ) {
- wfDebug( __METHOD__." selecting page $page \n" );
- $this->page = $page;
- if ( ! $this->dataLoaded ) {
- $this->load();
- }
- if ( ! isset( $this->multiPageXML ) ) {
- $this->initializeMultiPageXML();
+ function isMultipage() {
+ $handler = $this->getHandler();
+ return $handler && $handler->isMultiPage();
+ }
+
+ /**
+ * Returns the number of pages of a multipage document, or NULL for
+ * documents which aren't multipage documents
+ */
+ function pageCount() {
+ $handler = $this->getHandler();
+ if ( $handler && $handler->isMultiPage() ) {
+ return $handler->pageCount( $this );
+ } else {
+ return null;
}
- $o = $this->multiPageXML->BODY[0]->OBJECT[$page-1];
- $this->height = intval( $o['height'] );
- $this->width = intval( $o['width'] );
}
- function initializeMultiPageXML() {
- #
- # Check for files uploaded prior to DJVU support activation
- # They have a '0' in their metadata field.
- #
- if ( $this->metadata == '0' || $this->metadata == '' ) {
- $deja = new DjVuImage( $this->imagePath );
- $this->metadata = $deja->retrieveMetaData();
- $this->purgeMetadataCache();
+ static function getCommonsDB() {
+ static $dbc;
+ global $wgLoadBalancer, $wgSharedUploadDBname;
+ if ( !isset( $dbc ) ) {
+ $i = $wgLoadBalancer->getGroupIndex( 'commons' );
+ $dbinfo = $wgLoadBalancer->mServers[$i];
+ $dbc = new Database( $dbinfo['host'], $dbinfo['user'],
+ $dbinfo['password'], $wgSharedUploadDBname );
+ }
+ return $dbc;
+ }
- # Update metadata in the database
- $dbw =& wfGetDB( DB_MASTER );
- $dbw->update( 'image',
- array( 'img_metadata' => $this->metadata ),
- array( 'img_name' => $this->name ),
- __METHOD__
- );
+ /**
+ * Calculate the height of a thumbnail using the source and destination width
+ */
+ static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
+ // Exact integer multiply followed by division
+ if ( $srcWidth == 0 ) {
+ return 0;
+ } else {
+ return round( $srcHeight * $dstWidth / $srcWidth );
}
- wfSuppressWarnings();
- $this->multiPageXML = new SimpleXMLElement( $this->metadata );
- wfRestoreWarnings();
}
/**
- * Returns 'true' if this image is a multipage document, e.g. a DJVU
- * document.
+ * Get an image size array like that returned by getimagesize(), or false if it
+ * can't be determined.
*
- * @return Bool
+ * @param string $fileName The filename
+ * @return array
*/
- function isMultipage() {
- return ( $this->mime == 'image/vnd.djvu' );
+ function getImageSize( $fileName ) {
+ $handler = $this->getHandler();
+ return $handler->getImageSize( $this, $fileName );
}
/**
- * Returns the number of pages of a multipage document, or NULL for
- * documents which aren't multipage documents
+ * Get the thumbnail extension and MIME type for a given source MIME type
+ * @return array thumbnail extension and MIME type
*/
- function pageCount() {
- if ( ! $this->isMultipage() ) {
- return null;
- }
- if ( ! isset( $this->multiPageXML ) ) {
- $this->initializeMultiPageXML();
+ static function getThumbType( $ext, $mime ) {
+ $handler = MediaHandler::getHandler( $mime );
+ if ( $handler ) {
+ return $handler->getThumbType( $ext, $mime );
+ } else {
+ return array( $ext, $mime );
}
- return count( $this->multiPageXML->xpath( '//OBJECT' ) );
}
-
+
} //class
+
/**
- * Wrapper class for thumbnail images
- * @package MediaWiki
+ * @addtogroup Media
*/
-class ThumbnailImage {
+class ArchivedFile
+{
/**
- * @param string $path Filesystem path to the thumb
- * @param string $url URL path to the thumb
- * @private
+ * Returns a file object from the filearchive table
+ * In the future, all current and old image storage
+ * may use FileStore. There will be a "old" storage
+ * for current and previous file revisions as well as
+ * the "deleted" group for archived revisions
+ * @param $title, the corresponding image page title
+ * @param $id, the image id, a unique key
+ * @param $key, optional storage key
+ * @return ResultWrapper
*/
- function ThumbnailImage( $url, $width, $height, $path = false ) {
- $this->url = $url;
- $this->width = round( $width );
- $this->height = round( $height );
- # These should be integers when they get here.
- # If not, there's a bug somewhere. But let's at
- # least produce valid HTML code regardless.
- $this->path = $path;
+ function ArchivedFile( $title, $id=0, $key='' ) {
+ if( !is_object( $title ) ) {
+ throw new MWException( 'Image constructor given bogus title.' );
+ }
+ $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'";
+ if( $title->getNamespace() == NS_IMAGE ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'filearchive',
+ array(
+ 'fa_id',
+ 'fa_name',
+ 'fa_storage_key',
+ 'fa_storage_group',
+ 'fa_size',
+ 'fa_bits',
+ 'fa_width',
+ 'fa_height',
+ 'fa_metadata',
+ 'fa_media_type',
+ 'fa_major_mime',
+ 'fa_minor_mime',
+ 'fa_description',
+ 'fa_user',
+ 'fa_user_text',
+ 'fa_timestamp',
+ 'fa_deleted' ),
+ array(
+ 'fa_name' => $title->getDbKey(),
+ $conds ),
+ __METHOD__,
+ array( 'ORDER BY' => 'fa_timestamp DESC' ) );
+
+ if ( $dbr->numRows( $res ) == 0 ) {
+ // this revision does not exist?
+ return;
+ }
+ $ret = $dbr->resultObject( $res );
+ $row = $ret->fetchObject();
+
+ // initialize fields for filestore image object
+ $this->mId = intval($row->fa_id);
+ $this->mName = $row->fa_name;
+ $this->mGroup = $row->fa_storage_group;
+ $this->mKey = $row->fa_storage_key;
+ $this->mSize = $row->fa_size;
+ $this->mBits = $row->fa_bits;
+ $this->mWidth = $row->fa_width;
+ $this->mHeight = $row->fa_height;
+ $this->mMetaData = $row->fa_metadata;
+ $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime";
+ $this->mType = $row->fa_media_type;
+ $this->mDescription = $row->fa_description;
+ $this->mUser = $row->fa_user;
+ $this->mUserText = $row->fa_user_text;
+ $this->mTimestamp = $row->fa_timestamp;
+ $this->mDeleted = $row->fa_deleted;
+ } else {
+ throw new MWException( 'This title does not correspond to an image page.' );
+ return;
+ }
+ return true;
}
/**
- * @return string The thumbnail URL
+ * int $field one of DELETED_* bitfield constants
+ * for file or revision rows
+ * @return bool
*/
- function getUrl() {
- return $this->url;
+ function isDeleted( $field ) {
+ return ($this->mDeleted & $field) == $field;
}
-
+
/**
- * Return HTML <img ... /> tag for the thumbnail, will include
- * width and height attributes and a blank alt text (as required).
- *
- * You can set or override additional attributes by passing an
- * associative array of name => data pairs. The data will be escaped
- * for HTML output, so should be in plaintext.
- *
- * @param array $attribs
- * @return string
- * @public
+ * Determine if the current user is allowed to view a particular
+ * field of this FileStore image file, if it's marked as deleted.
+ * @param int $field
+ * @return bool
*/
- function toHtml( $attribs = array() ) {
- $attribs['src'] = $this->url;
- $attribs['width'] = $this->width;
- $attribs['height'] = $this->height;
- if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
-
- $html = '<img ';
- foreach( $attribs as $name => $data ) {
- $html .= $name . '="' . htmlspecialchars( $data ) . '" ';
+ function userCan( $field ) {
+ if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) {
+ // images
+ global $wgUser;
+ $permission = ( $this->mDeleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
+ ? 'hiderevision'
+ : 'deleterevision';
+ wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
+ return $wgUser->isAllowed( $permission );
+ } else {
+ return true;
}
- $html .= '/>';
- return $html;
}
-
}
+/**
+ * Aliases for backwards compatibility with 1.6
+ */
+define( 'MW_IMG_DELETED_FILE', Image::DELETED_FILE );
+define( 'MW_IMG_DELETED_COMMENT', Image::DELETED_COMMENT );
+define( 'MW_IMG_DELETED_USER', Image::DELETED_USER );
+define( 'MW_IMG_DELETED_RESTRICTED', Image::DELETED_RESTRICTED );
+
?>
diff --git a/includes/ImageFunctions.php b/includes/ImageFunctions.php
index 931fdff1..d04110d4 100644
--- a/includes/ImageFunctions.php
+++ b/includes/ImageFunctions.php
@@ -21,7 +21,7 @@ function wfImageDir( $fname ) {
}
/**
- * Returns the image directory of an image's thubnail
+ * Returns the image directory of an image's thumbnail
* The result is an absolute path.
*
* This function is called from thumb.php before Setup.php is included
diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php
index 9d58b7f6..fba7714c 100644
--- a/includes/ImageGallery.php
+++ b/includes/ImageGallery.php
@@ -3,7 +3,6 @@ if ( ! defined( 'MEDIAWIKI' ) )
die( 1 );
/**
- * @package MediaWiki
*/
/**
@@ -11,23 +10,32 @@ if ( ! defined( 'MEDIAWIKI' ) )
*
* Add images to the gallery using add(), then render that list to HTML using toHTML().
*
- * @package MediaWiki
+ * @addtogroup Media
*/
class ImageGallery
{
var $mImages, $mShowBytes, $mShowFilename;
var $mCaption = false;
var $mSkin = false;
-
+
/**
* Is the gallery on a wiki page (i.e. not a special page)
*/
var $mParsing;
/**
+ * Contextual title, used when images are being screened
+ * against the bad image list
+ */
+ private $contextTitle = false;
+
+ private $mPerRow = 4; // How many images wide should the gallery be?
+ private $mWidths = 120, $mHeights = 120; // How wide/tall each thumbnail should be
+
+ /**
* Create a new image gallery object.
*/
- function ImageGallery( ) {
+ function __construct( ) {
$this->mImages = array();
$this->mShowBytes = true;
$this->mShowFilename = true;
@@ -40,7 +48,7 @@ class ImageGallery
function setParsing( $val = true ) {
$this->mParsing = $val;
}
-
+
/**
* Set the caption (as plain text)
*
@@ -49,25 +57,58 @@ class ImageGallery
function setCaption( $caption ) {
$this->mCaption = htmlspecialchars( $caption );
}
-
+
/**
* Set the caption (as HTML)
*
* @param $caption Caption
*/
- function setCaptionHtml( $caption ) {
+ public function setCaptionHtml( $caption ) {
$this->mCaption = $caption;
}
/**
+ * Set how many images will be displayed per row.
+ *
+ * @param int $num > 0; invalid numbers will be rejected
+ */
+ public function setPerRow( $num ) {
+ if ($num > 0) {
+ $this->mPerRow = (int)$num;
+ }
+ }
+
+ /**
+ * Set how wide each image will be, in pixels.
+ *
+ * @param int $num > 0; invalid numbers will be ignored
+ */
+ public function setWidths( $num ) {
+ if ($num > 0) {
+ $this->mWidths = (int)$num;
+ }
+ }
+
+ /**
+ * Set how high each image will be, in pixels.
+ *
+ * @param int $num > 0; invalid numbers will be ignored
+ */
+ public function setHeights( $num ) {
+ if ($num > 0) {
+ $this->mHeights = (int)$num;
+ }
+ }
+
+ /**
* Instruct the class to use a specific skin for rendering
*
* @param $skin Skin object
*/
function useSkin( $skin ) {
- $this->mSkin =& $skin;
+ $this->mSkin = $skin;
}
-
+
/**
* Return the skin that should be used
*
@@ -76,9 +117,9 @@ class ImageGallery
function getSkin() {
if( !$this->mSkin ) {
global $wgUser;
- $skin =& $wgUser->getSkin();
+ $skin = $wgUser->getSkin();
} else {
- $skin =& $this->mSkin;
+ $skin = $this->mSkin;
}
return $skin;
}
@@ -143,14 +184,15 @@ class ImageGallery
*
*/
function toHTML() {
- global $wgLang, $wgGenerateThumbnailOnParse;
+ global $wgLang;
$sk = $this->getSkin();
$s = '<table class="gallery" cellspacing="0" cellpadding="0">';
if( $this->mCaption )
- $s .= '<td class="galleryheader" colspan="4"><big>' . $this->mCaption . '</big></td>';
-
+ $s .= "\n\t<caption>{$this->mCaption}</caption>";
+
+ $params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
$i = 0;
foreach ( $this->mImages as $pair ) {
$img =& $pair[0];
@@ -160,20 +202,19 @@ class ImageGallery
if( $nt->getNamespace() != NS_IMAGE ) {
# We're dealing with a non-image, spit out the name and be done with it.
- $thumbhtml = '<div style="height: 152px;">' . htmlspecialchars( $nt->getText() ) . '</div>';
- }
- else if( $this->mParsing && wfIsBadImage( $nt->getDBkey() ) ) {
+ $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
+ . htmlspecialchars( $nt->getText() ) . '</div>';
+ } elseif( $this->mParsing && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() ) ) {
# The image is blacklisted, just show it as a text link.
- $thumbhtml = '<div style="height: 152px;">'
+ $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
. $sk->makeKnownLinkObj( $nt, htmlspecialchars( $nt->getText() ) ) . '</div>';
- } else if( !( $thumb = $img->getThumbnail( 120, 120, $wgGenerateThumbnailOnParse ) ) ) {
+ } elseif( !( $thumb = $img->transform( $params ) ) ) {
# Error generating thumbnail.
- $thumbhtml = '<div style="height: 152px;">'
+ $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
. htmlspecialchars( $img->getLastError() ) . '</div>';
- }
- else {
- $vpad = floor( ( 150 - $thumb->height ) /2 ) - 2;
- $thumbhtml = '<div class="thumb" style="padding: ' . $vpad . 'px 0;">'
+ } else {
+ $vpad = floor( ( 1.25*$this->mHeights - $thumb->height ) /2 ) - 2;
+ $thumbhtml = "\n\t\t\t".'<div class="thumb" style="padding: ' . $vpad . 'px 0; width: '.($this->mWidths+30).'px;">'
. $sk->makeKnownLinkObj( $nt, $thumb->toHtml() ) . '</div>';
}
@@ -200,27 +241,55 @@ class ImageGallery
# in version 4.8.6 generated crackpot html in its absence, see:
# http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar
- $s .= ($i%4==0) ? '<tr>' : '';
- $s .= '<td><div class="gallerybox">' . $thumbhtml
- . '<div class="gallerytext">' . "\n" . $textlink . $text . $nb
- . "</div></div></td>\n";
- $s .= ($i%4==3) ? '</tr>' : '';
- $i++;
+ if ( $i % $this->mPerRow == 0 ) {
+ $s .= "\n\t<tr>";
+ }
+ $s .=
+ "\n\t\t" . '<td><div class="gallerybox" style="width: '.($this->mWidths*1.25).'px;">'
+ . $thumbhtml
+ . "\n\t\t\t" . '<div class="gallerytext">' . "\n"
+ . $textlink . $text . $nb
+ . "\n\t\t\t</div>"
+ . "\n\t\t</div></td>";
+ if ( $i % $this->mPerRow == $this->mPerRow - 1 ) {
+ $s .= "\n\t</tr>";
+ }
+ ++$i;
}
- if( $i %4 != 0 ) {
- $s .= "</tr>\n";
+ if( $i % $this->mPerRow != 0 ) {
+ $s .= "\n\t</tr>";
}
- $s .= '</table>';
+ $s .= "\n</table>";
return $s;
}
-
+
/**
* @return int Number of images in the gallery
*/
public function count() {
return count( $this->mImages );
}
+
+ /**
+ * Set the contextual title
+ *
+ * @param Title $title Contextual title
+ */
+ public function setContextTitle( $title ) {
+ $this->contextTitle = $title;
+ }
+
+ /**
+ * Get the contextual title, if applicable
+ *
+ * @return mixed Title or false
+ */
+ public function getContextTitle() {
+ return is_object( $this->contextTitle ) && $this->contextTitle instanceof Title
+ ? $this->contextTitle
+ : false;
+ }
} //class
?>
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index 43b99130..13f8e46a 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -1,6 +1,5 @@
<?php
/**
- * @package MediaWiki
*/
/**
@@ -11,7 +10,8 @@ if( !defined( 'MEDIAWIKI' ) )
/**
* Special handling for image description pages
- * @package MediaWiki
+ *
+ * @addtogroup Media
*/
class ImagePage extends Article {
@@ -29,59 +29,62 @@ class ImagePage extends Article {
}
function view() {
- global $wgOut, $wgShowEXIF;
+ global $wgOut, $wgShowEXIF, $wgRequest, $wgUser;
$this->img = new Image( $this->mTitle );
- if( $this->mTitle->getNamespace() == NS_IMAGE ) {
- if ($wgShowEXIF && $this->img->exists()) {
- $exif = $this->img->getExifData();
- $showmeta = count($exif) ? true : false;
- } else {
- $exif = false;
- $showmeta = false;
- }
+ $diff = $wgRequest->getVal( 'diff' );
+ $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
- if ($this->img->exists())
- $wgOut->addHTML($this->showTOC($showmeta));
+ if ( $this->mTitle->getNamespace() != NS_IMAGE || ( isset( $diff ) && $diffOnly ) )
+ return Article::view();
- $this->openShowImage();
+ if ($wgShowEXIF && $this->img->exists()) {
+ $exif = $this->img->getExifData();
+ $showmeta = count($exif) ? true : false;
+ } else {
+ $exif = false;
+ $showmeta = false;
+ }
- # No need to display noarticletext, we use our own message, output in openShowImage()
- if( $this->getID() ) {
- Article::view();
- } else {
- # Just need to set the right headers
- $wgOut->setArticleFlag( true );
- $wgOut->setRobotpolicy( 'index,follow' );
- $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
- $this->viewUpdates();
- }
+ if ($this->img->exists())
+ $wgOut->addHTML($this->showTOC($showmeta));
- # Show shared description, if needed
- if( $this->mExtraDescription ) {
- $fol = wfMsg( 'shareddescriptionfollows' );
- if( $fol != '-' ) {
- $wgOut->addWikiText( $fol );
- }
- $wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . '</div>' );
- }
+ $this->openShowImage();
- $this->closeShowImage();
- $this->imageHistory();
- $this->imageLinks();
- if( $exif ) {
- global $wgStylePath, $wgStyleVersion;
- $expand = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-expand' ) ) );
- $collapse = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-collapse' ) ) );
- $wgOut->addHTML( "<h2 id=\"metadata\">" . wfMsgHtml( 'metadata' ) . "</h2>\n" );
- $wgOut->addWikiText( $this->makeMetadataTable( $exif ) );
- $wgOut->addHTML(
- "<script type=\"text/javascript\" src=\"$wgStylePath/common/metadata.js?$wgStyleVersion\"></script>\n" .
- "<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" );
- }
- } else {
+ # No need to display noarticletext, we use our own message, output in openShowImage()
+ if ( $this->getID() ) {
Article::view();
+ } else {
+ # Just need to set the right headers
+ $wgOut->setArticleFlag( true );
+ $wgOut->setRobotpolicy( 'index,follow' );
+ $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
+ $this->viewUpdates();
+ }
+
+ # Show shared description, if needed
+ if ( $this->mExtraDescription ) {
+ $fol = wfMsg( 'shareddescriptionfollows' );
+ if( $fol != '-' && !wfEmptyMsg( 'shareddescriptionfollows', $fol ) ) {
+ $wgOut->addWikiText( $fol );
+ }
+ $wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . '</div>' );
+ }
+
+ $this->closeShowImage();
+ $this->imageHistory();
+ $this->imageLinks();
+
+ if ( $exif ) {
+ global $wgStylePath, $wgStyleVersion;
+ $expand = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-expand' ) ) );
+ $collapse = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-collapse' ) ) );
+ $wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'metadata' ), wfMsg( 'metadata' ) ). "\n" );
+ $wgOut->addWikiText( $this->makeMetadataTable( $exif ) );
+ $wgOut->addHTML(
+ "<script type=\"text/javascript\" src=\"$wgStylePath/common/metadata.js?$wgStyleVersion\"></script>\n" .
+ "<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" );
}
}
@@ -165,15 +168,19 @@ class ImagePage extends Article {
function openShowImage() {
global $wgOut, $wgUser, $wgImageLimits, $wgRequest, $wgLang;
- global $wgUseImageResize, $wgGenerateThumbnailOnParse;
$full_url = $this->img->getURL();
- $anchoropen = '';
- $anchorclose = '';
+ $linkAttribs = false;
$sizeSel = intval( $wgUser->getOption( 'imagesize') );
-
if( !isset( $wgImageLimits[$sizeSel] ) ) {
$sizeSel = User::getDefaultOption( 'imagesize' );
+
+ // The user offset might still be incorrect, specially if
+ // $wgImageLimits got changed (see bug #8858).
+ if( !isset( $wgImageLimits[$sizeSel] ) ) {
+ // Default to the first offset in $wgImageLimits
+ $sizeSel = 0;
+ }
}
$max = $wgImageLimits[$sizeSel];
$maxWidth = $max[0];
@@ -183,21 +190,25 @@ class ImagePage extends Article {
if ( $this->img->exists() ) {
# image
$page = $wgRequest->getIntOrNull( 'page' );
- if ( ! is_null( $page ) ) {
- $this->img->selectPage( $page );
- } else {
+ if ( is_null( $page ) ) {
+ $params = array();
$page = 1;
+ } else {
+ $params = array( 'page' => $page );
}
- $width = $this->img->getWidth();
- $height = $this->img->getHeight();
+ $width_orig = $this->img->getWidth();
+ $width = $width_orig;
+ $height_orig = $this->img->getHeight();
+ $height = $height_orig;
+ $mime = $this->img->getMimeType();
$showLink = false;
+ $linkAttribs = array( 'href' => $full_url );
if ( $this->img->allowInlineDisplay() and $width and $height) {
# image
# "Download high res version" link below the image
- $msg = wfMsgHtml('showbigimage', $width, $height, intval( $this->img->getSize()/1024 ) );
-
+ $msgsize = wfMsgHtml('file-info-size', $width_orig, $height_orig, $sk->formatSize( $this->img->getSize() ), $mime );
# We'll show a thumbnail of this image
if ( $width > $maxWidth || $height > $maxHeight ) {
# Calculate the thumbnail size.
@@ -213,38 +224,41 @@ class ImagePage extends Article {
# Note that $height <= $maxHeight now, but might not be identical
# because of rounding.
}
-
- if( $wgUseImageResize ) {
- $thumbnail = $this->img->getThumbnail( $width, -1, $wgGenerateThumbnailOnParse );
- if ( $thumbnail == null ) {
- $url = $this->img->getViewURL();
- } else {
- $url = $thumbnail->getURL();
- }
- } else {
- # No resize ability? Show the full image, but scale
- # it down in the browser so it fits on the page.
- $url = $this->img->getViewURL();
- }
- $anchoropen = "<a href=\"{$full_url}\">";
- $anchorclose = "</a><br />";
- if( $this->img->mustRender() ) {
- $showLink = true;
- } else {
- $anchorclose .= "\n$anchoropen{$msg}</a>";
- }
+ $msgbig = wfMsgHtml( 'show-big-image' );
+ $msgsmall = wfMsgExt( 'show-big-image-thumb',
+ array( 'parseinline' ), $width, $height );
} else {
- $url = $this->img->getViewURL();
+ # Image is small enough to show full size on image page
+ $msgbig = htmlspecialchars( $this->img->getName() );
+ $msgsmall = wfMsgExt( 'file-nohires', array( 'parseinline' ) );
+ }
+
+ $params['width'] = $width;
+ $thumbnail = $this->img->transform( $params );
+
+ $anchorclose = "<br />";
+ if( $this->img->mustRender() ) {
$showLink = true;
+ } else {
+ $anchorclose .=
+ $msgsmall .
+ '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $msgsize;
}
if ( $this->img->isMultipage() ) {
$wgOut->addHTML( '<table class="multipageimage"><tr><td>' );
}
- $wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen .
- "<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" .
- htmlspecialchars( $this->img->getTitle()->getPrefixedText() ).'" />' . $anchorclose . '</div>' );
+ $imgAttribs = array(
+ 'border' => 0,
+ 'alt' => $this->img->getTitle()->getPrefixedText()
+ );
+
+ if ( $thumbnail ) {
+ $wgOut->addHTML( '<div class="fullImageLink" id="file">' .
+ $thumbnail->toHtml( $imgAttribs, $linkAttribs ) .
+ $anchorclose . '</div>' );
+ }
if ( $this->img->isMultipage() ) {
$count = $this->img->pageCount();
@@ -252,22 +266,26 @@ class ImagePage extends Article {
if ( $page > 1 ) {
$label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
$link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page-1) );
- $this->img->selectPage( $page - 1 );
- $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
+ $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none',
+ array( 'page' => $page - 1 ) );
} else {
$thumb1 = '';
}
if ( $page < $count ) {
$label = wfMsg( 'imgmultipagenext' );
- $this->img->selectPage( $page + 1 );
$link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page+1) );
- $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
+ $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none',
+ array( 'page' => $page + 1 ) );
} else {
$thumb2 = '';
}
- $select = '<form name="pageselector" action="' . $this->img->getEscapeLocalUrl( '' ) . '" method="GET" onchange="document.pageselector.submit();">' ;
+ global $wgScript;
+ $select = '<form name="pageselector" action="' .
+ htmlspecialchars( $wgScript ) .
+ '" method="get" onchange="document.pageselector.submit();">' .
+ Xml::hidden( 'title', $this->getTitle()->getPrefixedDbKey() );
$select .= $wgOut->parse( wfMsg( 'imgmultigotopre' ), false ) .
' <select id="pageselector" name="page">';
for ( $i=1; $i <= $count; $i++ ) {
@@ -279,7 +297,7 @@ class ImagePage extends Article {
htmlspecialchars( wfMsg( 'imgmultigo' ) ) . '"></form>';
$wgOut->addHTML( '</td><td><div class="multipageimagenavbox">' .
- "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
+ "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
}
} else {
#if direct link is allowed but it's not a renderable image, show an icon.
@@ -296,25 +314,26 @@ class ImagePage extends Article {
if ($showLink) {
- $filename = wfEscapeWikiText( $this->img->getName() );
- // Hacky workaround: for some reason we use the incorrect MIME type
- // image/svg for SVG. This should be fixed internally, but at least
- // make the displayed type right.
- $mime = $this->img->getMimeType();
+ // Workaround for incorrect MIME type on SVGs uploaded in previous versions
if ($mime == 'image/svg') $mime = 'image/svg+xml';
- $info = wfMsg( 'fileinfo',
- ceil($this->img->getSize()/1024.0),
- $mime );
+ $filename = wfEscapeWikiText( $this->img->getName() );
+ $info = wfMsg( 'file-info', $sk->formatSize( $this->img->getSize() ), $mime );
+ $infores = '';
+
+ // Check for MIME type. Other types may have more information in the future.
+ if (substr($mime,0,9) == 'image/svg' ) {
+ $infores = wfMsg('file-svg', $width_orig, $height_orig ) . '<br />';
+ }
global $wgContLang;
$dirmark = $wgContLang->getDirMark();
if (!$this->img->isSafeFile()) {
$warning = wfMsg( 'mediawarning' );
$wgOut->addWikiText( <<<END
-<div class="fullMedia">
+<div class="fullMedia">$infores
<span class="dangerousLink">[[Media:$filename|$filename]]</span>$dirmark
-<span class="fileInfo"> ($info)</span>
+<span class="fileInfo"> $info</span>
</div>
<div class="mediaWarning">$warning</div>
@@ -322,8 +341,8 @@ END
);
} else {
$wgOut->addWikiText( <<<END
-<div class="fullMedia">
-[[Media:$filename|$filename]]$dirmark <span class="fileInfo"> ($info)</span>
+<div class="fullMedia">$infores
+[[Media:$filename|$filename]]$dirmark <span class="fileInfo"> $info</span>
</div>
END
);
@@ -360,7 +379,9 @@ END
$wgOut->addHTML($sharedtext);
if ($wgRepositoryBaseUrl && $wgFetchCommonsDescriptions) {
- $text = Http::get($url . '?action=render');
+ $renderUrl = wfAppendQuery( $url, 'action=render' );
+ wfDebug( "Fetching shared description from $renderUrl\n" );
+ $text = Http::get( $renderUrl );
if ($text)
$this->mExtraDescription = $text;
}
@@ -389,11 +410,11 @@ END
# "Upload a new version of this file" link
if( $wgUser->isAllowed( 'reupload' ) ) {
$ulink = $sk->makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) );
- $wgOut->addHtml( "<li><div>{$ulink}</div></li>" );
+ $wgOut->addHtml( "<li><div class='plainlinks'>{$ulink}</div></li>" );
}
# External editing link
- $elink = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'edit-externally' ), 'action=edit&externaledit=true&mode=file' );
+ $elink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'edit-externally' ), 'action=edit&externaledit=true&mode=file' );
$wgOut->addHtml( '<li>' . $elink . '<div>' . wfMsgWikiHtml( 'edit-externally-help' ) . '</div></li>' );
$wgOut->addHtml( '</ul>' );
@@ -449,9 +470,9 @@ END
{
global $wgUser, $wgOut;
- $wgOut->addHTML( '<h2 id="filelinks">' . wfMsg( 'imagelinks' ) . "</h2>\n" );
+ $wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'filelinks' ), wfMsg( 'imagelinks' ) ) . "\n" );
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$page = $dbr->tableName( 'page' );
$imagelinks = $dbr->tableName( 'imagelinks' );
@@ -619,7 +640,7 @@ END
$wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
return;
}
- if ( ! $this->mTitle->userCanEdit() ) {
+ if ( ! $this->mTitle->userCan( 'edit' ) ) {
$wgOut->readOnlyPage( $this->getContent(), true );
return;
}
@@ -645,9 +666,6 @@ END
}
$oldver = wfTimestampNow() . "!{$name}";
- $dbr =& wfGetDB( DB_SLAVE );
- $size = $dbr->selectField( 'oldimage', 'oi_size', array( 'oi_archive_name' => $oldimage ) );
-
if ( ! rename( $curfile, "${archive}/{$oldver}" ) ) {
$wgOut->showFileRenameError( $curfile, "${archive}/{$oldver}" );
return;
@@ -683,6 +701,7 @@ END
wfDebug( "ImagePage::doPurge purging " . $this->img->getName() . "\n" );
$update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
$update->doUpdate();
+ $this->img->upgradeRow();
$this->img->purgeCache();
} else {
wfDebug( "ImagePage::doPurge no image\n" );
@@ -694,7 +713,7 @@ END
/**
* @todo document
- * @package MediaWiki
+ * @addtogroup Media
*/
class ImageHistoryList {
function ImageHistoryList( &$skin ) {
@@ -702,8 +721,9 @@ class ImageHistoryList {
}
function beginImageHistoryList() {
- $s = "\n<h2 id=\"filehistory\">" . wfMsg( 'imghistory' ) . "</h2>\n" .
- "<p>" . wfMsg( 'imghistlegend' ) . "</p>\n".'<ul class="special">';
+ $s = "\n" .
+ Xml::element( 'h2', array( 'id' => 'filehistory' ), wfMsg( 'imghistory' ) ) .
+ "\n<p>" . wfMsg( 'imghistlegend' ) . "</p>\n".'<ul class="special">';
return $s;
}
@@ -716,9 +736,9 @@ class ImageHistoryList {
global $wgUser, $wgLang, $wgTitle, $wgContLang;
$datetime = $wgLang->timeanddate( $timestamp, true );
- $del = wfMsg( 'deleteimg' );
- $delall = wfMsg( 'deleteimgcompletely' );
- $cur = wfMsg( 'cur' );
+ $del = wfMsgHtml( 'deleteimg' );
+ $delall = wfMsgHtml( 'deleteimgcompletely' );
+ $cur = wfMsgHtml( 'cur' );
if ( $iscur ) {
$url = Image::imageUrl( $img );
@@ -734,10 +754,10 @@ class ImageHistoryList {
}
} else {
$url = htmlspecialchars( wfImageArchiveUrl( $img ) );
- if( $wgUser->getID() != 0 && $wgTitle->userCanEdit() ) {
+ if( $wgUser->getID() != 0 && $wgTitle->userCan( 'edit' ) ) {
$token = urlencode( $wgUser->editToken( $img ) );
$rlink = $this->skin->makeKnownLinkObj( $wgTitle,
- wfMsg( 'revertimg' ), 'action=revert&oldimage=' .
+ wfMsgHtml( 'revertimg' ), 'action=revert&oldimage=' .
urlencode( $img ) . "&wpEditToken=$token" );
$dlink = $this->skin->makeKnownLinkObj( $wgTitle,
$del, 'action=delete&oldimage=' . urlencode( $img ) .
@@ -746,7 +766,7 @@ class ImageHistoryList {
# Having live active links for non-logged in users
# means that bots and spiders crawling our site can
# inadvertently change content. Baaaad idea.
- $rlink = wfMsg( 'revertimg' );
+ $rlink = wfMsgHtml( 'revertimg' );
$dlink = $del;
}
}
@@ -754,7 +774,7 @@ class ImageHistoryList {
$userlink = $this->skin->userLink( $user, $usertext ) . $this->skin->userToolLinks( $user, $usertext );
$nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $size ) );
- $widthheight = wfMsg( 'widthheight', $width, $height );
+ $widthheight = wfMsgHtml( 'widthheight', $width, $height );
$style = $this->skin->getInternalLinkAttributes( $url, $datetime );
$s = "<li> ({$dlink}) ({$rlink}) <a href=\"{$url}\"{$style}>{$datetime}</a> . . {$userlink} . . {$widthheight} ({$nbytes})";
diff --git a/includes/ImageQueryPage.php b/includes/ImageQueryPage.php
new file mode 100644
index 00000000..93f090a1
--- /dev/null
+++ b/includes/ImageQueryPage.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Variant of QueryPage which uses a gallery to output results, thus
+ * suited for reports generating images
+ *
+ * @package MediaWiki
+ * @addtogroup SpecialPage
+ * @author Rob Church <robchur@gmail.com>
+ */
+class ImageQueryPage extends QueryPage {
+
+ /**
+ * Format and output report results using the given information plus
+ * OutputPage
+ *
+ * @param OutputPage $out OutputPage to print to
+ * @param Skin $skin User skin to use
+ * @param Database $dbr Database (read) connection to use
+ * @param int $res Result pointer
+ * @param int $num Number of available result rows
+ * @param int $offset Paging offset
+ */
+ protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
+ if( $num > 0 ) {
+ $gallery = new ImageGallery();
+ $gallery->useSkin( $skin );
+
+ # $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++ ) {
+ $image = $this->prepareImage( $row );
+ if( $image instanceof Image ) {
+ $gallery->add( $image, $this->getCellHtml( $row ) );
+ }
+ }
+
+ $out->addHtml( $gallery->toHtml() );
+ }
+ }
+
+ /**
+ * Prepare an image object given a result row
+ *
+ * @param object $row Result row
+ * @return Image
+ */
+ private function prepareImage( $row ) {
+ $namespace = isset( $row->namespace ) ? $row->namespace : NS_IMAGE;
+ $title = Title::makeTitleSafe( $namespace, $row->title );
+ return ( $title instanceof Title && $title->getNamespace() == NS_IMAGE )
+ ? new Image( $title )
+ : null;
+ }
+
+ /**
+ * Get additional HTML to be shown in a results' cell
+ *
+ * @param object $row Result row
+ * @return string
+ */
+ protected function getCellHtml( $row ) {
+ return '';
+ }
+
+}
+
+?>
diff --git a/includes/JobQueue.php b/includes/JobQueue.php
index 746cf5de..140130fa 100644
--- a/includes/JobQueue.php
+++ b/includes/JobQueue.php
@@ -4,6 +4,9 @@ if ( !defined( 'MEDIAWIKI' ) ) {
die( "This file is part of MediaWiki, it is not a valid entry point\n" );
}
+/**
+ * Class to both describe a background job and handle jobs.
+ */
abstract class Job {
var $command,
$title,
@@ -13,10 +16,20 @@ abstract class Job {
$error;
/*-------------------------------------------------------------------------
+ * Abstract functions
+ *------------------------------------------------------------------------*/
+
+ /**
+ * Run the job
+ * @return boolean success
+ */
+ abstract function run();
+
+ /*-------------------------------------------------------------------------
* Static functions
*------------------------------------------------------------------------*/
- /**
+ /**
* @deprecated use LinksUpdate::queueRecursiveJobs()
*/
/**
@@ -26,25 +39,41 @@ abstract class Job {
/**
* Pop a job off the front of the queue
* @static
+ * @param $offset Number of jobs to skip
* @return Job or false if there's no jobs
*/
- static function pop() {
+ static function pop($offset=0) {
wfProfileIn( __METHOD__ );
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
- // Get a job from the slave
- $row = $dbr->selectRow( 'job', '*', '', __METHOD__,
- array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 )
- );
+ /* Get a job from the slave, start with an offset,
+ scan full set afterwards, avoid hitting purged rows
- if ( $row === false ) {
- wfProfileOut( __METHOD__ );
- return false;
+ NB: If random fetch previously was used, offset
+ will always be ahead of few entries
+ */
+
+ $row = $dbr->selectRow( 'job', '*', "job_id >= ${offset}", __METHOD__,
+ array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ));
+
+ // Refetching without offset is needed as some of job IDs could have had delayed commits
+ // and have lower IDs than jobs already executed, blame concurrency :)
+ //
+ if ( $row === false) {
+ if ($offset!=0)
+ $row = $dbr->selectRow( 'job', '*', '', __METHOD__,
+ array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ));
+
+ if ($row === false ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
}
+ $offset = $row->job_id;
// Try to delete it from the master
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
$affected = $dbw->affectedRows();
$dbw->immediateCommit();
@@ -53,7 +82,7 @@ abstract class Job {
// Failed, someone else beat us to it
// Try getting a random row
$row = $dbw->selectRow( 'job', array( 'MIN(job_id) as minjob',
- 'MAX(job_id) as maxjob' ), '', __METHOD__ );
+ 'MAX(job_id) as maxjob' ), "job_id >= $offset", __METHOD__ );
if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) {
// No jobs to get
wfProfileOut( __METHOD__ );
@@ -61,7 +90,7 @@ abstract class Job {
}
// Get the random row
$row = $dbw->selectRow( 'job', '*',
- array( 'job_id' => mt_rand( $row->minjob, $row->maxjob ) ), __METHOD__ );
+ 'job_id >= ' . mt_rand( $row->minjob, $row->maxjob ), __METHOD__ );
if ( $row === false ) {
// Random job gone before we got the chance to select it
// Give up
@@ -72,7 +101,7 @@ abstract class Job {
$dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
$affected = $dbw->affectedRows();
$dbw->immediateCommit();
-
+
if ( !$affected ) {
// Random job gone before we exclusively deleted it
// Give up
@@ -80,22 +109,22 @@ abstract class Job {
return false;
}
}
-
+
// If execution got to here, there's a row in $row that has been deleted from the database
// by this thread. Hence the concurrent pop was successful.
$namespace = $row->job_namespace;
$dbkey = $row->job_title;
$title = Title::makeTitleSafe( $namespace, $dbkey );
$job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id );
-
+
// Remove any duplicates it may have later in the queue
$dbw->delete( 'job', $job->insertFields(), __METHOD__ );
-
+
wfProfileOut( __METHOD__ );
return $job;
}
- /**
+ /**
* Create an object of a subclass
*/
static function factory( $command, $title, $params = false, $id = 0 ) {
@@ -126,6 +155,27 @@ abstract class Job {
}
}
+ /**
+ * Batch-insert a group of jobs into the queue.
+ * This will be wrapped in a transaction with a forced commit.
+ *
+ * This may add duplicate at insert time, but they will be
+ * removed later on, when the first one is popped.
+ *
+ * @param $jobs array of Job objects
+ */
+ static function batchInsert( $jobs ) {
+ if( count( $jobs ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+ foreach( $jobs as $job ) {
+ $rows[] = $job->insertFields();
+ }
+ $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+ $dbw->commit();
+ }
+ }
+
/*-------------------------------------------------------------------------
* Non-static functions
*------------------------------------------------------------------------*/
@@ -147,8 +197,8 @@ abstract class Job {
function insert() {
$fields = $this->insertFields();
- $dbw =& wfGetDB( DB_MASTER );
-
+ $dbw = wfGetDB( DB_MASTER );
+
if ( $this->removeDuplicates ) {
$res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ );
if ( $dbw->numRows( $res ) ) {
@@ -158,7 +208,7 @@ abstract class Job {
$fields['job_id'] = $dbw->nextSequenceValue( 'job_job_id_seq' );
$dbw->insert( 'job', $fields, __METHOD__ );
}
-
+
protected function insertFields() {
return array(
'job_cmd' => $this->command,
@@ -167,34 +217,7 @@ abstract class Job {
'job_params' => Job::makeBlob( $this->params )
);
}
-
- /**
- * Batch-insert a group of jobs into the queue.
- * This will be wrapped in a transaction with a forced commit.
- *
- * This may add duplicate at insert time, but they will be
- * removed later on, when the first one is popped.
- *
- * @param $jobs array of Job objects
- */
- static function batchInsert( $jobs ) {
- if( count( $jobs ) ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin();
- foreach( $jobs as $job ) {
- $rows[] = $job->insertFields();
- }
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $dbw->commit();
- }
- }
- /**
- * Run the job
- * @return boolean success
- */
- abstract function run();
-
function toString() {
$paramString = '';
if ( $this->params ) {
@@ -222,6 +245,10 @@ abstract class Job {
}
}
+
+/**
+ * Background job to update links for a given title.
+ */
class RefreshLinksJob extends Job {
function __construct( $title, $params = '', $id = 0 ) {
parent::__construct( 'refreshLinks', $title, $params, $id );
@@ -237,7 +264,7 @@ class RefreshLinksJob extends Job {
$linkCache =& LinkCache::singleton();
$linkCache->clear();
-
+
if ( is_null( $this->title ) ) {
$this->error = "refreshLinks: Invalid title";
wfProfileOut( __METHOD__ );
diff --git a/includes/Licenses.php b/includes/Licenses.php
index dd1308b4..f4586ae5 100644
--- a/includes/Licenses.php
+++ b/includes/Licenses.php
@@ -1,9 +1,8 @@
<?php
/**
* A License class for use on Special:Upload
- *
- * @package MediaWiki
- * @subpackage SpecialPage
+ *
+ * @addtogroup SpecialPage
*
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
* @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
@@ -31,12 +30,12 @@ class Licenses {
/**#@-*/
/**
- * Constrictor
+ * Constructor
*
* @param $str String: the string to build the licenses member from, will use
* wfMsgForContent( 'licenses' ) if null (default: null)
*/
- function Licenses( $str = null ) {
+ function __construct( $str = null ) {
// PHP sucks, this should be possible in the constructor
$this->msg = is_null( $str ) ? wfMsgForContent( 'licenses' ) : $str;
$this->html = '';
@@ -147,6 +146,9 @@ class Licenses {
function getHtml() { return $this->html; }
}
+/**
+ * A License class for use on Special:Upload (represents a single type of license).
+ */
class License {
/**
* @var string
diff --git a/includes/LinkBatch.php b/includes/LinkBatch.php
index 61e1c040..065c540a 100644
--- a/includes/LinkBatch.php
+++ b/includes/LinkBatch.php
@@ -4,8 +4,7 @@
* Class representing a list of titles
* The execute() method checks them all for existence and adds them to a LinkCache object
+
- * @package MediaWiki
- * @subpackage Cache
+ * @addtogroup Cache
*/
class LinkBatch {
/**
@@ -13,7 +12,7 @@ class LinkBatch {
*/
var $data = array();
- function LinkBatch( $arr = array() ) {
+ function __construct( $arr = array() ) {
foreach( $arr as $item ) {
$this->addObj( $item );
}
@@ -120,7 +119,7 @@ class LinkBatch {
// Construct query
// This is very similar to Parser::replaceLinkHolders
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$page = $dbr->tableName( 'page' );
$set = $this->constructSet( 'page', $dbr );
if ( $set === false ) {
diff --git a/includes/LinkCache.php b/includes/LinkCache.php
index 8e56225b..53fb640a 100644
--- a/includes/LinkCache.php
+++ b/includes/LinkCache.php
@@ -1,13 +1,8 @@
<?php
/**
* Cache for article titles (prefixed DB keys) and ids linked from one source
- * @package MediaWiki
- * @subpackage Cache
- */
-
-/**
- * @package MediaWiki
- * @subpackage Cache
+ *
+ * @addtogroup Cache
*/
class LinkCache {
// Increment $mClassVer whenever old serialized versions of this class
@@ -29,7 +24,7 @@ class LinkCache {
return $instance;
}
- function LinkCache() {
+ function __construct() {
$this->mForUpdate = false;
$this->mPageLinks = array();
$this->mGoodLinks = array();
@@ -135,14 +130,14 @@ class LinkCache {
$id = $wgMemc->get( $key = $this->getKey( $title ) );
if( ! is_integer( $id ) ) {
if ( $this->mForUpdate ) {
- $db =& wfGetDB( DB_MASTER );
+ $db = wfGetDB( DB_MASTER );
if ( !( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) ) {
$options = array( 'FOR UPDATE' );
} else {
$options = array();
}
} else {
- $db =& wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_SLAVE );
$options = array();
}
diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php
index e03b59dd..39341d5d 100644
--- a/includes/LinkFilter.php
+++ b/includes/LinkFilter.php
@@ -14,7 +14,7 @@ class LinkFilter {
/**
* @static
*/
- function matchEntry( $text, $filterEntry ) {
+ static function matchEntry( $text, $filterEntry ) {
$regex = LinkFilter::makeRegex( $filterEntry );
return preg_match( $regex, $text );
}
@@ -22,10 +22,10 @@ class LinkFilter {
/**
* @static
*/
- function makeRegex( $filterEntry ) {
+ private static function makeRegex( $filterEntry ) {
$regex = '!http://';
if ( substr( $filterEntry, 0, 2 ) == '*.' ) {
- $regex .= '([A-Za-z0-9.-]+\.|)';
+ $regex .= '(?:[A-Za-z0-9.-]+\.|)';
$filterEntry = substr( $filterEntry, 2 );
}
$regex .= preg_quote( $filterEntry, '!' ) . '!Si';
@@ -47,8 +47,10 @@ class LinkFilter {
* Asterisks in any other location are considered invalid.
*
* @static
+ * @param $filterEntry String: domainparts
+ * @param $prot String: protocol
*/
- function makeLike( $filterEntry ) {
+ public static function makeLike( $filterEntry , $prot = 'http://' ) {
if ( substr( $filterEntry, 0, 2 ) == '*.' ) {
$subdomains = true;
$filterEntry = substr( $filterEntry, 2 );
@@ -74,17 +76,31 @@ class LinkFilter {
$path = '/';
$host = $filterEntry;
}
- $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
- if ( substr( $host, -1, 1 ) !== '.' ) {
- $host .= '.';
- }
- $like = "http://$host";
-
- if ( $subdomains ) {
- $like .= '%';
- }
- if ( !$subdomains || $path !== '/' ) {
- $like .= $path . '%';
+ // Reverse the labels in the hostname, convert to lower case
+ // For emails reverse domainpart only
+ if ( $prot == 'mailto:' && strpos($host, '@') ) {
+ // complete email adress
+ $mailparts = explode( '@', $host );
+ $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
+ $host = $domainpart . '@' . $mailparts[0];
+ $like = "$prot$host%";
+ } elseif ( $prot == 'mailto:' ) {
+ // domainpart of email adress only. do not add '.'
+ $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
+ $like = "$prot$host%";
+ } else {
+ $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) );
+ if ( substr( $host, -1, 1 ) !== '.' ) {
+ $host .= '.';
+ }
+ $like = "$prot$host";
+
+ if ( $subdomains ) {
+ $like .= '%';
+ }
+ if ( !$subdomains || $path !== '/' ) {
+ $like .= $path . '%';
+ }
}
return $like;
}
diff --git a/includes/Linker.php b/includes/Linker.php
index 0eabab2f..b12e2ad0 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -4,19 +4,15 @@
* These functions are used for primarily page content:
* links, embedded images, table of contents. Links are
* also used in the skin.
- * @package MediaWiki
- */
-
-/**
* For the moment, Skin is a descendent class of Linker.
* In the future, it should probably be further split
* so that ever other bit of the wiki doesn't have to
* go loading up Skin to get at it.
*
- * @package MediaWiki
+ * @addtogroup Skins
*/
class Linker {
- function Linker() {}
+ function __construct() {}
/**
* @deprecated
@@ -229,7 +225,7 @@ class Linker {
} else {
$threshold = $wgUser->getOption('stubthreshold') ;
if ( $threshold > 0 ) {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$s = $dbr->selectRow(
array( 'page' ),
array( 'page_len',
@@ -358,16 +354,8 @@ class Linker {
* the end of the link.
*/
function makeStubLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
- $u = $nt->escapeLocalURL( $query );
-
- if ( '' == $text ) {
- $text = htmlspecialchars( $nt->getPrefixedText() );
- }
$style = $this->getInternalLinkAttributesObj( $nt, $text, 'stub' );
-
- list( $inside, $trail ) = Linker::splitTrail( $trail );
- $s = "<a href=\"{$u}\"{$style}>{$prefix}{$text}{$inside}</a>{$trail}";
- return $s;
+ return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
}
/**
@@ -431,25 +419,19 @@ class Linker {
}
/** @todo document */
- function makeImageLinkObj( $nt, $label, $alt, $align = '', $width = false, $height = false, $framed = false,
- $thumb = false, $manual_thumb = '', $page = null )
+ function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false,
+ $thumb = false, $manual_thumb = '', $valign = '' )
{
- global $wgContLang, $wgUser, $wgThumbLimits, $wgGenerateThumbnailOnParse;
+ global $wgContLang, $wgUser, $wgThumbLimits;
$img = new Image( $nt );
- if ( ! is_null( $page ) ) {
- $img->selectPage( $page );
- }
-
if ( !$img->allowInlineDisplay() && $img->exists() ) {
return $this->makeKnownLinkObj( $nt );
}
- $url = $img->getViewURL();
$error = $prefix = $postfix = '';
-
- wfDebug( "makeImageLinkObj: '$width'x'$height', \"$label\"\n" );
+ $page = isset( $params['page'] ) ? $params['page'] : false;
if ( 'center' == $align )
{
@@ -458,6 +440,19 @@ class Linker {
$align = 'none';
}
+ if ( !isset( $params['width'] ) ) {
+ $params['width'] = $img->getWidth( $page );
+ if( $thumb || $framed ) {
+ $wopt = $wgUser->getOption( 'thumbsize' );
+
+ if( !isset( $wgThumbLimits[$wopt] ) ) {
+ $wopt = User::getDefaultOption( 'thumbsize' );
+ }
+
+ $params['width'] = min( $params['width'], $wgThumbLimits[$wopt] );
+ }
+ }
+
if ( $thumb || $framed ) {
# Create a thumbnail. Alignment depends on language
@@ -470,70 +465,39 @@ class Linker {
if ( $align == '' ) {
$align = $wgContLang->isRTL() ? 'left' : 'right';
}
-
-
- if ( $width === false ) {
- $wopt = $wgUser->getOption( 'thumbsize' );
-
- if( !isset( $wgThumbLimits[$wopt] ) ) {
- $wopt = User::getDefaultOption( 'thumbsize' );
- }
-
- $width = min( $img->getWidth(), $wgThumbLimits[$wopt] );
- }
-
- return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $width, $height, $framed, $manual_thumb ).$postfix;
+ return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
}
- if ( $width && $img->exists() ) {
-
- # Create a resized image, without the additional thumbnail
- # features
-
- if ( $height == false )
- $height = -1;
- if ( $manual_thumb == '') {
- $thumb = $img->getThumbnail( $width, $height, $wgGenerateThumbnailOnParse );
- if ( $thumb ) {
- // In most cases, $width = $thumb->width or $height = $thumb->height.
- // If not, we're scaling the image larger than it can be scaled,
- // so we send to the browser a smaller thumbnail, and let the client do the scaling.
-
- if ($height != -1 && $width > $thumb->width * $height / $thumb->height) {
- // $height is the limiting factor, not $width
- // set $width to the largest it can be, such that the resulting
- // scaled height is at most $height
- $width = floor($thumb->width * $height / $thumb->height);
- }
- $height = round($thumb->height * $width / $thumb->width);
+ if ( $params['width'] && $img->exists() ) {
+ # Create a resized image, without the additional thumbnail features
+ $thumb = $img->transform( $params );
+ } else {
+ $thumb = false;
+ }
- wfDebug( "makeImageLinkObj: client-size set to '$width x $height'\n" );
- $url = $thumb->getUrl();
- } else {
- $error = htmlspecialchars( $img->getLastError() );
- // Do client-side scaling...
- $height = intval( $img->getHeight() * $width / $img->getWidth() );
- }
- }
+ if ( $page ) {
+ $query = 'page=' . urlencode( $page );
} else {
- $width = $img->width;
- $height = $img->height;
+ $query = '';
+ }
+ $u = $nt->getLocalURL( $query );
+ $imgAttribs = array(
+ 'alt' => $alt,
+ 'longdesc' => $u
+ );
+ if ( $valign ) {
+ $imgAttribs['style'] = "vertical-align: $valign";
}
+ $linkAttribs = array(
+ 'href' => $u,
+ 'class' => 'image',
+ 'title' => $alt
+ );
- wfDebug( "makeImageLinkObj2: '$width'x'$height'\n" );
- $u = $nt->escapeLocalURL();
- if ( $error ) {
- $s = $error;
- } elseif ( $url == '' ) {
+ if ( !$thumb ) {
$s = $this->makeBrokenImageLinkObj( $img->getTitle() );
- //$s .= "<br />{$alt}<br />{$url}<br />\n";
} else {
- $s = '<a href="'.$u.'" class="image" title="'.$alt.'">' .
- '<img src="'.$url.'" alt="'.$alt.'" ' .
- ( $width
- ? ( 'width="'.$width.'" height="'.$height.'" ' )
- : '' ) .
- 'longdesc="'.$u.'" /></a>';
+ $s = $thumb->toHtml( $imgAttribs, $linkAttribs );
}
if ( '' != $align ) {
$s = "<div class=\"float{$align}\"><span>{$s}</span></div>";
@@ -545,86 +509,64 @@ class Linker {
* Make HTML for a thumbnail including image, border and caption
* $img is an Image object
*/
- function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $boxwidth = 180, $boxheight=false, $framed=false , $manual_thumb = "" ) {
- global $wgStylePath, $wgContLang, $wgGenerateThumbnailOnParse;
+ function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
+ global $wgStylePath, $wgContLang;
$thumbUrl = '';
$error = '';
- $width = $height = 0;
- if ( $img->exists() ) {
- $width = $img->getWidth();
- $height = $img->getHeight();
- }
- if ( 0 == $width || 0 == $height ) {
- $width = $height = 180;
- }
- if ( $boxwidth == 0 ) {
- $boxwidth = 180;
+ $page = isset( $params['page'] ) ? $params['page'] : false;
+
+ if ( empty( $params['width'] ) ) {
+ $params['width'] = 180;
}
- if ( $framed ) {
+ $thumb = false;
+ if ( $manual_thumb != '' ) {
+ # Use manually specified thumbnail
+ $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
+ if( $manual_title ) {
+ $manual_img = new Image( $manual_title );
+ $thumb = $manual_img->getUnscaledThumb();
+ }
+ } elseif ( $framed ) {
// Use image dimensions, don't scale
- $boxwidth = $width;
- $boxheight = $height;
- $thumbUrl = $img->getViewURL();
+ $thumb = $img->getUnscaledThumb( $page );
} else {
- if ( $boxheight === false )
- $boxheight = -1;
- if ( '' == $manual_thumb ) {
- $thumb = $img->getThumbnail( $boxwidth, $boxheight, $wgGenerateThumbnailOnParse );
- if ( $thumb ) {
- $thumbUrl = $thumb->getUrl();
- $boxwidth = $thumb->width;
- $boxheight = $thumb->height;
- } else {
- $error = $img->getLastError();
- }
- }
+ $thumb = $img->transform( $params );
}
- $oboxwidth = $boxwidth + 2;
- if ( $manual_thumb != '' ) # Use manually specified thumbnail
- {
- $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); #new Title ( $manual_thumb ) ;
- if( $manual_title ) {
- $manual_img = new Image( $manual_title );
- $thumbUrl = $manual_img->getViewURL();
- if ( $manual_img->exists() )
- {
- $width = $manual_img->getWidth();
- $height = $manual_img->getHeight();
- $boxwidth = $width ;
- $boxheight = $height ;
- $oboxwidth = $boxwidth + 2 ;
- }
- }
+ if ( $thumb ) {
+ $outerWidth = $thumb->getWidth() + 2;
+ } else {
+ $outerWidth = $params['width'] + 2;
}
- $u = $img->getEscapeLocalURL();
+ $query = $page ? 'page=' . urlencode( $page ) : '';
+ $u = $img->getTitle()->getLocalURL( $query );
$more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
$magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
$textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
- $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$oboxwidth}px;\">";
- if( $thumbUrl == '' ) {
- // Couldn't generate thumbnail? Scale the image client-side.
- $thumbUrl = $img->getViewURL();
- if( $boxheight == -1 ) {
- // Approximate...
- $boxheight = intval( $height * $boxwidth / $width );
- }
- }
- if ( $error ) {
- $s .= htmlspecialchars( $error );
+ $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
+ if ( !$thumb ) {
+ $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
$zoomicon = '';
} elseif( !$img->exists() ) {
$s .= $this->makeBrokenImageLinkObj( $img->getTitle() );
$zoomicon = '';
} else {
- $s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'.
- '<img src="'.$thumbUrl.'" alt="'.$alt.'" ' .
- 'width="'.$boxwidth.'" height="'.$boxheight.'" ' .
- 'longdesc="'.$u.'" class="thumbimage" /></a>';
+ $imgAttribs = array(
+ 'alt' => $alt,
+ 'longdesc' => $u,
+ 'class' => 'thumbimage'
+ );
+ $linkAttribs = array(
+ 'href' => $u,
+ 'class' => 'internal',
+ 'title' => $alt
+ );
+
+ $s .= $thumb->toHtml( $imgAttribs, $linkAttribs );
if ( $framed ) {
$zoomicon="";
} else {
@@ -680,8 +622,6 @@ class Linker {
*
* @param $title Title object.
* @param $text String: pre-sanitized HTML
- * @param $nourl Boolean: Mask absolute URLs, so the parser doesn't
- * linkify them (it is currently not context-aware)
* @return string HTML
*
* @public
@@ -756,10 +696,10 @@ class Linker {
/**
* @param $userId Integer: user id in database.
* @param $userText String: user name in database.
+ * @param $redContribsWhenNoEdits Bool: return a red contribs link when the user had no edits and this is true.
* @return string HTML fragment with talk and/or block links
- * @private
*/
- function userToolLinks( $userId, $userText ) {
+ public function userToolLinks( $userId, $userText, $redContribsWhenNoEdits = false ) {
global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans;
$talkable = !( $wgDisableAnonTalk && 0 == $userId );
$blockable = ( $wgSysopUserBans || 0 == $userId );
@@ -769,9 +709,15 @@ class Linker {
$items[] = $this->userTalkLink( $userId, $userText );
}
if( $userId ) {
+ // check if the user has an edit
+ if( $redContribsWhenNoEdits && User::edits( $userId ) == 0 ) {
+ $style = "class='new'";
+ } else {
+ $style = '';
+ }
$contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
- $items[] = $this->makeKnownLinkObj( $contribsPage ,
- wfMsgHtml( 'contribslink' ) );
+
+ $items[] = $this->makeKnownLinkObj( $contribsPage, wfMsgHtml( 'contribslink' ), '', '', '', '', $style );
}
if( $blockable && $wgUser->isAllowed( 'block' ) ) {
$items[] = $this->blockLink( $userId, $userText );
@@ -785,17 +731,22 @@ class Linker {
}
/**
+ * Alias for userToolLinks( $userId, $userText, true );
+ */
+ public function userToolLinksRedContribs( $userId, $userText ) {
+ return $this->userToolLinks( $userId, $userText, true );
+ }
+
+
+ /**
* @param $userId Integer: user id in database.
* @param $userText String: user name in database.
* @return string HTML fragment with user talk link
* @private
*/
function userTalkLink( $userId, $userText ) {
- global $wgLang;
- $talkname = $wgLang->getNsText( NS_TALK ); # use the shorter name
-
$userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
- $userTalkLink = $this->makeLinkObj( $userTalkPage, $talkname );
+ $userTalkLink = $this->makeLinkObj( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
return $userTalkLink;
}
@@ -860,7 +811,7 @@ class Linker {
* Since you can't set a default parameter for a reference, I've turned it
* temporarily to a value pass. Should be adjusted further. --brion
*
- * $param string $comment
+ * @param string $comment
* @param mixed $title Title object (to generate link to the section in autocomment) or null
* @param bool $local Whether section links should refer to local page
*/
@@ -1013,7 +964,7 @@ class Linker {
/** @todo document */
function tocList($toc) {
global $wgJsMimeType;
- $title = wfMsgForContent('toc') ;
+ $title = wfMsgHtml('toc') ;
return
'<table id="toc" class="toc" summary="' . $title .'"><tr><td>'
. '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
@@ -1023,8 +974,8 @@ class Linker {
. "</ul>\n</td></tr></table>"
. '<script type="' . $wgJsMimeType . '">'
. ' if (window.showTocToggle) {'
- . ' var tocShowText = "' . wfEscapeJsString( wfMsgForContent('showtoc') ) . '";'
- . ' var tocHideText = "' . wfEscapeJsString( wfMsgForContent('hidetoc') ) . '";'
+ . ' var tocShowText = "' . wfEscapeJsString( wfMsg('showtoc') ) . '";'
+ . ' var tocHideText = "' . wfEscapeJsString( wfMsg('hidetoc') ) . '";'
. ' showTocToggle();'
. ' } '
. "</script>\n";
@@ -1134,7 +1085,7 @@ class Linker {
global $wgUser;
wfProfileIn( __METHOD__ );
- $sk =& $wgUser->getSkin();
+ $sk = $wgUser->getSkin();
$outText = '';
if ( count( $templates ) > 0 ) {
@@ -1182,10 +1133,14 @@ class Linker {
*/
public function formatSize( $size ) {
global $wgLang;
+ // For small sizes no decimal places necessary
+ $round = 0;
if( $size > 1024 ) {
$size = $size / 1024;
if( $size > 1024 ) {
$size = $size / 1024;
+ // For MB and bigger two decimal places are smarter
+ $round = 2;
if( $size > 1024 ) {
$size = $size / 1024;
$msg = 'size-gigabytes';
@@ -1198,10 +1153,59 @@ class Linker {
} else {
$msg = 'size-bytes';
}
- $size = round( $size, 0 );
+ $size = round( $size, $round );
return wfMsgHtml( $msg, $wgLang->formatNum( $size ) );
}
-
+
+ /**
+ * Given the id of an interface element, constructs the appropriate title
+ * and accesskey attributes from the system messages. (Note, this is usu-
+ * ally the id but isn't always, because sometimes the accesskey needs to
+ * go on a different element than the id, for reverse-compatibility, etc.)
+ *
+ * @param string $name Id of the element, minus prefixes.
+ * @return string title and accesskey attributes, ready to drop in an
+ * element (e.g., ' title="This does something [x]" accesskey="x"').
+ */
+ public function tooltipAndAccesskey($name) {
+ $out = '';
+
+ $tooltip = wfMsg('tooltip-'.$name);
+ if (!wfEmptyMsg('tooltip-'.$name, $tooltip) && $tooltip != '-') {
+ // Compatibility: formerly some tooltips had [alt-.] hardcoded
+ $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
+ $out .= ' title="'.htmlspecialchars($tooltip);
+ }
+ $accesskey = wfMsg('accesskey-'.$name);
+ if ($accesskey && $accesskey != '-' && !wfEmptyMsg('accesskey-'.$name, $accesskey)) {
+ if ($out) $out .= " [$accesskey]\" accesskey=\"$accesskey\"";
+ else $out .= " title=\"[$accesskey]\" accesskey=\"$accesskey\"";
+ } elseif ($out) {
+ $out .= '"';
+ }
+ return $out;
+ }
+
+ /**
+ * Given the id of an interface element, constructs the appropriate title
+ * attribute from the system messages. (Note, this is usually the id but
+ * isn't always, because sometimes the accesskey needs to go on a different
+ * element than the id, for reverse-compatibility, etc.)
+ *
+ * @param string $name Id of the element, minus prefixes.
+ * @return string title attribute, ready to drop in an element
+ * (e.g., ' title="This does something"').
+ */
+ public function tooltip($name) {
+ $out = '';
+
+ $tooltip = wfMsg('tooltip-'.$name);
+ if (!wfEmptyMsg('tooltip-'.$name, $tooltip) && $tooltip != '-') {
+ $out = ' title="'.htmlspecialchars($tooltip).'"';
+ }
+
+ return $out;
+ }
}
?>
diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php
index 9e25bf07..856c665d 100644
--- a/includes/LinksUpdate.php
+++ b/includes/LinksUpdate.php
@@ -1,19 +1,15 @@
<?php
/**
- * See deferred.txt
- * @package MediaWiki
- */
-
-/**
- * @todo document
- * @package MediaWiki
+ * See docs/deferred.txt
+ *
+ * @todo document (e.g. one-sentence top-level class description).
*/
class LinksUpdate {
/**@{{
* @private
*/
- var $mId, //!< Page ID of the article linked from
+ var $mId, //!< Page ID of the article linked from
$mTitle, //!< Title object of the article linked from
$mLinks, //!< Map of title strings to IDs for the links in the document
$mImages, //!< DB keys of the images used, in the array key only
@@ -41,7 +37,7 @@ class LinksUpdate {
} else {
$this->mOptions = array( 'FOR UPDATE' );
}
- $this->mDb =& wfGetDB( DB_MASTER );
+ $this->mDb = wfGetDB( DB_MASTER );
if ( !is_object( $title ) ) {
throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
@@ -172,7 +168,7 @@ class LinksUpdate {
wfProfileIn( __METHOD__ );
$batchSize = 100;
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( array( 'templatelinks', 'page' ),
array( 'page_namespace', 'page_title' ),
array(
diff --git a/includes/LoadBalancer.php b/includes/LoadBalancer.php
index 396ef865..4ebe26c7 100644
--- a/includes/LoadBalancer.php
+++ b/includes/LoadBalancer.php
@@ -1,7 +1,6 @@
<?php
/**
*
- * @package MediaWiki
*/
@@ -9,7 +8,6 @@
* Database load balancing object
*
* @todo document
- * @package MediaWiki
*/
class LoadBalancer {
/* private */ var $mServers, $mConnections, $mLoads, $mGroupLoads;
@@ -24,7 +22,7 @@ class LoadBalancer {
*/
const AVG_STATUS_POLL = 2000;
- function LoadBalancer( $servers, $failFunction = false, $waitTimeout = 10, $waitForMasterNow = false )
+ function __construct( $servers, $failFunction = false, $waitTimeout = 10, $waitForMasterNow = false )
{
$this->mServers = $servers;
$this->mFailFunction = $failFunction;
@@ -32,7 +30,7 @@ class LoadBalancer {
$this->mWriteIndex = -1;
$this->mForce = -1;
$this->mConnections = array();
- $this->mLastIndex = 1;
+ $this->mLastIndex = -1;
$this->mLoads = array();
$this->mWaitForFile = false;
$this->mWaitForPos = false;
@@ -97,7 +95,9 @@ class LoadBalancer {
# Unset excessively lagged servers
$lags = $this->getLagTimes();
foreach ( $lags as $i => $lag ) {
- if ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
+ if ( $i != 0 && isset( $this->mServers[$i]['max lag'] ) &&
+ ( $lag === false || $lag > $this->mServers[$i]['max lag'] ) )
+ {
unset( $loads[$i] );
}
}
@@ -504,8 +504,7 @@ class LoadBalancer {
* Save master pos to the session and to memcached, if the session exists
*/
function saveMasterPos() {
- global $wgSessionStarted;
- if ( $wgSessionStarted && count( $this->mServers ) > 1 ) {
+ if ( session_id() != '' && count( $this->mServers ) > 1 ) {
# If this entire request was served from a slave without opening a connection to the
# master (however unlikely that may be), then we can fetch the position from the slave.
if ( empty( $this->mConnections[0] ) ) {
diff --git a/includes/LogPage.php b/includes/LogPage.php
index dd395126..af03bbba 100644
--- a/includes/LogPage.php
+++ b/includes/LogPage.php
@@ -21,7 +21,6 @@
/**
* Contain log classes
*
- * @package MediaWiki
*/
/**
@@ -29,7 +28,6 @@
* The logs are now kept in a table which is easier to manage and trim
* than ever-growing wiki pages.
*
- * @package MediaWiki
*/
class LogPage {
/* @access private */
@@ -44,7 +42,7 @@ class LogPage {
* 'upload', 'move'
* @param bool $rc Whether to update recent changes as well as the logging table
*/
- function LogPage( $type, $rc = true ) {
+ function __construct( $type, $rc = true ) {
$this->type = $type;
$this->updateRecentChanges = $rc;
}
@@ -55,7 +53,7 @@ class LogPage {
global $wgUser;
$fname = 'LogPage::saveContent';
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$uid = $wgUser->getID();
$this->timestamp = $now = wfTimestampNow();
@@ -119,7 +117,7 @@ class LogPage {
}
/**
- * @fixme: handle missing log types
+ * @todo handle missing log types
* @static
*/
function logHeader( $type ) {
@@ -134,6 +132,10 @@ class LogPage {
global $wgLang, $wgContLang, $wgLogActions;
$key = "$type/$action";
+
+ if( $key == 'patrol/patrol' )
+ return PatrolLog::makeActionText( $title, $params, $skin );
+
if( isset( $wgLogActions[$key] ) ) {
if( is_null( $title ) ) {
$rv=wfMsg( $wgLogActions[$key] );
@@ -183,8 +185,13 @@ class LogPage {
}
} else {
array_unshift( $params, $titleLink );
- if ( $translate && $key == 'block/block' ) {
- $params[1] = $wgLang->translateBlockExpiry($params[1]);
+ if ( $key == 'block/block' ) {
+ if ( $translate ) {
+ $params[1] = $wgLang->translateBlockExpiry( $params[1] );
+ }
+ $params[2] = isset( $params[2] )
+ ? self::formatBlockFlags( $params[2] )
+ : '';
}
$rv = wfMsgReal( $wgLogActions[$key], $params, true, !$skin );
}
@@ -241,6 +248,41 @@ class LogPage {
return explode( "\n", $blob );
}
}
+
+ /**
+ * Convert a comma-delimited list of block log flags
+ * into a more readable (and translated) form
+ *
+ * @param $flags Flags to format
+ * @return string
+ */
+ public static function formatBlockFlags( $flags ) {
+ $flags = explode( ',', trim( $flags ) );
+ if( count( $flags ) > 0 ) {
+ for( $i = 0; $i < count( $flags ); $i++ )
+ $flags[$i] = self::formatBlockFlag( $flags[$i] );
+ return '(' . implode( ', ', $flags ) . ')';
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Translate a block log flag if possible
+ *
+ * @param $flag Flag to translate
+ * @return string
+ */
+ public static function formatBlockFlag( $flag ) {
+ static $messages = array();
+ if( !isset( $messages[$flag] ) ) {
+ $k = 'block-log-flags-' . $flag;
+ $msg = wfMsg( $k );
+ $messages[$flag] = htmlspecialchars( wfEmptyMsg( $k, $msg ) ? $flag : $msg );
+ }
+ return $messages[$flag];
+ }
+
}
?>
diff --git a/includes/MacBinary.php b/includes/MacBinary.php
index 05c3ce5c..2f6ad4f4 100644
--- a/includes/MacBinary.php
+++ b/includes/MacBinary.php
@@ -22,12 +22,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @package MediaWiki
- * @subpackage SpecialPage
+ * @addtogroup SpecialPage
*/
class MacBinary {
- function MacBinary( $filename ) {
+ function __construct( $filename ) {
$this->open( $filename );
$this->loadHeader();
}
@@ -269,4 +268,4 @@ class MacBinary {
}
}
-?> \ No newline at end of file
+?>
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index 60bfd0f4..bf72a0c8 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -1,8 +1,7 @@
<?php
/**
* File for magic words
- * @package MediaWiki
- * @subpackage Parser
+ * @addtogroup Parser
*/
/**
@@ -21,7 +20,6 @@
* magic words which are also Parser variables, add a MagicWordwgVariableIDs
* hook. Use string keys.
*
- * @package MediaWiki
*/
class MagicWord {
/**#@+
@@ -55,6 +53,7 @@ class MagicWord {
'localhour',
'numberofarticles',
'numberoffiles',
+ 'numberofedits',
'sitename',
'server',
'servername',
@@ -108,7 +107,7 @@ class MagicWord {
/**#@-*/
- function MagicWord($id = 0, $syn = '', $cs = false) {
+ function __construct($id = 0, $syn = '', $cs = false) {
$this->mId = $id;
$this->mSynonyms = (array)$syn;
$this->mCaseSensitive = $cs;
diff --git a/includes/Math.php b/includes/Math.php
index 9fa631f7..88934e5f 100644
--- a/includes/Math.php
+++ b/includes/Math.php
@@ -1,7 +1,6 @@
<?php
/**
* Contain everything related to <math> </math> parsing
- * @package MediaWiki
*/
/**
@@ -11,7 +10,6 @@
*
* by Tomasz Wegrzanowski, with additions by Brion Vibber (2003, 2004)
*
- * @package MediaWiki
*/
class MathRenderer {
var $mode = MW_MATH_MODERN;
@@ -22,7 +20,7 @@ class MathRenderer {
var $mathml = '';
var $conservativeness = 0;
- function MathRenderer( $tex ) {
+ function __construct( $tex ) {
$this->tex = $tex;
}
@@ -156,7 +154,7 @@ class MathRenderer {
$md5_sql = pack('H32', $this->md5); # Binary packed, not hex
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$dbw->replace( 'math', array( 'math_inputhash' ),
array(
'math_inputhash' => $md5_sql,
@@ -185,7 +183,7 @@ class MathRenderer {
$fname = 'MathRenderer::_recall';
$this->md5 = md5( $this->tex );
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$rpage = $dbr->selectRow( 'math',
array( 'math_outputhash','math_html_conservativeness','math_html','math_mathml' ),
array( 'math_inputhash' => pack("H32", $this->md5)), # Binary packed, not hex
diff --git a/includes/MediaTransformOutput.php b/includes/MediaTransformOutput.php
new file mode 100644
index 00000000..60057e3a
--- /dev/null
+++ b/includes/MediaTransformOutput.php
@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * Base class for the output of MediaHandler::doTransform() and Image::transform().
+ *
+ * @addtogroup Media
+ */
+abstract class MediaTransformOutput {
+ /**
+ * Get the width of the output box
+ */
+ function getWidth() {
+ return $this->width;
+ }
+
+ /**
+ * Get the height of the output box
+ */
+ function getHeight() {
+ return $this->height;
+ }
+
+ /**
+ * @return string The thumbnail URL
+ */
+ function getUrl() {
+ return $this->url;
+ }
+
+ /**
+ * @return string Destination file path (local filesystem)
+ */
+ function getPath() {
+ return $this->path;
+ }
+
+ /**
+ * Fetch HTML for this transform output
+ * @param array $attribs Advisory associative array of HTML attributes supplied
+ * by the linker. These can be incorporated into the output in any way.
+ * @param array $linkAttribs Attributes of a suggested enclosing <a> tag.
+ * May be ignored.
+ */
+ abstract function toHtml( $attribs = array() , $linkAttribs = false );
+
+ /**
+ * This will be overridden to return true in error classes
+ */
+ function isError() {
+ return false;
+ }
+
+ /**
+ * Wrap some XHTML text in an anchor tag with the given attributes
+ */
+ protected function linkWrap( $linkAttribs, $contents ) {
+ if ( $linkAttribs ) {
+ return Xml::tags( 'a', $linkAttribs, $contents );
+ } else {
+ return $contents;
+ }
+ }
+}
+
+
+/**
+ * Media transform output for images
+ *
+ * @addtogroup Media
+ */
+class ThumbnailImage extends MediaTransformOutput {
+ /**
+ * @param string $path Filesystem path to the thumb
+ * @param string $url URL path to the thumb
+ * @private
+ */
+ function ThumbnailImage( $url, $width, $height, $path = false ) {
+ $this->url = $url;
+ # These should be integers when they get here.
+ # If not, there's a bug somewhere. But let's at
+ # least produce valid HTML code regardless.
+ $this->width = round( $width );
+ $this->height = round( $height );
+ $this->path = $path;
+ }
+
+ /**
+ * Return HTML <img ... /> tag for the thumbnail, will include
+ * width and height attributes and a blank alt text (as required).
+ *
+ * You can set or override additional attributes by passing an
+ * associative array of name => data pairs. The data will be escaped
+ * for HTML output, so should be in plaintext.
+ *
+ * If $linkAttribs is given, the image will be enclosed in an <a> tag.
+ *
+ * @param array $attribs
+ * @param array $linkAttribs
+ * @return string
+ * @public
+ */
+ function toHtml( $attribs = array(), $linkAttribs = false ) {
+ $attribs['src'] = $this->url;
+ $attribs['width'] = $this->width;
+ $attribs['height'] = $this->height;
+ if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
+ return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
+ }
+
+}
+
+/**
+ * Basic media transform error class
+ *
+ * @addtogroup Media
+ */
+class MediaTransformError extends MediaTransformOutput {
+ var $htmlMsg, $textMsg, $width, $height, $url, $path;
+
+ function __construct( $msg, $width, $height /*, ... */ ) {
+ $args = array_slice( func_get_args(), 3 );
+ $htmlArgs = array_map( 'htmlspecialchars', $args );
+ $htmlArgs = array_map( 'nl2br', $htmlArgs );
+
+ $this->htmlMsg = wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $msg, true ) ), $htmlArgs );
+ $this->textMsg = wfMsgReal( $msg, $args );
+ $this->width = intval( $width );
+ $this->height = intval( $height );
+ $this->url = false;
+ $this->path = false;
+ }
+
+ function toHtml( $attribs = array(), $linkAttribs = false ) {
+ return "<table class=\"MediaTransformError\" style=\"" .
+ "width: {$this->width}px; height: {$this->height}px;\"><tr><td>" .
+ $this->htmlMsg .
+ "</td></tr></table>";
+ }
+
+ function toText() {
+ return $this->textMsg;
+ }
+
+ function getHtmlMsg() {
+ return $this->htmlMsg;
+ }
+
+ function isError() {
+ return true;
+ }
+}
+
+/**
+ * Shortcut class for parameter validation errors
+ *
+ * @addtogroup Media
+ */
+class TransformParameterError extends MediaTransformError {
+ function __construct( $params ) {
+ parent::__construct( 'thumbnail_error',
+ max( @$params['width'], 180 ), max( @$params['height'], 180 ),
+ wfMsg( 'thumbnail_invalid_params' ) );
+ }
+}
+
+?>
diff --git a/includes/MemcachedSessions.php b/includes/MemcachedSessions.php
index e2dc52ca..3bcf5535 100644
--- a/includes/MemcachedSessions.php
+++ b/includes/MemcachedSessions.php
@@ -6,7 +6,6 @@
* be necessary to change the cookie settings to work across hostnames.
* See: http://www.php.net/manual/en/function.session-set-save-handler.php
*
- * @package MediaWiki
*/
/**
diff --git a/includes/MessageCache.php b/includes/MessageCache.php
index a269c620..e2cbf5f6 100644
--- a/includes/MessageCache.php
+++ b/includes/MessageCache.php
@@ -1,8 +1,7 @@
<?php
/**
*
- * @package MediaWiki
- * @subpackage Cache
+ * @addtogroup Cache
*/
/**
@@ -17,7 +16,6 @@ define( 'MSG_CACHE_VERSION', 1 );
* Message cache
* Performs various MediaWiki namespace-related functions
*
- * @package MediaWiki
*/
class MessageCache {
var $mCache, $mUseCache, $mDisable, $mExpiry;
@@ -298,10 +296,10 @@ class MessageCache {
* Loads all or main part of cacheable messages from the database
*/
function loadFromDB() {
- global $wgLang, $wgMaxMsgCacheEntrySize;
+ global $wgMaxMsgCacheEntrySize;
wfProfileIn( __METHOD__ );
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$this->mCache = array();
# Load titles for all oversized pages in the MediaWiki namespace
@@ -547,7 +545,7 @@ class MessageCache {
if ( $type == ' ' ) {
$message = substr( $entry, 1 );
- $this->mCache[$title] = $message;
+ $this->mCache[$title] = $entry;
return $message;
} elseif ( $entry == '!NONEXISTENT' ) {
return false;
diff --git a/includes/Metadata.php b/includes/Metadata.php
index 4e0d91b7..b995b223 100644
--- a/includes/Metadata.php
+++ b/includes/Metadata.php
@@ -18,7 +18,6 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @author Evan Prodromou <evan@wikitravel.org>
- * @package MediaWiki
*/
/**
@@ -74,7 +73,9 @@ function wfCreativeCommonsRdf($article) {
function rdfSetup() {
global $wgOut, $_SERVER;
- $rdftype = wfNegotiateType(wfAcceptToPrefs($_SERVER['HTTP_ACCEPT']), wfAcceptToPrefs(RDF_TYPE_PREFS));
+ $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : null;
+
+ $rdftype = wfNegotiateType(wfAcceptToPrefs($httpaccept), wfAcceptToPrefs(RDF_TYPE_PREFS));
if (!$rdftype) {
wfHttpError(406, "Not Acceptable", wfMsg("notacceptable"));
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index ca05dbb3..db35535d 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -1,7 +1,6 @@
<?php
/** Module defining helper functions for detecting and dealing with mime types.
*
- * @package MediaWiki
*/
/** Defines a set of well known mime types
@@ -23,9 +22,10 @@ image/x-bmp bmp
image/gif gif
image/jpeg jpeg jpg jpe
image/png png
-image/svg+xml svg
+image/svg+xml image/svg svg
image/tiff tiff tif
image/vnd.djvu djvu
+image/x-portable-pixmap ppm
text/plain txt
text/html html htm
video/ogg ogm ogg
@@ -51,9 +51,10 @@ image/x-bmp image/bmp [BITMAP]
image/gif [BITMAP]
image/jpeg [BITMAP]
image/png [BITMAP]
-image/svg image/svg+xml [DRAWING]
+image/svg+xml [DRAWING]
image/tiff [BITMAP]
image/vnd.djvu [BITMAP]
+image/x-portable-pixmap [BITMAP]
text/plain [TEXT]
text/html [TEXT]
video/ogg [VIDEO]
@@ -70,13 +71,13 @@ if ($wgLoadFileinfoExtension) {
if(!extension_loaded('fileinfo')) dl('fileinfo.' . PHP_SHLIB_SUFFIX);
}
-/** Implements functions related to mime types such as detection and mapping to
-* file extension,
-*
-* Instances of this class are stateles, there only needs to be one global instance
-* of MimeMagic. Please use MimeMagic::singleton() to get that instance.
-* @package MediaWiki
-*/
+/**
+ * Implements functions related to mime types such as detection and mapping to
+ * file extension.
+ *
+ * Instances of this class are stateles, there only needs to be one global instance
+ * of MimeMagic. Please use MimeMagic::singleton() to get that instance.
+ */
class MimeMagic {
/**
@@ -105,7 +106,7 @@ class MimeMagic {
*
* This constructor parses the mime.types and mime.info files and build internal mappings.
*/
- function MimeMagic() {
+ function __construct() {
/*
* --- load mime.types ---
*/
@@ -149,7 +150,7 @@ class MimeMagic {
if (empty($ext)) continue;
- if (@$this->mMimeToExt[$mime]) $this->mMimeToExt[$mime] .= ' '.$ext;
+ if ( !empty($this->mMimeToExt[$mime])) $this->mMimeToExt[$mime] .= ' '.$ext;
else $this->mMimeToExt[$mime]= $ext;
$extensions= explode(' ',$ext);
@@ -158,7 +159,7 @@ class MimeMagic {
$e= trim($e);
if (empty($e)) continue;
- if (@$this->mExtToMime[$e]) $this->mExtToMime[$e] .= ' '.$mime;
+ if ( !empty($this->mExtToMime[$e])) $this->mExtToMime[$e] .= ' '.$mime;
else $this->mExtToMime[$e]= $mime;
}
}
@@ -262,7 +263,7 @@ class MimeMagic {
function getTypesForExtension($ext) {
$ext= strtolower($ext);
- $r= @$this->mExtToMime[$ext];
+ $r= isset( $this->mExtToMime[$ext] ) ? $this->mExtToMime[$ext] : null;
return $r;
}
@@ -341,7 +342,7 @@ class MimeMagic {
}
- /** mime type detection. This uses detectMimeType to detect the mim type of the file,
+ /** mime type detection. This uses detectMimeType to detect the mime type of the file,
* but applies additional checks to determine some well known file formats that may be missed
* or misinterpreter by the default mime detection (namely xml based formats like XHTML or SVG).
*
@@ -399,8 +400,8 @@ class MimeMagic {
#print "<br>ANALYSING $file ($mime): doctype= $doctype; tag= $tag<br>";
- if (strpos($doctype,"-//W3C//DTD SVG")===0) $mime= "image/svg";
- elseif ($tag==="svg") $mime= "image/svg";
+ if (strpos($doctype,"-//W3C//DTD SVG")===0) $mime= "image/svg+xml";
+ elseif ($tag==="svg") $mime= "image/svg+xml";
elseif (strpos($doctype,"-//W3C//DTD XHTML")===0) $mime= "text/html";
elseif ($tag==="html") $mime= "text/html";
}
@@ -424,7 +425,9 @@ class MimeMagic {
$match= array();
$prog= "";
- if (preg_match('%/?([^\s]+/)(w+)%sim',$head,$match)) $script= $match[2];
+ if (preg_match('%/?([^\s]+/)(w+)%sim',$head,$match)) {
+ $script= $match[2]; // FIXME: $script variable not used; should this be "$prog = $match[2];" instead?
+ }
$mime= "application/x-$prog";
}
diff --git a/includes/Namespace.php b/includes/Namespace.php
index 78493902..dd67b55a 100644
--- a/includes/Namespace.php
+++ b/includes/Namespace.php
@@ -1,7 +1,6 @@
<?php
/**
* Provide things related to namespaces
- * @package MediaWiki
*/
/**
@@ -41,7 +40,6 @@ if( is_array( $wgExtraNamespaces ) ) {
* These are synonyms for the names given in the language file
* Users and translators should not change them
*
- * @package MediaWiki
*/
class Namespace {
@@ -125,5 +123,19 @@ class Namespace {
static function canTalk( $index ) {
return( $index >= NS_MAIN );
}
+
+ /**
+ * Does this namespace contain content, for the purposes
+ * of calculating statistics, etc?
+ *
+ * @param $index Index to check
+ * @return bool
+ */
+ public static function isContent( $index ) {
+ global $wgContentNamespaces;
+ return $index == NS_MAIN || in_array( $index, $wgContentNamespaces );
+ }
+
}
+
?>
diff --git a/includes/ObjectCache.php b/includes/ObjectCache.php
index 2b26cf4e..a493a75c 100644
--- a/includes/ObjectCache.php
+++ b/includes/ObjectCache.php
@@ -1,7 +1,6 @@
<?php
/**
- * @package MediaWiki
- * @subpackage Cache
+ * @addtogroup Cache
*/
/**
@@ -9,8 +8,7 @@
* It acts as a memcached server with no RAM, that is, all objects are
* cleared the moment they are set. All set operations succeed and all
* get operations return null.
- * @package MediaWiki
- * @subpackage Cache
+ * @addtogroup Cache
*/
class FakeMemCachedClient {
function add ($key, $val, $exp = 0) { return true; }
diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php
new file mode 100644
index 00000000..d7e7c90f
--- /dev/null
+++ b/includes/OutputHandler.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Standard output handler for use with ob_start
+ */
+function wfOutputHandler( $s ) {
+ global $wgDisableOutputCompression;
+ $s = wfMangleFlashPolicy( $s );
+ if ( !$wgDisableOutputCompression && !ini_get( 'zlib.output_compression' ) ) {
+ if ( !defined( 'MW_NO_OUTPUT_COMPRESSION' ) ) {
+ $s = wfGzipHandler( $s );
+ }
+ if ( !ini_get( 'output_handler' ) ) {
+ wfDoContentLength( strlen( $s ) );
+ }
+ }
+ return $s;
+}
+
+/**
+ * Handler that compresses data with gzip if allowed by the Accept header.
+ * Unlike ob_gzhandler, it works for HEAD requests too.
+ */
+function wfGzipHandler( $s ) {
+ if ( function_exists( 'gzencode' ) && !headers_sent() ) {
+ $tokens = preg_split( '/[,; ]/', $_SERVER['HTTP_ACCEPT_ENCODING'] );
+ if ( in_array( 'gzip', $tokens ) ) {
+ header( 'Content-Encoding: gzip' );
+ $s = gzencode( $s, 3 );
+
+ # Set vary header if it hasn't been set already
+ $headers = headers_list();
+ $foundVary = false;
+ foreach ( $headers as $header ) {
+ if ( substr( $header, 0, 5 ) == 'Vary:' ) {
+ $foundVary = true;
+ break;
+ }
+ }
+ if ( !$foundVary ) {
+ header( 'Vary: Accept-Encoding' );
+ }
+ }
+ }
+ return $s;
+}
+
+/**
+ * Mangle flash policy tags which open up the site to XSS attacks.
+ */
+function wfMangleFlashPolicy( $s ) {
+ return preg_replace( '/\<\s*cross-domain-policy\s*\>/i', '<NOT-cross-domain-policy>', $s );
+}
+
+/**
+ * Add a Content-Length header if possible. This makes it cooperate with squid better.
+ */
+function wfDoContentLength( $length ) {
+ if ( !headers_sent() && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0' ) {
+ header( "Content-Length: $length" );
+ }
+}
+
+?>
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 6d3cc0ac..03e832a4 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -2,12 +2,10 @@
if ( ! defined( 'MEDIAWIKI' ) )
die( 1 );
/**
- * @package MediaWiki
*/
/**
* @todo document
- * @package MediaWiki
*/
class OutputPage {
var $mMetatags, $mKeywords;
@@ -34,7 +32,7 @@ class OutputPage {
* Constructor
* Initialise private variables
*/
- function OutputPage() {
+ function __construct() {
$this->mMetatags = $this->mKeywords = $this->mLinktags = array();
$this->mHTMLtitle = $this->mPagetitle = $this->mBodytext =
$this->mRedirect = $this->mLastModified =
@@ -49,6 +47,7 @@ class OutputPage {
$this->mParserOptions = null;
$this->mSquidMaxage = 0;
$this->mScripts = '';
+ $this->mHeadItems = array();
$this->mETag = false;
$this->mRevisionId = null;
$this->mNewSectionLink = false;
@@ -71,7 +70,7 @@ class OutputPage {
# To add an http-equiv meta tag, precede the name with "http:"
function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); }
function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
- function addScript( $script ) { $this->mScripts .= $script; }
+ function addScript( $script ) { $this->mScripts .= "\t\t".$script; }
/**
* Add a self-contained script tag with the given contents
@@ -79,10 +78,24 @@ class OutputPage {
*/
function addInlineScript( $script ) {
global $wgJsMimeType;
- $this->mScripts .= "<script type=\"$wgJsMimeType\"><!--\n$script\n--></script>";
+ $this->mScripts .= "<script type=\"$wgJsMimeType\">/*<![CDATA[*/\n$script\n/*]]>*/</script>";
}
- function getScript() { return $this->mScripts; }
+ function getScript() {
+ return $this->mScripts . $this->getHeadItems();
+ }
+
+ function getHeadItems() {
+ $s = '';
+ foreach ( $this->mHeadItems as $item ) {
+ $s .= $item;
+ }
+ return $s;
+ }
+
+ function addHeadItem( $name, $value ) {
+ $this->mHeadItems[$name] = $value;
+ }
function setETag($tag) { $this->mETag = $tag; }
function setArticleBodyOnly($only) { $this->mArticleBodyOnly = $only; }
@@ -254,7 +267,7 @@ class OutputPage {
$lb->setArray( $arr );
$lb->execute();
- $sk =& $wgUser->getSkin();
+ $sk = $wgUser->getSkin();
foreach ( $categories as $category => $unused ) {
$title = Title::makeTitleSafe( NS_CATEGORY, $category );
$text = $wgContLang->convertHtml( $title->getText() );
@@ -315,14 +328,26 @@ class OutputPage {
$this->addWikiTextTitle($text, $title, $linestart);
}
- private function addWikiTextTitle($text, &$title, $linestart) {
+ function addWikiTextTitleTidy($text, &$title, $linestart = true) {
+ $this->addWikiTextTitle( $text, $title, $linestart, true );
+ }
+
+ public function addWikiTextTitle($text, &$title, $linestart, $tidy = false) {
global $wgParser;
+
$fname = 'OutputPage:addWikiTextTitle';
wfProfileIn($fname);
+
wfIncrStats('pcache_not_possible');
- $parserOutput = $wgParser->parse( $text, $title, $this->parserOptions(),
+
+ $popts = $this->parserOptions();
+ $popts->setTidy($tidy);
+
+ $parserOutput = $wgParser->parse( $text, $title, $popts,
$linestart, true, $this->mRevisionId );
+
$this->addParserOutput( $parserOutput );
+
wfProfileOut($fname);
}
@@ -345,6 +370,7 @@ class OutputPage {
$this->mSubtitle .= $parserOutput->mSubtitle ;
}
$this->mNoGallery = $parserOutput->getNoGallery();
+ $this->mHeadItems = array_merge( $this->mHeadItems, (array)$parserOutput->mHeadItems );
wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
}
@@ -366,6 +392,7 @@ class OutputPage {
* @param string $text
* @param Article $article
* @param bool $cache
+ * @deprecated Use Article::outputWikitext
*/
public function addPrimaryWikiText( $text, $article, $cache = true ) {
global $wgParser, $wgUser;
@@ -384,17 +411,19 @@ class OutputPage {
}
/**
- * For anything that isn't primary text or interface message
- *
- * @param string $text
- * @param bool $linestart Is this the start of a line?
+ * @deprecated use addWikiTextTidy()
*/
public function addSecondaryWikiText( $text, $linestart = true ) {
global $wgTitle;
- $popts = $this->parserOptions();
- $popts->setTidy(true);
- $this->addWikiTextTitle($text, $wgTitle, $linestart);
- $popts->setTidy(false);
+ $this->addWikiTextTitleTidy($text, $wgTitle, $linestart);
+ }
+
+ /**
+ * Add wikitext with tidy enabled
+ */
+ public function addWikiTextTidy( $text, $linestart = true ) {
+ global $wgTitle;
+ $this->addWikiTextTitleTidy($text, $wgTitle, $linestart);
}
@@ -476,7 +505,7 @@ class OutputPage {
# maintain different caches for logged-in users and non-logged in ones
$wgRequest->response()->header( 'Vary: Accept-Encoding, Cookie' );
if( !$this->uncacheableBecauseRequestvars() && $this->mEnableClientCache ) {
- if( $wgUseSquid && ! isset( $_COOKIE[ini_get( 'session.name') ] ) &&
+ if( $wgUseSquid && session_id() == '' &&
! $this->isPrintable() && $this->mSquidMaxage != 0 )
{
if ( $wgUseESI ) {
@@ -536,13 +565,16 @@ class OutputPage {
if ( $wgUseAjax ) {
$this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajax.js?$wgStyleVersion\"></script>\n" );
+
+ wfRunHooks( 'AjaxAddScript', array( &$this ) );
+
if( $wgAjaxSearch ) {
- $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js\"></script>\n" );
+ $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js?$wgStyleVersion\"></script>\n" );
$this->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", sajax_onload);</script>\n" );
}
if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
- $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxwatch.js\"></script>\n" );
+ $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxwatch.js?$wgStyleVersion\"></script>\n" );
}
}
@@ -750,7 +782,7 @@ class OutputPage {
$this->returnToMain( false );
}
- /** @obsolete */
+ /** @deprecated */
public function errorpage( $title, $msg ) {
throw new ErrorPageError( $title, $msg );
}
@@ -792,10 +824,10 @@ class OutputPage {
$groupName = User::getGroupName( $key );
$groupPage = User::getGroupPage( $key );
if( $groupPage ) {
- $skin =& $wgUser->getSkin();
- $groups[] = '"'.$skin->makeLinkObj( $groupPage, $groupName ).'"';
+ $skin = $wgUser->getSkin();
+ $groups[] = $skin->makeLinkObj( $groupPage, $groupName );
} else {
- $groups[] = '"'.$groupName.'"';
+ $groups[] = $groupName;
}
}
}
@@ -860,7 +892,7 @@ class OutputPage {
$this->returnToMain( true, $mainPage );
}
- /** @obsolete */
+ /** @deprecated */
public function databaseError( $fname, $sql, $error, $errno ) {
throw new MWException( "OutputPage::databaseError is obsolete\n" );
}
@@ -881,10 +913,22 @@ class OutputPage {
$this->setPageTitle( wfMsg( 'viewsource' ) );
$this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) );
+ list( $cascadeSources, $restrictions ) = $wgTitle->getCascadeProtectionSources();
+
# Determine if protection is due to the page being a system message
# and show an appropriate explanation
- if( $wgTitle->getNamespace() == NS_MEDIAWIKI && !$wgUser->isAllowed( 'editinterface' ) ) {
+ if( $wgTitle->getNamespace() == NS_MEDIAWIKI ) {
$this->addWikiText( wfMsg( 'protectedinterface' ) );
+ } if ( $cascadeSources && count($cascadeSources) > 0 ) {
+ $titles = '';
+
+ foreach ( $cascadeSources as $title ) {
+ $titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
+ }
+
+ $notice = wfMsgExt( 'cascadeprotected', array('parsemag'), count($cascadeSources) ) . "\n$titles";
+
+ $this->addWikiText( $notice );
} else {
$this->addWikiText( wfMsg( 'protectedpagetext' ) );
}
@@ -900,17 +944,8 @@ class OutputPage {
if( is_string( $source ) ) {
$this->addWikiText( wfMsg( 'viewsourcetext' ) );
- if( $source === '' ) {
- global $wgTitle;
- if ( $wgTitle->getNamespace() == NS_MEDIAWIKI ) {
- $source = wfMsgWeirdKey ( $wgTitle->getText() );
- } else {
- $source = '';
- }
- }
$rows = $wgUser->getIntOption( 'rows' );
$cols = $wgUser->getIntOption( 'cols' );
-
$text = "\n<textarea name='wpTextbox1' id='wpTextbox1' cols='$cols' rows='$rows' readonly='readonly'>" .
htmlspecialchars( $source ) . "\n</textarea>";
$this->addHTML( $text );
@@ -921,32 +956,32 @@ class OutputPage {
$this->returnToMain( false );
}
- /** @obsolete */
+ /** @deprecated */
public function fatalError( $message ) {
throw new FatalError( $message );
}
- /** @obsolete */
+ /** @deprecated */
public function unexpectedValueError( $name, $val ) {
throw new FatalError( wfMsg( 'unexpected', $name, $val ) );
}
- /** @obsolete */
+ /** @deprecated */
public function fileCopyError( $old, $new ) {
throw new FatalError( wfMsg( 'filecopyerror', $old, $new ) );
}
- /** @obsolete */
+ /** @deprecated */
public function fileRenameError( $old, $new ) {
throw new FatalError( wfMsg( 'filerenameerror', $old, $new ) );
}
- /** @obsolete */
+ /** @deprecated */
public function fileDeleteError( $name ) {
throw new FatalError( wfMsg( 'filedeleteerror', $name ) );
}
- /** @obsolete */
+ /** @deprecated */
public function fileNotFoundError( $name ) {
throw new FatalError( wfMsg( 'filenotfound', $name ) );
}
@@ -1082,6 +1117,7 @@ class OutputPage {
$ret .= $sk->getHeadScripts();
$ret .= $this->mScripts;
$ret .= $sk->getUserStyles();
+ $ret .= $this->getHeadItems();
if ($wgUseTrackbacks && $this->isArticleRelated())
$ret .= $wgTitle->trackbackRDF();
@@ -1118,11 +1154,11 @@ class OutputPage {
"/<.*?>/" => '',
"/_/" => ' '
);
- $ret .= "<meta name=\"keywords\" content=\"" .
+ $ret .= "\t\t<meta name=\"keywords\" content=\"" .
htmlspecialchars(preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ))) . "\" />\n";
}
foreach ( $this->mLinktags as $tag ) {
- $ret .= '<link';
+ $ret .= "\t\t<link";
foreach( $tag as $attr => $val ) {
$ret .= " $attr=\"" . htmlspecialchars( $val ) . "\"";
}
diff --git a/includes/PageHistory.php b/includes/PageHistory.php
index aea0f0ed..b1cf41f0 100644
--- a/includes/PageHistory.php
+++ b/includes/PageHistory.php
@@ -3,7 +3,6 @@
* Page history
*
* Split off from Article.php and Skin.php, 2003-12-22
- * @package MediaWiki
*/
/**
@@ -14,9 +13,7 @@
* Construct it by passing in an Article, and call $h->history() to print the
* history.
*
- * @package MediaWiki
*/
-
class PageHistory {
const DIR_PREV = 0;
const DIR_NEXT = 1;
@@ -33,7 +30,7 @@ class PageHistory {
* @param Article $article
* @returns nothing
*/
- function PageHistory($article) {
+ function __construct($article) {
global $wgUser;
$this->mArticle =& $article;
@@ -101,6 +98,8 @@ class PageHistory {
$wgOut->redirect( $wgTitle->getLocalURL( "action=history&limit={$limit}&dir=prev" ) );
return;
}
+
+ wfRunHooks( 'PageHistoryBeforeList', array( &$this->mArticle ) );
/**
* Do the list
@@ -159,7 +158,7 @@ class PageHistory {
'class' => 'historysubmit',
'type' => 'submit',
'accesskey' => wfMsg( 'accesskey-compareselectedversions' ),
- 'title' => wfMsg( 'tooltip-compareselectedversions' ),
+ 'title' => wfMsg( 'tooltip-compareselectedversions' ).' ['.wfMsg( 'accesskey-compareselectedversions' ).']',
'value' => wfMsg( 'compareselectedversions' ),
) ) )
: '';
@@ -179,7 +178,7 @@ class PageHistory {
* @return string HTML output for the row
*/
function historyLine( $row, $next, $counter = '', $notificationtimestamp = false, $latest = false, $firstInList = false ) {
- global $wgUser;
+ global $wgUser, $wgLang;
$rev = new Revision( $row );
$rev->setTitle( $this->mTitle );
@@ -199,31 +198,58 @@ class PageHistory {
if( $firstInList ) {
// We don't currently handle well changing the top revision's settings
$del = wfMsgHtml( 'rev-delundel' );
+ } else if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ // If revision was hidden from sysops
+ $del = wfMsgHtml( 'rev-delundel' );
} else {
$del = $this->mSkin->makeKnownLinkObj( $revdel,
wfMsg( 'rev-delundel' ),
'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) .
'&oldid=' . urlencode( $rev->getId() ) );
}
- $s .= "(<small>$del</small>) ";
+ $s .= " (<small>$del</small>) ";
}
- $s .= " $link <span class='history-user'>$user</span>";
+ $s .= " $link";
+ #getUser is safe, but this avoids making the invalid untargeted contribs links
+ if( $row->rev_deleted & Revision::DELETED_USER ) {
+ $user = '<span class="history-deleted">' . wfMsg('rev-deleted-user') . '</span>';
+ }
+ $s .= " <span class='history-user'>$user</span>";
if( $row->rev_minor_edit ) {
$s .= ' ' . wfElement( 'span', array( 'class' => 'minor' ), wfMsg( 'minoreditletter') );
}
- $s .= $this->mSkin->revComment( $rev );
+ if (!is_null($size = $rev->getSize())) {
+ if ($size == 0)
+ $stxt = wfMsgHtml('historyempty');
+ else
+ $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) );
+ $s .= " <span class=\"history-size\">$stxt</span>";
+ }
+
+ #getComment is safe, but this is better formatted
+ if( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
+ $s .= " <span class=\"history-deleted\"><span class=\"comment\">" .
+ wfMsgHtml( 'rev-deleted-comment' ) . "</span></span>";
+ } else {
+ $s .= $this->mSkin->revComment( $rev );
+ }
+
if ($notificationtimestamp && ($row->rev_timestamp >= $notificationtimestamp)) {
$s .= ' <span class="updatedmarker">' . wfMsgHtml( 'updatedmarker' ) . '</span>';
}
+ #add blurb about text having been deleted
if( $row->rev_deleted & Revision::DELETED_TEXT ) {
$s .= ' ' . wfMsgHtml( 'deletedrev' );
}
if( $wgUser->isAllowed( 'rollback' ) && $latest ) {
$s .= ' '.$this->mSkin->generateRollback( $rev );
}
+
+ wfRunHooks( 'PageHistoryLineEnding', array( &$row , &$s ) );
+
$s .= "</li>\n";
return $s;
@@ -332,7 +358,7 @@ class PageHistory {
function getLatestId() {
if( is_null( $this->mLatestId ) ) {
$id = $this->mTitle->getArticleID();
- $db =& wfGetDB(DB_SLAVE);
+ $db = wfGetDB(DB_SLAVE);
$this->mLatestId = $db->selectField( 'page',
"page_latest",
array( 'page_id' => $id ),
@@ -349,7 +375,7 @@ class PageHistory {
function fetchRevisions($limit, $offset, $direction) {
$fname = 'PageHistory::fetchRevisions';
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
if ($direction == PageHistory::DIR_PREV)
list($dirs, $oper) = array("ASC", ">=");
@@ -365,8 +391,7 @@ class PageHistory {
$res = $dbr->select(
'revision',
- array('rev_id', 'rev_page', 'rev_text_id', 'rev_user', 'rev_comment', 'rev_user_text',
- 'rev_timestamp', 'rev_minor_edit', 'rev_deleted'),
+ Revision::selectFields(),
array_merge(array("rev_page=$page_id"), $offsets),
$fname,
array('ORDER BY' => "rev_timestamp $dirs",
@@ -391,7 +416,7 @@ class PageHistory {
if ($wgUser->isAnon() || !$wgShowUpdatedMarker)
return $this->mNotificationTimestamp = false;
- $dbr =& wfGetDB(DB_SLAVE);
+ $dbr = wfGetDB(DB_SLAVE);
$this->mNotificationTimestamp = $dbr->selectField(
'watchlist',
@@ -497,6 +522,9 @@ class PageHistory {
}
+/**
+ * @addtogroup Pager
+ */
class PageHistoryPager extends ReverseChronologicalPager {
public $mLastRow = false, $mPageHistory;
@@ -508,8 +536,7 @@ class PageHistoryPager extends ReverseChronologicalPager {
function getQueryInfo() {
return array(
'tables' => 'revision',
- 'fields' => array('rev_id', 'rev_page', 'rev_text_id', 'rev_user', 'rev_comment', 'rev_user_text',
- 'rev_timestamp', 'rev_minor_edit', 'rev_deleted'),
+ 'fields' => Revision::selectFields(),
'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ),
'options' => array( 'USE INDEX' => 'page_timestamp' )
);
diff --git a/includes/PageQueryPage.php b/includes/PageQueryPage.php
new file mode 100644
index 00000000..5b82ebf6
--- /dev/null
+++ b/includes/PageQueryPage.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Variant of QueryPage which formats the result as a simple link to the page
+ *
+ * @package MediaWiki
+ * @addtogroup SpecialPage
+ */
+class PageQueryPage extends QueryPage {
+
+ /**
+ * Format the result as a simple link to the page
+ *
+ * @param Skin $skin
+ * @param object $row 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() ) ) );
+ }
+}
+
+?>
diff --git a/includes/Pager.php b/includes/Pager.php
index 0987cc06..a475dc16 100644
--- a/includes/Pager.php
+++ b/includes/Pager.php
@@ -2,6 +2,7 @@
/**
* Basic pager interface.
+ * @addtogroup Pager
*/
interface Pager {
function getNavigationBar();
@@ -46,6 +47,8 @@ interface Pager {
* please see the examples in PageHistory.php and SpecialIpblocklist.php. You just need
* to override formatRow(), getQueryInfo() and getIndexField(). Don't forget to call the
* parent constructor if you override it.
+ *
+ * @addtogroup Pager
*/
abstract class IndexPager implements Pager {
public $mRequest;
@@ -69,17 +72,18 @@ abstract class IndexPager implements Pager {
public $mResult;
function __construct() {
- global $wgRequest;
+ global $wgRequest, $wgUser;
$this->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' );
- $this->mLimit = $this->mRequest->getInt( 'limit', $this->mDefaultLimit );
- if ( $this->mLimit <= 0 || $this->mLimit > 50000 ) {
- $this->mLimit = $this->mDefaultLimit;
- }
+
+ # 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 );
@@ -386,8 +390,45 @@ abstract class IndexPager implements Pager {
abstract function getIndexField();
}
+
+/**
+ * IndexPager with an alphabetic list and a formatted navigation bar
+ * @addtogroup 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
+ */
+ function getNavigationBar() {
+ global $wgLang;
+
+ $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 */
+ );
+
+ $pagingLinks = $this->getPagingLinks( $linkTexts );
+ $limitLinks = $this->getLimitLinks();
+ $limits = implode( ' | ', $limitLinks );
+
+ $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits);
+ return $this->mNavigationBar;
+
+ }
+}
+
/**
* IndexPager with a formatted navigation bar
+ * @addtogroup Pager
*/
abstract class ReverseChronologicalPager extends IndexPager {
public $mDefaultDirection = true;
@@ -413,13 +454,15 @@ abstract class ReverseChronologicalPager extends IndexPager {
$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);
return $this->mNavigationBar;
}
}
/**
* Table-based display with a user-selectable sort order
+ * @addtogroup Pager
*/
abstract class TablePager extends IndexPager {
var $mSort;
diff --git a/includes/Parser.php b/includes/Parser.php
index 8d67279d..8e36e170 100644
--- a/includes/Parser.php
+++ b/includes/Parser.php
@@ -2,8 +2,7 @@
/**
* File for Parser and related classes
*
- * @package MediaWiki
- * @subpackage Parser
+ * @addtogroup Parser
*/
/**
@@ -57,9 +56,10 @@ define( 'MW_COLON_STATE_COMMENTDASH', 6 );
define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
/**
- * PHP Parser
- *
- * Processes wiki markup
+ * PHP Parser - Processes wiki markup (which uses a more user-friendly
+ * syntax, such as "[[link]]" for making links), and provides a one-way
+ * transformation of that wiki markup it into XHTML output / markup
+ * (which in turn the browser understands, and can display).
*
* <pre>
* There are four main entry points into the Parser class:
@@ -86,10 +86,11 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 );
* * only within ParserOptions
* </pre>
*
- * @package MediaWiki
+ * @addtogroup Parser
*/
class Parser
{
+ const VERSION = MW_PARSER_VERSION;
/**#@+
* @private
*/
@@ -114,7 +115,7 @@ class Parser
$ot, // Shortcut alias, see setOutputType()
$mRevisionId, // ID to display in {{REVISIONID}} tags
$mRevisionTimestamp, // The timestamp of the specified revision ID
- $mRevIdForTs; // The revision ID which was used to fetch the timestamp
+ $mRevIdForTs; // The revision ID which was used to fetch the timestamp
/**#@-*/
@@ -162,6 +163,7 @@ class Parser
$this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH );
$this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH );
$this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH );
+ $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH );
$this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH );
$this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH );
$this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH );
@@ -211,7 +213,7 @@ class Parser
'titles' => array()
);
$this->mRevisionTimestamp = $this->mRevisionId = null;
-
+
/**
* Prefix for temporary replacement strings for the multipass parser.
* \x07 should never appear in input as it's disallowed in XML.
@@ -262,7 +264,6 @@ class Parser
* Convert wikitext to HTML
* Do not call this function recursively.
*
- * @private
* @param string $text Text we want to parse
* @param Title &$title A title object
* @param array $options
@@ -271,7 +272,7 @@ class Parser
* @param int $revid number to pass in {{REVISIONID}}
* @return ParserOutput a ParserOutput
*/
- function parse( $text, &$title, $options, $linestart = true, $clearState = true, $revid = null ) {
+ public function parse( $text, &$title, $options, $linestart = true, $clearState = true, $revid = null ) {
/**
* First pass--just handle <nowiki> sections, pass the rest off
* to internalParse() which does all the real work.
@@ -724,7 +725,7 @@ class Parser
$descriptorspec = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
- 2 => array('file', '/dev/null', 'a')
+ 2 => array('file', '/dev/null', 'a') // FIXME: this line in UNIX-specific, it generates a warning on Windows, because /dev/null is not a valid Windows file.
);
$pipes = array();
$process = proc_open("$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes);
@@ -872,7 +873,7 @@ class Parser
array_push ( $td_history , false );
array_push ( $last_tag_history , '' );
}
- else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 ) == '|+' ) {
+ else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 ) == '|+' ) {
// This might be cell elements, td, th or captions
if ( substr ( $line , 0 , 2 ) == '|+' ) {
$first_character = '+';
@@ -1002,6 +1003,7 @@ class Parser
$text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ) );
$text = $this->replaceVariables( $text, $args );
+ wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
// Tables need to come after variable replacement for things to work
// properly; putting them before other transformations should keep
@@ -1086,7 +1088,7 @@ class Parser
}
$url = wfMsg( $urlmsg, $id);
- $sk =& $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin();
$la = $sk->getExternalLinkAttributes( $url, $keyword.$id );
$text = "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>";
}
@@ -1287,7 +1289,8 @@ class Parser
$output .= '</i>';
if ($state == 'bi')
$output .= '</b>';
- if ($state == 'both')
+ # There might be lonely ''''', so make sure we have a buffer
+ if ($state == 'both' && $buffer)
$output .= '<b><i>'.$buffer.'</i></b>';
return $output;
}
@@ -1306,7 +1309,7 @@ class Parser
$fname = 'Parser::replaceExternalLinks';
wfProfileIn( $fname );
- $sk =& $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin();
$bits = preg_split( EXT_LINK_BRACKETED, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
@@ -1395,7 +1398,7 @@ class Parser
$s = array_shift( $bits );
$i = 0;
- $sk =& $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin();
while ( $i < count( $bits ) ){
$protocol = $bits[$i++];
@@ -1468,7 +1471,7 @@ class Parser
* @param string
* @return string
* @static
- * @fixme This can merge genuinely required bits in the path or query string,
+ * @todo This can merge genuinely required bits in the path or query string,
* breaking legit URLs. A proper fix would treat the various parts of
* the URL differently; as a workaround, just use the output for
* statistical records, not for actual linking/output.
@@ -1503,7 +1506,7 @@ class Parser
* @private
*/
function maybeMakeExternalImage( $url ) {
- $sk =& $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin();
$imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
$imagesexception = !empty($imagesfrom);
$text = false;
@@ -1533,7 +1536,7 @@ class Parser
# the % is needed to support urlencoded titles as well
if ( !$tc ) { $tc = Title::legalChars() . '#%'; }
- $sk =& $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin();
#split the entire text string on occurences of [[
$a = explode( '[[', ' ' . $s );
@@ -1552,7 +1555,6 @@ class Parser
$e2 = wfMsgForContent( 'linkprefix' );
$useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
-
if( is_null( $this->mTitle ) ) {
throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
}
@@ -1569,10 +1571,11 @@ class Parser
$prefix = '';
}
- if($wgContLang->hasVariants())
+ if($wgContLang->hasVariants()) {
$selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText());
- else
+ } else {
$selflink = array($this->mTitle->getPrefixedText());
+ }
$useSubpages = $this->areSubpagesAllowed();
wfProfileOut( $fname.'-setup' );
@@ -1626,7 +1629,7 @@ class Parser
$might_be_img = true;
$text = $m[2];
if ( strpos( $m[1], '%' ) !== false ) {
- $m[1] = urldecode($m[1]);
+ $m[1] = urldecode($m[1]);
}
$trail = "";
} else { # Invalid form; output directly
@@ -1640,7 +1643,7 @@ class Parser
# Don't allow internal links to pages containing
# PROTO: where PROTO is a valid URL protocol; these
# should be external links.
- if (preg_match('/^(\b(?:' . wfUrlProtocols() . '))/', $m[1])) {
+ if (preg_match('/^\b(?:' . wfUrlProtocols() . ')/', $m[1])) {
$s .= $prefix . '[[' . $line ;
continue;
}
@@ -1723,8 +1726,8 @@ class Parser
wfProfileIn( "$fname-interwiki" );
if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) {
$this->mOutput->addLanguageLink( $nt->getFullText() );
- $s = rtrim($s . "\n");
- $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail;
+ $s = rtrim($s . $prefix);
+ $s .= trim($trail, "\n") == '' ? '': $prefix . $trail;
wfProfileOut( "$fname-interwiki" );
continue;
}
@@ -1778,11 +1781,12 @@ class Parser
}
}
- if( ( in_array( $nt->getPrefixedText(), $selflink ) ) &&
- ( $nt->getFragment() === '' ) ) {
- # Self-links are handled specially; generally de-link and change to bold.
- $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
- continue;
+ # Self-link checking
+ if( $nt->getFragment() === '' ) {
+ if( in_array( $nt->getPrefixedText(), $selflink, true ) ) {
+ $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
+ continue;
+ }
}
# Special and Media are pseudo-namespaces; no pages actually exist in them
@@ -1862,7 +1866,7 @@ class Parser
*/
function makeKnownLinkHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
list( $inside, $trail ) = Linker::splitTrail( $trail );
- $sk =& $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin();
$link = $sk->makeKnownLinkObj( $nt, $text, $query, $inside, $prefix );
return $this->armorLinks( $link ) . $trail;
}
@@ -1923,9 +1927,9 @@ class Parser
# Look at the first character
if( $target != '' && $target{0} == '/' ) {
# / at end means we don't want the slash to be shown
- if( substr( $target, -1, 1 ) == '/' ) {
- $target = substr( $target, 1, -1 );
- $noslash = $target;
+ $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
+ if( $trailingSlashes ) {
+ $noslash = $target = substr( $target, 1, -strlen($m[0][0]) );
} else {
$noslash = substr( $target, 1 );
}
@@ -2134,9 +2138,9 @@ class Parser
wfProfileIn( "$fname-paragraph" );
# No prefix (not in list)--go to paragraph mode
// XXX: use a stack for nestable elements like span, table and div
- $openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
+ $openmatch = preg_match('/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
$closematch = preg_match(
- '/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.
+ '/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.
'<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t );
if ( $openmatch or $closematch ) {
$paragraphStack = false;
@@ -2538,6 +2542,8 @@ class Parser
return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() );
case 'numberofadmins':
return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() );
+ case 'numberofedits':
+ return $varCache[$index] = $wgContLang->formatNum( SiteStats::edits() );
case 'currenttimestamp':
return $varCache[$index] = wfTimestampNow();
case 'localtimestamp':
@@ -2852,7 +2858,7 @@ class Parser
return $text;
}
-
+
/// Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
static function createAssocArgs( $args ) {
$assocArgs = array();
@@ -2872,10 +2878,10 @@ class Parser
}
}
}
-
+
return $assocArgs;
}
-
+
/**
* Return the text of a template, after recursively
* replacing any variables or templates within the template.
@@ -2888,7 +2894,7 @@ class Parser
* @private
*/
function braceSubstitution( $piece ) {
- global $wgContLang, $wgLang, $wgAllowDisplayTitle;
+ global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces;
$fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/;
wfProfileIn( $fname );
wfProfileIn( __METHOD__.'-setup' );
@@ -3031,6 +3037,19 @@ class Parser
} else {
# set $text to cached message.
$text = $linestart . $this->mTemplates[$piece['title']];
+ #treat title for cached page the same as others
+ $ns = NS_TEMPLATE;
+ $subpage = '';
+ $part1 = $this->maybeDoSubpageLink( $part1, $subpage );
+ if ($subpage !== '') {
+ $ns = $this->mTitle->getNamespace();
+ }
+ $title = Title::newFromText( $part1, $ns );
+ //used by include size checking
+ $titleText = $title->getPrefixedText();
+ //used by edit section links
+ $replaceHeadings = true;
+
}
}
@@ -3066,6 +3085,9 @@ class Parser
$isHTML = true;
$this->disableCache();
}
+ } else if ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) {
+ $found = false; //access denied
+ wfDebug( "$fname: template inclusion denied for " . $title->getPrefixedDBkey() );
} else {
$articleContent = $this->fetchTemplate( $title );
if ( $articleContent !== false ) {
@@ -3155,7 +3177,7 @@ class Parser
# If the template begins with a table or block-level
# element, it should be treated as beginning a new line.
- if (!$piece['lineStart'] && preg_match('/^({\\||:|;|#|\*)/', $text)) /*}*/{
+ if (!$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\*)/', $text)) /*}*/{
$text = "\n" . $text;
}
} elseif ( !$noargs ) {
@@ -3271,7 +3293,7 @@ class Parser
return wfMsg('scarytranscludedisabled');
$url = $title->getFullUrl( "action=$action" );
-
+
if (strlen($url) > 255)
return wfMsg('scarytranscludetoolong');
return $this->fetchScaryTemplateMaybeFromCache($url);
@@ -3279,7 +3301,7 @@ class Parser
function fetchScaryTemplateMaybeFromCache($url) {
global $wgTranscludeCacheExpiry;
- $dbr =& wfGetDB(DB_SLAVE);
+ $dbr = wfGetDB(DB_SLAVE);
$obj = $dbr->selectRow('transcache', array('tc_time', 'tc_contents'),
array('tc_url' => $url));
if ($obj) {
@@ -3294,7 +3316,7 @@ class Parser
if (!$text)
return wfMsg('scarytranscludefailed', $url);
- $dbw =& wfGetDB(DB_MASTER);
+ $dbw = wfGetDB(DB_MASTER);
$dbw->replace('transcache', array('tc_url'), array(
'tc_url' => $url,
'tc_time' => time(),
@@ -3395,7 +3417,7 @@ class Parser
global $wgMaxTocLevel, $wgContLang;
$doNumberHeadings = $this->mOptions->getNumberHeadings();
- if( !$this->mTitle->userCanEdit() ) {
+ if( !$this->mTitle->quickUserCan( 'edit' ) ) {
$showEditLink = 0;
} else {
$showEditLink = $this->mOptions->getEditSection();
@@ -3437,7 +3459,7 @@ class Parser
}
# We need this to perform operations on the HTML
- $sk =& $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin();
# headline counter
$headlineCount = 0;
@@ -3723,11 +3745,7 @@ class Parser
}
# Trim trailing whitespace
- # __END__ tag allows for trailing
- # whitespace to be deliberately included
$text = rtrim( $text );
- $mw =& MagicWord::get( 'end' );
- $mw->matchAndRemove( $text );
return $text;
}
@@ -3847,7 +3865,7 @@ class Parser
wfProfileIn($fname);
- if ( $wgTitle ) {
+ if ( $wgTitle && !( $wgTitle instanceof FakeTitle ) ) {
$this->mTitle = $wgTitle;
} else {
$this->mTitle = Title::newFromText('msg');
@@ -3966,12 +3984,12 @@ class Parser
$pdbks = array();
$colours = array();
- $sk =& $this->mOptions->getSkin();
+ $sk = $this->mOptions->getSkin();
$linkCache =& LinkCache::singleton();
if ( !empty( $this->mLinkHolders['namespaces'] ) ) {
wfProfileIn( $fname.'-check' );
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$page = $dbr->tableName( 'page' );
$threshold = $wgUser->getOption('stubthreshold');
@@ -4161,8 +4179,8 @@ class Parser
if(isset($categoryMap[$vardbk])){
$oldkey = $categoryMap[$vardbk];
if($oldkey != $vardbk)
- $varCategories[$oldkey]=$vardbk;
- }
+ $varCategories[$oldkey]=$vardbk;
+ }
}
// rebuild the categories in original order (if there are replacements)
@@ -4303,6 +4321,7 @@ class Parser
*/
function renderImageGallery( $text, $params ) {
$ig = new ImageGallery();
+ $ig->setContextTitle( $this->mTitle );
$ig->setShowBytes( false );
$ig->setShowFilename( false );
$ig->setParsing();
@@ -4314,6 +4333,15 @@ class Parser
$caption = $this->replaceInternalLinks( $caption );
$ig->setCaptionHtml( $caption );
}
+ if( isset( $params['perrow'] ) ) {
+ $ig->setPerRow( $params['perrow'] );
+ }
+ if( isset( $params['widths'] ) ) {
+ $ig->setWidths( $params['widths'] );
+ }
+ if( isset( $params['heights'] ) ) {
+ $ig->setHeights( $params['heights'] );
+ }
$lines = explode( "\n", $text );
foreach ( $lines as $line ) {
@@ -4359,10 +4387,8 @@ class Parser
* Parse image options text and use it to make an image
*/
function makeImage( $nt, $options ) {
- global $wgUseImageResize, $wgDjvuRenderer;
-
- $align = '';
-
+ # @TODO: let the MediaHandler specify its transform parameters
+ #
# Check if the options text is of the form "options|alt text"
# Options are:
# * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
@@ -4372,61 +4398,74 @@ class Parser
# * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
# * center center the image
# * framed Keep original image size, no magnify-button.
-
- $part = explode( '|', $options);
-
+ # vertical-align values (no % or length right now):
+ # * baseline
+ # * sub
+ # * super
+ # * top
+ # * text-top
+ # * middle
+ # * bottom
+ # * text-bottom
+
+
+ $part = array_map( 'trim', explode( '|', $options) );
+
+ $mwAlign = array();
+ $alignments = array( 'left', 'right', 'center', 'none', 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom' );
+ foreach ( $alignments as $alignment ) {
+ $mwAlign[$alignment] =& MagicWord::get( 'img_'.$alignment );
+ }
$mwThumb =& MagicWord::get( 'img_thumbnail' );
$mwManualThumb =& MagicWord::get( 'img_manualthumb' );
- $mwLeft =& MagicWord::get( 'img_left' );
- $mwRight =& MagicWord::get( 'img_right' );
- $mwNone =& MagicWord::get( 'img_none' );
$mwWidth =& MagicWord::get( 'img_width' );
- $mwCenter =& MagicWord::get( 'img_center' );
$mwFramed =& MagicWord::get( 'img_framed' );
$mwPage =& MagicWord::get( 'img_page' );
$caption = '';
- $width = $height = $framed = $thumb = false;
- $page = null;
+ $params = array();
+ $framed = $thumb = false;
$manual_thumb = '' ;
+ $align = $valign = '';
+ $sk = $this->mOptions->getSkin();
foreach( $part as $val ) {
- if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
+ if ( !is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
$thumb=true;
} elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) {
# use manually specified thumbnail
$thumb=true;
$manual_thumb = $match;
- } elseif ( ! is_null( $mwRight->matchVariableStartToEnd($val) ) ) {
- # remember to set an alignment, don't render immediately
- $align = 'right';
- } elseif ( ! is_null( $mwLeft->matchVariableStartToEnd($val) ) ) {
- # remember to set an alignment, don't render immediately
- $align = 'left';
- } elseif ( ! is_null( $mwCenter->matchVariableStartToEnd($val) ) ) {
- # remember to set an alignment, don't render immediately
- $align = 'center';
- } elseif ( ! is_null( $mwNone->matchVariableStartToEnd($val) ) ) {
- # remember to set an alignment, don't render immediately
- $align = 'none';
- } elseif ( isset( $wgDjvuRenderer ) && $wgDjvuRenderer
- && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
- # Select a page in a multipage document
- $page = $match;
- } elseif ( $wgUseImageResize && !$width && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
- wfDebug( "img_width match: $match\n" );
- # $match is the image width in pixels
- $m = array();
- if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
- $width = intval( $m[1] );
- $height = intval( $m[2] );
+ } else {
+ foreach( $alignments as $alignment ) {
+ if ( ! is_null( $mwAlign[$alignment]->matchVariableStartToEnd($val) ) ) {
+ switch ( $alignment ) {
+ case 'left': case 'right': case 'center': case 'none':
+ $align = $alignment; break;
+ default:
+ $valign = $alignment;
+ }
+ continue 2;
+ }
+ }
+ if ( ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
+ # Select a page in a multipage document
+ $params['page'] = $match;
+ } elseif ( !isset( $params['width'] ) && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
+ wfDebug( "img_width match: $match\n" );
+ # $match is the image width in pixels
+ $m = array();
+ if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
+ $params['width'] = intval( $m[1] );
+ $params['height'] = intval( $m[2] );
+ } else {
+ $params['width'] = intval($match);
+ }
+ } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
+ $framed=true;
} else {
- $width = intval($match);
+ $caption = $val;
}
- } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
- $framed=true;
- } else {
- $caption = $val;
}
}
# Strip bad stuff out of the alt text
@@ -4439,8 +4478,7 @@ class Parser
$alt = Sanitizer::stripAllTags( $alt );
# Linker does the rest
- $sk =& $this->mOptions->getSkin();
- return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb, $page );
+ return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $params, $framed, $thumb, $manual_thumb, $valign );
}
/**
@@ -4518,24 +4556,6 @@ class Parser
$uniq = preg_quote( $this->uniqPrefix(), '/' );
$comment = "(?:$uniq-!--.*?QINU)";
$secs = preg_split(
- /*
- "/
- ^(
- (?:$comment|<\/?noinclude>)* # Initial comments will be stripped
- (?:
- (=+) # Should this be limited to 6?
- .+? # Section title...
- \\2 # Ending = count must match start
- |
- ^
- <h([1-6])\b.*?>
- .*?
- <\/h\\3\s*>
- )
- (?:$comment|<\/?noinclude>|\s+)* # Trailing whitespace ok
- )$
- /mix",
- */
"/
(
^
@@ -4559,7 +4579,8 @@ class Parser
// "Section 0" returns the content before any other section.
$rv = $secs[0];
} else {
- $rv = "";
+ //track missing section, will replace if found.
+ $rv = $newtext;
}
} elseif( $mode == "replace" ) {
if( $section == 0 ) {
@@ -4614,8 +4635,10 @@ class Parser
}
}
}
- # reinsert stripped tags
- $rv = trim( $stripState->unstripBoth( $rv ) );
+ if (is_string($rv))
+ # reinsert stripped tags
+ $rv = trim( $stripState->unstripBoth( $rv ) );
+
return $rv;
}
@@ -4628,34 +4651,35 @@ class Parser
*
* @param $text String: text to look in
* @param $section Integer: section number
+ * @param $deftext: default to return if section is not found
* @return string text of the requested section
*/
- function getSection( $text, $section ) {
- return $this->extractSections( $text, $section, "get" );
+ public function getSection( $text, $section, $deftext='' ) {
+ return $this->extractSections( $text, $section, "get", $deftext );
}
- function replaceSection( $oldtext, $section, $text ) {
+ public function replaceSection( $oldtext, $section, $text ) {
return $this->extractSections( $oldtext, $section, "replace", $text );
}
/**
- * Get the timestamp associated with the current revision, adjusted for
+ * Get the timestamp associated with the current revision, adjusted for
* the default server-local timestamp
*/
function getRevisionTimestamp() {
if ( is_null( $this->mRevisionTimestamp ) ) {
wfProfileIn( __METHOD__ );
global $wgContLang;
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
array( 'rev_id' => $this->mRevisionId ), __METHOD__ );
-
+
// Normalize timestamp to internal MW format for timezone processing.
// This has the added side-effect of replacing a null value with
// the current time, which gives us more sensible behavior for
// previews.
$timestamp = wfTimestamp( TS_MW, $timestamp );
-
+
// The cryptic '' timezone parameter tells to use the site-default
// timezone offset instead of the user settings.
//
@@ -4663,12 +4687,12 @@ class Parser
// to other users, and potentially even used inside links and such,
// it needs to be consistent for all visitors.
$this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
-
+
wfProfileOut( __METHOD__ );
}
return $this->mRevisionTimestamp;
}
-
+
/**
* Mutator for $mDefaultSort
*
@@ -4677,7 +4701,7 @@ class Parser
public function setDefaultSort( $sort ) {
$this->mDefaultSort = $sort;
}
-
+
/**
* Accessor for $mDefaultSort
* Will use the title/prefixed title if none is set
@@ -4693,241 +4717,13 @@ class Parser
: $this->mTitle->getPrefixedText();
}
}
-
-}
-
-/**
- * @todo document
- * @package MediaWiki
- */
-class ParserOutput
-{
- var $mText, # The output text
- $mLanguageLinks, # List of the full text of language links, in the order they appear
- $mCategories, # Map of category names to sort keys
- $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}}
- $mCacheTime, # Time when this object was generated, or -1 for uncacheable. Used in ParserCache.
- $mVersion, # Compatibility check
- $mTitleText, # title text of the chosen language variant
- $mLinks, # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken.
- $mTemplates, # 2-D map of NS/DBK to ID for the template references. ID=zero for broken.
- $mImages, # DB keys of the images used, in the array key only
- $mExternalLinks, # External link URLs, in the key only
- $mHTMLtitle, # Display HTML title
- $mSubtitle, # Additional subtitle
- $mNewSection, # Show a new section link?
- $mNoGallery; # No gallery on category page? (__NOGALLERY__)
-
- function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(),
- $containsOldMagic = false, $titletext = '' )
- {
- $this->mText = $text;
- $this->mLanguageLinks = $languageLinks;
- $this->mCategories = $categoryLinks;
- $this->mContainsOldMagic = $containsOldMagic;
- $this->mCacheTime = '';
- $this->mVersion = MW_PARSER_VERSION;
- $this->mTitleText = $titletext;
- $this->mLinks = array();
- $this->mTemplates = array();
- $this->mImages = array();
- $this->mExternalLinks = array();
- $this->mHTMLtitle = "" ;
- $this->mSubtitle = "" ;
- $this->mNewSection = false;
- $this->mNoGallery = false;
- }
- function getText() { return $this->mText; }
- function &getLanguageLinks() { return $this->mLanguageLinks; }
- function getCategoryLinks() { return array_keys( $this->mCategories ); }
- function &getCategories() { return $this->mCategories; }
- function getCacheTime() { return $this->mCacheTime; }
- function getTitleText() { return $this->mTitleText; }
- function &getLinks() { return $this->mLinks; }
- function &getTemplates() { return $this->mTemplates; }
- function &getImages() { return $this->mImages; }
- function &getExternalLinks() { return $this->mExternalLinks; }
- function getNoGallery() { return $this->mNoGallery; }
- function getSubtitle() { return $this->mSubtitle; }
-
- function containsOldMagic() { return $this->mContainsOldMagic; }
- function setText( $text ) { return wfSetVar( $this->mText, $text ); }
- function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
- function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); }
- function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
- function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
- function setTitleText( $t ) { return wfSetVar($this->mTitleText, $t); }
- function setSubtitle( $st ) { return wfSetVar( $this->mSubtitle, $st ); }
-
- function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; }
- function addImage( $name ) { $this->mImages[$name] = 1; }
- function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; }
- function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; }
-
- function setNewSection( $value ) {
- $this->mNewSection = (bool)$value;
- }
- function getNewSection() {
- return (bool)$this->mNewSection;
- }
-
- function addLink( $title, $id = null ) {
- $ns = $title->getNamespace();
- $dbk = $title->getDBkey();
- if ( !isset( $this->mLinks[$ns] ) ) {
- $this->mLinks[$ns] = array();
- }
- if ( is_null( $id ) ) {
- $id = $title->getArticleID();
- }
- $this->mLinks[$ns][$dbk] = $id;
- }
-
- function addTemplate( $title, $id ) {
- $ns = $title->getNamespace();
- $dbk = $title->getDBkey();
- if ( !isset( $this->mTemplates[$ns] ) ) {
- $this->mTemplates[$ns] = array();
- }
- $this->mTemplates[$ns][$dbk] = $id;
- }
-
- /**
- * Return true if this cached output object predates the global or
- * per-article cache invalidation timestamps, or if it comes from
- * an incompatible older version.
- *
- * @param string $touched the affected article's last touched timestamp
- * @return bool
- * @public
- */
- function expired( $touched ) {
- global $wgCacheEpoch;
- return $this->getCacheTime() == -1 || // parser says it's uncacheable
- $this->getCacheTime() < $touched ||
- $this->getCacheTime() <= $wgCacheEpoch ||
- !isset( $this->mVersion ) ||
- version_compare( $this->mVersion, MW_PARSER_VERSION, "lt" );
- }
}
/**
- * Set options of the Parser
- * @todo document
- * @package MediaWiki
+ * @todo document, briefly.
+ * @addtogroup Parser
*/
-class ParserOptions
-{
- # All variables are supposed to be private in theory, although in practise this is not the case.
- var $mUseTeX; # Use texvc to expand <math> tags
- var $mUseDynamicDates; # Use DateFormatter to format dates
- var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
- var $mAllowExternalImages; # Allow external images inline
- var $mAllowExternalImagesFrom; # If not, any exception?
- var $mSkin; # Reference to the preferred skin
- var $mDateFormat; # Date format index
- var $mEditSection; # Create "edit section" links
- var $mNumberHeadings; # Automatically number headings
- var $mAllowSpecialInclusion; # Allow inclusion of special pages
- var $mTidy; # Ask for tidy cleanup
- var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR
- var $mMaxIncludeSize; # Maximum size of template expansions, in bytes
- var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
-
- var $mUser; # Stored user object, just used to initialise the skin
-
- function getUseTeX() { return $this->mUseTeX; }
- function getUseDynamicDates() { return $this->mUseDynamicDates; }
- function getInterwikiMagic() { return $this->mInterwikiMagic; }
- function getAllowExternalImages() { return $this->mAllowExternalImages; }
- function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; }
- function getEditSection() { return $this->mEditSection; }
- function getNumberHeadings() { return $this->mNumberHeadings; }
- function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; }
- function getTidy() { return $this->mTidy; }
- function getInterfaceMessage() { return $this->mInterfaceMessage; }
- function getMaxIncludeSize() { return $this->mMaxIncludeSize; }
- function getRemoveComments() { return $this->mRemoveComments; }
-
- function &getSkin() {
- if ( !isset( $this->mSkin ) ) {
- $this->mSkin = $this->mUser->getSkin();
- }
- return $this->mSkin;
- }
-
- function getDateFormat() {
- if ( !isset( $this->mDateFormat ) ) {
- $this->mDateFormat = $this->mUser->getDatePreference();
- }
- return $this->mDateFormat;
- }
-
- function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
- function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
- function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
- function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
- function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); }
- function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); }
- function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); }
- function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
- function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); }
- function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); }
- function setSkin( $x ) { $this->mSkin = $x; }
- function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); }
- function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); }
- function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
-
- function ParserOptions( $user = null ) {
- $this->initialiseFromUser( $user );
- }
-
- /**
- * Get parser options
- * @static
- */
- static function newFromUser( $user ) {
- return new ParserOptions( $user );
- }
-
- /** Get user options */
- function initialiseFromUser( $userInput ) {
- global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
- global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize;
- $fname = 'ParserOptions::initialiseFromUser';
- wfProfileIn( $fname );
- if ( !$userInput ) {
- global $wgUser;
- if ( isset( $wgUser ) ) {
- $user = $wgUser;
- } else {
- $user = new User;
- }
- } else {
- $user =& $userInput;
- }
-
- $this->mUser = $user;
-
- $this->mUseTeX = $wgUseTeX;
- $this->mUseDynamicDates = $wgUseDynamicDates;
- $this->mInterwikiMagic = $wgInterwikiMagic;
- $this->mAllowExternalImages = $wgAllowExternalImages;
- $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
- $this->mSkin = null; # Deferred
- $this->mDateFormat = null; # Deferred
- $this->mEditSection = true;
- $this->mNumberHeadings = $user->getOption( 'numberheadings' );
- $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;
- $this->mTidy = false;
- $this->mInterfaceMessage = false;
- $this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
- $this->mRemoveComments = true;
- wfProfileOut( $fname );
- }
-}
-
class OnlyIncludeReplacer {
var $output = '';
@@ -4940,6 +4736,10 @@ class OnlyIncludeReplacer {
}
}
+/**
+ * @todo document, briefly.
+ * @addtogroup Parser
+ */
class StripState {
var $general, $nowiki;
diff --git a/includes/ParserCache.php b/includes/ParserCache.php
index 37a42b7f..1489fcf9 100644
--- a/includes/ParserCache.php
+++ b/includes/ParserCache.php
@@ -1,13 +1,8 @@
<?php
/**
*
- * @package MediaWiki
- * @subpackage Cache
- */
-
-/**
- *
- * @package MediaWiki
+ * @addtogroup Cache
+ * @todo document
*/
class ParserCache {
/**
@@ -28,14 +23,14 @@ class ParserCache {
*
* @param object $memCached
*/
- function ParserCache( &$memCached ) {
+ function __construct( &$memCached ) {
$this->mMemc =& $memCached;
}
function getKey( &$article, &$user ) {
global $action;
$hash = $user->getPageRenderingHash();
- if( !$article->mTitle->userCanEdit() ) {
+ if( !$article->mTitle->quickUserCan( 'edit' ) ) {
// section edit links are suppressed even if the user has them on
$edit = '!edit=0';
} else {
@@ -95,31 +90,30 @@ class ParserCache {
function save( $parserOutput, &$article, &$user ){
global $wgParserCacheExpireTime;
$key = $this->getKey( $article, $user );
-
+
if( $parserOutput->getCacheTime() != -1 ) {
-
+
$now = wfTimestampNow();
$parserOutput->setCacheTime( $now );
-
+
// Save the timestamp so that we don't have to load the revision row on view
$parserOutput->mTimestamp = $article->getTimestamp();
-
+
$parserOutput->mText .= "\n<!-- Saved in parser cache with key $key and timestamp $now -->\n";
wfDebug( "Saved in parser cache with key $key and timestamp $now\n" );
-
+
if( $parserOutput->containsOldMagic() ){
$expire = 3600; # 1 hour
} else {
$expire = $wgParserCacheExpireTime;
}
$this->mMemc->set( $key, $parserOutput, $expire );
-
+
} else {
wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" );
}
-
}
-
+
}
?>
diff --git a/includes/ParserOptions.php b/includes/ParserOptions.php
new file mode 100644
index 00000000..e335720f
--- /dev/null
+++ b/includes/ParserOptions.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * Set options of the Parser
+ * @todo document
+ * @addtogroup Parser
+ */
+class ParserOptions
+{
+ # All variables are supposed to be private in theory, although in practise this is not the case.
+ var $mUseTeX; # Use texvc to expand <math> tags
+ var $mUseDynamicDates; # Use DateFormatter to format dates
+ var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
+ var $mAllowExternalImages; # Allow external images inline
+ var $mAllowExternalImagesFrom; # If not, any exception?
+ var $mSkin; # Reference to the preferred skin
+ var $mDateFormat; # Date format index
+ var $mEditSection; # Create "edit section" links
+ var $mNumberHeadings; # Automatically number headings
+ var $mAllowSpecialInclusion; # Allow inclusion of special pages
+ var $mTidy; # Ask for tidy cleanup
+ var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR
+ var $mMaxIncludeSize; # Maximum size of template expansions, in bytes
+ var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
+
+ var $mUser; # Stored user object, just used to initialise the skin
+
+ function getUseTeX() { return $this->mUseTeX; }
+ function getUseDynamicDates() { return $this->mUseDynamicDates; }
+ function getInterwikiMagic() { return $this->mInterwikiMagic; }
+ function getAllowExternalImages() { return $this->mAllowExternalImages; }
+ function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; }
+ function getEditSection() { return $this->mEditSection; }
+ function getNumberHeadings() { return $this->mNumberHeadings; }
+ function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; }
+ function getTidy() { return $this->mTidy; }
+ function getInterfaceMessage() { return $this->mInterfaceMessage; }
+ function getMaxIncludeSize() { return $this->mMaxIncludeSize; }
+ function getRemoveComments() { return $this->mRemoveComments; }
+
+ function getSkin() {
+ if ( !isset( $this->mSkin ) ) {
+ $this->mSkin = $this->mUser->getSkin();
+ }
+ return $this->mSkin;
+ }
+
+ function getDateFormat() {
+ if ( !isset( $this->mDateFormat ) ) {
+ $this->mDateFormat = $this->mUser->getDatePreference();
+ }
+ return $this->mDateFormat;
+ }
+
+ function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
+ function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
+ function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
+ function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
+ function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); }
+ function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); }
+ function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); }
+ function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
+ function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); }
+ function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); }
+ function setSkin( $x ) { $this->mSkin = $x; }
+ function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); }
+ function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); }
+ function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
+
+ function __construct( $user = null ) {
+ $this->initialiseFromUser( $user );
+ }
+
+ /**
+ * Get parser options
+ * @static
+ */
+ static function newFromUser( $user ) {
+ return new ParserOptions( $user );
+ }
+
+ /** Get user options */
+ function initialiseFromUser( $userInput ) {
+ global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
+ global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize;
+ $fname = 'ParserOptions::initialiseFromUser';
+ wfProfileIn( $fname );
+ if ( !$userInput ) {
+ global $wgUser;
+ if ( isset( $wgUser ) ) {
+ $user = $wgUser;
+ } else {
+ $user = new User;
+ }
+ } else {
+ $user =& $userInput;
+ }
+
+ $this->mUser = $user;
+
+ $this->mUseTeX = $wgUseTeX;
+ $this->mUseDynamicDates = $wgUseDynamicDates;
+ $this->mInterwikiMagic = $wgInterwikiMagic;
+ $this->mAllowExternalImages = $wgAllowExternalImages;
+ $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
+ $this->mSkin = null; # Deferred
+ $this->mDateFormat = null; # Deferred
+ $this->mEditSection = true;
+ $this->mNumberHeadings = $user->getOption( 'numberheadings' );
+ $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;
+ $this->mTidy = false;
+ $this->mInterfaceMessage = false;
+ $this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
+ $this->mRemoveComments = true;
+ wfProfileOut( $fname );
+ }
+}
+
+?>
diff --git a/includes/ParserOutput.php b/includes/ParserOutput.php
new file mode 100644
index 00000000..03f1819c
--- /dev/null
+++ b/includes/ParserOutput.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * @todo document
+ * @addtogroup Parser
+ */
+class ParserOutput
+{
+ var $mText, # The output text
+ $mLanguageLinks, # List of the full text of language links, in the order they appear
+ $mCategories, # Map of category names to sort keys
+ $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}}
+ $mCacheTime, # Time when this object was generated, or -1 for uncacheable. Used in ParserCache.
+ $mVersion, # Compatibility check
+ $mTitleText, # title text of the chosen language variant
+ $mLinks, # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken.
+ $mTemplates, # 2-D map of NS/DBK to ID for the template references. ID=zero for broken.
+ $mImages, # DB keys of the images used, in the array key only
+ $mExternalLinks, # External link URLs, in the key only
+ $mHTMLtitle, # Display HTML title
+ $mSubtitle, # Additional subtitle
+ $mNewSection, # Show a new section link?
+ $mNoGallery, # No gallery on category page? (__NOGALLERY__)
+ $mHeadItems; # Items to put in the <head> section
+
+ function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(),
+ $containsOldMagic = false, $titletext = '' )
+ {
+ $this->mText = $text;
+ $this->mLanguageLinks = $languageLinks;
+ $this->mCategories = $categoryLinks;
+ $this->mContainsOldMagic = $containsOldMagic;
+ $this->mCacheTime = '';
+ $this->mVersion = Parser::VERSION;
+ $this->mTitleText = $titletext;
+ $this->mLinks = array();
+ $this->mTemplates = array();
+ $this->mImages = array();
+ $this->mExternalLinks = array();
+ $this->mHTMLtitle = "" ;
+ $this->mSubtitle = "" ;
+ $this->mNewSection = false;
+ $this->mNoGallery = false;
+ $this->mHeadItems = array();
+ }
+
+ function getText() { return $this->mText; }
+ function &getLanguageLinks() { return $this->mLanguageLinks; }
+ function getCategoryLinks() { return array_keys( $this->mCategories ); }
+ function &getCategories() { return $this->mCategories; }
+ function getCacheTime() { return $this->mCacheTime; }
+ function getTitleText() { return $this->mTitleText; }
+ function &getLinks() { return $this->mLinks; }
+ function &getTemplates() { return $this->mTemplates; }
+ function &getImages() { return $this->mImages; }
+ function &getExternalLinks() { return $this->mExternalLinks; }
+ function getNoGallery() { return $this->mNoGallery; }
+ function getSubtitle() { return $this->mSubtitle; }
+
+ function containsOldMagic() { return $this->mContainsOldMagic; }
+ function setText( $text ) { return wfSetVar( $this->mText, $text ); }
+ function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
+ function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); }
+ function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
+ function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
+ function setTitleText( $t ) { return wfSetVar($this->mTitleText, $t); }
+ function setSubtitle( $st ) { return wfSetVar( $this->mSubtitle, $st ); }
+
+ function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; }
+ function addImage( $name ) { $this->mImages[$name] = 1; }
+ function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; }
+ function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; }
+
+ function setNewSection( $value ) {
+ $this->mNewSection = (bool)$value;
+ }
+ function getNewSection() {
+ return (bool)$this->mNewSection;
+ }
+
+ function addLink( $title, $id = null ) {
+ $ns = $title->getNamespace();
+ $dbk = $title->getDBkey();
+ if ( !isset( $this->mLinks[$ns] ) ) {
+ $this->mLinks[$ns] = array();
+ }
+ if ( is_null( $id ) ) {
+ $id = $title->getArticleID();
+ }
+ $this->mLinks[$ns][$dbk] = $id;
+ }
+
+ function addTemplate( $title, $id ) {
+ $ns = $title->getNamespace();
+ $dbk = $title->getDBkey();
+ if ( !isset( $this->mTemplates[$ns] ) ) {
+ $this->mTemplates[$ns] = array();
+ }
+ $this->mTemplates[$ns][$dbk] = $id;
+ }
+
+ /**
+ * Return true if this cached output object predates the global or
+ * per-article cache invalidation timestamps, or if it comes from
+ * an incompatible older version.
+ *
+ * @param string $touched the affected article's last touched timestamp
+ * @return bool
+ * @public
+ */
+ function expired( $touched ) {
+ global $wgCacheEpoch;
+ return $this->getCacheTime() == -1 || // parser says it's uncacheable
+ $this->getCacheTime() < $touched ||
+ $this->getCacheTime() <= $wgCacheEpoch ||
+ !isset( $this->mVersion ) ||
+ version_compare( $this->mVersion, Parser::VERSION, "lt" );
+ }
+
+ /**
+ * Add some text to the <head>.
+ * If $tag is set, the section with that tag will only be included once
+ * in a given page.
+ */
+ function addHeadItem( $section, $tag = false ) {
+ if ( $tag !== false ) {
+ $this->mHeadItems[$tag] = $section;
+ } else {
+ $this->mHeadItems[] = $section;
+ }
+ }
+}
+
+?>
diff --git a/includes/PatrolLog.php b/includes/PatrolLog.php
new file mode 100644
index 00000000..a22839ff
--- /dev/null
+++ b/includes/PatrolLog.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * Class containing static functions for working with
+ * logs of patrol events
+ *
+ * @author Rob Church <robchur@gmail.com>
+ */
+class PatrolLog {
+
+ /**
+ * Record a log event for a change being patrolled
+ *
+ * @param mixed $change Change identifier or RecentChange object
+ * @param bool $auto Was this patrol event automatic?
+ */
+ public static function record( $change, $auto = false ) {
+ if( !( is_object( $change ) && $change instanceof RecentChange ) ) {
+ $change = RecentChange::newFromId( $change );
+ if( !is_object( $change ) )
+ return false;
+ }
+ $title = Title::makeTitleSafe( $change->getAttribute( 'rc_namespace' ),
+ $change->getAttribute( 'rc_title' ) );
+ if( is_object( $title ) ) {
+ $params = self::buildParams( $change, $auto );
+ $log = new LogPage( 'patrol', false ); # False suppresses RC entries
+ $log->addEntry( 'patrol', $title, '', $params );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Generate the log action text corresponding to a patrol log item
+ *
+ * @param Title $title Title of the page that was patrolled
+ * @param array $params Log parameters (from logging.log_params)
+ * @param Skin $skin Skin to use for building links, etc.
+ * @return string
+ */
+ public static function makeActionText( $title, $params, $skin ) {
+ # This is a bit of a hack, but...if $skin is not a Skin, then *do nothing*
+ # -- this is fine, because the action text we would be queried for under
+ # these conditions would have gone into recentchanges, which we aren't
+ # supposed to be updating
+ if( is_object( $skin ) ) {
+ list( $cur, $prev, $auto ) = $params;
+ # Standard link to the page in question
+ $link = $skin->makeLinkObj( $title );
+ # Generate a diff link
+ $bits[] = 'oldid=' . urlencode( $cur );
+ $bits[] = 'diff=prev';
+ $bits = implode( '&', $bits );
+ $diff = $skin->makeLinkObj( $title, htmlspecialchars( wfMsg( 'patrol-log-diff', $cur ) ), $bits );
+ # Indicate whether or not the patrolling was automatic
+ $auto = $auto ? wfMsgHtml( 'patrol-log-auto' ) : '';
+ # Put it all together
+ return wfMsgHtml( 'patrol-log-line', $diff, $link, $auto );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Prepare log parameters for a patrolled change
+ *
+ * @param RecentChange $change RecentChange to represent
+ * @param bool $auto Whether the patrol event was automatic
+ * @return array
+ */
+ private static function buildParams( $change, $auto ) {
+ return array(
+ $change->getAttribute( 'rc_this_oldid' ),
+ $change->getAttribute( 'rc_last_oldid' ),
+ (int)$auto
+ );
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/includes/Profiler.php b/includes/Profiler.php
index 30cda63f..da3a82ed 100644
--- a/includes/Profiler.php
+++ b/includes/Profiler.php
@@ -1,7 +1,6 @@
<?php
/**
* This file is only included if profiling is enabled
- * @package MediaWiki
*/
$wgProfiling = true;
@@ -41,14 +40,13 @@ if (!function_exists('memory_get_usage')) {
/**
* @todo document
- * @package MediaWiki
+ * @addtogroup Profiler
*/
class Profiler {
var $mStack = array (), $mWorkStack = array (), $mCollated = array ();
var $mCalls = array (), $mTotals = array ();
- function Profiler()
- {
+ function __construct() {
// Push an entry for the pre-profile setup time onto the stack
global $wgRequestTime;
if ( !empty( $wgRequestTime ) ) {
@@ -57,7 +55,6 @@ class Profiler {
} else {
$this->profileIn( '-total' );
}
-
}
function profileIn($functionname) {
@@ -291,7 +288,7 @@ class Profiler {
* @return Integer
* @private
*/
- function calltreeCount(& $stack, $start) {
+ function calltreeCount($stack, $start) {
$level = $stack[$start][1];
$count = 0;
for ($i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i --) {
@@ -308,7 +305,7 @@ class Profiler {
global $wguname, $wgProfilePerHost;
$fname = 'Profiler::logToDB';
- $dbw = & wfGetDB(DB_MASTER);
+ $dbw = wfGetDB(DB_MASTER);
if (!is_object($dbw))
return false;
$errorState = $dbw->ignoreErrors( true );
diff --git a/includes/ProfilerSimple.php b/includes/ProfilerSimple.php
index e69bfc47..f43c7dfc 100644
--- a/includes/ProfilerSimple.php
+++ b/includes/ProfilerSimple.php
@@ -1,20 +1,17 @@
<?php
-/**
- * Simple profiler base class
- * @package MediaWiki
- */
-/**
- * @todo document
- * @package MediaWiki
- */
require_once(dirname(__FILE__).'/Profiler.php');
+/**
+ * Simple profiler base class.
+ * @todo document methods (?)
+ * @addtogroup Profiler
+ */
class ProfilerSimple extends Profiler {
var $mMinimumTime = 0;
var $mProfileID = false;
- function ProfilerSimple() {
+ function __construct() {
global $wgRequestTime,$wgRUstart;
if (!empty($wgRequestTime) && !empty($wgRUstart)) {
$this->mWorkStack[] = array( '-total', 0, $wgRequestTime,$this->getCpuTime($wgRUstart));
@@ -26,7 +23,6 @@ class ProfilerSimple extends Profiler {
if (!is_array($entry)) {
$entry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0);
$this->mCollated["-setup"] =& $entry;
-
}
$entry['cpu'] += $elapsedcpu;
$entry['cpu_sq'] += $elapsedcpu*$elapsedcpu;
@@ -57,7 +53,7 @@ class ProfilerSimple extends Profiler {
if ($wgDebugFunctionEntry) {
$this->debug(str_repeat(' ', count($this->mWorkStack)).'Entering '.$functionname."\n");
}
- $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), microtime(true), $this->getCpuTime());
+ $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), microtime(true), $this->getCpuTime());
}
function profileOut($functionname) {
@@ -87,7 +83,6 @@ class ProfilerSimple extends Profiler {
if (!is_array($entry)) {
$entry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0);
$this->mCollated[$functionname] =& $entry;
-
}
$entry['cpu'] += $elapsedcpu;
$entry['cpu_sq'] += $elapsedcpu*$elapsedcpu;
diff --git a/includes/ProfilerSimpleUDP.php b/includes/ProfilerSimpleUDP.php
index a8527c38..500f1cbd 100644
--- a/includes/ProfilerSimpleUDP.php
+++ b/includes/ProfilerSimpleUDP.php
@@ -1,11 +1,13 @@
<?php
-/* ProfilerSimpleUDP class, that sends out messages for 'udpprofile' daemon
- (the one from wikipedia/udpprofile CVS )
-*/
require_once(dirname(__FILE__).'/Profiler.php');
require_once(dirname(__FILE__).'/ProfilerSimple.php');
+/**
+ * ProfilerSimpleUDP class, that sends out messages for 'udpprofile' daemon
+ * (the one from mediawiki/trunk/udpprofile SVN )
+ * @addtogroup Profiler
+ */
class ProfilerSimpleUDP extends ProfilerSimple {
function getFunctionReport() {
global $wgUDPProfilerHost;
@@ -15,8 +17,7 @@ class ProfilerSimpleUDP extends ProfilerSimple {
# Less than minimum, ignore
return;
}
-
-
+
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$plength=0;
$packet="";
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index f96262fe..3cafbd55 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -17,27 +17,42 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
- *
- * @package MediaWiki
- * @subpackage SpecialPage
*/
+/**
+ * @todo document, briefly.
+ * @addtogroup SpecialPage
+ */
class ProtectionForm {
var $mRestrictions = array();
var $mReason = '';
+ var $mCascade = false;
+ var $mExpiry = null;
- function ProtectionForm( &$article ) {
+ function __construct( &$article ) {
global $wgRequest, $wgUser;
global $wgRestrictionTypes, $wgRestrictionLevels;
$this->mArticle =& $article;
$this->mTitle =& $article->mTitle;
if( $this->mTitle ) {
+ $this->mTitle->loadRestrictions();
+
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 ) );
}
+
+ $this->mCascade = $this->mTitle->areRestrictionsCascading();
+
+ if ( $this->mTitle->mRestrictionsExpiry == 'infinity' ) {
+ $this->mExpiry = 'infinite';
+ } else if ( strlen($this->mTitle->mRestrictionsExpiry) == 0 ) {
+ $this->mExpiry = '';
+ } else {
+ $this->mExpiry = wfTimestamp( TS_RFC2822, $this->mTitle->mRestrictionsExpiry );
+ }
}
// The form will be available in read-only to show levels.
@@ -48,6 +63,9 @@ class ProtectionForm {
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 ) ) {
@@ -56,9 +74,21 @@ class ProtectionForm {
}
}
}
+
+ function execute() {
+ global $wgRequest;
+ if( $wgRequest->wasPosted() ) {
+ if( $this->save() ) {
+ global $wgOut;
+ $wgOut->redirect( $this->mTitle->getFullUrl() );
+ }
+ } else {
+ $this->show();
+ }
+ }
- function show() {
- global $wgOut;
+ function show( $err = null ) {
+ global $wgOut, $wgUser;
$wgOut->setRobotpolicy( 'noindex,nofollow' );
@@ -69,17 +99,47 @@ class ProtectionForm {
return;
}
- if( $this->save() ) {
- $wgOut->redirect( $this->mTitle->getFullUrl() );
- return;
+ list( $cascadeSources, $restrictions ) = $this->mTitle->getCascadeProtectionSources();
+
+ if ( "" != $err ) {
+ $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) );
+ $wgOut->addHTML( "<p class='error'>{$err}</p>\n" );
+ }
+
+ if ( $cascadeSources && count($cascadeSources) > 0 ) {
+ $titles = '';
+
+ foreach ( $cascadeSources as $title ) {
+ $titles .= '* [[:' . $title->getPrefixedText() . "]]\n";
+ }
+
+ $notice = wfMsgExt( 'protect-cascadeon', array('parsemag'), count($cascadeSources) ) . "\r\n$titles";
+
+ $wgOut->addWikiText( $notice );
}
$wgOut->setPageTitle( wfMsg( 'confirmprotect' ) );
$wgOut->setSubtitle( wfMsg( 'protectsub', $this->mTitle->getPrefixedText() ) );
- $wgOut->addWikiText(
- wfMsg( $this->disabled ? "protect-viewtext" : "protect-text",
- wfEscapeWikiText( $this->mTitle->getPrefixedText() ) ) );
+ # 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';
+ }
+ } else {
+ $message = 'protect-text';
+ }
+ $wgOut->addWikiText( wfMsg( $message, wfEscapeWikiText( $this->mTitle->getPrefixedText() ) ) );
$wgOut->addHTML( $this->buildForm() );
@@ -88,20 +148,43 @@ class ProtectionForm {
function save() {
global $wgRequest, $wgUser, $wgOut;
- if( !$wgRequest->wasPosted() ) {
- return false;
- }
-
+
if( $this->disabled ) {
+ $this->show();
return false;
}
$token = $wgRequest->getVal( 'wpEditToken' );
if( !$wgUser->matchEditToken( $token ) ) {
- throw new FatalError( wfMsg( 'sessionfailure' ) );
+ $this->show( wfMsg( 'sessionfailure' ) );
+ return false;
}
- $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason );
+ 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 ) {
+ $this->show( wfMsg( 'protect_expiry_invalid' ) );
+ return false;
+ }
+
+ $expiry = wfTimestamp( TS_MW, $expiry );
+
+ if ( $expiry < wfTimestampNow() ) {
+ $this->show( wfMsg( 'protect_expiry_old' ) );
+ return false;
+ }
+
+ }
+
+ $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason, $this->mCascade, $expiry );
if( !$ok ) {
throw new FatalError( "Unknown error at restriction save time." );
}
@@ -117,6 +200,7 @@ class ProtectionForm {
// 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)' ) );
@@ -148,13 +232,25 @@ class ProtectionForm {
$out .= "</tbody>\n";
$out .= "</table>\n";
+ global $wgEnableCascadingProtection;
+
+ if ($wgEnableCascadingProtection)
+ $out .= $this->buildCascadeInput();
+
+ $out .= "<table>\n";
+ $out .= "<tbody>\n";
+
+ $out .= $this->buildExpiryInput();
+
if( !$this->disabled ) {
- $out .= "<table>\n";
- $out .= "<tbody>\n";
$out .= "<tr><td>" . $this->buildReasonInput() . "</td></tr>\n";
$out .= "<tr><td></td><td>" . $this->buildSubmit() . "</td></tr>\n";
- $out .= "</tbody>\n";
- $out .= "</table>\n";
+ }
+
+ $out .= "</tbody>\n";
+ $out .= "</table>\n";
+
+ if ( !$this->disabled ) {
$out .= "</form>\n";
$out .= $this->buildCleanupScript();
}
@@ -202,11 +298,38 @@ class ProtectionForm {
wfElement( 'input', array(
'size' => 60,
'name' => $id,
- 'id' => $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() {
+ $id = 'mwProtect-expiry';
+
+ $ci = "<tr> <td align=\"right\">";
+ $ci .= wfElement( 'label', array (
+ 'id' => "$id-label",
+ 'for' => $id ),
+ wfMsg( 'protectexpiry' ) );
+ $ci .= "</td> <td align=\"left\">";
+ $ci .= wfElement( 'input', array(
+ 'size' => 60,
+ 'name' => $id,
+ 'id' => $id,
+ 'value' => $this->mExpiry ) + $this->disabledAttrib );
+ $ci .= "</td></tr>";
+
+ return $ci;
}
function buildSubmit() {
return wfElement( 'input', array(
+ 'id' => 'mw-Protect-submit',
'type' => 'submit',
'value' => wfMsg( 'confirm' ) ) );
}
@@ -219,8 +342,17 @@ class ProtectionForm {
}
function buildCleanupScript() {
- return '<script type="text/javascript">protectInitialize("mwProtectSet","' .
- wfEscapeJsString( wfMsg( 'protect-unchain' ) ) . '")</script>';
+ global $wgRestrictionLevels, $wgGroupPermissions;
+ $script = 'var wgCascadeableLevels=';
+ $CascadeableLevels = array();
+ foreach( $wgRestrictionLevels as $key ) {
+ if ( isset($wgGroupPermissions[$key]['protect']) && $wgGroupPermissions[$key]['protect'] ) {
+ $CascadeableLevels[]="'" . wfEscapeJsString($key) . "'";
+ }
+ }
+ $script .= "[" . implode(',',$CascadeableLevels) . "];\n";
+ $script .= 'protectInitialize("mwProtectSet","' . wfEscapeJsString( wfMsg( 'protect-unchain' ) ) . '")';
+ return '<script type="text/javascript">' . $script . '</script>';
}
/**
@@ -239,5 +371,4 @@ class ProtectionForm {
}
}
-
?>
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
index 22ea4947..f72b640f 100644
--- a/includes/ProxyTools.php
+++ b/includes/ProxyTools.php
@@ -1,27 +1,63 @@
<?php
/**
* Functions for dealing with proxies
- * @package MediaWiki
*/
+/**
+ * Extracts the XFF string from the request header
+ * Checks first for "X-Forwarded-For", then "Client-ip"
+ * Note: headers are spoofable
+ * @return string
+ */
function wfGetForwardedFor() {
if( function_exists( 'apache_request_headers' ) ) {
// More reliable than $_SERVER due to case and -/_ folding
$set = apache_request_headers();
$index = 'X-Forwarded-For';
+ $index2 = '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];
+ } else if( isset( $set[$index2] ) ) {
+ return $set[$index2];
} else {
return null;
}
}
-/** Work out the IP address based on various globals */
+/**
+ * Returns the browser/OS data from the request header
+ * Note: headers are spoofable
+ * @return string
+ */
+function wfGetAgent() {
+ if( function_exists( 'apache_request_headers' ) ) {
+ // More reliable than $_SERVER due to case and -/_ folding
+ $set = apache_request_headers();
+ $index = 'User-Agent';
+ } else {
+ // Subject to spoofing with headers like X_Forwarded_For
+ $set = $_SERVER;
+ $index = 'HTTP_USER_AGENT';
+ }
+ if( isset( $set[$index] ) ) {
+ return $set[$index];
+ } else {
+ return '';
+ }
+}
+
+/**
+ * Work out the IP address based on various globals
+ * For trusted proxies, use the XFF client IP (first of the chain)
+ * @return string
+ */
function wfGetIP() {
global $wgIP;
@@ -66,6 +102,13 @@ function wfGetIP() {
return $ip;
}
+/**
+ * Checks if an IP is a trusted proxy providor
+ * Useful to tell if X-Fowarded-For data is possibly bogus
+ * Squid cache servers for the site and AOL are whitelisted
+ * @param string $ip
+ * @return bool
+ */
function wfIsTrustedProxy( $ip ) {
global $wgSquidServers, $wgSquidServersNoPurge;
@@ -130,6 +173,7 @@ function wfProxyCheck() {
/**
* Convert a network specification in CIDR notation to an integer network and a number of bits
+ * @return array(string, int)
*/
function wfParseCIDR( $range ) {
return IP::parseCIDR( $range );
@@ -137,6 +181,7 @@ function wfParseCIDR( $range ) {
/**
* Check if an IP address is in the local proxy list
+ * @return bool
*/
function wfIsLocallyBlockedProxy( $ip ) {
global $wgProxyList;
@@ -169,6 +214,7 @@ function wfIsLocallyBlockedProxy( $ip ) {
/**
* 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(
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index ff6355e7..143c8be6 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -1,42 +1,45 @@
<?php
/**
* Contain a class for special pages
- * @package MediaWiki
*/
/**
- * List of query page classes and their associated special pages, for periodic update purposes
+ * List of query page classes and their associated special pages,
+ * for periodic updates.
+ *
+ * DO NOT CHANGE THIS LIST without testing that
+ * maintenance/updateSpecialPages.php still works.
*/
global $wgQueryPages; // not redundant
$wgQueryPages = array(
// QueryPage subclass Special page name Limit (false for none, none for the default)
//----------------------------------------------------------------------------
- array( 'AncientPagesPage', 'Ancientpages' ),
- array( 'BrokenRedirectsPage', 'BrokenRedirects' ),
- array( 'CategoriesPage', 'Categories' ),
- array( 'DeadendPagesPage', 'Deadendpages' ),
- array( 'DisambiguationsPage', 'Disambiguations' ),
- array( 'DoubleRedirectsPage', 'DoubleRedirects' ),
- array( 'ListUsersPage', 'Listusers' ),
- array( 'ListredirectsPage', 'Listredirects' ),
- array( 'LonelyPagesPage', 'Lonelypages' ),
- array( 'LongPagesPage', 'Longpages' ),
- array( 'MostcategoriesPage', 'Mostcategories' ),
- array( 'MostimagesPage', 'Mostimages' ),
- array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ),
- array( 'MostlinkedPage', 'Mostlinked' ),
- array( 'MostrevisionsPage', 'Mostrevisions' ),
- array( 'NewPagesPage', 'Newpages' ),
- array( 'ShortPagesPage', 'Shortpages' ),
- array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ),
- array( 'UncategorizedPagesPage', 'Uncategorizedpages' ),
- array( 'UncategorizedImagesPage', 'Uncategorizedimages' ),
- array( 'UnusedCategoriesPage', 'Unusedcategories' ),
- array( 'UnusedimagesPage', 'Unusedimages' ),
- array( 'WantedCategoriesPage', 'Wantedcategories' ),
- array( 'WantedPagesPage', 'Wantedpages' ),
- array( 'UnwatchedPagesPage', 'Unwatchedpages' ),
- array( 'UnusedtemplatesPage', 'Unusedtemplates' ),
+ array( 'AncientPagesPage', 'Ancientpages' ),
+ array( 'BrokenRedirectsPage', 'BrokenRedirects' ),
+ array( 'DeadendPagesPage', 'Deadendpages' ),
+ array( 'DisambiguationsPage', 'Disambiguations' ),
+ array( 'DoubleRedirectsPage', 'DoubleRedirects' ),
+ array( 'ListredirectsPage', 'Listredirects' ),
+ array( 'LonelyPagesPage', 'Lonelypages' ),
+ array( 'LongPagesPage', 'Longpages' ),
+ array( 'MostcategoriesPage', 'Mostcategories' ),
+ array( 'MostimagesPage', 'Mostimages' ),
+ array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ),
+ array( 'MostlinkedPage', 'Mostlinked' ),
+ array( 'MostrevisionsPage', 'Mostrevisions' ),
+ array( 'FewestrevisionsPage', 'Fewestrevisions' ),
+ array( 'NewPagesPage', 'Newpages' ),
+ array( 'ShortPagesPage', 'Shortpages' ),
+ array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ),
+ array( 'UncategorizedPagesPage', 'Uncategorizedpages' ),
+ array( 'UncategorizedImagesPage', 'Uncategorizedimages' ),
+ array( 'UnusedCategoriesPage', 'Unusedcategories' ),
+ array( 'UnusedimagesPage', 'Unusedimages' ),
+ array( 'WantedCategoriesPage', 'Wantedcategories' ),
+ array( 'WantedPagesPage', 'Wantedpages' ),
+ array( 'UnwatchedPagesPage', 'Unwatchedpages' ),
+ array( 'UnusedtemplatesPage', 'Unusedtemplates' ),
+ array( 'WithoutInterwikiPage', 'Withoutinterwiki' ),
);
wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) );
@@ -49,8 +52,7 @@ if ( !$wgDisableCounters )
* This is a class for doing query pages; since they're almost all the same,
* we factor out some of the functionality into a superclass, and let
* subclasses derive from it.
- *
- * @package MediaWiki
+ * @addtogroup SpecialPage
*/
class QueryPage {
/**
@@ -59,7 +61,7 @@ class QueryPage {
* @var bool
*/
var $listoutput = false;
-
+
/**
* The offset and limit in use, as passed to the query() function
*
@@ -197,8 +199,8 @@ class QueryPage {
*/
function recache( $limit, $ignoreErrors = true ) {
$fname = get_class($this) . '::recache';
- $dbw =& wfGetDB( DB_MASTER );
- $dbr =& wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) );
+ $dbw = wfGetDB( DB_MASTER );
+ $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) );
if ( !$dbw || !$dbr ) {
return false;
}
@@ -282,7 +284,7 @@ class QueryPage {
$sname = $this->getName();
$fname = get_class($this) . '::doQuery';
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$wgOut->setSyndicated( $this->isSyndicated() );
@@ -306,7 +308,7 @@ class QueryPage {
$updated = $wgLang->timeAndDate( $tRow->qci_timestamp, true, true );
$cacheNotice = wfMsg( 'perfcachedts', $updated );
$wgOut->addMeta( 'Data-Cache-Time', $tRow->qci_timestamp );
- $wgOut->addScript( '<script language="JavaScript">var dataCacheTime = \'' . $tRow->qci_timestamp . '\';</script>' );
+ $wgOut->addInlineScript( "var dataCacheTime = '{$tRow->qci_timestamp}';" );
} else {
$cacheNotice = wfMsg( 'perfcached' );
}
@@ -330,58 +332,99 @@ class QueryPage {
$num = $dbr->numRows($res);
$this->preprocessResults( $dbr, $res );
-
- $sk = $wgUser->getSkin( );
-
- if($shownavigation) {
- $wgOut->addHTML( $this->getPageHeader() );
- $top = wfShowingResults( $offset, $num);
- $wgOut->addHTML( "<p>{$top}\n" );
-
- # often disable 'next' link when we reach the end
- $atend = $num < $limit;
-
- $sl = wfViewPrevNext( $offset, $limit ,
- $wgContLang->specialPage( $sname ),
- wfArrayToCGI( $this->linkParameters() ), $atend );
- $wgOut->addHTML( "<br />{$sl}</p>\n" );
+ $sk = $wgUser->getSkin();
+
+ # Top header and navigation
+ if( $shownavigation ) {
+ $wgOut->addHtml( $this->getPageHeader() );
+ if( $num > 0 ) {
+ $wgOut->addHtml( '<p>' . wfShowingResults( $offset, $num ) . '</p>' );
+ # Disable the "next" link when we reach the end
+ $paging = wfViewPrevNext( $offset, $limit, $wgContLang->specialPage( $sname ),
+ wfArrayToCGI( $this->linkParameters() ), ( $num < $limit ) );
+ $wgOut->addHtml( '<p>' . $paging . '</p>' );
+ } 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( '<p>' . wfMsgHtml( 'specialpage-empty' ) . '</p>' );
+ 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
+ $this->outputResults( $wgOut,
+ $wgUser->getSkin(),
+ $dbr, # Should use a ResultWrapper for this
+ $res,
+ $dbr->numRows( $res ),
+ $offset );
+
+ # Repeat the paging links at the bottom
+ if( $shownavigation ) {
+ $wgOut->addHtml( '<p>' . $paging . '</p>' );
}
- if ( $num > 0 ) {
- $s = array();
- if ( ! $this->listoutput )
- $s[] = $this->openList( $offset );
-
- # Only read at most $num rows, because $res may contain the whole 1000
- for ( $i = 0; $i < $num && $obj = $dbr->fetchObject( $res ); $i++ ) {
- $format = $this->formatResult( $sk, $obj );
- if ( $format ) {
- $attr = ( isset ( $obj->usepatrol ) && $obj->usepatrol &&
- $obj->patrolled == 0 ) ? ' class="not-patrolled"' : '';
- $s[] = $this->listoutput ? $format : "<li{$attr}>{$format}</li>\n";
+
+ return $num;
+ }
+
+ /**
+ * Format and output report results using the given information plus
+ * OutputPage
+ *
+ * @param OutputPage $out OutputPage to print to
+ * @param Skin $skin User skin to use
+ * @param Database $dbr Database (read) connection to use
+ * @param int $res Result pointer
+ * @param int $num Number of available result rows
+ * @param int $offset Paging offset
+ */
+ protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
+ 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++ ) {
+ $line = $this->formatResult( $skin, $row );
+ if( $line ) {
+ $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
+ ? ' class="not-patrolled"'
+ : '';
+ $html[] = $this->listoutput
+ ? $format
+ : "<li{$attr}>{$line}</li>\n";
}
}
-
- if($this->tryLastResult()) {
- // flush the very last result
- $obj = null;
- $format = $this->formatResult( $sk, $obj );
- if( $format ) {
- $attr = ( isset ( $obj->usepatrol ) && $obj->usepatrol &&
- $obj->patrolled == 0 ) ? ' class="not-patrolled"' : '';
- $s[] = "<li{$attr}>{$format}</li>\n";
+
+ # Flush the final result
+ if( $this->tryLastResult() ) {
+ $row = null;
+ $line = $this->formatResult( $skin, $row );
+ if( $line ) {
+ $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
+ ? ' class="not-patrolled"'
+ : '';
+ $html[] = $this->listoutput
+ ? $format
+ : "<li{$attr}>{$line}</li>\n";
}
}
-
- $dbr->freeResult( $res );
- if ( ! $this->listoutput )
- $s[] = $this->closeList();
- $str = $this->listoutput ? $wgContLang->listToText( $s ) : implode( '', $s );
- $wgOut->addHTML( $str );
- }
- if($shownavigation) {
- $wgOut->addHTML( "<p>{$sl}</p>\n" );
+
+ if( !$this->listoutput )
+ $html[] = $this->closeList();
+
+ $html = $this->listoutput
+ ? $wgContLang->listToText( $html )
+ : implode( '', $html );
+
+ $out->addHtml( $html );
}
- return $num;
}
function openList( $offset ) {
@@ -411,7 +454,7 @@ class QueryPage {
$this->feedUrl() );
$feed->outHeader();
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$sql = $this->getSQL() . $this->getOrder();
$sql = $dbr->limitResult( $sql, $limit, 0 );
$res = $dbr->query( $sql, 'QueryPage::doFeed' );
@@ -482,20 +525,4 @@ class QueryPage {
}
}
-/**
- * This is a subclass for very simple queries that are just looking for page
- * titles that match some criteria. It formats each result item as a link to
- * that page.
- *
- * @package MediaWiki
- */
-class PageQueryPage extends QueryPage {
-
- function formatResult( $skin, $result ) {
- global $wgContLang;
- $nt = Title::makeTitle( $result->namespace, $result->title );
- return $skin->makeKnownLinkObj( $nt, htmlspecialchars( $wgContLang->convert( $nt->getPrefixedText() ) ) );
- }
-}
-
?>
diff --git a/includes/RawPage.php b/includes/RawPage.php
index a0b76886..93484829 100644
--- a/includes/RawPage.php
+++ b/includes/RawPage.php
@@ -7,12 +7,11 @@
* License: GPL (http://www.gnu.org/copyleft/gpl.html)
*
* @author Gabriel Wicke <wicke@wikidev.net>
- * @package MediaWiki
*/
/**
- * @todo document
- * @package MediaWiki
+ * A simple method to retrieve the plain source of an article,
+ * using "action=raw" in the GET request string.
*/
class RawPage {
var $mArticle, $mTitle, $mRequest;
@@ -20,9 +19,8 @@ class RawPage {
var $mSmaxage, $mMaxage;
var $mContentType, $mExpandTemplates;
- function RawPage( &$article, $request = false ) {
+ function __construct( &$article, $request = false ) {
global $wgRequest, $wgInputEncoding, $wgSquidMaxage, $wgJsMimeType;
- global $wgUser;
$allowedCTypes = array('text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit');
$this->mArticle =& $article;
@@ -39,7 +37,7 @@ class RawPage {
$maxage = $this->mRequest->getInt( 'maxage', $wgSquidMaxage );
$this->mExpandTemplates = $this->mRequest->getVal( 'templates' ) === 'expand';
$this->mUseMessageCache = $this->mRequest->getBool( 'usemsgcache' );
-
+
$oldid = $this->mRequest->getInt( 'oldid' );
switch ( $wgRequest->getText( 'direction' ) ) {
case 'next':
@@ -85,8 +83,7 @@ class RawPage {
// Output may contain user-specific data; vary for open sessions
$this->mPrivateCache = ( $this->mSmaxage == 0 ) ||
- ( isset( $_COOKIE[ini_get( 'session.name' )] ) ||
- $wgUser->isLoggedIn() );
+ ( session_id() != '' );
if ( $ctype == '' or ! in_array( $ctype, $allowedCTypes ) ) {
$this->mContentType = 'text/x-wiki';
@@ -137,7 +134,13 @@ class RawPage {
# 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 );
- echo $this->getRawText();
+ $text = $this->getRawText();
+
+ if( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) {
+ wfDebug( __METHOD__ . ': RawPageViewBeforeOutput hook broke raw page output.' );
+ }
+
+ echo $text;
$wgOut->disable();
}
@@ -189,6 +192,20 @@ class RawPage {
header( "HTTP/1.0 404 Not Found" );
}
+ // Special-case for empty CSS/JS
+ //
+ // Internet Explorer for Mac handles empty files badly;
+ // particularly so when keep-alive is active. It can lead
+ // to long timeouts as it seems to sit there waiting for
+ // more data that never comes.
+ //
+ // Give it a comment...
+ if( strlen( $text ) == 0 &&
+ ($this->mContentType == 'text/css' ||
+ $this->mContentType == 'text/javascript' ) ) {
+ return "/* Empty */";
+ }
+
return $this->parseArticleText( $text );
}
diff --git a/includes/RecentChange.php b/includes/RecentChange.php
index 1c7791c2..fced4343 100644
--- a/includes/RecentChange.php
+++ b/includes/RecentChange.php
@@ -1,7 +1,6 @@
<?php
/**
*
- * @package MediaWiki
*/
/**
@@ -39,7 +38,6 @@
* numberofWatchingusers
*
* @todo document functions and variables
- * @package MediaWiki
*/
class RecentChange
{
@@ -49,7 +47,7 @@ class RecentChange
# Factory methods
- /* static */ function newFromRow( $row )
+ public static function newFromRow( $row )
{
$rc = new RecentChange;
$rc->loadFromRow( $row );
@@ -72,7 +70,7 @@ class RecentChange
* @return RecentChange
*/
public static function newFromId( $rcid ) {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'recentchanges', '*', array( 'rc_id' => $rcid ), __METHOD__ );
if( $res && $dbr->numRows( $res ) > 0 ) {
$row = $dbr->fetchObject( $res );
@@ -118,7 +116,7 @@ class RecentChange
global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPPort, $wgRC2UDPPrefix;
$fname = 'RecentChange::save';
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
if ( !is_array($this->mExtra) ) {
$this->mExtra = array();
}
@@ -216,7 +214,7 @@ class RecentChange
{
$fname = 'RecentChange::markPatrolled';
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$dbw->update( 'recentchanges',
array( /* SET */
@@ -504,6 +502,8 @@ class RecentChange
function getIRCLine() {
global $wgUseRCPatrol;
+ // 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);
diff --git a/includes/Revision.php b/includes/Revision.php
index c5235e22..71f214e3 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -1,19 +1,17 @@
<?php
/**
- * @package MediaWiki
* @todo document
*/
/**
- * @package MediaWiki
* @todo document
*/
class Revision {
- const DELETED_TEXT = 1;
- const DELETED_COMMENT = 2;
- const DELETED_USER = 4;
+ const DELETED_TEXT = 1;
+ const DELETED_COMMENT = 2;
+ const DELETED_USER = 4;
const DELETED_RESTRICTED = 8;
-
+
/**
* Load a page revision from a given revision ID number.
* Returns null if no such revision can be found.
@@ -79,7 +77,7 @@ class Revision {
* @access public
* @static
*/
- public static function loadFromPageId( &$db, $pageid, $id = 0 ) {
+ public static function loadFromPageId( $db, $pageid, $id = 0 ) {
$conds=array('page_id=rev_page','rev_page'=>intval( $pageid ), 'page_id'=>intval( $pageid ));
if( $id ) {
$conds['rev_id']=intval($id);
@@ -145,10 +143,10 @@ class Revision {
* @static
*/
private static function newFromConds( $conditions ) {
- $db =& wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_SLAVE );
$row = Revision::loadFromConds( $db, $conditions );
if( is_null( $row ) ) {
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$row = Revision::loadFromConds( $dbw, $conditions );
}
return $row;
@@ -164,7 +162,7 @@ class Revision {
* @access private
* @static
*/
- private static function loadFromConds( &$db, $conditions ) {
+ private static function loadFromConds( $db, $conditions ) {
$res = Revision::fetchFromConds( $db, $conditions );
if( $res ) {
$row = $res->fetchObject();
@@ -226,7 +224,7 @@ class Revision {
* @access private
* @static
*/
- private static function fetchFromConds( &$db, $conditions ) {
+ private static function fetchFromConds( $db, $conditions ) {
$res = $db->select(
array( 'page', 'revision' ),
array( 'page_namespace',
@@ -240,7 +238,8 @@ class Revision {
'rev_user',
'rev_minor_edit',
'rev_timestamp',
- 'rev_deleted' ),
+ 'rev_deleted',
+ 'rev_len' ),
$conditions,
'Revision::fetchRow',
array( 'LIMIT' => 1 ) );
@@ -249,6 +248,25 @@ class Revision {
}
/**
+ * Return the list of revision fields that should be selected to create
+ * a new revision.
+ */
+ static function selectFields() {
+ return array(
+ 'rev_id',
+ 'rev_page',
+ 'rev_text_id',
+ 'rev_timestamp',
+ 'rev_comment',
+ 'rev_minor_edit',
+ 'rev_user',
+ 'rev_user_text,'.
+ 'rev_deleted',
+ 'rev_len'
+ );
+ }
+
+ /**
* @param object $row
* @access private
*/
@@ -263,6 +281,11 @@ class Revision {
$this->mMinorEdit = intval( $row->rev_minor_edit );
$this->mTimestamp = $row->rev_timestamp;
$this->mDeleted = intval( $row->rev_deleted );
+
+ if( !isset( $row->rev_len ) || is_null( $row->rev_len ) )
+ $this->mSize = null;
+ else
+ $this->mSize = intval( $row->rev_len );
if( isset( $row->page_latest ) ) {
$this->mCurrent = ( $row->rev_id == $row->page_latest );
@@ -293,7 +316,8 @@ class Revision {
$this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
$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;
+
// 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;
@@ -301,6 +325,9 @@ class Revision {
$this->mTitle = null; # Load on demand if needed
$this->mCurrent = false;
+ # If we still have no len_size, see it we have the text to figure it out
+ if ( !$this->mSize )
+ $this->mSize = is_null($this->mText) ? null : strlen($this->mText);
} else {
throw new MWException( 'Revision constructor passed invalid row format.' );
}
@@ -325,6 +352,13 @@ class Revision {
}
/**
+ * Returns the length of the text in this revision, or null if unknown.
+ */
+ function getSize() {
+ return $this->mSize;
+ }
+
+ /**
* Returns the title of the page associated with this entry.
* @return Title
*/
@@ -332,7 +366,7 @@ class Revision {
if( isset( $this->mTitle ) ) {
return $this->mTitle;
}
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
array( 'page', 'revision' ),
array( 'page_namespace', 'page_title' ),
@@ -459,6 +493,18 @@ class Revision {
}
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
@@ -565,7 +611,7 @@ class Revision {
# Old revisions kept around in a legacy encoding?
# Upconvert on demand.
global $wgInputEncoding, $wgContLang;
- $text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding . '//IGNORE', $text );
+ $text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding, $text );
}
}
wfProfileOut( $fname );
@@ -666,6 +712,7 @@ class Revision {
'rev_user_text' => $this->mUserText,
'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
'rev_deleted' => $this->mDeleted,
+ 'rev_len' => $this->mSize,
), $fname
);
@@ -706,7 +753,7 @@ class Revision {
if( !$row ) {
// Text data is immutable; check slaves first.
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $this->getTextId() ),
@@ -715,7 +762,7 @@ class Revision {
if( !$row ) {
// Possible slave lag!
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$row = $dbw->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $this->getTextId() ),
@@ -802,12 +849,12 @@ class Revision {
* @param integer $id
*/
static function getTimestampFromID( $id ) {
- $dbr =& wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$timestamp = $dbr->selectField( 'revision', 'rev_timestamp',
array( 'rev_id' => $id ), __METHOD__ );
if ( $timestamp === false ) {
# Not in slave, try master
- $dbw =& wfGetDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER );
$timestamp = $dbw->selectField( 'revision', 'rev_timestamp',
array( 'rev_id' => $id ), __METHOD__ );
}
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index 0c0f7244..fa5416dc 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -20,8 +20,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @package MediaWiki
- * @subpackage Parser
+ * @addtogroup Parser
*/
/**
@@ -29,7 +28,7 @@
* Sanitizer::normalizeCharReferences and Sanitizer::decodeCharReferences
*/
define( 'MW_CHAR_REFS_REGEX',
- '/&([A-Za-z0-9]+);
+ '/&([A-Za-z0-9\x80-\xff]+);
|&\#([0-9]+);
|&\#x([0-9A-Za-z]+);
|&\#X([0-9A-Za-z]+);
@@ -316,7 +315,20 @@ $wgHtmlEntities = array(
'zwj' => 8205,
'zwnj' => 8204 );
-/** @package MediaWiki */
+/**
+ * Character entity aliases accepted by MediaWiki
+ */
+global $wgHtmlEntityAliases;