~ubuntu-branches/ubuntu/maverick/mediawiki/maverick

« back to all changes in this revision

Viewing changes to includes/Title.php

  • Committer: Bazaar Package Importer
  • Author(s): Romain Beauxis
  • Date: 2009-06-19 01:38:50 UTC
  • mfrom: (16.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20090619013850-dsn4lrxvs90ab4rx
Tags: 1:1.15.0-1
* New upstream release. 
* Upstream added support for OASIS documents.
Closes: #530328
* Refreshed quilt patches
* Bumped standards versions to 3.8.2
* Bumped compat to 7
* Pointed to GPL-2 in debian/copyright
* Added php5-sqlite to possible DB backend dependencies.
Closes: #501569
* Proofread README.Debian, upgrade is documented there.
Closes: #520121

Show diffs side-by-side

added added

removed removed

Lines of Context:
69
69
        var $mLength = -1;                ///< The page length, 0 for special pages
70
70
        var $mRedirect = null;            ///< Is the article at this title a redirect?
71
71
        var $mNotificationTimestamp = array(); ///< Associative array of user ID -> timestamp/false
 
72
        var $mBacklinkCache = null; ///< Cache of links to this title
72
73
        //@}
73
74
 
74
75
 
294
295
        /**
295
296
         * Extract a redirect destination from a string and return the
296
297
         * Title, or null if the text doesn't contain a valid redirect
 
298
         * This will only return the very next target, useful for
 
299
         * the redirect table and other checks that don't need full recursion
297
300
         *
298
 
         * @param $text \type{String} Text with possible redirect
 
301
         * @param $text \type{\string} Text with possible redirect
299
302
         * @return \type{Title} The corresponding Title
300
303
         */
301
304
        public static function newFromRedirect( $text ) {
 
305
                return self::newFromRedirectInternal( $text );
 
306
        }
 
307
        
 
308
        /**
 
309
         * Extract a redirect destination from a string and return the
 
310
         * Title, or null if the text doesn't contain a valid redirect
 
311
         * This will recurse down $wgMaxRedirects times or until a non-redirect target is hit
 
312
         * in order to provide (hopefully) the Title of the final destination instead of another redirect
 
313
         *
 
314
         * @param $text \type{\string} Text with possible redirect
 
315
         * @return \type{Title} The corresponding Title
 
316
         */
 
317
        public static function newFromRedirectRecurse( $text ) {
 
318
                $titles = self::newFromRedirectArray( $text );
 
319
                return $titles ? array_pop( $titles ) : null;
 
320
        }
 
321
        
 
322
        /**
 
323
         * Extract a redirect destination from a string and return an
 
324
         * array of Titles, or null if the text doesn't contain a valid redirect
 
325
         * The last element in the array is the final destination after all redirects
 
326
         * have been resolved (up to $wgMaxRedirects times)
 
327
         *
 
328
         * @param $text \type{\string} Text with possible redirect
 
329
         * @return \type{\array} Array of Titles, with the destination last
 
330
         */
 
331
        public static function newFromRedirectArray( $text ) {
 
332
                global $wgMaxRedirects;
 
333
                // are redirects disabled?
 
334
                if( $wgMaxRedirects < 1 )
 
335
                        return null;
 
336
                $title = self::newFromRedirectInternal( $text );
 
337
                if( is_null( $title ) )
 
338
                        return null;
 
339
                // recursive check to follow double redirects
 
340
                $recurse = $wgMaxRedirects;
 
341
                $titles = array( $title );
 
342
                while( --$recurse > 0 ) {
 
343
                        if( $title->isRedirect() ) {
 
344
                                $article = new Article( $title, 0 );
 
345
                                $newtitle = $article->getRedirectTarget();
 
346
                        } else {
 
347
                                break;
 
348
                        }
 
349
                        // Redirects to some special pages are not permitted
 
350
                        if( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
 
351
                                // the new title passes the checks, so make that our current title so that further recursion can be checked
 
352
                                $title = $newtitle;
 
353
                                $titles[] = $newtitle;
 
354
                        } else {
 
355
                                break;
 
356
                        }
 
357
                }
 
358
                return $titles;
 
359
        }
 
360
        
 
361
        /**
 
362
         * Really extract the redirect destination
 
363
         * Do not call this function directly, use one of the newFromRedirect* functions above
 
364
         *
 
365
         * @param $text \type{\string} Text with possible redirect
 
366
         * @return \type{Title} The corresponding Title
 
367
         */
 
368
        protected static function newFromRedirectInternal( $text ) {
302
369
                $redir = MagicWord::get( 'redirect' );
303
370
                $text = trim($text);
304
371
                if( $redir->matchStartAndRemove( $text ) ) {
316
383
                                        $m[1] = urldecode( ltrim( $m[1], ':' ) );
317
384
                                }
318
385
                                $title = Title::newFromText( $m[1] );
319
 
                                // Redirects to some special pages are not permitted
320
 
                                if( $title instanceof Title 
321
 
                                                && !$title->isSpecial( 'Userlogout' )
322
 
                                                && !$title->isSpecial( 'Filepath' ) ) 
323
 
                                {
324
 
                                        return $title;
 
386
                                // If the title is a redirect to bad special pages or is invalid, return null
 
387
                                if( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
 
388
                                        return null;
325
389
                                }
 
390
                                return $title;
326
391
                        }
327
392
                }
328
393
                return null;
802
867
         * @return \type{\string} the URL
803
868
         */
804
869
        public function getLinkUrl( $query = array(), $variant = false ) {
 
870
                wfProfileIn( __METHOD__ );
805
871
                if( !is_array( $query ) ) {
 
872
                        wfProfileOut( __METHOD__ );
806
873
                        throw new MWException( 'Title::getLinkUrl passed a non-array for '.
807
874
                        '$query' );
808
875
                }
809
876
                if( $this->isExternal() ) {
810
 
                        return $this->getFullURL( $query );
811
 
                } elseif( $this->getPrefixedText() === ''
812
 
                and $this->getFragment() !== '' ) {
813
 
                        return $this->getFragmentForURL();
 
877
                        $ret = $this->getFullURL( $query );
 
878
                } elseif( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
 
879
                        $ret = $this->getFragmentForURL();
814
880
                } else {
815
 
                        return $this->getLocalURL( $query, $variant )
816
 
                                . $this->getFragmentForURL();
 
881
                        $ret = $this->getLocalURL( $query, $variant ) . $this->getFragmentForURL();
817
882
                }
 
883
                wfProfileOut( __METHOD__ );
 
884
                return $ret;
818
885
        }
819
886
 
820
887
        /**
992
1059
         */
993
1060
        public function userCan( $action, $doExpensiveQueries = true ) {
994
1061
                global $wgUser;
995
 
                return ( $this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries ) === array());
 
1062
                return ($this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries, true ) === array());
