MediaWiki  REL1_24
ApiQueryBacklinksprop.php
Go to the documentation of this file.
00001 <?php
00035 class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
00036 
00037     // Data for the various modules implemented by this class
00038     private static $settings = array(
00039         'redirects' => array(
00040             'code' => 'rd',
00041             'prefix' => 'rd',
00042             'linktable' => 'redirect',
00043             'what' => 'redirects to',
00044             'description' => 'Returns all redirects to the given pages.',
00045             'props' => array(
00046                 'fragment' => 'Fragment of each redirect, if any',
00047             ),
00048             'showredirects' => false,
00049             'show' => array(
00050                 'fragment' => 'Only show redirects with a fragment',
00051                 '!fragment' => 'Only show redirects without a fragment',
00052             ),
00053         ),
00054         'linkshere' => array(
00055             'code' => 'lh',
00056             'prefix' => 'pl',
00057             'linktable' => 'pagelinks',
00058             'from_namespace' => true,
00059             'what' => 'pages linking to',
00060             'description' => 'Find all pages that link to the given pages.',
00061             'showredirects' => true,
00062         ),
00063         'transcludedin' => array(
00064             'code' => 'ti',
00065             'prefix' => 'tl',
00066             'linktable' => 'templatelinks',
00067             'from_namespace' => true,
00068             'what' => 'pages transcluding',
00069             'description' => 'Find all pages that transclude the given pages.',
00070             'showredirects' => true,
00071         ),
00072         'fileusage' => array(
00073             'code' => 'fu',
00074             'prefix' => 'il',
00075             'linktable' => 'imagelinks',
00076             'from_namespace' => true,
00077             'to_namespace' => NS_FILE,
00078             'what' => 'pages using',
00079             'exampletitle' => 'File:Example.jpg',
00080             'description' => 'Find all pages that use the given files.',
00081             'showredirects' => true,
00082         ),
00083     );
00084 
00085     public function __construct( ApiQuery $query, $moduleName ) {
00086         parent::__construct( $query, $moduleName, self::$settings[$moduleName]['code'] );
00087     }
00088 
00089     public function execute() {
00090         $this->run();
00091     }
00092 
00093     public function executeGenerator( $resultPageSet ) {
00094         $this->run( $resultPageSet );
00095     }
00096 
00100     private function run( ApiPageSet $resultPageSet = null ) {
00101         $settings = self::$settings[$this->getModuleName()];
00102 
00103         $db = $this->getDB();
00104         $params = $this->extractRequestParams();
00105         $prop = array_flip( $params['prop'] );
00106         $emptyString = $db->addQuotes( '' );
00107 
00108         $pageSet = $this->getPageSet();
00109         $titles = $pageSet->getGoodTitles() + $pageSet->getMissingTitles();
00110         $map = $pageSet->getAllTitlesByNamespace();
00111 
00112         // Determine our fields to query on
00113         $p = $settings['prefix'];
00114         $hasNS = !isset( $settings['to_namespace'] );
00115         if ( $hasNS ) {
00116             $bl_namespace = "{$p}_namespace";
00117             $bl_title = "{$p}_title";
00118         } else {
00119             $bl_namespace = $settings['to_namespace'];
00120             $bl_title = "{$p}_to";
00121 
00122             $titles = array_filter( $titles, function ( $t ) use ( $bl_namespace ) {
00123                 return $t->getNamespace() === $bl_namespace;
00124             } );
00125             $map = array_intersect_key( $map, array( $bl_namespace => true ) );
00126         }
00127         $bl_from = "{$p}_from";
00128 
00129         if ( !$titles ) {
00130             return; // nothing to do
00131         }
00132 
00133         // Figure out what we're sorting by, and add associated WHERE clauses.
00134         // MySQL's query planner screws up if we include a field in ORDER BY
00135         // when it's constant in WHERE, so we have to test that for each field.
00136         $sortby = array();
00137         if ( $hasNS && count( $map ) > 1 ) {
00138             $sortby[$bl_namespace] = 'ns';
00139         }
00140         $theTitle = null;
00141         foreach ( $map as $nsTitles ) {
00142             reset( $nsTitles );
00143             $key = key( $nsTitles );
00144             if ( $theTitle === null ) {
00145                 $theTitle = $key;
00146             }
00147             if ( count( $nsTitles ) > 1 || $key !== $theTitle ) {
00148                 $sortby[$bl_title] = 'title';
00149                 break;
00150             }
00151         }
00152         $miser_ns = null;
00153         if ( $params['namespace'] !== null ) {
00154             if ( empty( $settings['from_namespace'] ) && $this->getConfig()->get( 'MiserMode' ) ) {
00155                 $miser_ns = $params['namespace'];
00156             } else {
00157                 $this->addWhereFld( "{$p}_from_namespace", $params['namespace'] );
00158                 if ( !empty( $settings['from_namespace'] ) && count( $params['namespace'] ) > 1 ) {
00159                     $sortby["{$p}_from_namespace"] = 'int';
00160                 }
00161             }
00162         }
00163         $sortby[$bl_from] = 'int';
00164 
00165         // Now use the $sortby to figure out the continuation
00166         if ( !is_null( $params['continue'] ) ) {
00167             $cont = explode( '|', $params['continue'] );
00168             $this->dieContinueUsageIf( count( $cont ) != count( $sortby ) );
00169             $where = '';
00170             $i = count( $sortby ) - 1;
00171             $cont_ns = 0;
00172             $cont_title = '';
00173             foreach ( array_reverse( $sortby, true ) as $field => $type ) {
00174                 $v = $cont[$i];
00175                 switch ( $type ) {
00176                     case 'ns':
00177                         $cont_ns = (int)$v;
00178                         /* fall through */
00179                     case 'int':
00180                         $v = (int)$v;
00181                         $this->dieContinueUsageIf( $v != $cont[$i] );
00182                         break;
00183 
00184                     case 'title':
00185                         $cont_title = $v;
00186                         /* fall through */
00187                     default:
00188                         $v = $db->addQuotes( $v );
00189                         break;
00190                 }
00191 
00192                 if ( $where === '' ) {
00193                     $where = "$field >= $v";
00194                 } else {
00195                     $where = "$field > $v OR ($field = $v AND ($where))";
00196                 }
00197 
00198                 $i--;
00199             }
00200             $this->addWhere( $where );
00201         }
00202 
00203         // Populate the rest of the query
00204         $this->addTables( array( $settings['linktable'], 'page' ) );
00205         $this->addWhere( "$bl_from = page_id" );
00206 
00207         if ( $this->getModuleName() === 'redirects' ) {
00208             $this->addWhere( "rd_interwiki = $emptyString OR rd_interwiki IS NULL" );
00209         }
00210 
00211         $this->addFields( array_keys( $sortby ) );
00212         $this->addFields( array( 'bl_namespace' => $bl_namespace, 'bl_title' => $bl_title ) );
00213         if ( is_null( $resultPageSet ) ) {
00214             $fld_pageid = isset( $prop['pageid'] );
00215             $fld_title = isset( $prop['title'] );
00216             $fld_redirect = isset( $prop['redirect'] );
00217 
00218             $this->addFieldsIf( 'page_id', $fld_pageid );
00219             $this->addFieldsIf( array( 'page_title', 'page_namespace' ), $fld_title );
00220             $this->addFieldsIf( 'page_is_redirect', $fld_redirect );
00221 
00222             // prop=redirects
00223             $fld_fragment = isset( $prop['fragment'] );
00224             $this->addFieldsIf( 'rd_fragment', $fld_fragment );
00225         } else {
00226             $this->addFields( $resultPageSet->getPageTableFields() );
00227         }
00228 
00229         $this->addFieldsIf( 'page_namespace', $miser_ns !== null );
00230 
00231         if ( $hasNS ) {
00232             $lb = new LinkBatch( $titles );
00233             $this->addWhere( $lb->constructSet( $p, $db ) );
00234         } else {
00235             $where = array();
00236             foreach ( $titles as $t ) {
00237                 if ( $t->getNamespace() == $bl_namespace ) {
00238                     $where[] = "$bl_title = " . $db->addQuotes( $t->getDBkey() );
00239                 }
00240             }
00241             $this->addWhere( $db->makeList( $where, LIST_OR ) );
00242         }
00243 
00244         if ( $params['show'] !== null ) {
00245             // prop=redirects only
00246             $show = array_flip( $params['show'] );
00247             if ( isset( $show['fragment'] ) && isset( $show['!fragment'] ) ||
00248                 isset( $show['redirect'] ) && isset( $show['!redirect'] )
00249             ) {
00250                 $this->dieUsageMsg( 'show' );
00251             }
00252             $this->addWhereIf( "rd_fragment != $emptyString", isset( $show['fragment'] ) );
00253             $this->addWhereIf(
00254                 "rd_fragment = $emptyString OR rd_fragment IS NULL",
00255                 isset( $show['!fragment'] )
00256             );
00257             $this->addWhereIf( array( 'page_is_redirect' => 1 ), isset( $show['redirect'] ) );
00258             $this->addWhereIf( array( 'page_is_redirect' => 0 ), isset( $show['!redirect'] ) );
00259         }
00260 
00261         // Override any ORDER BY from above with what we calculated earlier.
00262         $this->addOption( 'ORDER BY', array_keys( $sortby ) );
00263 
00264         $this->addOption( 'LIMIT', $params['limit'] + 1 );
00265 
00266         $res = $this->select( __METHOD__ );
00267 
00268         if ( is_null( $resultPageSet ) ) {
00269             $count = 0;
00270             foreach ( $res as $row ) {
00271                 if ( ++$count > $params['limit'] ) {
00272                     // We've reached the one extra which shows that
00273                     // there are additional pages to be had. Stop here...
00274                     $this->setContinue( $row, $sortby );
00275                     break;
00276                 }
00277 
00278                 if ( $miser_ns !== null && !in_array( $row->page_namespace, $miser_ns ) ) {
00279                     // Miser mode namespace check
00280                     continue;
00281                 }
00282 
00283                 // Get the ID of the current page
00284                 $id = $map[$row->bl_namespace][$row->bl_title];
00285 
00286                 $vals = array();
00287                 if ( $fld_pageid ) {
00288                     $vals['pageid'] = $row->page_id;
00289                 }
00290                 if ( $fld_title ) {
00291                     ApiQueryBase::addTitleInfo( $vals,
00292                         Title::makeTitle( $row->page_namespace, $row->page_title )
00293                     );
00294                 }
00295                 if ( $fld_fragment && $row->rd_fragment !== null && $row->rd_fragment !== '' ) {
00296                     $vals['fragment'] = $row->rd_fragment;
00297                 }
00298                 if ( $fld_redirect && $row->page_is_redirect ) {
00299                     $vals['redirect'] = '';
00300                 }
00301                 $fit = $this->addPageSubItem( $id, $vals );
00302                 if ( !$fit ) {
00303                     $this->setContinue( $row, $sortby );
00304                     break;
00305                 }
00306             }
00307         } else {
00308             $titles = array();
00309             $count = 0;
00310             foreach ( $res as $row ) {
00311                 if ( ++$count > $params['limit'] ) {
00312                     // We've reached the one extra which shows that
00313                     // there are additional pages to be had. Stop here...
00314                     $this->setContinue( $row, $sortby );
00315                     break;
00316                 }
00317                 $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
00318             }
00319             $resultPageSet->populateFromTitles( $titles );
00320         }
00321     }
00322 
00323     private function setContinue( $row, $sortby ) {
00324         $cont = array();
00325         foreach ( $sortby as $field => $v ) {
00326             $cont[] = $row->$field;
00327         }
00328         $this->setContinueEnumParameter( 'continue', join( '|', $cont ) );
00329     }
00330 
00331     public function getCacheMode( $params ) {
00332         return 'public';
00333     }
00334 
00335     public function getAllowedParams() {
00336         $settings = self::$settings[$this->getModuleName()];
00337 
00338         $ret = array(
00339             'prop' => array(
00340                 ApiBase::PARAM_TYPE => array(
00341                     'pageid',
00342                     'title',
00343                 ),
00344                 ApiBase::PARAM_ISMULTI => true,
00345                 ApiBase::PARAM_DFLT => 'pageid|title',
00346             ),
00347             'namespace' => array(
00348                 ApiBase::PARAM_ISMULTI => true,
00349                 ApiBase::PARAM_TYPE => 'namespace',
00350             ),
00351             'limit' => array(
00352                 ApiBase::PARAM_DFLT => 10,
00353                 ApiBase::PARAM_TYPE => 'limit',
00354                 ApiBase::PARAM_MIN => 1,
00355                 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
00356                 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
00357             ),
00358             'continue' => null,
00359         );
00360 
00361         if ( !empty( $settings['showredirects'] ) ) {
00362             $ret['prop'][ApiBase::PARAM_TYPE][] = 'redirect';
00363             $ret['prop'][ApiBase::PARAM_DFLT] .= '|redirect';
00364         }
00365         if ( isset( $settings['props'] ) ) {
00366             $ret['prop'][ApiBase::PARAM_TYPE] = array_merge(
00367                 $ret['prop'][ApiBase::PARAM_TYPE], array_keys( $settings['props'] )
00368             );
00369         }
00370 
00371         $show = array();
00372         if ( !empty( $settings['showredirects'] ) ) {
00373             $show[] = 'redirect';
00374             $show[] = '!redirect';
00375         }
00376         if ( isset( $settings['show'] ) ) {
00377             $show = array_merge( $show, array_keys( $settings['show'] ) );
00378         }
00379         if ( $show ) {
00380             $ret['show'] = array(
00381                 ApiBase::PARAM_TYPE => $show,
00382                 ApiBase::PARAM_ISMULTI => true,
00383             );
00384         }
00385 
00386         return $ret;
00387     }
00388 
00389     public function getParamDescription() {
00390         $settings = self::$settings[$this->getModuleName()];
00391         $p = $this->getModulePrefix();
00392 
00393         $ret = array(
00394             'prop' => array(
00395                 'Which properties to get:',
00396             ),
00397             'show' => array(
00398                 'Show only items that meet this criteria.',
00399             ),
00400             'namespace' => 'Only include pages in these namespaces',
00401             'limit' => 'How many to return',
00402             'continue' => 'When more results are available, use this to continue',
00403         );
00404 
00405         if ( empty( $settings['from_namespace'] ) && $this->getConfig()->get( 'MiserMode' ) ) {
00406             $ret['namespace'] = array(
00407                 $ret['namespace'],
00408                 "NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
00409                 'returned before continuing; in extreme cases, zero results may be returned.',
00410             );
00411             if ( isset( $ret['type'] ) ) {
00412                 $ret['namespace'][] = "Note that you can use {$p}type=subcat or {$p}type=file " .
00413                     "instead of {$p}namespace=14 or 6.";
00414             }
00415         }
00416 
00417         $props = array(
00418             'pageid' => 'Adds the ID of page',
00419             'title' => 'Adds the title and namespace ID of the page',
00420         );
00421         if ( !empty( $settings['showredirects'] ) ) {
00422             $props['redirect'] = 'Indicate if the page is a redirect';
00423         }
00424         if ( isset( $settings['props'] ) ) {
00425             $props += $settings['props'];
00426         }
00427         foreach ( $props as $k => $v ) {
00428             $ret['props'][] = sprintf( "%-9s - %s", $k, $v );
00429         }
00430 
00431         $show = array();
00432         if ( !empty( $settings['showredirects'] ) ) {
00433             $show += array(
00434                 'redirect' => 'Only show redirects',
00435                 '!redirect' => 'Only show non-redirects',
00436             );
00437         }
00438         if ( isset( $settings['show'] ) ) {
00439             $show += $settings['show'];
00440         }
00441         foreach ( $show as $k => $v ) {
00442             $ret['show'][] = sprintf( "%-9s - %s", $k, $v );
00443         }
00444 
00445         return $ret;
00446     }
00447 
00448     public function getDescription() {
00449         return self::$settings[$this->getModuleName()]['description'];
00450     }
00451 
00452     public function getExamples() {
00453         $settings = self::$settings[$this->getModuleName()];
00454         $name = $this->getModuleName();
00455         $what = $settings['what'];
00456         $title = isset( $settings['exampletitle'] ) ? $settings['exampletitle'] : 'Main Page';
00457         $etitle = rawurlencode( $title );
00458 
00459         return array(
00460             "api.php?action=query&prop={$name}&titles={$etitle}"
00461                 => "Get a list of $what [[$title]]",
00462             "api.php?action=query&generator={$name}&titles={$etitle}&prop=info"
00463                 => "Get information about $what [[$title]]",
00464         );
00465     }
00466 
00467     public function getHelpUrls() {
00468         $name = $this->getModuleName();
00469         $prefix = $this->getModulePrefix();
00470         return "https://www.mediawiki.org/wiki/API:Properties#{$name}_.2F_{$prefix}";
00471     }
00472 }