Index: trunk/extensions/DynamicPageList2/DynamicPageList2.php |
— | — | @@ -19,13 +19,13 @@ |
20 | 20 | * @author w:de:Benutzer:Unendlich |
21 | 21 | * @author m:User:Dangerman <cyril.dangerville@gmail.com> |
22 | 22 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License |
23 | | - * @version 0.6.4 |
| 23 | + * @version 0.7.0 |
24 | 24 | */ |
25 | 25 | |
26 | 26 | /* |
27 | 27 | * Current version |
28 | 28 | */ |
29 | | -define('DPL2_VERSION', '0.6.4'); |
| 29 | +define('DPL2_VERSION', '0.7.0'); |
30 | 30 | |
31 | 31 | /** |
32 | 32 | * Register the extension with MediaWiki |
— | — | @@ -61,6 +61,11 @@ |
62 | 62 | 'addpagetoucheddate' => array('default' => 'false', 'false', 'true'), |
63 | 63 | 'adduser' => array('default' => 'false', 'false', 'true'), |
64 | 64 | /** |
| 65 | + * category= Cat11 | Cat12 | ... |
| 66 | + * category = Cat21 | Cat22 | ... |
| 67 | + * ... |
| 68 | + * [Special value] catX='' (empty string without quotes) means pseudo-categoy of Uncategorized pages |
| 69 | + * Means pages have to be in category (Cat11 OR (inclusive) Cat2 OR...) AND (Cat21 OR Cat22 OR...) AND... |
65 | 70 | * @todo define 'category' options (retrieve list of categories from 'categorylinks' table?) |
66 | 71 | */ |
67 | 72 | 'category' => NULL, |
— | — | @@ -120,11 +125,27 @@ |
121 | 126 | * 'none' mode is implemented as a specific submode of 'inline' with <BR/> as inline text |
122 | 127 | */ |
123 | 128 | 'mode' => array('default' => 'unordered', 'category', 'inline', 'none', 'ordered', 'unordered'), |
| 129 | + /** |
| 130 | + * namespace= Ns1 | Ns2 | ... |
| 131 | + * [Special value] NsX='' (empty string without quotes) means Main namespace |
| 132 | + * Means pages have to be in namespace Ns1 OR Ns2 OR... |
| 133 | + */ |
124 | 134 | 'namespace' => NULL, |
125 | 135 | /** |
| 136 | + * notcategory= Cat1 |
| 137 | + * notcategory = Cat2 |
| 138 | + * ... |
| 139 | + * Means pages can be NEITHER in category Cat1 NOR in Cat2 NOR... |
126 | 140 | * @todo define 'notcategory' options (retrieve list of categories from 'categorylinks' table?) |
127 | 141 | */ |
128 | 142 | 'notcategory' => NULL, |
| 143 | + /** |
| 144 | + * notnamespace= Ns1 |
| 145 | + * notnamespace= Ns2 |
| 146 | + * ... |
| 147 | + * [Special value] NsX='' (empty string without quotes) means Main namespace |
| 148 | + * Means pages have to be NEITHER in namespace Ns1 NOR Ns2 NOR... |
| 149 | + */ |
129 | 150 | 'notnamespace' => NULL, |
130 | 151 | 'order' => array('default' => 'ascending', 'ascending', 'descending'), |
131 | 152 | /** |
— | — | @@ -166,6 +187,7 @@ |
167 | 188 | 'DPL2_ERR_CATDATEBUTMORETHAN1CAT' => 1, |
168 | 189 | 'DPL2_ERR_MORETHAN1TYPEOFDATE' => 1, |
169 | 190 | 'DPL2_ERR_WRONGORDERMETHOD' => 1, |
| 191 | + 'DPL2_ERR_NOCLVIEW' => 1, |
170 | 192 | // WARNINGS |
171 | 193 | 'DPL2_WARN_UNKNOWNPARAM' => 2, |
172 | 194 | 'DPL2_WARN_WRONGPARAM' => 2, |
— | — | @@ -203,7 +225,7 @@ |
204 | 226 | |
205 | 227 | error_reporting(E_ALL); |
206 | 228 | |
207 | | - global $wgDPL2Options, $wgUser, $wgContLang, $wgDPL2MaxCategoryCount, $wgDPL2MinCategoryCount, $wgDPL2MaxResultCount, $wgDPL2AllowUnlimitedCategories, $wgDPL2AllowUnlimitedResults; |
| 229 | + global $wgUser, $wgContLang, $wgDPL2Options, $wgDPL2MaxCategoryCount, $wgDPL2MinCategoryCount, $wgDPL2MaxResultCount, $wgDPL2AllowUnlimitedCategories, $wgDPL2AllowUnlimitedResults; |
208 | 230 | |
209 | 231 | // INVALIDATE CACHE |
210 | 232 | $parser->disableCache(); |
— | — | @@ -255,7 +277,7 @@ |
256 | 278 | |
257 | 279 | foreach($aParams as $iParam => $sParam) { |
258 | 280 | |
259 | | - $aParam = explode("=", $sParam, 2); |
| 281 | + $aParam = explode('=', $sParam, 2); |
260 | 282 | if( count( $aParam ) < 2 ) |
261 | 283 | continue; |
262 | 284 | $sType = trim($aParam[0]); |
— | — | @@ -263,26 +285,31 @@ |
264 | 286 | |
265 | 287 | switch ($sType) { |
266 | 288 | case 'category': |
267 | | - $aCategories = array(); // Categories in one line separated by '|' are linked using 'OR' |
268 | | - $aParams = explode("|", $sArg); |
| 289 | + // Init array of categories to include |
| 290 | + $aCategories = array(); |
| 291 | + $aParams = explode('|', $sArg); |
269 | 292 | foreach($aParams as $sParam) { |
270 | 293 | $sParam=trim($sParam); |
271 | | - $title = Title::newFromText($localParser->transformMsg($sParam, $pOptions)); |
272 | | - if( $title != NULL ) |
273 | | - $aCategories[] = $title; |
| 294 | + if($sParam == '') // include uncategorized pages (special value: empty string) |
| 295 | + $aCategories[] = ''; |
| 296 | + else { |
| 297 | + $title = Title::newFromText($localParser->transformMsg($sParam, $pOptions)); |
| 298 | + if( $title != NULL ) |
| 299 | + $aCategories[] = $title->getDbKey(); |
| 300 | + } |
274 | 301 | } |
275 | 302 | if( !empty($aCategories) ) |
276 | | - $aIncludeCategories[] = $aCategories; |
| 303 | + $aIncludeCategories[] = $aCategories; |
277 | 304 | break; |
278 | 305 | |
279 | 306 | case 'notcategory': |
280 | 307 | $title = Title::newFromText($localParser->transformMsg($sArg, $pOptions)); |
281 | 308 | if( $title != NULL ) |
282 | | - $aExcludeCategories[] = $title; |
| 309 | + $aExcludeCategories[] = $title->getDbKey(); |
283 | 310 | break; |
284 | 311 | |
285 | 312 | case 'namespace': |
286 | | - $aParams = explode("|", $sArg); |
| 313 | + $aParams = explode('|', $sArg); |
287 | 314 | foreach($aParams as $sParam) { |
288 | 315 | $sParam=trim($sParam); |
289 | 316 | // Reject pseudo-namespaces (negative indices): Media (-2) and Special (-1). |
— | — | @@ -531,10 +558,10 @@ |
532 | 559 | $sSqlCl_to = ''; |
533 | 560 | $sSqlCats = ''; |
534 | 561 | $sSqlCl_timestamp = ''; |
535 | | - $sSqlCl1Table = ''; |
536 | | - $sSqlCond_page_cl1 = ''; |
537 | | - $sSqlCl2Table = ''; |
538 | | - $sSqlCond_page_cl2 = ''; |
| 562 | + $sSqlClHeadTable = ''; |
| 563 | + $sSqlCond_page_cl_head = ''; |
| 564 | + $sSqlClTableForGC = ''; |
| 565 | + $sSqlCond_page_cl_gc = ''; |
539 | 566 | $sRevisionTable = $dbr->tableName( 'revision' ); |
540 | 567 | $sSqlRevision = ''; |
541 | 568 | $sSqlRev_timestamp = ''; |
— | — | @@ -544,22 +571,22 @@ |
545 | 572 | foreach($aOrderMethods as $sOrderMethod) { |
546 | 573 | switch ($sOrderMethod) { |
547 | 574 | case 'category': |
548 | | - $sSqlCl_to = 'cl1.cl_to, '; |
549 | | - $sSqlCl1Table = $sCategorylinksTable . ' AS cl1'; |
550 | | - $sSqlCond_page_cl1 = 'page_id=cl1.cl_from'; |
| 575 | + $sSqlCl_to = 'cl_head.cl_to, '; // Gives category headings in the result |
| 576 | + $sSqlClHeadTable = $sCategorylinksTable . ' AS cl_head'; |
| 577 | + $sSqlCond_page_cl_head = 'page_id=cl_head.cl_from'; |
551 | 578 | break; |
552 | 579 | case 'firstedit': |
553 | | - $sSqlRevision = $sRevisionTable. ', '; |
| 580 | + $sSqlRevision = $sRevisionTable . ', '; |
554 | 581 | $sSqlRev_timestamp = ', min(rev_timestamp) AS rev_timestamp'; |
555 | 582 | $sSqlCond_page_rev = ' AND page_id=rev_page'; |
556 | 583 | break; |
557 | 584 | case 'lastedit': |
558 | | - $sSqlRevision = $sRevisionTable. ', '; |
| 585 | + $sSqlRevision = $sRevisionTable . ', '; |
559 | 586 | $sSqlRev_timestamp = ', max(rev_timestamp) AS rev_timestamp'; |
560 | 587 | $sSqlCond_page_rev = ' AND page_id=rev_page'; |
561 | 588 | break; |
562 | 589 | case 'user': |
563 | | - $sSqlRevision = $sRevisionTable. ', '; |
| 590 | + $sSqlRevision = $sRevisionTable . ', '; |
564 | 591 | break; |
565 | 592 | } |
566 | 593 | } |
— | — | @@ -567,64 +594,90 @@ |
568 | 595 | if ($bAddFirstCategoryDate) |
569 | 596 | //format cl_timestamp field (type timestamp) to string in same format as rev_timestamp field |
570 | 597 | //to make it compatible with $wgLang->date() function used in function DPL2OutputListStyle() to show "firstcategorydate" |
571 | | - $sSqlCl_timestamp = ", DATE_FORMAT( c1.cl_timestamp, '%Y%m%d%H%i%s' ) AS cl_timestamp"; |
| 598 | + $sSqlCl_timestamp = ", DATE_FORMAT(c1.cl_timestamp, '%Y%m%d%H%i%s') AS cl_timestamp"; |
572 | 599 | if ($bAddPageTouchedDate) |
573 | 600 | $sSqlPage_touched = ', page_touched'; |
574 | 601 | if ($bAddUser) |
575 | 602 | $sSqlRev_user = ', rev_user, rev_user_text'; |
576 | 603 | if ($bAddCategories) { |
577 | | - $sSqlCats = ", GROUP_CONCAT(DISTINCT cl2.cl_to ORDER BY cl2.cl_to ASC SEPARATOR ' | ') AS cats"; |
578 | | - $sSqlCl2Table = "$sCategorylinksTable AS cl2"; |
579 | | - $sSqlCond_page_cl2 = 'page_id=cl2.cl_from'; |
| 604 | + $sSqlCats = ", GROUP_CONCAT(DISTINCT cl_gc.cl_to ORDER BY cl_gc.cl_to ASC SEPARATOR ' | ') AS cats"; // Gives list of all categories linked from each article, if any. |
| 605 | + $sSqlClTableForGC = $sCategorylinksTable . ' AS cl_gc'; // Categorylinks table used by the Group Concat (GC) function above |
| 606 | + $sSqlCond_page_cl_gc = 'page_id=cl_gc.cl_from'; |
580 | 607 | } |
581 | 608 | |
582 | 609 | // SELECT ... FROM |
583 | | - $sSqlSelectFrom = "SELECT DISTINCT " . $sSqlCl_to . "page_namespace, page_title" . $sSqlPage_touched . $sSqlRev_timestamp . $sSqlRev_user . $sSqlCats . $sSqlCl_timestamp . " FROM " . $sSqlRevision . $sPageTable; |
| 610 | + $sSqlSelectFrom = 'SELECT DISTINCT ' . $sSqlCl_to . 'page_namespace, page_title' . $sSqlPage_touched . $sSqlRev_timestamp . $sSqlRev_user . $sSqlCats . $sSqlCl_timestamp . ' FROM ' . $sSqlRevision . $sPageTable; |
584 | 611 | |
585 | | - // JOIN ... |
586 | | - if($bAddCategories || $aOrderMethods[0] == 'category') { |
587 | | - $b2tables = ($sSqlCl1Table != '') && ($sSqlCl2Table != ''); |
588 | | - $sSqlSelectFrom .= ' LEFT JOIN (' .$sSqlCl1Table . ($b2tables ? ', ' : '') . $sSqlCl2Table.') ON ('. $sSqlCond_page_cl1 . ($b2tables ? ' AND ' : '') . $sSqlCond_page_cl2 .')'; |
| 612 | + // JOIN ... |
| 613 | + if($sSqlClHeadTable != '' || $sSqlClTableForGC != '') { |
| 614 | + $b2tables = ($sSqlClHeadTable != '') && ($sSqlClTableForGC != ''); |
| 615 | + $sSqlSelectFrom .= ' LEFT OUTER JOIN (' . $sSqlClHeadTable . ($b2tables ? ', ' : '') . $sSqlClTableForGC . ') ON (' . $sSqlCond_page_cl_head . ($b2tables ? ' AND ' : '') . $sSqlCond_page_cl_gc . ')'; |
589 | 616 | } |
590 | | - $iCurrentTableNumber = 0; |
| 617 | + |
| 618 | + // Include categories... |
| 619 | + // If we include the Uncategorized, we use the 'dpl_clview': VIEW of the categorylinks table where we have cl_to='' (empty string) for all uncategorized pages. This VIEW must have been created by the administrator of the mediawiki DB at installation. See the documentation. |
| 620 | + $sDplClView = $dbr->tableName( 'dpl_clview' ); |
| 621 | + // If the view is not there, we can't perform logical operations on the Uncategorized. |
| 622 | + $dpl_clview_exists = false; |
| 623 | + $res = $dbr->query( "SHOW TABLE STATUS LIKE '" . trim($sDplClView, '`') . "'" ); |
| 624 | + if ($dbr->numRows( $res ) != 0) { |
| 625 | + $dbr->freeResult($res); |
| 626 | + $dpl_clview_exists = true; |
| 627 | + } |
| 628 | + |
| 629 | + $iClTable = 0; |
591 | 630 | for ($i = 0; $i < $iIncludeCatCount; $i++) { |
592 | | - $sSqlSelectFrom .= " INNER JOIN $sCategorylinksTable AS c" . ($iCurrentTableNumber+1); |
593 | | - $sSqlSelectFrom .= ' ON page_id = c' . ($iCurrentTableNumber+1) . '.cl_from'; |
594 | | - $sSqlSelectFrom .= ' AND (c' . ($iCurrentTableNumber+1) . '.cl_to=' . $dbr->addQuotes( $aIncludeCategories[$i][0]->getDbKey() ); |
| 631 | + $sSqlSelectFrom .= ' INNER JOIN '; |
| 632 | + // If we want the Uncategorized... |
| 633 | + if( in_array('', $aIncludeCategories[$i]) ) |
| 634 | + if($dpl_clview_exists) |
| 635 | + $sSqlSelectFrom .= $sDplClView; |
| 636 | + else { |
| 637 | + $sSqlCreate_dpl_clview = 'CREATE VIEW ' . $sDplClView . " AS SELECT COALESCE(cl_from, page_id) AS cl_from, COALESCE(cl_to, '') AS cl_to FROM " . $sPageTable . ' LEFT OUTER JOIN ' . $sCategorylinksTable . ' ON page_id=cl_from'; |
| 638 | + $output .= $logger->msg(DPL2_ERR_NOCLVIEW, $sDplClView, $sSqlCreate_dpl_clview); |
| 639 | + return $output; |
| 640 | + } |
| 641 | + else |
| 642 | + $sSqlSelectFrom .= $sCategorylinksTable; |
| 643 | + $sSqlSelectFrom .= ' AS cl' . $iClTable . |
| 644 | + ' ON page_id=cl' . $iClTable . '.cl_from' . |
| 645 | + ' AND (cl' . $iClTable . '.cl_to=' . $dbr->addQuotes($aIncludeCategories[$i][0]); |
595 | 646 | for ($j = 1; $j < count($aIncludeCategories[$i]); $j++) |
596 | | - $sSqlSelectFrom .= ' OR c' . ($iCurrentTableNumber+1) . '.cl_to=' . $dbr->addQuotes( $aIncludeCategories[$i][$j]->getDbKey() ); |
| 647 | + $sSqlSelectFrom .= ' OR cl' . $iClTable . '.cl_to=' . $dbr->addQuotes($aIncludeCategories[$i][$j]); |
597 | 648 | $sSqlSelectFrom .= ') '; |
598 | | - $iCurrentTableNumber++; |
| 649 | + $iClTable++; |
599 | 650 | } |
| 651 | + |
| 652 | + // Exclude categories... and start the WHERE clause on category fields for exclusion of categories |
600 | 653 | $sSqlWhere = ' WHERE 1=1 '; |
601 | 654 | for ($i = 0; $i < $iExcludeCatCount; $i++) { |
602 | | - $sSqlSelectFrom .= " LEFT OUTER JOIN $sCategorylinksTable AS c" . ($iCurrentTableNumber+1); |
603 | | - $sSqlSelectFrom .= ' ON page_id = c' . ($iCurrentTableNumber+1) . '.cl_from'; |
604 | | - $sSqlSelectFrom .= ' AND c' . ($iCurrentTableNumber+1) . '.cl_to='. |
605 | | - $dbr->addQuotes( $aExcludeCategories[$i]->getDbKey() ); |
606 | | - $sSqlWhere .= ' AND c' . ($iCurrentTableNumber+1) . '.cl_to IS NULL'; |
607 | | - $iCurrentTableNumber++; |
| 655 | + $sSqlSelectFrom .= |
| 656 | + ' LEFT OUTER JOIN ' . $sCategorylinksTable . ' AS cl' . $iClTable . |
| 657 | + ' ON page_id=cl' . $iClTable . '.cl_from' . |
| 658 | + ' AND cl' . $iClTable . '.cl_to=' . $dbr->addQuotes($aExcludeCategories[$i]); |
| 659 | + $sSqlWhere .= ' AND cl' . $iClTable . '.cl_to IS NULL'; |
| 660 | + $iClTable++; |
608 | 661 | } |
609 | 662 | |
610 | | - // WHERE ... |
| 663 | + // WHERE... (actually finish the WHERE clause we may have started if we excluded categories - see above) |
611 | 664 | // Namespace IS ... |
612 | 665 | if ( !empty($aNamespaces)) { |
613 | | - $sSqlWhere .= ' AND (page_namespace IN (' . implode (',', $aNamespaces) . '))'; |
| 666 | + $sSqlWhere .= ' AND page_namespace IN (' . implode (', ', $aNamespaces) . ')'; |
614 | 667 | } |
615 | 668 | // Namespace IS NOT ... |
616 | 669 | if ( !empty($aExcludeNamespaces)) { |
617 | | - $sSqlWhere .= ' AND (page_namespace NOT IN (' . implode (',', $aExcludeNamespaces) . '))'; |
| 670 | + $sSqlWhere .= ' AND page_namespace NOT IN (' . implode (', ', $aExcludeNamespaces) . ')'; |
618 | 671 | } |
619 | 672 | // rev_minor_edit IS |
620 | 673 | if( isset($sMinorEdits) && $sMinorEdits == 'exclude' ) |
621 | | - $sSqlWhere .= ' AND rev_minor_edit = 0'; |
| 674 | + $sSqlWhere .= ' AND rev_minor_edit=0'; |
622 | 675 | // page_is_redirect IS ... |
623 | 676 | switch ($sRedirects) { |
624 | 677 | case 'only': |
625 | | - $sSqlWhere .= ' AND page_is_redirect = 1'; |
| 678 | + $sSqlWhere .= ' AND page_is_redirect=1'; |
626 | 679 | break; |
627 | 680 | case 'exclude': |
628 | | - $sSqlWhere .= ' AND page_is_redirect = 0'; |
| 681 | + $sSqlWhere .= ' AND page_is_redirect=0'; |
629 | 682 | break; |
630 | 683 | } |
631 | 684 | |
— | — | @@ -641,10 +694,10 @@ |
642 | 695 | $sSqlWhere .= ', '; |
643 | 696 | switch ($sOrderMethod) { |
644 | 697 | case 'category': |
645 | | - $sSqlWhere .= 'cl1.cl_to'; |
| 698 | + $sSqlWhere .= 'cl_head.cl_to'; |
646 | 699 | break; |
647 | 700 | case 'categoryadd': |
648 | | - $sSqlWhere .= 'c1.cl_timestamp'; |
| 701 | + $sSqlWhere .= 'cl0.cl_timestamp'; |
649 | 702 | break; |
650 | 703 | case 'firstedit': |
651 | 704 | case 'lastedit': |