996
1063
        }
997
1064
 
998
1065
        /**
1024
1091
                }
1025
1092
 
1026
1093
                // Edit blocks should not affect reading. Account creation blocks handled at userlogin.
1027
 
                if ( $user->isBlockedFrom( $this ) && $action != 'read' && $action != 'createaccount' ) {
 
1094
                if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) {
1028
1095
                        $block = $user->mBlock;
1029
1096
 
1030
1097
                        // This is from OutputPage::blockedPage
1094
1161
         * @param $action \type{\string} action that permission needs to be checked for
1095
1162
         * @param $user \type{User} user to check
1096
1163
         * @param $doExpensiveQueries \type{\bool} Set this to false to avoid doing unnecessary queries.
 
1164
         * @param $short \type{\bool} Set this to true to stop after the first permission error.
1097
1165
         * @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
1098
1166
         */
1099
 
        private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true ) {
 
1167
        private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries=true, $short=false ) {
1100
1168
                wfProfileIn( __METHOD__ );
1101
1169
 
1102
1170
                $errors = array();
1103
1171
 
 
1172
                // First stop is permissions checks, which fail most often, and which are easiest to test.
 
1173
                if ( $action == 'move' ) {
 
1174
                        if( !$user->isAllowed( 'move-rootuserpages' )
 
1175
                                        && $this->getNamespace() == NS_USER && !$this->isSubpage() )
 
1176
                        {
 
1177
                                // Show user page-specific message only if the user can move other pages
 
1178
                                $errors[] = array( 'cant-move-user-page' );
 
1179
                        }
 
1180
                        
 
1181
                        // Check if user is allowed to move files if it's a file
 
1182
                        if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
 
1183
                                $errors[] = array( 'movenotallowedfile' );
 
1184
                        }
 
1185
                        
 
1186
                        if( !$user->isAllowed( 'move' ) ) {
 
1187
                                // User can't move anything
 
1188
                                $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
 
1189
                        }
 
1190
                } elseif ( $action == 'create' ) {
 
1191
                        if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
 
1192
                                ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) )
 
1193
                        {
 
1194
                                $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin');
 
1195
                        }
 
1196
                } elseif( $action == 'move-target' ) {
 
1197
                        if( !$user->isAllowed( 'move' ) ) {
 
1198
                                // User can't move anything
 
1199
                                $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
 
1200
                        } elseif( !$user->isAllowed( 'move-rootuserpages' )
 
1201
                                && $this->getNamespace() == NS_USER && !$this->isSubpage() )
 
1202
                        {
 
1203
                                // Show user page-specific message only if the user can move other pages
 
1204
                                $errors[] = array( 'cant-move-to-user-page' );
 
1205
                        }
 
1206
                } elseif( !$user->isAllowed( $action ) ) {
 
1207
                        $return = null;
 
1208
                        $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
 
1209
                                User::getGroupsWithPermission( $action ) );
 
