Index: trunk/extensions/BotQuery/query.php |
— | — | @@ -172,6 +172,17 @@ |
173 | 173 | "nlnamespaces - limits which namespace to enumerate. Default 0 (Main)", |
174 | 174 | "Example: query.php?what=nolanglinks&nllimit=50", |
175 | 175 | )), |
| 176 | + 'category' => array( 'genPagesInCategory', true, |
| 177 | + array( 'cptitle', 'cplimit', 'cpfrom' ), |
| 178 | + array( null, 200, '' ), |
| 179 | + array( |
| 180 | + "Adds pages in a given category to the output list.", |
| 181 | + "Parameters supported:", |
| 182 | + "cptitle - A category name, either with or without the 'Category:' prefix.", |
| 183 | + "cplimit - How many total pages (in category) to return.", |
| 184 | + "cpfrom - The category sort key to continue paging. Starts at the begining by default.", |
| 185 | + "Example: query.php?what=category&cptitle=Days", |
| 186 | + )), |
176 | 187 | 'users' => array( 'genUserPages', true, |
177 | 188 | array( 'usfrom', 'uslimit' ), |
178 | 189 | array( null, 50 ), |
— | — | @@ -271,6 +282,7 @@ |
272 | 283 | $this->totalStartTime = wfTime(); |
273 | 284 | |
274 | 285 | $this->data = array(); |
| 286 | + |
275 | 287 | $this->pageIdByText = array(); // reverse page ID lookup |
276 | 288 | $this->requestsize = 0; |
277 | 289 | $this->db = $db; |
— | — | @@ -289,26 +301,33 @@ |
290 | 302 | $this->titles = null; |
291 | 303 | $this->pageids = null; |
292 | 304 | $this->normalizedTitles = array(); |
| 305 | + |
| 306 | + // These fields contain ids useful for other generators (redirectPageIds + nonRedirPageIds == existingPageIds) |
| 307 | + $this->existingPageIds = array(); // all existsing pages |
| 308 | + $this->redirectPageIds = array(); // all redirect pages |
| 309 | + $this->nonRedirPageIds = array(); // all regular, non-redirect pages |
293 | 310 | } |
294 | 311 | |
295 | 312 | /** |
296 | 313 | * The core function - executes meta generators, populates basic page info, and then fills in the required additional data for all pages |
297 | 314 | */ |
298 | 315 | function execute() { |
299 | | - |
300 | 316 | // Process metadata generators |
301 | 317 | $this->callGenerators( true ); |
302 | | - |
303 | | - // Query page table and initialize page ids. |
304 | | - if( $this->genPageInfo() ) { |
305 | | - // Process page-related generators |
306 | | - $this->callGenerators( false ); |
307 | | - } |
| 318 | + // Process 'titles' and 'pageids' parameters, and any other pages assembled by meta generators |
| 319 | + $this->genPageInfo(); |
| 320 | + // Process page-related generators |
| 321 | + $this->callGenerators( false ); |
308 | 322 | |
309 | | - // Report empty query |
310 | | - if( !$this->data ) { |
| 323 | + // Report empty query - if pages and meta elements have no subelements |
| 324 | + if( ( !array_key_exists('pages', $this->data) || count($this->data['pages']) === 0 ) && |
| 325 | + ( !array_key_exists('meta', $this->data) || count($this->data['meta']) === 0 ) ) { |
311 | 326 | $this->dieUsage( 'Nothing to do', 'emptyresult' ); |
312 | 327 | } |
| 328 | + // All items under 'pages' will be presented as 'page' xml elements |
| 329 | + if( array_key_exists('pages', $this->data) && count($this->data['pages']) > 0 ) { |
| 330 | + $this->data['pages']['_element'] = 'page'; |
| 331 | + } |
313 | 332 | } |
314 | 333 | |
315 | 334 | /** |
— | — | @@ -409,213 +428,75 @@ |
410 | 429 | * As the result of this method, $this->redirectPageIds and existingPageIds (arrays) will be available for other generators. |
411 | 430 | */ |
412 | 431 | function genPageInfo() { |
413 | | - global $wgUser, $wgRequest; |
414 | 432 | $this->startProfiling(); |
415 | 433 | $where = array(); |
416 | | - |
417 | | - // |
418 | | - // Pages in a category |
419 | | - // |
420 | | - $categoryName = $wgRequest->getVal('category'); |
421 | | - if( $categoryName !== null ) { |
422 | | - $categoryObj = &Title::newFromText( $categoryName ); |
423 | | - if( !$categoryObj || |
424 | | - ($categoryObj->getNamespace() !== NS_MAIN && $categoryObj->getNamespace() !== NS_CATEGORY) || |
425 | | - $categoryObj->isExternal() ) { |
426 | | - $this->dieUsage( "bad category name $categoryName", 'pi_invalidcategory' ); |
427 | | - } |
428 | | - // TODO: Should we check if the user has the rights to view this category? |
| 434 | + |
| 435 | + // Assemble a list of titles to process. This method will modify $where and $this->requestsize |
| 436 | + $nonexistentPages = &$this->parseTitles( $where ); |
| 437 | + |
| 438 | + // Assemble a list of pageids to process. This method will modify $where and $this->requestsize |
| 439 | + $pageids = &$this->parsePageIds( $where ); |
| 440 | + |
| 441 | + // Have anything to do? |
| 442 | + if( $this->requestsize > 0 ) { |
| 443 | + // Validate limits |
| 444 | + $this->validateLimit( 'pi_botquerytoobig', $this->requestsize, 500, 20000 ); |
429 | 445 | |
430 | | - $conds = array( 'cl_to' => $categoryObj->getDBkey() ); |
431 | | - $cpfrom = $wgRequest->getVal('cpfrom'); |
432 | | - if ( $cpfrom != '' ) { |
433 | | - $conds[] = 'cl_sortkey >= ' . $this->db->addQuotes($cpfrom); |
434 | | - } |
435 | | - $cplimit = $wgRequest->getInt( 'cplimit', 200 ); |
436 | | - $this->validateLimit( 'pi_badcplimit', $cplimit, 500, 5000 ); |
437 | | - |
| 446 | + // Query page information with the given lists of titles & pageIDs |
438 | 447 | $this->startDbProfiling(); |
439 | | - $res = $this->db->select( 'categorylinks', |
440 | | - array( 'cl_from', 'cl_sortkey' ), |
441 | | - $conds, |
442 | | - $this->classname . '::genPageInfoCategories', |
443 | | - array( 'ORDER BY' => 'cl_sortkey', 'LIMIT' => $cplimit+1 )); |
444 | | - $this->endDbProfiling('pageInfoCat'); |
445 | | - $count = 0; |
446 | | - while ( $row = $this->db->fetchObject( $res ) ) { |
447 | | - if( ++$count > $cplimit ) { |
448 | | - // We've reached the one extra which shows that there are additional pages to be had. Stop here... |
449 | | - $this->addStatusMessage( 'category', array('next' => $row->cl_sortkey) ); |
450 | | - break; |
| 448 | + $res = $this->db->select( |
| 449 | + 'page', |
| 450 | + array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_touched', 'page_latest' ), |
| 451 | + $this->db->makeList( $where, LIST_OR ), |
| 452 | + $this->classname . '::genPageInfo' ); |
| 453 | + $this->endDbProfiling('pageInfo'); |
| 454 | + |
| 455 | + while( $row = $this->db->fetchObject( $res ) ) { |
| 456 | + $this->storePageInfo( $row ); |
| 457 | + if( $nonexistentPages !== null ) { |
| 458 | + unset( $nonexistentPages[$row->page_namespace][$row->page_title] ); // Strike out link |
451 | 459 | } |
452 | | - $this->addRaw( 'pageids', $row->cl_from ); |
453 | 460 | } |
454 | 461 | $this->db->freeResult( $res ); |
455 | | - } |
456 | | - |
457 | | - // |
458 | | - // List of titles |
459 | | - // |
460 | | - $titles = $this->addRaw( 'titles', $wgRequest->getVal('titles') ); |
461 | | - if( $titles !== null ) { |
462 | | - $titles = explode( '|', $titles ); |
463 | | - $linkBatch = new LinkBatch; |
464 | | - foreach ( $titles as &$titleString ) { |
465 | | - $titleObj = &Title::newFromText( $titleString ); |
466 | | - if ( !$titleObj ) { |
467 | | - $this->dieUsage( "bad title $titleString", 'pi_invalidtitle' ); |
| 462 | + |
| 463 | + // Create records for non-existent page IDs |
| 464 | + if( $pageids !== null ) { |
| 465 | + foreach( array_diff_key($pageids, $this->existingPageIds) as $pageid ) { |
| 466 | + $data = &$this->data['pages'][$pageid]; |
| 467 | + $data['id'] = 0; |
| 468 | + $data['badId'] = $pageid; |
468 | 469 | } |
469 | | - if ( !$titleObj->userCanRead() ) { |
470 | | - $this->dieUsage( "No read permission for $titleString", 'pi_titleaccessdenied' ); |
471 | | - } |
472 | | - $linkBatch->addObj( $titleObj ); |
473 | | - |
474 | | - // Make sure we remember the original title that was given to us |
475 | | - // This way the caller can correlate new titles with the originally requested if they change namespaces, etc |
476 | | - if( $titleString !== $titleObj->getPrefixedText() ) { |
477 | | - $this->normalizedTitles[$titleString] = $titleObj; |
478 | | - } |
479 | 470 | } |
480 | | - if ( $linkBatch->isEmpty() ) { |
481 | | - $this->dieUsage( "no titles could be found", 'pi_novalidtitles' ); |
482 | | - } |
483 | | - // Create a list of pages to query |
484 | | - $where[] = $linkBatch->constructSet( 'page', $this->db ); |
485 | | - $this->requestsize += $linkBatch->getSize(); |
486 | 471 | |
487 | | - // we don't need the batch any more, data can be destroyed |
488 | | - $nonexistentPages = &$linkBatch->data; |
489 | | - } else { |
490 | | - $nonexistentPages = array(); // empty data to keep unset() happy |
491 | | - } |
492 | | - // |
493 | | - // List of Page IDs |
494 | | - // |
495 | | - $pageids = $this->addRaw( 'pageids', $wgRequest->getVal('pageids') ); |
496 | | - if ( $pageids !== null ) { |
497 | | - $pageids = explode( '|', $pageids ); |
498 | | - $pageids = array_map( 'intval', $pageids ); |
499 | | - $pageids = array_unique($pageids); |
500 | | - sort( $pageids, SORT_NUMERIC ); |
501 | | - if( $pageids[0] <= 0 ) { |
502 | | - $this->dieUsage( "pageids contains a bad id", 'pi_badpageid' ); |
| 472 | + // Add entries for non-existent page titles |
| 473 | + $i = -1; |
| 474 | + if( $nonexistentPages !== null ) { |
| 475 | + foreach( $nonexistentPages as $namespace => &$stuff ) { |
| 476 | + foreach( $stuff as $dbk => &$arbitrary ) { |
| 477 | + $title = Title::makeTitle( $namespace, $dbk ); |
| 478 | + // Must do this check even for non-existent pages, as some generators can give related information |
| 479 | + if ( !$title->userCanRead() ) { |
| 480 | + $this->dieUsage( "No read permission for $titleString", 'pi_nopageaccessdenied' ); |
| 481 | + } |
| 482 | + $data = &$this->data['pages'][$i]; |
| 483 | + $this->pageIdByText[$title->getPrefixedText()] = $i; |
| 484 | + $data['_obj'] = $title; |
| 485 | + $data['title'] = $title->getPrefixedText(); |
| 486 | + $data['ns'] = $title->getNamespace(); |
| 487 | + $data['id'] = 0; |
| 488 | + $i--; |
| 489 | + } |
| 490 | + } |
503 | 491 | } |
504 | | - $where['page_id'] = $pageids; |
505 | | - $this->requestsize += count($pageids); |
506 | | - } |
507 | | - |
508 | | - // Do we have anything to do? |
509 | | - if( $this->requestsize == 0 ) { |
510 | | - // Do not end profiling here, as it will introduce an element to the data object, and the usage screen may not be shown. |
511 | | - return false; // Nothing to do for any of the page generators |
512 | | - } |
513 | | - |
514 | | - // |
515 | | - // User restrictions |
516 | | - // |
517 | | - $this->validateLimit( 'pi_botquerytoobig', $this->requestsize, 500, 20000 ); |
518 | | - |
519 | | - // |
520 | | - // Make sure that this->data['pages'] is empty |
521 | | - // |
522 | | - if( array_key_exists('pages', $this->data) ) { |
523 | | - die( "internal error - 'pages' should not yet exist" ); |
524 | | - } |
525 | | - $this->data['pages'] = array(); |
526 | 492 | |
527 | | - // |
528 | | - // Query page information with the given lists of titles & pageIDs |
529 | | - // |
530 | | - $this->redirectPageIds = array(); |
531 | | - $this->startDbProfiling(); |
532 | | - $res = $this->db->select( 'page', |
533 | | - array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_touched', 'page_latest' ), |
534 | | - $this->db->makeList( $where, LIST_OR ), |
535 | | - $this->classname . '::genPageInfo' ); |
536 | | - $this->endDbProfiling('pageInfo'); |
537 | | - while( $row = $this->db->fetchObject( $res ) ) { |
538 | | - $title = Title::makeTitle( $row->page_namespace, $row->page_title ); |
539 | | - if ( !$title->userCanRead() ) { |
540 | | - $this->db->freeResult( $res ); |
541 | | - $this->dieUsage( "No read permission for $titleString", 'pi_pageidaccessdenied' ); |
| 493 | + // When normalized title differs from what was given, append the given title(s) |
| 494 | + foreach( $this->normalizedTitles as $givenTitle => &$title ) { |
| 495 | + $data = &$this->data['pages'][$i--]; |
| 496 | + $data['title'] = $givenTitle; |
| 497 | + $data['normalizedTitle'] = $title->getPrefixedText(); |
542 | 498 | } |
543 | | - $data = &$this->data['pages'][$row->page_id]; |
544 | | - $this->pageIdByText[$title->getPrefixedText()] = $row->page_id; |
545 | | - $data['_obj'] = $title; |
546 | | - $data['ns'] = $title->getNamespace(); |
547 | | - $data['title'] = $title->getPrefixedText(); |
548 | | - $data['id'] = $row->page_id; |
549 | | - $data['touched'] = $row->page_touched; |
550 | | - $data['revid'] = $row->page_latest; |
551 | | - if ( $row->page_is_redirect ) { |
552 | | - $data['redirect'] = ''; |
553 | | - $this->redirectPageIds[] = $row->page_id; |
554 | | - } |
555 | | - |
556 | | - // Strike out link |
557 | | - unset( $nonexistentPages[$row->page_namespace][$row->page_title] ); |
558 | 499 | } |
559 | | - $this->db->freeResult( $res ); |
560 | | - |
561 | | - // |
562 | | - // At this point we assume that this->data['pages'] contains ONLY valid existing entries. |
563 | | - // Create lists that can later be used to filter other tables by page Id or other useful query strings |
564 | | - // |
565 | | - $this->existingPageIds = array_keys( $this->data['pages'] ); |
566 | | - $this->nonRedirPageIds = array_diff($this->existingPageIds, $this->redirectPageIds); |
567 | | - |
568 | | - // |
569 | | - // Create records for non-existent page IDs |
570 | | - // |
571 | | - if( $pageids !== null ) { |
572 | | - foreach( array_diff_key($pageids, $this->existingPageIds) as $pageid ) { |
573 | | - $data = &$this->data['pages'][$pageid]; |
574 | | - $data['id'] = 0; |
575 | | - $data['bad_id'] = $pageid; |
576 | | - } |
577 | | - } |
578 | | - |
579 | | - $this->data['pages']['_element'] = 'page'; |
580 | | - |
581 | | - // |
582 | | - // Add entries for non-existent page titles |
583 | | - // |
584 | | - $i = -1; |
585 | | - foreach( $nonexistentPages as $namespace => &$stuff ) { |
586 | | - foreach( $stuff as $dbk => &$arbitrary ) { |
587 | | - $title = Title::makeTitle( $namespace, $dbk ); |
588 | | - // Must do this check even for non-existent pages, as some generators can give related information |
589 | | - if ( !$title->userCanRead() ) { |
590 | | - $this->dieUsage( "No read permission for $titleString", 'pi_nopageaccessdenied' ); |
591 | | - } |
592 | | - $data = &$this->data['pages'][$i]; |
593 | | - $this->pageIdByText[$title->getPrefixedText()] = $i; |
594 | | - $data['_obj'] = $title; |
595 | | - $data['title'] = $title->getPrefixedText(); |
596 | | - $data['ns'] = $title->getNamespace(); |
597 | | - $data['id'] = 0; |
598 | | - $i--; |
599 | | - } |
600 | | - } |
601 | | - |
602 | | - // |
603 | | - // Mark redirects as such. More information can be given with 'redirects' property |
604 | | - // |
605 | | - foreach( $this->redirectPageIds as $pageid ) { |
606 | | - $this->data['pages'][$pageid]['redirect'] = ''; |
607 | | - } |
608 | | - |
609 | | - // |
610 | | - // When normalized title differs from what was given, append the given title(s) |
611 | | - // |
612 | | - foreach( $this->normalizedTitles as $givenTitle => &$title ) { |
613 | | - $pageId = $this->pageIdByText[$title->getPrefixedText()]; |
614 | | - $data = &$this->data['pages'][$pageId]['rawTitles']; |
615 | | - $data['_element'] = 'title'; |
616 | | - $data[] = $givenTitle; |
617 | | - } |
618 | 500 | $this->endProfiling('pageInfo'); |
619 | | - return true; // success |
620 | 501 | } |
621 | 502 | |
622 | 503 | |
— | — | @@ -640,7 +521,7 @@ |
641 | 522 | $meta['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // "case-insensitive" option is reserved for future |
642 | 523 | |
643 | 524 | $this->data['meta']['site'] = $meta; |
644 | | - $this->endProfiling($prop); |
| 525 | + $this->endProfiling( $prop ); |
645 | 526 | } |
646 | 527 | |
647 | 528 | /** |
— | — | @@ -655,7 +536,7 @@ |
656 | 537 | $meta[$ns] = array( "id"=>$ns, "*" => $title ); |
657 | 538 | } |
658 | 539 | $this->data['meta']['namespaces'] = $meta; |
659 | | - $this->endProfiling($prop); |
| 540 | + $this->endProfiling( $prop ); |
660 | 541 | } |
661 | 542 | |
662 | 543 | /** |
— | — | @@ -679,7 +560,7 @@ |
680 | 561 | } |
681 | 562 | |
682 | 563 | $this->data['meta']['user'] = $meta; |
683 | | - $this->endProfiling($prop); |
| 564 | + $this->endProfiling( $prop ); |
684 | 565 | } |
685 | 566 | |
686 | 567 | /** |
— | — | @@ -731,14 +612,14 @@ |
732 | 613 | $this->classname . '::genMetaRecentChanges', |
733 | 614 | $options |
734 | 615 | ); |
735 | | - $this->endDbProfiling($prop); |
| 616 | + $this->endDbProfiling( $prop ); |
736 | 617 | while ( $row = $this->db->fetchObject( $res ) ) { |
737 | 618 | if( $row->rc_cur_id != 0 ) { |
738 | 619 | $this->addRaw( 'pageids', $row->rc_cur_id ); |
739 | 620 | } |
740 | 621 | } |
741 | 622 | $this->db->freeResult( $res ); |
742 | | - $this->endProfiling($prop); |
| 623 | + $this->endProfiling( $prop ); |
743 | 624 | } |
744 | 625 | |
745 | 626 | /** |
— | — | @@ -759,7 +640,7 @@ |
760 | 641 | $this->classname . '::genUserPages', |
761 | 642 | array( 'ORDER BY' => 'user_name', 'LIMIT' => $uslimit ) |
762 | 643 | ); |
763 | | - $this->endDbProfiling($prop); |
| 644 | + $this->endDbProfiling( $prop ); |
764 | 645 | |
765 | 646 | $userNS = $wgContLang->getNsText(NS_USER); |
766 | 647 | if( !$userNS ) $userNS = 'User'; |
— | — | @@ -769,7 +650,7 @@ |
770 | 651 | $this->addRaw( 'titles', $userNS . $row->user_name ); |
771 | 652 | } |
772 | 653 | $this->db->freeResult( $res ); |
773 | | - $this->endProfiling($prop); |
| 654 | + $this->endProfiling( $prop ); |
774 | 655 | } |
775 | 656 | |
776 | 657 | /** |
— | — | @@ -782,7 +663,6 @@ |
783 | 664 | global $wgContLang; |
784 | 665 | $this->startProfiling(); |
785 | 666 | extract( $this->getParams( $prop, $genInfo )); |
786 | | - |
787 | 667 | $this->validateLimit( 'aplimit', $aplimit, 50, 1000 ); |
788 | 668 | |
789 | 669 | $ns = $wgContLang->getNsText($apnamespace); |
— | — | @@ -795,11 +675,11 @@ |
796 | 676 | $this->startDbProfiling(); |
797 | 677 | $res = $this->db->select( |
798 | 678 | 'page', |
799 | | - 'page_title', |
| 679 | + array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_touched', 'page_latest' ), |
800 | 680 | array( 'page_namespace' => intval($apnamespace), 'page_title>=' . $this->db->addQuotes($apfrom) ), |
801 | 681 | $this->classname . '::genMetaAllPages', |
802 | 682 | array( 'USE INDEX' => 'name_title', 'LIMIT' => $aplimit+1, 'ORDER BY' => 'page_namespace, page_title' )); |
803 | | - $this->endDbProfiling($prop); |
| 683 | + $this->endDbProfiling( $prop ); |
804 | 684 | |
805 | 685 | // Add found page ids to the list of requested titles - they will be auto-populated later |
806 | 686 | $count = 0; |
— | — | @@ -809,19 +689,16 @@ |
810 | 690 | $this->addStatusMessage( $prop, array('next' => $row->page_title) ); |
811 | 691 | break; |
812 | 692 | } |
813 | | - $this->addRaw( 'titles', $ns . $row->page_title ); |
| 693 | + $this->storePageInfo( $row ); |
814 | 694 | } |
815 | 695 | $this->db->freeResult( $res ); |
816 | | - $this->endProfiling($prop); |
| 696 | + $this->endProfiling( $prop ); |
817 | 697 | } |
818 | 698 | |
819 | 699 | /** |
820 | 700 | * Add pages by the namespace without language links to the output |
821 | 701 | */ |
822 | 702 | function genMetaNoLangLinksPages(&$prop, &$genInfo) { |
823 | | - // |
824 | | - // TODO: This is very inefficient - we can get the actual page information, instead we make two identical query. |
825 | | - // |
826 | 703 | global $wgContLang; |
827 | 704 | $this->startProfiling(); |
828 | 705 | extract( $this->getParams( $prop, $genInfo )); |
— | — | @@ -832,7 +709,7 @@ |
833 | 710 | // Find all pages without any rows in the langlinks table |
834 | 711 | // |
835 | 712 | $sql = 'SELECT' |
836 | | - . ' page_id, page_title' |
| 713 | + . ' page_id, page_namespace, page_title, page_is_redirect, page_touched, page_latest' |
837 | 714 | . " FROM $page LEFT JOIN $langlinks ON page_id = ll_from" |
838 | 715 | . ' WHERE' |
839 | 716 | . ' ll_from IS NULL AND page_namespace=' . intval($nlnamespace) . ' AND page_title>=' . $this->db->addQuotes($nlfrom) |
— | — | @@ -841,7 +718,7 @@ |
842 | 719 | |
843 | 720 | $this->startDbProfiling(); |
844 | 721 | $res = $this->db->query( $sql, $this->classname . '::genMetaNoLangLinksPages' ); |
845 | | - $this->endDbProfiling($prop); |
| 722 | + $this->endDbProfiling( $prop ); |
846 | 723 | |
847 | 724 | // Add found page ids to the list of requested titles - they will be auto-populated later |
848 | 725 | $count = 0; |
— | — | @@ -851,12 +728,56 @@ |
852 | 729 | $this->addStatusMessage( $prop, array('next' => $row->page_title) ); |
853 | 730 | break; |
854 | 731 | } |
855 | | - $this->addRaw( 'pageids', $row->page_id ); |
| 732 | + $this->storePageInfo( $row ); |
856 | 733 | } |
857 | 734 | $this->db->freeResult( $res ); |
858 | | - $this->endProfiling($prop); |
| 735 | + $this->endProfiling( $prop ); |
859 | 736 | } |
860 | 737 | |
| 738 | + /** |
| 739 | + * Add pages in a category |
| 740 | + */ |
| 741 | + function genPagesInCategory(&$prop, &$genInfo) { |
| 742 | + $this->startProfiling(); |
| 743 | + extract( $this->getParams( $prop, $genInfo )); |
| 744 | + |
| 745 | + // Validate parameters |
| 746 | + if( $cptitle === null ) { |
| 747 | + $this->dieUsage( "Missing category title parameter cptitle", 'cp_missingcptitle' ); |
| 748 | + } |
| 749 | + $categoryObj = &Title::newFromText( $cptitle ); |
| 750 | + if( !$categoryObj || |
| 751 | + ($categoryObj->getNamespace() !== NS_MAIN && $categoryObj->getNamespace() !== NS_CATEGORY) || |
| 752 | + $categoryObj->isExternal() ) { |
| 753 | + $this->dieUsage( "bad category name $cptitle", 'cp_invalidcategory' ); |
| 754 | + } |
| 755 | + $conds = array( 'cl_to' => $categoryObj->getDBkey() ); |
| 756 | + if ( $cpfrom != '' ) { |
| 757 | + $conds[] = 'cl_sortkey >= ' . $this->db->addQuotes($cpfrom); |
| 758 | + } |
| 759 | + $this->validateLimit( 'cplimit', $cplimit, 500, 5000 ); |
| 760 | + |
| 761 | + $this->startDbProfiling(); |
| 762 | + $res = $this->db->select( |
| 763 | + 'categorylinks', |
| 764 | + array( 'cl_from', 'cl_sortkey' ), |
| 765 | + $conds, |
| 766 | + $this->classname . '::genPagesInCategory', |
| 767 | + array( 'ORDER BY' => 'cl_sortkey', 'LIMIT' => $cplimit+1 )); |
| 768 | + $this->endDbProfiling( $prop ); |
| 769 | + |
| 770 | + $count = 0; |
| 771 | + while ( $row = $this->db->fetchObject( $res ) ) { |
| 772 | + if( ++$count > $cplimit ) { |
| 773 | + // We've reached the one extra which shows that there are additional pages to be had. Stop here... |
| 774 | + $this->addStatusMessage( 'category', array('next' => $row->cl_sortkey) ); |
| 775 | + break; |
| 776 | + } |
| 777 | + $this->addRaw( 'pageids', $row->cl_from ); |
| 778 | + } |
| 779 | + $this->db->freeResult( $res ); |
| 780 | + $this->endProfiling( $prop ); |
| 781 | + } |
861 | 782 | |
862 | 783 | // |
863 | 784 | // ************************************* PAGE INFO GENERATORS ************************************* |
— | — | @@ -905,7 +826,8 @@ |
906 | 827 | |
907 | 828 | $this->startDbProfiling(); |
908 | 829 | $res = $this->db->query( $sql, $this->classname . '::genRedirectInfo' ); |
909 | | - $this->endDbProfiling('redirects'); |
| 830 | + $this->endDbProfiling( $prop ); |
| 831 | + |
910 | 832 | while ( $row = $this->db->fetchObject( $res ) ) { |
911 | 833 | $this->addPageSubElement( $row->a_id, 'redirect', 'to', $this->getLinkInfo( $row->b_namespace, $row->b_title, $row->b_id, $row->b_is_redirect ), false); |
912 | 834 | if( $row->b_is_redirect ) { |
— | — | @@ -913,7 +835,7 @@ |
914 | 836 | } |
915 | 837 | } |
916 | 838 | $this->db->freeResult( $res ); |
917 | | - $this->endProfiling($prop); |
| 839 | + $this->endProfiling( $prop ); |
918 | 840 | } |
919 | 841 | |
920 | 842 | var $genPageLinksSettings = array( // database column name prefix, output element name |
— | — | @@ -939,7 +861,7 @@ |
940 | 862 | "{$prefix}_title to_title" ), |
941 | 863 | array( "{$prefix}_from" => $this->nonRedirPageIds ), |
942 | 864 | $this->classname . "::genPageLinks_{$code}" ); |
943 | | - $this->endDbProfiling($prop); |
| 865 | + $this->endDbProfiling( $prop ); |
944 | 866 | |
945 | 867 | while ( $row = $this->db->fetchObject( $res ) ) { |
946 | 868 | if( $langlinks ) { |
— | — | @@ -950,7 +872,7 @@ |
951 | 873 | $this->addPageSubElement( $row->from_id, $prop, $code, $values); |
952 | 874 | } |
953 | 875 | $this->db->freeResult( $res ); |
954 | | - $this->endProfiling($prop); |
| 876 | + $this->endProfiling( $prop ); |
955 | 877 | } |
956 | 878 | |
957 | 879 | var $genPageBackLinksSettings = array( // database column name prefix, output element name |
— | — | @@ -1079,7 +1001,7 @@ |
1080 | 1002 | $where, |
1081 | 1003 | $this->classname . "::genPageBackLinks_{$code}", |
1082 | 1004 | $options ); |
1083 | | - $this->endDbProfiling($prop); |
| 1005 | + $this->endDbProfiling( $prop ); |
1084 | 1006 | |
1085 | 1007 | $count = 0; |
1086 | 1008 | while ( $row = $this->db->fetchObject( $res ) ) { |
— | — | @@ -1094,7 +1016,7 @@ |
1095 | 1017 | $this->addPageSubElement( $pageId, $prop, $code, $values ); |
1096 | 1018 | } |
1097 | 1019 | $this->db->freeResult( $res ); |
1098 | | - $this->endProfiling($prop); |
| 1020 | + $this->endProfiling( $prop ); |
1099 | 1021 | } |
1100 | 1022 | |
1101 | 1023 | /** |
— | — | @@ -1173,8 +1095,8 @@ |
1174 | 1096 | } |
1175 | 1097 | $this->db->freeResult( $res ); |
1176 | 1098 | } |
1177 | | - $this->endDbProfiling($prop); |
1178 | | - $this->endProfiling($prop); |
| 1099 | + $this->endDbProfiling( $prop ); |
| 1100 | + $this->endProfiling( $prop ); |
1179 | 1101 | } |
1180 | 1102 | |
1181 | 1103 | /** |
— | — | @@ -1207,8 +1129,8 @@ |
1208 | 1130 | $this->addPageSubElement( $row->rev_page, $prop, '*', Revision::getRevisionText( $row ), false); |
1209 | 1131 | } |
1210 | 1132 | $this->db->freeResult( $res ); |
1211 | | - $this->endDbProfiling($prop); // Revision::getRevisionText is also a database call, so we include them in this scope |
1212 | | - $this->endProfiling($prop); |
| 1133 | + $this->endDbProfiling( $prop ); // Revision::getRevisionText is also a database call, so include it in this scope |
| 1134 | + $this->endProfiling( $prop ); |
1213 | 1135 | } |
1214 | 1136 | |
1215 | 1137 | // |
— | — | @@ -1216,6 +1138,93 @@ |
1217 | 1139 | // |
1218 | 1140 | |
1219 | 1141 | /** |
| 1142 | + * Take $row with fields from 'page' table and create needed page entries in $this->data |
| 1143 | + */ |
| 1144 | + function storePageInfo( &$row ) { |
| 1145 | + $title = Title::makeTitle( $row->page_namespace, $row->page_title ); |
| 1146 | + if ( !$title->userCanRead() ) { |
| 1147 | + $this->db->freeResult( $res ); |
| 1148 | + $this->dieUsage( "No read permission for $titleString", 'pi_pageidaccessdenied' ); |
| 1149 | + } |
| 1150 | + $pageid = $row->page_id; |
| 1151 | + $data = &$this->data['pages'][$pageid]; |
| 1152 | + $this->pageIdByText[$title->getPrefixedText()] = $pageid; |
| 1153 | + $data['_obj'] = $title; |
| 1154 | + $data['ns'] = $title->getNamespace(); |
| 1155 | + $data['title'] = $title->getPrefixedText(); |
| 1156 | + $data['id'] = $pageid; |
| 1157 | + $data['touched'] = $row->page_touched; |
| 1158 | + $data['revid'] = $row->page_latest; |
| 1159 | + |
| 1160 | + $this->existingPageIds[] = $pageid; |
| 1161 | + if ( $row->page_is_redirect ) { |
| 1162 | + $data['redirect'] = ''; |
| 1163 | + $this->redirectPageIds[] = $pageid; |
| 1164 | + } else { |
| 1165 | + $this->nonRedirPageIds[] = $pageid; |
| 1166 | + } |
| 1167 | + } |
| 1168 | + |
| 1169 | + /** |
| 1170 | + * Process the list of given titles, update $where and $this->requestsize, and return the data of the LinkBatch object |
| 1171 | + */ |
| 1172 | + function parseTitles( &$where ) { |
| 1173 | + global $wgRequest; |
| 1174 | + $titles = $this->addRaw( 'titles', $wgRequest->getVal('titles') ); |
| 1175 | + if( $titles !== null ) { |
| 1176 | + $titles = explode( '|', $titles ); |
| 1177 | + $linkBatch = new LinkBatch; |
| 1178 | + foreach ( $titles as &$titleString ) { |
| 1179 | + $titleObj = &Title::newFromText( $titleString ); |
| 1180 | + if ( !$titleObj ) { |
| 1181 | + $this->dieUsage( "bad title $titleString", 'pi_invalidtitle' ); |
| 1182 | + } |
| 1183 | + if ( !$titleObj->userCanRead() ) { |
| 1184 | + $this->dieUsage( "No read permission for $titleString", 'pi_titleaccessdenied' ); |
| 1185 | + } |
| 1186 | + $linkBatch->addObj( $titleObj ); |
| 1187 | + |
| 1188 | + // Make sure we remember the original title that was given to us |
| 1189 | + // This way the caller can correlate new titles with the originally requested if they change namespaces, etc |
| 1190 | + if( $titleString !== $titleObj->getPrefixedText() ) { |
| 1191 | + $this->normalizedTitles[$titleString] = $titleObj; |
| 1192 | + } |
| 1193 | + } |
| 1194 | + if ( $linkBatch->isEmpty() ) { |
| 1195 | + $this->dieUsage( "no valid titles were given", 'pi_novalidtitles' ); |
| 1196 | + } |
| 1197 | + // Create a list of pages to query |
| 1198 | + $where[] = $linkBatch->constructSet( 'page', $this->db ); |
| 1199 | + $this->requestsize += $linkBatch->getSize(); |
| 1200 | + |
| 1201 | + // we don't need the batch any more, data can be destroyed |
| 1202 | + return $linkBatch->data; |
| 1203 | + } else { |
| 1204 | + return null; |
| 1205 | + } |
| 1206 | + } |
| 1207 | + |
| 1208 | + /** |
| 1209 | + * Process the list of given titles, update $where and $this->requestsize, and return the data of the LinkBatch object |
| 1210 | + */ |
| 1211 | + function parsePageIds( &$where ) { |
| 1212 | + global $wgRequest; |
| 1213 | + $pageids = $this->addRaw( 'pageids', $wgRequest->getVal('pageids') ); |
| 1214 | + if ( $pageids !== null ) { |
| 1215 | + $pageids = explode( '|', $pageids ); |
| 1216 | + $pageids = array_map( 'intval', $pageids ); |
| 1217 | + $pageids = array_unique($pageids); |
| 1218 | + sort( $pageids, SORT_NUMERIC ); |
| 1219 | + if( $pageids[0] <= 0 ) { |
| 1220 | + $this->dieUsage( "'pageids' contains a bad id", 'pi_badpageid' ); |
| 1221 | + } |
| 1222 | + $where['page_id'] = $pageids; |
| 1223 | + $this->requestsize += count($pageids); |
| 1224 | + } |
| 1225 | + return $pageids; |
| 1226 | + } |
| 1227 | + |
| 1228 | + /** |
1220 | 1229 | * From two parameter arrays, makes an array of the values provided by the user. |
1221 | 1230 | */ |
1222 | 1231 | function getParams( &$property, &$generator ) { |
— | — | @@ -1410,10 +1419,6 @@ |
1411 | 1420 | " what - What information the server should return. See properties section.", |
1412 | 1421 | " titles - A list of titles, separated by the pipe '|' symbol.", |
1413 | 1422 | " pageids - A list of page ids, separated by the pipe '|' symbol.", |
1414 | | - " category - A category name, either with or without the 'Category:' prefix.", |
1415 | | - " When present, all pages in the given category will be included in the output.", |
1416 | | - " cplimit - How many total pages (in category) to return", |
1417 | | - " cpfrom - The category sort key to continue paging. Starts at the begining by default", |
1418 | 1423 | " noprofile - When present, each sql query execution time will be hidden. (not implemented)", |
1419 | 1424 | "", |
1420 | 1425 | "*Examples*", |
— | — | @@ -1423,10 +1428,7 @@ |
1424 | 1429 | " query.php?format=xml&what=revisions&titles=Main_Page&rvlimit=100&rvstart=20060401000000&rvcomments", |
1425 | 1430 | " Get a list of 100 last revisions of the main page with comments, but only if it happened after midnight April 1st 2006", |
1426 | 1431 | "", |
1427 | | - " query.php?format=xml&category=Days", |
1428 | | - " Get a list of pages that belong to the category Days", |
1429 | 1432 | "", |
1430 | | - "", |
1431 | 1433 | "*Supported Formats*", |
1432 | 1434 | $formats, |
1433 | 1435 | "", |
— | — | @@ -1438,11 +1440,11 @@ |
1439 | 1441 | "", |
1440 | 1442 | "*Credits*", |
1441 | 1443 | " This feature is maintained by Yuri Astrakhan (FirstnameLastname@gmail.com)", |
1442 | | - " You can also leave your comments and suggestions at http://en.wikipedia.org/wiki/User_talk:Yurik", |
| 1444 | + " Please leave your comments and suggestions at http://en.wikipedia.org/wiki/User_talk:Yurik", |
1443 | 1445 | "", |
1444 | 1446 | " This extension came as the result of IRC discussion between Yuri Astrakhan (en:Yurik), Tim Starling (en:Tim Starling), and Daniel Kinzler(de:Duesentrieb)", |
1445 | | - " The extension was first implemented by Tim to provide interlanguage links and history.", |
1446 | | - " It was later completelly rewritten by Yuri to allow for modular properties, meta information, and various formatting options.", |
| 1447 | + " The extension was first implemented by Tim to provide interlanguage links and history summary.", |
| 1448 | + " It was later completelly rewritten by Yuri, introducing the rest of properties, meta information, and various formatting options.", |
1447 | 1449 | "", |
1448 | 1450 | "*User Status*", |
1449 | 1451 | " You are " . ($wgUser->isAnon() ? "an anonymous" : "a logged-in") . " " . ($wgUser->isBot() ? "bot" : "user") . " " . $wgUser->getName(), |
— | — | @@ -1552,7 +1554,6 @@ |
1553 | 1555 | * Prints data in html format. Escapes all unsafe characters. Adds an HTML warning in the begining. |
1554 | 1556 | */ |
1555 | 1557 | function printHTML( &$data ) { |
1556 | | - global $wgRequest; |
1557 | 1558 | ?> |
1558 | 1559 | <html> |
1559 | 1560 | <head> |