00001 <?php
00002
# See title.doc
00003
00004 $wgTitleInterwikiCache = array();
00005
00006
# Title class
00007
#
00008
# * Represents a title, which may contain an interwiki designation or namespace
00009
# * Can fetch various kinds of data from the database, albeit inefficiently.
00010
#
00011
class Title {
00012
# All member variables should be considered private
00013
# Please use the accessor functions
00014
00015 var $mTextform; # Text form (spaces not underscores) of the main part
00016 var $mUrlform; # URL-encoded form of the main part
00017 var $mDbkeyform; # Main part with underscores
00018 var $mNamespace; #
Namespace index, i.e. one of the NS_xxxx constants
00019 var $mInterwiki; # Interwiki prefix (or null string)
00020 var $mFragment; #
Title fragment (i.e. the bit after the #)
00021 var $mArticleID; # Article ID, fetched from the link cache on demand
00022 var $mRestrictions; # Array of groups allowed to edit
this article
00023
# Only null or "sysop" are supported
00024
var $mRestrictionsLoaded; # Boolean
for initialisation on demand
00025 var $mPrefixedText; # Text form including
namespace/interwiki, initialised on demand
00026 var $mDefaultNamespace; #
Namespace index when there
is no
namespace
00027
# Zero except in {{transclusion}} tags
00028
00029
#----------------------------------------------------------------------------
00030
# Construction
00031
#----------------------------------------------------------------------------
00032
00033 function
Title()
00034 {
00035 $this->mInterwiki = $this->mUrlform =
00036 $this->mTextform = $this->mDbkeyform =
"";
00037 $this->mArticleID = -1;
00038 $this->mNamespace = 0;
00039 $this->mRestrictionsLoaded =
false;
00040 $this->mRestrictions = array();
00041 $this->mDefaultNamespace = 0;
00042 }
00043
00044
# From a prefixed DB key
00045
function newFromDBkey( $key )
00046 {
00047 $t =
new Title();
00048 $t->mDbkeyform = $key;
00049
if( $t->secureAndSplit() )
00050
return $t;
00051
else
00052
return NULL;
00053 }
00054
00055
# From text, such as what you would find in a link
00056
function newFromText( $text, $defaultNamespace = 0 )
00057 {
00058 $fname =
"Title::newFromText";
00059
wfProfileIn( $fname );
00060
00061
if( is_object( $text ) ) {
00062
wfDebugDieBacktrace(
"Called with object instead of string." );
00063 }
00064 global
$wgInputEncoding;
00065 $text =
do_html_entity_decode( $text, ENT_COMPAT, $wgInputEncoding );
00066
00067 $text =
wfMungeToUtf8( $text );
00068
00069
00070
# What was this for? TS 2004-03-03
00071
# $text = urldecode( $text );
00072
00073 $t =
new Title();
00074 $t->mDbkeyform = str_replace(
" ",
"_", $text );
00075 $t->mDefaultNamespace = $defaultNamespace;
00076
00077
wfProfileOut( $fname );
00078
if ( !is_object( $t ) ) {
00079 var_dump( debug_backtrace() );
00080 }
00081
if( $t->secureAndSplit() ) {
00082
return $t;
00083 }
else {
00084
return NULL;
00085 }
00086 }
00087
00088
# From a URL-encoded title
00089
function newFromURL( $url )
00090 {
00091 global
$wgLang, $wgServer,
$wgIsMySQL,
$wgIsPg;
00092 $t =
new Title();
00093
00094
# For compatibility with old buggy URLs. "+" is not valid in titles,
00095
# but some URLs used it as a space replacement and they still come
00096
# from some external search tools.
00097
$s = str_replace(
"+",
" ", $url );
00098
00099
# For links that came from outside, check for alternate/legacy
00100
# character encoding.
00101
wfDebug(
"Servr: $wgServer\n" );
00102
if( empty( $_SERVER[
"HTTP_REFERER"] ) ||
00103 strncmp($wgServer, $_SERVER[
"HTTP_REFERER"], strlen( $wgServer ) ) )
00104 {
00105
$s =
$wgLang->checkTitleEncoding( $s );
00106 }
else {
00107
wfDebug(
"Refer: {$_SERVER['HTTP_REFERER']}\n" );
00108 }
00109
00110 $t->mDbkeyform = str_replace(
" ",
"_", $s );
00111
if( $t->secureAndSplit() ) {
00112
# check that lenght of title is < cur_title size
00113
if (
$wgIsMySQL) {
00114
$sql =
"SHOW COLUMNS FROM cur LIKE \"cur_title\";";
00115 $cur_title_object =
wfFetchObject(
wfQuery( $sql, DB_READ ));
00116
00117 preg_match(
"/\((.*)\)/", $cur_title_object->Type, $cur_title_type);
00118 $cur_title_size=$cur_title_type[1];
00119 }
else {
00120
00121
00122 $cur_title_size=255;
00123 }
00124
00125
if (strlen($t->mDbkeyform) > $cur_title_size ) {
00126
return NULL;
00127 }
00128
00129
return $t;
00130 }
else {
00131
return NULL;
00132 }
00133 }
00134
00135
# From a cur_id
00136
# This is inefficiently implemented, the cur row is requested but not
00137
# used for anything else
00138
function newFromID( $
id )
00139 {
00140 $fname =
"Title::newFromID";
00141
$row =
wfGetArray(
"cur", array(
"cur_namespace",
"cur_title" ),
00142 array(
"cur_id" => $
id ), $fname );
00143
if (
$row !==
false ) {
00144
$title = Title::makeTitle( $row->cur_namespace, $row->cur_title );
00145 }
else {
00146
$title = NULL;
00147 }
00148
return $title;
00149 }
00150
00151
# From a namespace index and a DB key
00152
function makeTitle( $ns, $title )
00153 {
00154 $t =
new Title();
00155 $t->mDbkeyform = Title::makeName( $ns, $title );
00156
if( $t->secureAndSplit() ) {
00157
return $t;
00158 }
else {
00159
return NULL;
00160 }
00161 }
00162
00163 function newMainPage()
00164 {
00165
return Title::newFromText(
wfMsg(
"mainpage" ) );
00166 }
00167
00168
#----------------------------------------------------------------------------
00169
# Static functions
00170
#----------------------------------------------------------------------------
00171
00172
# Get the prefixed DB key associated with an ID
00173
function nameOf( $
id )
00174 {
00175
$sql =
"SELECT cur_namespace,cur_title FROM cur WHERE " .
00176
"cur_id={$id}";
00177
$res =
wfQuery( $sql, DB_READ,
"Article::nameOf" );
00178
if ( 0 ==
wfNumRows( $res ) ) {
return NULL; }
00179
00180
$s =
wfFetchObject( $res );
00181
$n = Title::makeName( $s->cur_namespace, $s->cur_title );
00182
return $n;
00183 }
00184
00185
# Get a regex character class describing the legal characters in a link
00186
function legalChars()
00187 {
00188
# Missing characters:
00189
# * []|# Needed for link syntax
00190
# * % and + are corrupted by Apache when they appear in the path
00191
# * % seems to work though
00192
#
00193
# Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but
00194
# this breaks interlanguage links
00195
00196 $set =
" %!\"$&'()*,\\-.\\/0-9:;<=>?@A-Z\\\\^_`a-z{}~\\x80-\\xFF";
00197
return $set;
00198 }
00199
00200
# Returns a stripped-down a title string ready for the search index
00201
# Takes a namespace index and a text-form main part
00202
function
indexTitle( $ns, $title )
00203 {
00204 global
$wgDBminWordLen,
$wgLang;
00205
00206
$lc =
SearchEngine::legalSearchChars() .
"&#;";
00207 $t =
$wgLang->stripForSearch( $title );
00208 $t = preg_replace(
"/[^{$lc}]+/",
" ", $t );
00209 $t = strtolower( $t );
00210
00211
# Handle 's, s'
00212
$t = preg_replace(
"/([{$lc}]+)'s( |$)/",
"\\1 \\1's ", $t );
00213 $t = preg_replace(
"/([{$lc}]+)s'( |$)/",
"\\1s ", $t );
00214
00215 $t = preg_replace(
"/\\s+/",
" ", $t );
00216
00217
if ( $ns ==
Namespace::getImage() ) {
00218 $t = preg_replace(
"/ (png|gif|jpg|jpeg|ogg)$/",
"", $t );
00219 }
00220
return trim( $t );
00221 }
00222
00223
# Make a prefixed DB key from a DB key and a namespace index
00224
function makeName( $ns, $title )
00225 {
00226 global
$wgLang;
00227
00228
$n =
$wgLang->getNsText( $ns );
00229
if (
"" ==
$n ) {
return $title; }
00230
else {
return "{$n}:{$title}"; }
00231 }
00232
00233
# Arguably static
00234
# Returns the URL associated with an interwiki prefix
00235
# The URL contains $1, which is replaced by the title
00236
function getInterwikiLink( $key )
00237 {
00238 global
$wgMemc,
$wgDBname,
$wgInterwikiExpiry,
$wgTitleInterwikiCache;
00239
00240 $k =
"$wgDBname:interwiki:$key";
00241
00242
if( array_key_exists( $k, $wgTitleInterwikiCache ) )
00243
return $wgTitleInterwikiCache[$k]->iw_url;
00244
00245
$s =
$wgMemc->get( $k );
00246
# Ignore old keys with no iw_local
00247
if(
$s && isset( $s->iw_local ) ) {
00248
$wgTitleInterwikiCache[$k] =
$s;
00249
return $s->iw_url;
00250 }
00251 $dkey =
wfStrencode( $key );
00252 $query =
"SELECT iw_url,iw_local FROM interwiki WHERE iw_prefix='$dkey'";
00253
$res =
wfQuery( $query, DB_READ,
"Title::getInterwikiLink" );
00254
if(!
$res)
return "";
00255
00256
$s =
wfFetchObject( $res );
00257
if(!
$s) {
00258
$s = (object)
false;
00259
$s->iw_url =
"";
00260 }
00261
$wgMemc->set( $k, $s, $wgInterwikiExpiry );
00262
$wgTitleInterwikiCache[$k] =
$s;
00263
return $s->iw_url;
00264 }
00265
00266 function isLocal() {
00267 global
$wgTitleInterwikiCache,
$wgDBname;
00268
00269
if ( $this->mInterwiki !=
"" ) {
00270
# Make sure key is loaded into cache
00271
$this->getInterwikiLink( $this->mInterwiki );
00272 $k =
"$wgDBname:interwiki:" . $this->mInterwiki;
00273
return (
bool)(
$wgTitleInterwikiCache[$k]->iw_local);
00274 }
else {
00275
return true;
00276 }
00277 }
00278
00279
# Update the cur_touched field for an array of title objects
00280
# Inefficient unless the IDs are already loaded into the link cache
00281
function touchArray( $titles, $timestamp =
"" ) {
00282
if ( count( $titles ) == 0 ) {
00283
return;
00284 }
00285
if ( $timestamp ==
"" ) {
00286 $timestamp =
wfTimestampNow();
00287 }
00288
$sql =
"UPDATE cur SET cur_touched='{$timestamp}' WHERE cur_id IN (";
00289
$first =
true;
00290
00291 foreach ( $titles as $title ) {
00292
if ( !
$first ) {
00293
$sql .=
",";
00294 }
00295
00296
$first =
false;
00297
$sql .=
$title->getArticleID();
00298 }
00299
$sql .=
")";
00300
if ( !
$first ) {
00301
wfQuery( $sql, DB_WRITE,
"Title::touchArray" );
00302 }
00303 }
00304
00305
#----------------------------------------------------------------------------
00306
# Other stuff
00307
#----------------------------------------------------------------------------
00308
00309
# Simple accessors
00310
# See the definitions at the top of this file
00311
00312 function getText() {
return $this->mTextform; }
00313 function getPartialURL() {
return $this->mUrlform; }
00314 function getDBkey() {
return $this->mDbkeyform; }
00315 function getNamespace() {
return $this->mNamespace; }
00316 function setNamespace( $n ) { $this->mNamespace =
$n; }
00317 function getInterwiki() {
return $this->mInterwiki; }
00318 function getFragment() {
return $this->mFragment; }
00319 function getDefaultNamespace() {
return $this->mDefaultNamespace; }
00320
00321
# Get title for search index
00322
function getIndexTitle()
00323 {
00324
return Title::indexTitle( $this->mNamespace, $this->mTextform );
00325 }
00326
00327
# Get prefixed title with underscores
00328
function getPrefixedDBkey()
00329 {
00330
$s = $this->prefix( $this->mDbkeyform );
00331
$s = str_replace(
" ",
"_", $s );
00332
return $s;
00333 }
00334
00335
# Get prefixed title with spaces
00336
# This is the form usually used for display
00337
function getPrefixedText()
00338 {
00339
if ( empty( $this->mPrefixedText ) ) {
00340
$s = $this->prefix( $this->mTextform );
00341
$s = str_replace(
"_",
" ", $s );
00342 $this->mPrefixedText =
$s;
00343 }
00344
return $this->mPrefixedText;
00345 }
00346
00347
# Get a URL-encoded title (not an actual URL) including interwiki
00348
function getPrefixedURL()
00349 {
00350
$s = $this->prefix( $this->mDbkeyform );
00351
$s = str_replace(
" ",
"_", $s );
00352
00353
$s =
wfUrlencode ( $s ) ;
00354
00355
# Cleaning up URL to make it look nice -- is this safe?
00356
$s = preg_replace(
"/%3[Aa]/",
":", $s );
00357
$s = preg_replace(
"/%2[Ff]/",
"/", $s );
00358
$s = str_replace(
"%28",
"(", $s );
00359
$s = str_replace(
"%29",
")", $s );
00360
00361
return $s;
00362 }
00363
00364
# Get a real URL referring to this title, with interwiki link and fragment
00365
function getFullURL( $query =
"" )
00366 {
00367 global
$wgLang,
$wgArticlePath, $wgServer,
$wgScript;
00368
00369
if (
"" == $this->mInterwiki ) {
00370 $p =
$wgArticlePath;
00371
return $wgServer . $this->getLocalUrl( $query );
00372 }
00373
00374 $p = $this->getInterwikiLink( $this->mInterwiki );
00375
$n =
$wgLang->getNsText( $this->mNamespace );
00376
if (
"" !=
$n ) {
$n .=
":"; }
00377 $u = str_replace(
"$1", $n . $this->mUrlform, $p );
00378
if (
"" != $this->mFragment ) {
00379 $u .=
"#" .
wfUrlencode( $this->mFragment );
00380 }
00381
return $u;
00382 }
00383
00384
# Get a URL with an optional query string, no fragment
00385
# * If $query=="", it will use $wgArticlePath
00386
# * Returns a full for an interwiki link, loses any query string
00387
# * Optionally adds the server and escapes for HTML
00388
# * Setting $query to "-" makes an old-style URL with nothing in the
00389
# query except a title
00390
00391 function getURL() {
00392 die(
"Call to obsolete obsolete function Title::getURL()" );
00393 }
00394
00395 function getLocalURL( $query =
"" )
00396 {
00397 global
$wgLang,
$wgArticlePath,
$wgScript;
00398
00399
if ( $this->isExternal() ) {
00400
return $this->getFullURL();
00401 }
00402
00403 $dbkey =
wfUrlencode( $this->getPrefixedDBkey() );
00404
if ( $query ==
"" ) {
00405
$url = str_replace(
"$1", $dbkey, $wgArticlePath );
00406 }
else {
00407
if ( $query ==
"-" ) {
00408 $query =
"";
00409 }
00410
if (
$wgScript !=
"" ) {
00411
$url =
"{$wgScript}?title={$dbkey}&{$query}";
00412 }
else {
00413
# Top level wiki
00414
$url =
"/{$dbkey}?{$query}";
00415 }
00416 }
00417
return $url;
00418 }
00419
00420 function escapeLocalURL( $query =
"" ) {
00421
return wfEscapeHTML( $this->getLocalURL( $query ) );
00422 }
00423
00424 function escapeFullURL( $query =
"" ) {
00425
return wfEscapeHTML( $this->getFullURL( $query ) );
00426 }
00427
00428 function getInternalURL( $query =
"" ) {
00429
# Used in various Squid-related code, in case we have a different
00430
# internal hostname for the server than the exposed one.
00431
global
$wgInternalServer;
00432
return $wgInternalServer . $this->getLocalURL( $query );
00433 }
00434
00435
# Get the edit URL, or a null string if it is an interwiki link
00436
function getEditURL()
00437 {
00438 global $wgServer,
$wgScript;
00439
00440
if (
"" != $this->mInterwiki ) {
return ""; }
00441
$s = $this->getLocalURL(
"action=edit" );
00442
00443
return $s;
00444 }
00445
00446
# Get HTML-escaped displayable text
00447
# For the title field in <a> tags
00448
function getEscapedText()
00449 {
00450
return wfEscapeHTML( $this->getPrefixedText() );
00451 }
00452
00453
# Is the title interwiki?
00454
function isExternal() {
return (
"" != $this->mInterwiki ); }
00455
00456
# Does the title correspond to a protected article?
00457
function isProtected()
00458 {
00459
if ( -1 == $this->mNamespace ) {
return true; }
00460 $a = $this->getRestrictions();
00461
if ( in_array(
"sysop", $a ) ) {
return true; }
00462
return false;
00463 }
00464
00465
# Is the page a log page, i.e. one where the history is messed up by
00466
# LogPage.php? This used to be used for suppressing diff links in recent
00467
# changes, but now that's done by setting a flag in the recentchanges
00468
# table. Hence, this probably is no longer used.
00469
function isLog()
00470 {
00471
if ( $this->mNamespace !=
Namespace::getWikipedia() ) {
00472
return false;
00473 }
00474
if ( ( 0 == strcmp(
wfMsg(
"uploadlogpage" ), $this->mDbkeyform ) ) ||
00475 ( 0 == strcmp(
wfMsg(
"dellogpage" ), $this->mDbkeyform ) ) ) {
00476
return true;
00477 }
00478
return false;
00479 }
00480
00481
# Is $wgUser is watching this page?
00482
function userIsWatching()
00483 {
00484 global
$wgUser;
00485
00486
if ( -1 == $this->mNamespace ) {
return false; }
00487
if ( 0 ==
$wgUser->getID() ) {
return false; }
00488
00489
return $wgUser->isWatched( $
this );
00490 }
00491
00492
# Can $wgUser edit this page?
00493
function userCanEdit()
00494 {
00495 global
$wgUser;
00496
if ( -1 == $this->mNamespace ) {
return false; }
00497
if (
NS_MEDIAWIKI == $this->mNamespace && !
$wgUser->isSysop() ) {
return false; }
00498
# if ( 0 == $this->getArticleID() ) { return false; }
00499
if ( $this->mDbkeyform ==
"_" ) {
return false; }
00500
# protect global styles and js
00501
if (
NS_MEDIAWIKI == $this->mNamespace
00502 && preg_match(
"/\\.(css|js)$/", $this->mTextform )
00503 && !
$wgUser->isSysop() )
00504 {
return false; }
00505
00506
# protect css/js subpages of user pages
00507
# XXX: this might be better using restrictions
00508
# XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working
00509
if(
Namespace::getUser() == $this->mNamespace
00510 and preg_match(
"/\\.(css|js)$/", $this->mTextform )
00511 and !$wgUser->isSysop()
00512 and !preg_match("/^".preg_quote($wgUser->getName(), '/')."/", $this->mTextform) )
00513 {
return false; }
00514 $ur =
$wgUser->getRights();
00515 foreach ( $this->getRestrictions() as $r ) {
00516
if (
"" != $r && ( ! in_array( $r, $ur ) ) ) {
00517
return false;
00518 }
00519 }
00520
return true;
00521 }
00522
00523 function userCanRead() {
00524 global
$wgUser;
00525 global
$wgWhitelistRead;
00526
00527
if( 0 !=
$wgUser->getID() )
return true;
00528
if( !is_array( $wgWhitelistRead ) )
return true;
00529
00530 $name = $this->getPrefixedText();
00531
if( in_array( $name, $wgWhitelistRead ) )
return true;
00532
00533
# Compatibility with old settings
00534
if( $this->getNamespace() == NS_ARTICLE ) {
00535
if( in_array(
":" . $name, $wgWhitelistRead ) )
return true;
00536 }
00537
return false;
00538 }
00539
00540 function isCssJsSubpage() {
00541
return (
Namespace::getUser() == $this->mNamespace and preg_match(
"/\\.(css|js)$/", $this->mTextform ) );
00542 }
00543 function isCssSubpage() {
00544
return (
Namespace::getUser() == $this->mNamespace and preg_match(
"/\\.css$/", $this->mTextform ) );
00545 }
00546 function isJsSubpage() {
00547
return (
Namespace::getUser() == $this->mNamespace and preg_match(
"/\\.js$/", $this->mTextform ) );
00548 }
00549 function userCanEditCssJsSubpage() {
00550
# protect css/js subpages of user pages
00551
# XXX: this might be better using restrictions
00552
global
$wgUser;
00553
return (
$wgUser->isSysop() or preg_match("/^".preg_quote($wgUser->getName())."/", $this->mTextform) );
00554 }
00555
00556 # Accessor/initialisation for mRestrictions
00557 function getRestrictions()
00558 {
00559 $id = $this->getArticleID();
00560
if ( 0 == $id ) {
return array(); }
00561
00562
if ( ! $this->mRestrictionsLoaded ) {
00563
$res =
wfGetSQL(
"cur",
"cur_restrictions",
"cur_id=$id" );
00564 $this->mRestrictions = explode(
",", trim( $res ) );
00565 $this->mRestrictionsLoaded =
true;
00566 }
00567
return $this->mRestrictions;
00568 }
00569
00570
# Is there a version of this page in the deletion archive?
00571
function isDeleted() {
00572 $ns = $this->getNamespace();
00573 $t =
wfStrencode( $this->getDBkey() );
00574
$sql =
"SELECT COUNT(*) AS n FROM archive WHERE ar_namespace=$ns AND ar_title='$t'";
00575
if(
$res =
wfQuery( $sql, DB_READ ) ) {
00576
$s =
wfFetchObject( $res );
00577
return $s->n;
00578 }
00579
return 0;
00580 }
00581
00582
# Get the article ID from the link cache
00583
# Used very heavily, e.g. in Parser::replaceInternalLinks()
00584
function getArticleID()
00585 {
00586 global
$wgLinkCache;
00587
00588
if ( -1 != $this->mArticleID ) {
return $this->mArticleID; }
00589 $this->mArticleID =
$wgLinkCache->addLinkObj( $
this );
00590
return $this->mArticleID;
00591 }
00592
00593
# This clears some fields in this object, and clears any associated keys in the
00594
# "bad links" section of $wgLinkCache. This is called from Article::insertNewArticle()
00595
# to allow loading of the new cur_id. It's also called from Article::doDeleteArticle()
00596
function resetArticleID( $newid )
00597 {
00598 global
$wgLinkCache;
00599
$wgLinkCache->clearBadLink( $this->getPrefixedDBkey() );
00600
00601
if ( 0 == $newid ) { $this->mArticleID = -1; }
00602
else { $this->mArticleID = $newid; }
00603 $this->mRestrictionsLoaded =
false;
00604 $this->mRestrictions = array();
00605 }
00606
00607
# Updates cur_touched
00608
# Called from LinksUpdate.php
00609
function invalidateCache() {
00610 $now =
wfTimestampNow();
00611 $ns = $this->getNamespace();
00612 $ti =
wfStrencode( $this->getDBkey() );
00613
$sql =
"UPDATE cur SET cur_touched='$now' WHERE cur_namespace=$ns AND cur_title='$ti'";
00614
return wfQuery( $sql, DB_WRITE,
"Title::invalidateCache" );
00615 }
00616
00617
# Prefixes some arbitrary text with the namespace or interwiki prefix of this object
00618
function prefix( $name )
00619 {
00620 global
$wgLang;
00621
00622 $p =
"";
00623
if (
"" != $this->mInterwiki ) {
00624 $p = $this->mInterwiki .
":";
00625 }
00626
if ( 0 != $this->mNamespace ) {
00627 $p .=
$wgLang->getNsText( $this->mNamespace ) .
":";
00628 }
00629
return $p . $name;
00630 }
00631
00632
# Secure and split - main initialisation function for this object
00633
#
00634
# Assumes that mDbkeyform has been set, and is urldecoded
00635
# and uses undersocres, but not otherwise munged. This function
00636
# removes illegal characters, splits off the winterwiki and
00637
# namespace prefixes, sets the other forms, and canonicalizes
00638
# everything.
00639
#
00640
function secureAndSplit()
00641 {
00642 global
$wgLang,
$wgLocalInterwiki, $wgCapitalLinks;
00643 $fname =
"Title::secureAndSplit";
00644
wfProfileIn( $fname );
00645
00646
static $imgpre =
false;
00647
static $rxTc =
false;
00648
00649
# Initialisation
00650
if ( $imgpre ===
false ) {
00651 $imgpre =
":" .
$wgLang->getNsText( Namespace::getImage() ) .
":";
00652
# % is needed as well
00653
$rxTc =
"/[^" . Title::legalChars() .
"]/";
00654 }
00655
00656 $this->mInterwiki = $this->mFragment =
"";
00657 $this->mNamespace = $this->mDefaultNamespace; # Usually
NS_MAIN
00658
00659
# Clean up whitespace
00660
#
00661
$t = preg_replace(
"/[\\s_]+/",
"_", $this->mDbkeyform );
00662 $t = preg_replace( '/^_*(.*?)_*$/', '$1', $t );
00663
00664
if (
"" == $t ) {
00665
wfProfileOut( $fname );
00666
return false;
00667 }
00668
00669 $this->mDbkeyform = $t;
00670 $done =
false;
00671
00672
# :Image: namespace
00673
if ( 0 == strncasecmp( $imgpre, $t, strlen( $imgpre ) ) ) {
00674 $t = substr( $t, 1 );
00675 }
00676
00677
# Initial colon indicating main namespace
00678
if (
":" == $t{0} ) {
00679 $r = substr( $t, 1 );
00680 $this->mNamespace =
NS_MAIN;
00681 }
else {
00682
# Namespace or interwiki prefix
00683
if ( preg_match(
"/^(.+?)_*:_*(.*)$/", $t, $m ) ) {
00684
#$p = strtolower( $m[1] );
00685
$p = $m[1];
00686 $lowerNs = strtolower( $p );
00687
if ( $ns =
Namespace::getCanonicalIndex( $lowerNs ) ) {
00688
# Canonical namespace
00689
$t = $m[2];
00690 $this->mNamespace = $ns;
00691 } elseif ( $ns = $wgLang->getNsIndex( $lowerNs )) {
00692
# Ordinary namespace
00693
$t = $m[2];
00694 $this->mNamespace = $ns;
00695 } elseif ( $this->getInterwikiLink( $p ) ) {
00696
# Interwiki link
00697
$t = $m[2];
00698 $this->mInterwiki = $p;
00699
00700
if ( !preg_match(
"/^([A-Za-z0-9_\\x80-\\xff]+):(.*)$/", $t, $m ) ) {
00701 $done =
true;
00702 } elseif($this->mInterwiki != $wgLocalInterwiki) {
00703 $done =
true;
00704 }
00705 }
00706 }
00707 $r = $t;
00708 }
00709
00710
# Redundant interwiki prefix to the local wiki
00711
if ( 0 == strcmp( $this->mInterwiki, $wgLocalInterwiki ) ) {
00712 $this->mInterwiki =
"";
00713 }
00714
# We already know that some pages won't be in the database!
00715
#
00716
if (
"" != $this->mInterwiki || -1 == $this->mNamespace ) {
00717 $this->mArticleID = 0;
00718 }
00719 $f = strstr( $r,
"#" );
00720
if (
false !== $f ) {
00721 $this->mFragment = substr( $f, 1 );
00722 $r = substr( $r, 0, strlen( $r ) - strlen( $f ) );
00723 }
00724
00725
# Reject illegal characters.
00726
#
00727
if( preg_match( $rxTc, $r ) ) {
00728
return false;
00729 }
00730
00731
# "." and ".." conflict with the directories of those namesa
00732
if ( $r ===
"." || $r ===
".." || strpos( $r,
"./" ) !==
false ) {
00733
return false;
00734 }
00735
00736
# Initial capital letter
00737
if( $wgCapitalLinks && $this->mInterwiki ==
"") {
00738 $t =
$wgLang->ucfirst( $r );
00739 }
00740
00741
# Fill fields
00742
$this->mDbkeyform = $t;
00743 $this->mUrlform =
wfUrlencode( $t );
00744
00745 $this->mTextform = str_replace(
"_",
" ", $t );
00746
00747
wfProfileOut( $fname );
00748
return true;
00749 }
00750
00751
# Get a title object associated with the talk page of this article
00752
function getTalkPage() {
00753
return Title::makeTitle( Namespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
00754 }
00755
00756
# Get a title object associated with the subject page of this talk page
00757
function getSubjectPage() {
00758
return Title::makeTitle( Namespace::getSubject( $this->getNamespace() ), $this->getDBkey() );
00759 }
00760
00761
# Get an array of Title objects linking to this title
00762
# Also stores the IDs in the link cache
00763
function getLinksTo() {
00764 global
$wgLinkCache;
00765 $id = $this->getArticleID();
00766
$sql =
"SELECT cur_namespace,cur_title,cur_id FROM cur,links WHERE l_from=cur_id AND l_to={$id}";
00767
$res =
wfQuery( $sql, DB_READ,
"Title::getLinksTo" );
00768 $retVal = array();
00769
if (
wfNumRows( $res ) ) {
00770
while (
$row =
wfFetchObject( $res ) ) {
00771
if ( $titleObj = Title::makeTitle( $row->cur_namespace, $row->cur_title ) ) {
00772
$wgLinkCache->addGoodLink( $row->cur_id, $titleObj->getPrefixedDBkey() );
00773 $retVal[] = $titleObj;
00774 }
00775 }
00776 }
00777
wfFreeResult( $res );
00778
return $retVal;
00779 }
00780
00781
# Get an array of Title objects linking to this non-existent title
00782
# Also stores the IDs in the link cache
00783
function getBrokenLinksTo() {
00784 global
$wgLinkCache;
00785 $encTitle =
wfStrencode( $this->getPrefixedDBkey() );
00786
$sql =
"SELECT cur_namespace,cur_title,cur_id FROM brokenlinks,cur " .
00787
"WHERE bl_from=cur_id AND bl_to='$encTitle'";
00788
$res =
wfQuery( $sql, DB_READ,
"Title::getBrokenLinksTo" );
00789 $retVal = array();
00790
if (
wfNumRows( $res ) ) {
00791
while (
$row =
wfFetchObject( $res ) ) {
00792 $titleObj = Title::makeTitle( $row->cur_namespace, $row->cur_title );
00793
$wgLinkCache->addGoodLink( $row->cur_id, $titleObj->getPrefixedDBkey() );
00794 $retVal[] = $titleObj;
00795 }
00796 }
00797
wfFreeResult( $res );
00798
return $retVal;
00799 }
00800
00801 function getSquidURLs() {
00802
return array(
00803 $this->getInternalURL(),
00804 $this->getInternalURL(
"action=history" )
00805 );
00806 }
00807
00808 function moveNoAuth( &$nt ) {
00809
return $this->moveTo( $nt,
false );
00810 }
00811
00812
# Move a title to a new location
00813
# Returns true on success, message name on failure
00814
# auth indicates whether wgUser's permissions should be checked
00815
function moveTo( &$nt, $auth =
true ) {
00816
if( !$this or !$nt ) {
00817
return "badtitletext";
00818 }
00819
00820 $fname =
"Title::move";
00821 $oldid = $this->getArticleID();
00822 $newid = $nt->getArticleID();
00823
00824
if ( strlen( $nt->getDBkey() ) < 1 ) {
00825
return "articleexists";
00826 }
00827
if ( ( !
Namespace::isMovable( $this->getNamespace() ) ) ||
00828 (
"" == $this->getDBkey() ) ||
00829 (
"" != $this->getInterwiki() ) ||
00830 ( !$oldid ) ||
00831 ( !
Namespace::isMovable( $nt->getNamespace() ) ) ||
00832 (
"" == $nt->getDBkey() ) ||
00833 (
"" != $nt->getInterwiki() ) ) {
00834
return "badarticleerror";
00835 }
00836
00837
if ( $auth && ( !$this->userCanEdit() || !$nt->userCanEdit() ) ) {
00838
return "protectedpage";
00839 }
00840
00841
# The move is allowed only if (1) the target doesn't exist, or
00842
# (2) the target is a redirect to the source, and has no history
00843
# (so we can undo bad moves right after they're done).
00844
00845
if ( 0 != $newid ) { # Target exists;
check for validity
00846
if ( ! $this->isValidMoveTarget( $nt ) ) {
00847
return "articleexists";
00848 }
00849 $this->moveOverExistingRedirect( $nt );
00850 }
else { # Target didn't exist,
do normal move.
00851 $this->moveToNewTitle( $nt, $newid );
00852 }
00853
00854
# Update watchlists
00855
00856 $oldnamespace = $this->getNamespace() & ~1;
00857 $newnamespace = $nt->getNamespace() & ~1;
00858 $oldtitle = $this->getDBkey();
00859 $newtitle = $nt->getDBkey();
00860
00861
if( $oldnamespace != $newnamespace && $oldtitle != $newtitle ) {
00862
WatchedItem::duplicateEntries( $
this, $nt );
00863 }
00864
00865
# Update search engine
00866
$u =
new SearchUpdate( $oldid, $nt->getPrefixedDBkey() );
00867 $u->doUpdate();
00868 $u =
new SearchUpdate( $newid, $this->getPrefixedDBkey(),
"" );
00869 $u->doUpdate();
00870
00871
return true;
00872 }
00873
00874
# Move page to title which is presently a redirect to the source page
00875
00876 function moveOverExistingRedirect( &$nt )
00877 {
00878 global
$wgUser,
$wgLinkCache,
$wgUseSquid,
$wgMwRedir;
00879 $fname =
"Title::moveOverExistingRedirect";
00880
$comment =
wfMsg(
"1movedto2", $this->getPrefixedText(), $nt->getPrefixedText() );
00881
00882 $now =
wfTimestampNow();
00883 $won =
wfInvertTimestamp( $now );
00884 $newid = $nt->getArticleID();
00885 $oldid = $this->getArticleID();
00886
00887
# Change the name of the target page:
00888
wfUpdateArray(
00889 'cur',
00890 array(
00891 'cur_touched' => $now,
00892 'cur_namespace' => $nt->getNamespace(),
00893 'cur_title' => $nt->getDBkey()
00894 ),
00895 array( 'cur_id' => $oldid ),
00896 $fname
00897 );
00898
$wgLinkCache->clearLink( $nt->getPrefixedDBkey() );
00899
00900
# Repurpose the old redirect. We don't save it to history since
00901
# by definition if we've got here it's rather uninteresting.
00902
00903 $redirectText =
$wgMwRedir->getSynonym( 0 ) .
" [[" . $nt->getPrefixedText() .
"]]\n";
00904
wfUpdateArray(
00905 'cur',
00906 array(
00907 'cur_touched' => $now,
00908 'cur_timestamp' => $now,
00909 'inverse_timestamp' => $won,
00910 'cur_namespace' => $this->getNamespace(),
00911 'cur_title' => $this->getDBkey(),
00912 'cur_text' =>
$wgMwRedir->getSynonym( 0 ) .
" [[" . $nt->getPrefixedText() .
"]]\n",
00913 'cur_comment' =>
$comment,
00914 'cur_user' =>
$wgUser->getID(),
00915 'cur_minor_edit' => 0,
00916 'cur_counter' => 0,
00917 'cur_restrictions' => '',
00918 'cur_user_text' =>
$wgUser->getName(),
00919 'cur_is_redirect' => 1,
00920 'cur_is_new' => 1
00921 ),
00922 array( 'cur_id' => $newid ),
00923 $fname
00924 );
00925
00926
$wgLinkCache->clearLink( $this->getPrefixedDBkey() );
00927
00928
# Fix the redundant names for the past revisions of the target page.
00929
# The redirect should have no old revisions.
00930
wfUpdateArray(
00931 'old',
00932 array(
00933 'old_namespace' => $nt->getNamespace(),
00934 'old_title' => $nt->getDBkey(),
00935 ),
00936 array(
00937 'old_namespace' => $this->getNamespace(),
00938 'old_title' => $this->getDBkey(),
00939 ),
00940 $fname
00941 );
00942
00943
RecentChange::notifyMoveOverRedirect( $now, $
this, $nt, $wgUser, $comment );
00944
00945
# Swap links
00946
00947
# Load titles and IDs
00948
$linksToOld = $this->getLinksTo();
00949 $linksToNew = $nt->getLinksTo();
00950
00951
# Delete them all
00952
$sql =
"DELETE FROM links WHERE l_to=$oldid OR l_to=$newid";
00953
wfQuery( $sql, DB_WRITE, $fname );
00954
00955
# Reinsert
00956
if ( count( $linksToOld ) || count( $linksToNew )) {
00957
$sql =
"INSERT INTO links (l_from,l_to) VALUES ";
00958
$first =
true;
00959
00960
# Insert links to old title
00961
foreach ( $linksToOld as $linkTitle ) {
00962
if (
$first ) {
00963
$first =
false;
00964 }
else {
00965
$sql .=
",";
00966 }
00967 $id = $linkTitle->getArticleID();
00968
$sql .=
"($id,$newid)";
00969 }
00970
00971
# Insert links to new title
00972
foreach ( $linksToNew as $linkTitle ) {
00973
if (
$first ) {
00974
$first =
false;
00975 }
else {
00976
$sql .=
",";
00977 }
00978 $id = $linkTitle->getArticleID();
00979
$sql .=
"($id, $oldid)";
00980 }
00981
00982
wfQuery( $sql, DB_WRITE, $fname );
00983 }
00984
00985
# Now, we record the link from the redirect to the new title.
00986
# It should have no other outgoing links...
00987
$sql =
"DELETE FROM links WHERE l_from={$newid}";
00988
wfQuery( $sql, DB_WRITE, $fname );
00989
$sql =
"INSERT INTO links (l_from,l_to) VALUES ({$newid},{$oldid})";
00990
wfQuery( $sql, DB_WRITE, $fname );
00991
00992
# Clear linkscc
00993
LinkCache::linksccClearLinksTo( $oldid );
00994
LinkCache::linksccClearLinksTo( $newid );
00995
00996
# Purge squid
00997
if (
$wgUseSquid ) {
00998 $urls = array_merge( $nt->getSquidURLs(), $this->getSquidURLs() );
00999 $u =
new SquidUpdate( $urls );
01000 $u->doUpdate();
01001 }
01002 }
01003
01004
# Move page to non-existing title.
01005
# Sets $newid to be the new article ID
01006
01007 function moveToNewTitle( &$nt, &$newid )
01008 {
01009 global
$wgUser,
$wgLinkCache,
$wgUseSquid;
01010 $fname =
"MovePageForm::moveToNewTitle";
01011
$comment =
wfMsg(
"1movedto2", $this->getPrefixedText(), $nt->getPrefixedText() );
01012
01013 $now =
wfTimestampNow();
01014 $won =
wfInvertTimestamp( $now );
01015 $newid = $nt->getArticleID();
01016 $oldid = $this->getArticleID();
01017
01018
# Rename cur entry
01019
wfUpdateArray(
01020 'cur',
01021 array(
01022 'cur_touched' => $now,
01023 'cur_namespace' => $nt->getNamespace(),
01024 'cur_title' => $nt->getDBkey()
01025 ),
01026 array( 'cur_id' => $oldid ),
01027 $fname
01028 );
01029
01030
$wgLinkCache->clearLink( $nt->getPrefixedDBkey() );
01031
01032
# Insert redirct
01033
wfInsertArray( 'cur', array(
01034 'cur_namespace' => $this->getNamespace(),
01035 'cur_title' => $this->getDBkey(),
01036 'cur_comment' =>
$comment,
01037 'cur_user' =>
$wgUser->getID(),
01038 'cur_user_text' =>
$wgUser->getName(),
01039 'cur_timestamp' => $now,
01040 'inverse_timestamp' => $won,
01041 'cur_touched' => $now,
01042 'cur_is_redirect' => 1,
01043 'cur_is_new' => 1,
01044 'cur_text' =>
"#REDIRECT [[" . $nt->getPrefixedText() .
"]]\n" )
01045 );
01046 $newid =
wfInsertId();
01047
$wgLinkCache->clearLink( $this->getPrefixedDBkey() );
01048
01049
# Rename old entries
01050
wfUpdateArray(
01051 'old',
01052 array(
01053 'old_namespace' => $nt->getNamespace(),
01054 'old_title' => $nt->getDBkey()
01055 ),
01056 array(
01057 'old_namespace' => $this->getNamespace(),
01058 'old_title' => $this->getDBkey()
01059 ), $fname
01060 );
01061
01062
# Record in RC
01063
RecentChange::notifyMoveToNew( $now, $
this, $nt, $wgUser, $comment );
01064
01065
# Purge squid and linkscc as per article creation
01066
Article::onArticleCreate( $nt );
01067
01068
# Any text links to the old title must be reassigned to the redirect
01069
$sql =
"UPDATE links SET l_to={$newid} WHERE l_to={$oldid}";
01070
wfQuery( $sql, DB_WRITE, $fname );
01071
LinkCache::linksccClearLinksTo( $oldid );
01072
01073
# Record the just-created redirect's linking to the page
01074
$sql =
"INSERT INTO links (l_from,l_to) VALUES ({$newid},{$oldid})";
01075
wfQuery( $sql, DB_WRITE, $fname );
01076
01077
# Non-existent target may have had broken links to it; these must
01078
# now be removed and made into good links.
01079
$update =
new LinksUpdate( $oldid, $nt->getPrefixedDBkey() );
01080 $update->fixBrokenLinks();
01081
01082
# Purge old title from squid
01083
# The new title, and links to the new title, are purged in Article::onArticleCreate()
01084
$titles = $nt->getLinksTo();
01085
if (
$wgUseSquid ) {
01086 $urls = $this->getSquidURLs();
01087 foreach ( $titles as $linkTitle ) {
01088 $urls[] = $linkTitle->getInternalURL();
01089 }
01090 $u =
new SquidUpdate( $urls );
01091 $u->doUpdate();
01092 }
01093 }
01094
01095
# Checks if $this can be moved to $nt
01096
# Both titles must exist in the database, otherwise it will blow up
01097
function isValidMoveTarget( $nt )
01098 {
01099 $fname =
"Title::isValidMoveTarget";
01100
01101
# Is it a redirect?
01102
$id = $nt->getArticleID();
01103
$sql =
"SELECT cur_is_redirect,cur_text FROM cur " .
01104
"WHERE cur_id={$id}";
01105
$res =
wfQuery( $sql, DB_READ, $fname );
01106 $obj =
wfFetchObject( $res );
01107
01108
if ( 0 == $obj->cur_is_redirect ) {
01109
# Not a redirect
01110
return false;
01111 }
01112
01113
# Does the redirect point to the source?
01114
if ( preg_match(
"/\\[\\[\\s*([^\\]]*)]]/", $obj->cur_text, $m ) ) {
01115 $redirTitle = Title::newFromText( $m[1] );
01116
if ( 0 != strcmp( $redirTitle->getPrefixedDBkey(), $this->getPrefixedDBkey() ) ) {
01117
return false;
01118 }
01119 }
01120
01121
# Does the article have a history?
01122
$row =
wfGetArray( 'old', array( 'old_id' ), array(
01123 'old_namespace' => $nt->getNamespace(),
01124 'old_title' => $nt->getDBkey() )
01125 );
01126
01127
# Return true if there was no history
01128
return $row ===
false;
01129 }
01130
01131
# Create a redirect, fails if the title already exists, does not notify RC
01132
# Returns success
01133
function createRedirect( $dest, $comment ) {
01134 global
$wgUser;
01135
if ( $this->getArticleID() ) {
01136
return false;
01137 }
01138
01139 $now =
wfTimestampNow();
01140 $won =
wfInvertTimestamp( $now );
01141
01142
wfInsertArray( 'cur', array(
01143 'cur_namespace' => $this->getNamespace(),
01144 'cur_title' => $this->getDBkey(),
01145 'cur_comment' =>
$comment,
01146 'cur_user' =>
$wgUser->getID(),
01147 'cur_user_text' =>
$wgUser->getName(),
01148 'cur_timestamp' => $now,
01149 'inverse_timestamp' => $won,
01150 'cur_touched' => $now,
01151 'cur_is_redirect' => 1,
01152 'cur_is_new' => 1,
01153 'cur_text' =>
"#REDIRECT [[" . $dest->getPrefixedText() .
"]]\n"
01154 ));
01155 $newid =
wfInsertId();
01156 $this->resetArticleID( $newid );
01157
01158
# Link table
01159
if ( $dest->getArticleID() ) {
01160
wfInsertArray( 'links', array(
01161 'l_to' => $dest->getArticleID(),
01162 'l_from' => $newid
01163 ));
01164 }
else {
01165
wfInsertArray( 'brokenlinks', array(
01166 'bl_to' => $dest->getPrefixedDBkey(),
01167 'bl_from' => $newid
01168 ));
01169 }
01170
01171 Article::onArticleCreate( $
this );
01172
return true;
01173 }
01174
01175
# Get categories to wich belong this title and return an array of
01176
# categories names.
01177
function getParentCategories( )
01178 {
01179 global
$wgLang,
$wgUser;
01180
01181
#$titlekey = wfStrencode( $this->getArticleID() );
01182
$titlekey = $this->getArticleId();
01183 $cns =
Namespace::getCategory();
01184 $sk =&
$wgUser->getSkin();
01185 $parents = array();
01186
01187
# get the parents categories of this title from the database
01188
$sql =
"SELECT DISTINCT cur_id FROM cur,categorylinks
01189
WHERE cl_from='$titlekey' AND cl_to=cur_title AND cur_namespace='$cns'
01190
ORDER BY cl_sortkey" ;
01191
$res =
wfQuery ( $sql, DB_READ ) ;
01192
01193
if(
wfNumRows($res) > 0) {
01194
while ( $x =
wfFetchObject ( $res ) ) $data[] = $x ;
01195
wfFreeResult ( $res ) ;
01196 }
else {
01197 $data = '';
01198 }
01199
return $data;
01200 }
01201
01202
# will get the parents and grand-parents
01203
# TODO : not sure what's happening when a loop happen like:
01204
# Encyclopedia > Astronomy > Encyclopedia
01205
function getAllParentCategories(&$stack)
01206 {
01207 global
$wgUser,
$wgLang;
01208 $result = '';
01209
01210
# getting parents
01211
$parents = $this->getParentCategories( );
01212
01213
if($parents == '')
01214 {
01215
# The current element has no more parent so we dump the stack
01216
# and make a clean line of categories
01217
$sk =&
$wgUser->getSkin() ;
01218
01219 foreach ( array_reverse($stack) as $child => $parent )
01220 {
01221
# make a link of that parent
01222
$result .= $sk->makeLink($wgLang->getNSText ( Namespace::getCategory() ).
":".$parent,$parent);
01223 $result .= ' > ';
01224 $lastchild = $child;
01225 }
01226
# append the last child.
01227
# TODO : We should have a last child unless there is an error in the
01228
# "categorylinks" table.
01229
if(isset($lastchild)) { $result .= $lastchild; }
01230
01231 $result .=
"<br/>\n";
01232
01233
# now we can empty the stack
01234
$stack = array();
01235
01236 }
else {
01237
# look at parents of current category
01238
foreach($parents as $parent)
01239 {
01240
# create a title object for the parent
01241
$tpar = Title::newFromID($parent->cur_id);
01242
# add it to the stack
01243
$stack[$this->getText()] = $tpar->getText();
01244
# grab its parents
01245
$result .= $tpar->getAllParentCategories($stack);
01246 }
01247 }
01248
01249
if(isset($result)) {
return $result; }
01250
else {
return ''; };
01251 }
01252
01253
01254 }
01255 ?>