1210
                        if( $groups ) {
 
1211
                                $return = array( 'badaccess-groups',
 
1212
                                        array( implode( ', ', $groups ), count( $groups ) ) );
 
1213
                        } else {
 
1214
                                $return = array( "badaccess-group0" );
 
1215
                        }
 
1216
                        $errors[] = $return;
 
1217
                }
 
1218
 
 
1219
                # Short-circuit point
 
1220
                if( $short && count($errors) > 0 ) {
 
1221
                        wfProfileOut( __METHOD__ );
 
1222
                        return $errors;
 
1223
                }
 
1224
 
1104
1225
                // Use getUserPermissionsErrors instead
1105
1226
                if( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
1106
1227
                        wfProfileOut( __METHOD__ );
1107
1228
                        return $result ? array() : array( array( 'badaccess-group0' ) );
1108
1229
                }
1109
 
 
 
1230
                // Check getUserPermissionsErrors hook
1110
1231
                if( !wfRunHooks( 'getUserPermissionsErrors', array(&$this,&$user,$action,&$result) ) ) {
1111
1232
                        if( is_array($result) && count($result) && !is_array($result[0]) )
1112
1233
                                $errors[] = $result; # A single array representing an error
1117
1238
                        else if( $result === false )
1118
1239
                                $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
1119
1240
                }
 
1241
                # Short-circuit point
 
1242
                if( $short && count($errors) > 0 ) {
 
1243
                        wfProfileOut( __METHOD__ );
 
1244
                        return $errors;
 
1245
                }
 
1246
                // Check getUserPermissionsErrorsExpensive hook
1120
1247
                if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array(&$this,&$user,$action,&$result) ) ) {
1121
1248
                        if( is_array($result) && count($result) && !is_array($result[0]) )
1122
1249
                                $errors[] = $result; # A single array representing an error
1127
1254
                        else if( $result === false )
1128
1255
                                $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
1129
1256
                }
 
1257
                # Short-circuit point
 
1258
                if( $short && count($errors) > 0 ) {
 
1259
                        wfProfileOut( __METHOD__ );
 
1260
                        return $errors;
 
1261
                }
1130
1262
                
1131
 
                // TODO: document
 
1263
                # Only 'createaccount' and 'execute' can be performed on
 
1264
                # special pages, which don't actually exist in the DB.
1132
1265
                $specialOKActions = array( 'createaccount', 'execute' );
1133
1266
                if( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions) ) {
1134
1267
                        $errors[] = array('ns-specialprotected');
1135
1268
                }
1136
1269
 
 
1270
                # Check $wgNamespaceProtection for restricted namespaces
1137
1271
                if( $this->isNamespaceProtected() ) {
1138
1272
                        $ns = $this->getNamespace() == NS_MAIN ?
1139
1273
                                wfMsg( 'nstab-main' ) : $this->getNsText();
1141
1275
                                array('protectedinterface') : array( 'namespaceprotected',  $ns );
1142
1276
                }
1143
1277
 
1144
 
                # protect css/js subpages of user pages
 
1278
                # Protect css/js subpages of user pages
1145
1279
                # XXX: this might be better using restrictions
1146
1280
                # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working
1147
1281
                if( $this->isCssJsSubpage() && !$user->isAllowed('editusercssjs')
1150
1284
                        $errors[] = array('customcssjsprotected');
1151
1285
                }
1152
1286
 
 
1287
                # Check against page_restrictions table requirements on this
 
1288
                # page. The user must possess all required rights for this action.
 
1289
                foreach( $this->getRestrictions($action) as $right ) {
 
1290
                        // Backwards compatibility, rewrite sysop -> protect
 
1291
                        if( $right == 'sysop' ) {
 
1292
                                $right = 'protect';
 
1293
                        }
 
1294
                        if( '' != $right && !$user->isAllowed( $right ) ) {
 
1295
                                // Users with 'editprotected' permission can edit protected pages
 
1296
                                if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
 
1297
                                        // Users with 'editprotected' permission cannot edit protected pages
 
1298
                                        // with cascading option turned on.
 
1299
                                        if( $this->mCascadeRestriction ) {
 
1300
                                                $errors[] = array( 'protectedpagetext', $right );
 
1301
                                        }
 
1302
                                } else {
 
1303
                                        $errors[] = array( 'protectedpagetext', $right );
 
1304
                                }
 
1305
                        }
 
1306
                }
 
1307
                # Short-circuit point
 
1308
                if( $short && count($errors) > 0 ) {
 
1309
                        wfProfileOut( __METHOD__ );
 
1310
                        return $errors;
 
1311
                }
 
1312
                
1153
1313
                if( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
1154
1314
                        # We /could/ use the protection level on the source page, but it's fairly ugly
1155
1315
                        #  as we have to establish a precedence hierarchy for pages included by multiple
1172
1332
                                }
1173
1333
                        }
1174
1334
                }
1175
 
 
1176
 
                foreach( $this->getRestrictions($action) as $right ) {
1177
 
                        // Backwards compatibility, rewrite sysop -> protect
1178
 
                        if( $right == 'sysop' ) {
1179
 
                                $right = 'protect';
1180
 
                        }
1181
 
                        if( '' != $right && !$user->isAllowed( $right ) ) {
1182
 
                                // Users with 'editprotected' permission can edit protected pages
1183
 
                                if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
1184
 
                                        // Users with 'editprotected' permission cannot edit protected pages
1185
 
                                        // with cascading option turned on.
1186
 
                                        if( $this->mCascadeRestriction ) {
1187
 
                                                $errors[] = array( 'protectedpagetext', $right );
1188
 
                                        } else {
1189
 
                                                // Nothing, user can edit!
1190
 
                                        }
1191
 
                                } else {
1192
 
                                        $errors[] = array( 'protectedpagetext', $right );
1193
 
                                }
1194
 
                        }
 
1335
                # Short-circuit point
 
1336
                if( $short && count($errors) > 0 ) {
 
1337
                        wfProfileOut( __METHOD__ );
 
1338
                        return $errors;
1195
1339
                }
1196
1340
 
1197
1341
                if( $action == 'protect' ) {
1212
1356
                                        $errors[] = array( 'titleprotected', User::whoIs($pt_user), $pt_reason );
1213
1357
                                }
1214
1358
                        }
1215
 
 
1216
 
                        if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1217
 
                                ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) 
1218
 
                        {
1219
 
                                $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin');
1220
 
                        }
1221
1359
                } elseif( $action == 'move' ) {
1222
 
                        if( !$user->isAllowed( 'move' ) ) {
1223
 
                                // User can't move anything
1224
 
                                $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
1225
 
                        } elseif( !$user->isAllowed( 'move-rootuserpages' ) 
1226
 
                                        && $this->getNamespace() == NS_USER && !$this->isSubpage() ) 
1227
 
                        {
1228
 
                                // Show user page-specific message only if the user can move other pages
1229
 
                                $errors[] = array( 'cant-move-user-page' );
1230
 
                        }
1231
 
                        // Check if user is allowed to move files if it's a file
1232
 
                        if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
1233
 
                                $errors[] = array( 'movenotallowedfile' );
1234
 
                        }
1235
1360
                        // Check for immobile pages
1236
1361
                        if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
1237
1362
                                // Specific message for this case
1241
1366
                                $errors[] = array( 'immobile-page' );
1242
1367
                        }
1243
1368
                } elseif( $action == 'move-target' ) {
1244
 
                        if( !$user->isAllowed( 'move' ) ) {
1245
 
                                // User can't move anything
1246
 
                                $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
1247
 
                        } elseif( !$user->isAllowed( 'move-rootuserpages' ) 
1248
 
                                && $this->getNamespace() == NS_USER && !$this->isSubpage() ) 
1249
 
                        {
1250
 
                                // Show user page-specific message only if the user can move other pages
1251
 
                                $errors[] = array( 'cant-move-to-user-page' );
1252
 
                        }
1253
1369
                        if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
1254
1370
                                $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
1255
1371
                        } elseif( !$this->isMovable() ) {
1256
1372
                                $errors[] = array( 'immobile-target-page' );
1257
1373
                        }
1258
 
                } elseif( !$user->isAllowed( $action ) ) {
1259
 
                        $return = null;
1260
 
                        $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
1261
 
                                User::getGroupsWithPermission( $action ) );
1262
 
                        if( $groups ) {
1263
 
                                $return = array( 'badaccess-groups',
1264
 
                                        array( implode( ', ', $groups ), count( $groups ) ) );
1265
 
                        } else {
1266
 
                                $return = array( "badaccess-group0" );
1267
 
                        }
1268
 
                        $errors[] = $return;
1269
1374
                }
1270
1375
 
1271
1376
                wfProfileOut( __METHOD__ );
1412
1517
                }
1413
1518
 
1414
1519
                # Shortcut for public wikis, allows skipping quite a bit of code
1415
 
                if ($wgGroupPermissions['*']['read'])
 
1520
                if ( !empty( $wgGroupPermissions['*']['read'] ) )
1416
1521
                        return true;
1417
1522
 
1418
1523
                if( $wgUser->isAllowed( 'read' ) ) {
1510
1615
                        return $this->mHasSubpages;
1511
1616
                }
1512
1617
 
1513
 
                $db = wfGetDB( DB_SLAVE );
1514
 
                return $this->mHasSubpages = (bool)$db->selectField( 'page', '1',
1515
 
                        "page_namespace = {$this->mNamespace} AND page_title LIKE '"
1516
 
                        . $db->escapeLike( $this->mDbkeyform ) . "/%'",
1517
 
                        __METHOD__
 
1618
                $subpages = $this->getSubpages( 1 );
 
1619
                if( $subpages instanceof TitleArray )
 
1620
                        return $this->mHasSubpages = (bool)$subpages->count();
 
1621
                return $this->mHasSubpages = false;
 
1622
        }
 
1623
        
 
1624
        /**
 
1625
         * Get all subpages of this page.
 
1626
         * @param $limit Maximum number of subpages to fetch; -1 for no limit
 
1627
         * @return mixed TitleArray, or empty array if this page's namespace
 
1628
         *  doesn't allow subpages
 
1629
         */
 
1630
        public function getSubpages( $limit = -1 ) {
 
1631
                if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
 
1632
                        return array();
 
1633
 
 
1634
                $dbr = wfGetDB( DB_SLAVE );
 
1635
                $conds['page_namespace'] = $this->getNamespace();
 
1636
                $conds[] = 'page_title LIKE ' . $dbr->addQuotes(
 
1637
                                $dbr->escapeLike( $this->getDBkey() ) . '/%' );
 
1638
                $options = array();
 
1639
                if( $limit > -1 )
 
1640
                        $options['LIMIT'] = $limit;
 
1641
                return $this->mSubpages = TitleArray::newFromResult(
 
1642
                        $dbr->select( 'page',
 
1643
                                array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
 
1644
                                $conds,
 
1645
                                __METHOD__,
 
1646
                                $options
 
1647
                        )
1518
1648
                );
1519
1649
        }
1520
1650
 
1849
1979
         * @return \type{\int} the number of archived revisions
1850
1980
         */
1851
1981
        public function isDeleted() {
1852
 
                $fname = 'Title::isDeleted';
1853
 
                if ( $this->getNamespace() < 0 ) {
 
1982
                if( $this->getNamespace() < 0 ) {
1854
1983
                        $n = 0;
1855
1984
                } else {
1856
1985
                        $dbr = wfGetDB( DB_SLAVE );
1857
 
                        $n = $dbr->selectField( 'archive', 'COUNT(*)', array( 'ar_namespace' => $this->getNamespace(),
1858
 
                                'ar_title' => $this->getDBkey() ), $fname );
 
1986
                        $n = $dbr->selectField( 'archive', 'COUNT(*)', 
 
1987
                                array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
 
1988
                                __METHOD__
 
1989
                        );
1859
1990
                        if( $this->getNamespace() == NS_FILE ) {
1860
1991
                                $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
1861
 
                                        array( 'fa_name' => $this->getDBkey() ), $fname );
 
1992
                                        array( 'fa_name' => $this->getDBkey() ),
 
1993
                                        __METHOD__
 
1994
                                );
1862
1995
                        }
1863
1996
                }
1864
1997
                return (int)$n;
1865
1998
        }
 
1999
        
 
2000
        /**
 
2001
         * Is there a version of this page in the deletion archive?
 
2002
         * @return bool
 
2003
         */
 
2004
        public function isDeletedQuick() {
 
2005
                if( $this->getNamespace() < 0 ) {
 
2006
                        return false;
 
2007
                }
 
2008
                $dbr = wfGetDB( DB_SLAVE );
 
2009
                $deleted = (bool)$dbr->selectField( 'archive', '1',
 
2010
                        array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
 
2011
                        __METHOD__
 
2012
                );
 
2013
                if( !$deleted && $this->getNamespace() == NS_FILE ) {
 
2014
                        $deleted = (bool)$dbr->selectField( 'filearchive', '1',
 
2015
                                array( 'fa_name' => $this->getDBkey() ),
 
2016
                                __METHOD__
 
2017
                        );
 
2018
                }
 
2019
                return $deleted;
 
2020
        }
1866
2021
 
1867
2022
        /**
1868
2023
         * Get the article ID for this Title from the link cache,
1955
2110
                $linkCache = LinkCache::singleton();
1956
2111
                $linkCache->clearBadLink( $this->getPrefixedDBkey() );
1957
2112
 
1958
 
                if ( 0 == $newid ) { $this->mArticleID = -1; }
 
2113
                if ( $newid === false ) { $this->mArticleID = -1; }
1959
2114
                else { $this->mArticleID = $newid; }
1960
2115
                $this->mRestrictionsLoaded = false;
1961
2116
                $this->mRestrictions = array();
2064
2219
 
2065
2220
                # Namespace or interwiki prefix
2066
2221
                $firstPass = true;
 
2222
                $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
2067
2223
                do {
2068
2224
                        $m = array();
2069
 
                        if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) {
 
2225
                        if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
2070
2226
                                $p = $m[1];
2071
 
                                if ( $ns = $wgContLang->getNsIndex( $p )) {
 
2227
                                if ( $ns = $wgContLang->getNsIndex( $p ) ) {
2072
2228
                                        # Ordinary namespace
2073
2229
                                        $dbkey = $m[2];
2074
2230
                                        $this->mNamespace = $ns;
 
2231
                                        # For Talk:X pages, check if X has a "namespace" prefix
 
2232
                                        if( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
 
2233
                                                if( $wgContLang->getNsIndex( $x[1] ) )
 
2234
                                                        return false; # Disallow Talk:File:x type titles...
 
2235
                                                else if( Interwiki::isValidInterwiki( $x[1] ) )
 
2236
                                                        return false; # Disallow Talk:Interwiki:x type titles...
 
2237
                                        }
2075
2238
                                } elseif( Interwiki::isValidInterwiki( $p ) ) {
2076
2239
                                        if( !$firstPass ) {
2077
2240
                                                # Can't make a local interwiki link to an interwiki link.
2254
2417
         * WARNING: do not use this function on arbitrary user-supplied titles!
2255
2418
         * On heavily-used templates it will max out the memory.
2256
2419
         *
2257
 
         * @param $options \type{\string} may be FOR UPDATE
 
2420
         * @param array $options may be FOR UPDATE
2258
2421
         * @return \type{\arrayof{Title}} the Title objects linking here
2259
2422
         */
2260
 
        public function getLinksTo( $options = '', $table = 'pagelinks', $prefix = 'pl' ) {
 
2423
        public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
2261
2424
                $linkCache = LinkCache::singleton();
2262
2425
 
2263
 
                if ( $options ) {
 
2426
                if ( count( $options ) > 0 ) {
2264
2427
                        $db = wfGetDB( DB_MASTER );
2265
2428
                } else {
2266
2429
                        $db = wfGetDB( DB_SLAVE );
2295
2458
         * WARNING: do not use this function on arbitrary user-supplied titles!
2296
2459
         * On heavily-used templates it will max out the memory.
2297
2460
         *
2298
 
         * @param $options \type{\string} may be FOR UPDATE
 
2461
         * @param array $options may be FOR UPDATE
2299
2462
         * @return \type{\arrayof{Title}} the Title objects linking here
2300
2463
         */
2301
 
        public function getTemplateLinksTo( $options = '' ) {
 
2464
        public function getTemplateLinksTo( $options = array() ) {
2302
2465
                return $this->getLinksTo( $options, 'templatelinks', 'tl' );
2303
2466
        }
2304
2467
 
2306
2469
         * Get an array of Title objects referring to non-existent articles linked from this page
2307
2470
         *
2308
2471
         * @todo check if needed (used only in SpecialBrokenRedirects.php, and should use redirect table in this case)
2309
 
         * @param $options \type{\string} may be FOR UPDATE
2310
2472
         * @return \type{\arrayof{Title}} the Title objects
2311
2473
         */
2312
 
        public function getBrokenLinksFrom( $options = '' ) {
 
2474
        public function getBrokenLinksFrom() {
2313
2475
                if ( $this->getArticleId() == 0 ) {
2314
2476
                        # All links from article ID 0 are false positives
2315
2477
                        return array();
2316
2478
                }
2317
2479
 
2318
 
                if ( $options ) {
2319
 
                        $db = wfGetDB( DB_MASTER );
2320
 
                } else {
2321
 
                        $db = wfGetDB( DB_SLAVE );
2322
 
                }
2323
 
 
2324
 
                $res = $db->safeQuery(
2325
 
                          "SELECT pl_namespace, pl_title
2326
 
                             FROM !
2327
 
                        LEFT JOIN !
2328
 
                               ON pl_namespace=page_namespace
2329
 
                              AND pl_title=page_title
2330
 
                            WHERE pl_from=?
2331
 
                              AND page_namespace IS NULL
2332
 
                                  !",
2333
 
                        $db->tableName( 'pagelinks' ),
2334
 
                        $db->tableName( 'page' ),
2335
 
                        $this->getArticleId(),
2336
 
                        $options );
 
2480
                $dbr = wfGetDB( DB_SLAVE );
 
2481
                $res = $dbr->select(
 
2482
                        array( 'page', 'pagelinks' ),
 
2483
                        array( 'pl_namespace', 'pl_title' ),
 
2484
                        array(
 
2485
                                'pl_from' => $this->getArticleId(),
 
2486
                                'page_namespace IS NULL'
 
2487
                        ),
 
2488
                        __METHOD__, array(),
 
2489
                        array(
 
2490
                                'page' => array( 
 
2491
                                        'LEFT JOIN', 
 
2492
                                        array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
 
2493
                                )
 
2494
                        )
 
2495
                );
2337
2496
 
2338
2497
                $retVal = array();
2339
 
                if ( $db->numRows( $res ) ) {
2340
 
                        foreach( $res as $row ) {
2341
 
                                $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
2342
 
                        }
 
2498
                foreach( $res as $row ) {
 
2499
                        $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
2343
2500
                }
2344
 
                $db->freeResult( $res );
2345
2501
                return $retVal;
2346
2502
        }
2347
2503
 
2459
2615
                                $nt->getUserPermissionsErrors('edit', $wgUser) );
2460
2616
                }
2461
2617
 
2462
 
                $match = EditPage::matchSpamRegex( $reason );
 
2618
                $match = EditPage::matchSummarySpamRegex( $reason );
2463
2619
                if( $match !== false ) {
2464
2620
                        // This is kind of lame, won't display nice
2465
2621
                        $errors[] = array('spamprotectiontext');
2559
2715
                        );
2560
2716
                        # Update the protection log
2561
2717
                        $log = new LogPage( 'protect' );
2562
 
                        $comment = wfMsgForContent('prot_1movedto2',$this->getPrefixedText(), $nt->getPrefixedText() );
2563
 
                        if( $reason ) $comment .= ': ' . $reason;
 
2718
                        $comment = wfMsgForContent( 'prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
 
2719
                        if( $reason ) $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
2564
2720
                        $log->addEntry( 'move_prot', $nt, $comment, array($this->getPrefixedText()) ); // FIXME: $params?
2565
2721
                }
2566
2722
 
2601
2757
                # Update message cache for interface messages
2602
2758
                if( $nt->getNamespace() == NS_MEDIAWIKI ) {
2603
2759
                        global $wgMessageCache;
2604
 
                        $oldarticle = new Article( $this );
2605
 
                        $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() );
 
2760
 
 
2761
                        # @bug 17860: old article can be deleted, if this the case,
 
2762
                        # delete it from message cache
 
2763
                        if ( $this->getArticleID === 0 ) {
 
2764
                                $wgMessageCache->replace( $this->getDBkey(), false );
 
2765
                        } else {
 
2766
                                $oldarticle = new Article( $this );
 
2767
                                $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() );
 
2768
                        }
 
2769
 
2606
2770
                        $newarticle = new Article( $nt );
2607
2771
                        $wgMessageCache->replace( $nt->getDBkey(), $newarticle->getContent() );
2608
2772
                }
2830
2994
        }
2831
2995
        
2832
2996
        /**
 
2997
         * Move this page's subpages to be subpages of $nt
 
2998
         * @param $nt Title Move target
 
2999
         * @param $auth bool Whether $wgUser's permissions should be checked
 
3000
         * @param $reason string The reason for the move
 
3001
         * @param $createRedirect bool Whether to create redirects from the old subpages to the new ones
 
3002
         *  Ignored if the user doesn't have the 'suppressredirect' right
 
3003
         * @return mixed array with old page titles as keys, and strings (new page titles) or
 
3004
         *  arrays (errors) as values, or an error array with numeric indices if no pages were moved
 
3005
         */
 
3006
        public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
 
3007
                global $wgUser, $wgMaximumMovedPages;
 
3008
                // Check permissions
 
3009
                if( !$this->userCan( 'move-subpages' ) )
 
3010
                        return array( 'cant-move-subpages' );
 
3011
                // Do the source and target namespaces support subpages?
 
3012
                if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
 
3013
                        return array( 'namespace-nosubpages',
 
3014
                                MWNamespace::getCanonicalName( $this->getNamespace() ) );
 
3015
                if( !MWNamespace::hasSubpages( $nt->getNamespace() ) )
 
3016
                        return array( 'namespace-nosubpages',
 
3017
                                MWNamespace::getCanonicalName( $nt->getNamespace() ) );
 
3018
 
 
3019
                $subpages = $this->getSubpages($wgMaximumMovedPages + 1);
 
3020
                $retval = array();
 
3021
                $count = 0;
 
3022
                foreach( $subpages as $oldSubpage ) {
 
3023
                        $count++;
 
3024
                        if( $count > $wgMaximumMovedPages ) {
 
3025
                                $retval[$oldSubpage->getPrefixedTitle()] =
 
3026
                                                array( 'movepage-max-pages',
 
3027
                                                        $wgMaximumMovedPages );
 
3028
                                break;
 
3029
                        }
 
3030
 
 
3031
                        if( $oldSubpage->getArticleId() == $this->getArticleId() )
 
3032
                                // When moving a page to a subpage of itself,
 
3033
                                // don't move it twice
 
3034
                                continue;
 
3035
                        $newPageName = preg_replace(
 
3036
                                        '#^'.preg_quote( $this->getDBKey(), '#' ).'#',
 
3037
                                        $nt->getDBKey(), $oldSubpage->getDBKey() );
 
3038
                        if( $oldSubpage->isTalkPage() ) {
 
3039
                                $newNs = $nt->getTalkPage()->getNamespace();
 
3040
                        } else {
 
3041
                                $newNs = $nt->getSubjectPage()->getNamespace();
 
3042
                        }
 
3043
                        # Bug 14385: we need makeTitleSafe because the new page names may
 
3044
                        # be longer than 255 characters.
 
3045
                        $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
 
3046
 
 
3047
                        $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
 
3048
                        if( $success === true ) {
 
3049
                                $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
 
3050
                        } else {
 
3051
                                $retval[$oldSubpage->getPrefixedText()] = $success;
 
3052
                        }
 
3053
                }
 
3054
                return $retval;
 
3055
        }
 
3056
        
 
3057
        /**
2833
3058
         * Checks if this page is just a one-rev redirect.
2834
3059
         * Adds lock, so don't use just for light purposes.
2835
3060
         *
2842
3067
                        array( 'page_is_redirect', 'page_latest', 'page_id' ),
2843
3068
                        $this->pageCond(),
2844
3069
                        __METHOD__,
2845
 
                        'FOR UPDATE'
 
3070
                        array( 'FOR UPDATE' )
2846
3071
                );
2847
3072
                # Cache some fields we may want
2848
3073
                $this->mArticleID = $row ? intval($row->page_id) : 0;
2860
3085
                                'page_latest != rev_id'
2861
3086
                        ), 
2862
3087
                        __METHOD__,
2863
 
                        'FOR UPDATE'
 
3088
                        array( 'FOR UPDATE' )
2864
3089
                );
2865
3090
                # Return true if there was no history
2866
3091
                return ($row === false);
3034
3259
        }
3035
3260
        
3036
3261
        /**
 
3262
         * Get the first revision of the page
 
3263
         *
 
3264
         * @param $flags \type{\int} GAID_FOR_UPDATE
 
3265
         * @return Revision (or NULL if page doesn't exist)
 
3266
         */
 
3267
        public function getFirstRevision( $flags=0 ) {
 
3268
                $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 
3269
                $pageId = $this->getArticleId($flags);
 
3270
                if( !$pageId ) return NULL;
 
3271
                $row = $db->selectRow( 'revision', '*',
 
3272
                        array( 'rev_page' => $pageId ),
 
3273
                        __METHOD__,
 
3274
                        array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
 
3275
                );
 
3276
                if( !$row ) {
 
3277
                        return NULL;
 
3278
                } else {
 
3279
                        return new Revision( $row );
 
3280
                }
 
3281
        }
 
3282
        
 
3283
        /**
3037
3284
         * Check if this is a new page
3038
3285
         *
3039
3286
         * @return bool
3074
3321
                        'rev_page = ' . intval( $this->getArticleId() ) .
3075
3322
                        ' AND rev_id > ' . intval( $old ) .
3076
3323
                        ' AND rev_id < ' . intval( $new ),
3077
 
                        __METHOD__,
3078
 
                        array( 'USE INDEX' => 'PRIMARY' ) );
 
3324
                        __METHOD__
 
3325
                );
3079
3326
        }
3080
3327
 
3081
3328
        /**
3094
3341
        /**
3095
3342
         * Callback for usort() to do title sorts by (namespace, title)
3096
3343
         */
3097
 
        static function compare( $a, $b ) {
 
3344
        public static function compare( $a, $b ) {
3098
3345
                if( $a->getNamespace() == $b->getNamespace() ) {
3099
3346
                        return strcmp( $a->getText(), $b->getText() );
3100
3347
                } else {
3144
3391
                if( $this->mInterwiki != '' ) {
3145
3392
                        return true;  // any interwiki link might be viewable, for all we know
3146
3393
                }
3147
 
                switch( $this->mNamespace ) {                   
 
3394
                switch( $this->mNamespace ) {
3148
3395
                case NS_MEDIA:
3149
3396
                case NS_FILE:
3150
3397
                        return wfFindFile( $this );  // file exists, possibly in a foreign repo
3250
3497
         * @return \type{\string} Trackback URL
3251
3498
         */
3252
3499
        public function trackbackURL() {
3253
 
                global $wgScriptPath, $wgServer;
 
3500
                global $wgScriptPath, $wgServer, $wgScriptExtension;
3254
3501
 
3255
 
                return "$wgServer$wgScriptPath/trackback.php?article="
 
3502
                return "$wgServer$wgScriptPath/trackback$wgScriptExtension?article="
3256
3503
                        . htmlspecialchars(urlencode($this->getPrefixedDBkey()));
3257
3504
        }
3258
3505
 
3396
3643
                }
3397
3644
                return $redirs;
3398
3645
        }
 
3646
        
 
3647
        /**
 
3648
         * Check if this Title is a valid redirect target
 
3649
         *
 
3650
         * @return \type{\bool} TRUE or FALSE
 
3651
         */
 
3652
        public function isValidRedirectTarget() {
 
3653
                global $wgInvalidRedirectTargets;
 
3654
                
 
3655
                // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here
 
3656
                if( $this->isSpecial( 'Userlogout' ) ) {
 
3657
                        return false;
 
3658
                }
 
3659
                
 
3660
                foreach( $wgInvalidRedirectTargets as $target ) {
 
3661
                        if( $this->isSpecial( $target ) ) {
 
3662
                                return false;
 
3663
                        }
 
3664
                }
 
3665
                
 
3666
                return true;
 
3667
        }
 
3668
 
 
3669
        /**
 
3670
         * Get a backlink cache object
 
3671
         */
 
3672
        function getBacklinkCache() {
 
3673
                if ( is_null( $this->mBacklinkCache ) ) {
 
3674
                        $this->mBacklinkCache = new BacklinkCache( $this );
 
3675
                }
 
3676
                return $this->mBacklinkCache;
 
3677
        }
3399
3678
}