r71781 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r71780‎ | r71781 | r71782 >
Date:08:04, 27 August 2010
Author:questpc
Status:deferred
Tags:
Comment:
v0.2.1 proper commit (previous was broken)
Modified paths:
  • /trunk/extensions/CategoryBrowser/CategoryBrowserBasic.php (replaced) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserCtrl.php (added) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserMain.php (deleted) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserModel.php (added) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserView.php (added) (history)

Diff [purge]

Index: trunk/extensions/CategoryBrowser/CategoryBrowserMain.php
@@ -1,891 +0,0 @@
2 -<?php
3 -/**
4 - * ***** BEGIN LICENSE BLOCK *****
5 - * This file is part of CategoryBrowser.
6 - *
7 - * CategoryBrowser is free software; you can redistribute it and/or modify
8 - * it under the terms of the GNU General Public License as published by
9 - * the Free Software Foundation; either version 2 of the License, or
10 - * (at your option) any later version.
11 - *
12 - * CategoryBrowser is distributed in the hope that it will be useful,
13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 - * GNU General Public License for more details.
16 - *
17 - * You should have received a copy of the GNU General Public License
18 - * along with CategoryBrowser; if not, write to the Free Software
19 - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 - *
21 - * ***** END LICENSE BLOCK *****
22 - *
23 - * CategoryBrowser is an AJAX-enabled category filter and browser for MediaWiki.
24 - *
25 - * To activate this extension :
26 - * * Create a new directory named CategoryBrowser into the directory "extensions" of MediaWiki.
27 - * * Place the files from the extension archive there.
28 - * * Add this line at the end of your LocalSettings.php file :
29 - * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
30 - *
31 - * @version 0.2.0
32 - * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
33 - * @author Dmitriy Sintsov <questpc@rambler.ru>
34 - * @addtogroup Extensions
35 - */
36 -
37 -if ( !defined( 'MEDIAWIKI' ) ) {
38 - die( "This file is a part of MediaWiki extension.\n" );
39 -}
40 -
41 -abstract class CB_AbstractPager {
42 -
43 - var $db;
44 -
45 - /* pager position (actual offset)
46 - * 0 means pager has no previous elements
47 - * -1 means pager has no elements at all
48 - */
49 - var $offset = -1;
50 - /* provided "source" offset */
51 - var $query_offset;
52 - /* indicates, whether the pager has further elements */
53 - var $hasMoreEntries = false;
54 - /* maximal number of entries per page (actual number of entries on page) */
55 - var $limit = 0;
56 - /* provided "source" limit */
57 - var $query_limit;
58 - /* array of current entries */
59 - var $entries;
60 -
61 - /*
62 - * abstract query (doesn't instantinate)
63 - * @param offset - suggested SQL offset
64 - * @param limit - suggested SQL limit
65 - */
66 - function __construct( $offset, $limit ) {
67 - $this->db = & wfGetDB( DB_SLAVE );
68 - $this->query_limit = intval( $limit );
69 - $this->query_offset = intval( $offset );
70 - }
71 -
72 - /*
73 - *
74 - * initializes hasMoreEntries and array of entries from DB result
75 - */
76 - function setEntries( &$db_result ) {
77 - $this->hasMoreEntries = false;
78 - $this->entries = array();
79 - $count = $this->db->numRows( $db_result );
80 - if ( $count < 1 ) { return; }
81 - $this->offset = $this->query_offset;
82 - $this->limit = $this->query_limit;
83 - if ( $this->hasMoreEntries = $count > $this->limit ) {
84 - // do not include "overflow" entry, it belongs to the next page
85 - $count--;
86 - }
87 - // do not include last row (which was loaded only to set hasMoreEntries)
88 - for ( $i = 0; $i < $count; $i++ ) {
89 - $row = $this->db->fetchObject( $db_result );
90 - $this->entries[] = $row;
91 - }
92 - }
93 -
94 - // returns previous SQL select offset
95 - function getPrevOffset() {
96 - $prev_offset = $this->offset - $this->limit;
97 - return ( ( $prev_offset >= 0 ) ? $prev_offset : 0 );
98 - }
99 -
100 - // returns next SQL select offset
101 - function getNextOffset() {
102 - return ( ( $this->hasMoreEntries ) ? $this->offset + $this->limit : 0 );
103 - }
104 -
105 - /*
106 - * suggests, what kind of view should be used for instance of the model
107 - * @return name of view method
108 - * otherwise throws an error
109 - * warning: will fail, when called before calling $this->getCurrentRows() !
110 - * warning: $this->limit is not set properly before calling $this->getCurrentRows() !
111 - */
112 - function getListType() {
113 - // it is not enough to check $this->entries[0],
114 - // because some broken tables might have just some (not all) cat_title = NULL or page_title = NULL
115 - foreach ( $this->entries as &$entry ) {
116 - if ( isset( $entry->page_namespace ) && $entry->page_namespace == NS_FILE ) { return 'generateFilesList'; }
117 - if ( isset( $entry->page_title ) ) { return 'generatePagesList'; }
118 - if ( isset( $entry->cat_title ) ) { return 'generateCatList'; }
119 - if ( isset( $entry->cl_sortkey ) ) { return 'generateCatList'; }
120 - }
121 - throw new MWException( 'Entries not initialized in ' . __METHOD__ );
122 - }
123 -
124 -} /* end of CB_AbstractPager class */
125 -
126 -/*
127 - * subentries (subcategories, pages, files) pager
128 - * TODO: gracefully set offset = 0 when too large offset was given
129 - */
130 -class CB_SubPager extends CB_AbstractPager {
131 -
132 - var $page_table;
133 - var $category_table;
134 - var $categorylinks_table;
135 - // database ID of parent category
136 - var $parentCatId;
137 - // database fields to query
138 - var $select_fields;
139 - // namespace SQL condition (WHERE part)
140 - var $ns_cond;
141 - // javascript function used to navigate between the pages
142 - var $js_nav_func;
143 -
144 - /*
145 - * creates subcategory list pager
146 - *
147 - * @param $parentCatId id of parent category
148 - * @param $offset SQL offset
149 - * @param $limit SQL limit
150 - *
151 - * TODO: query count of parentCatId subcategories/pages/files in category table for progress / percentage display
152 - */
153 - function __construct( $parentCatId, $offset, $limit, $js_nav_func, $select_fields = '*', $ns_cond = '' ) {
154 - parent::__construct( $offset, $limit );
155 - $this->page_table = $this->db->tableName( 'page' );
156 - $this->category_table = $this->db->tableName( 'category' );
157 - $this->categorylinks_table = $this->db->tableName( 'categorylinks' );
158 - $this->parentCatId = $parentCatId;
159 - $this->select_fields = $select_fields;
160 - $this->ns_cond = $ns_cond;
161 - $this->js_nav_func = $js_nav_func;
162 - }
163 -
164 - /*
165 - * set offset, limit, hasMoreEntries and entries
166 - * @param $offset SQL offset
167 - * @param $limit SQL limit
168 - */
169 - function getCurrentRows() {
170 - /* TODO: change the query to more optimal one (no subselects)
171 - * SELECT cl_sortkey,cat_id,cat_title,cat_subcats,cat_pages,cat_files FROM `wiki_page` INNER JOIN `wiki_categorylinks` FORCE INDEX (cl_sortkey) ON (cl_from = page_id) LEFT JOIN `wiki_category` ON (cat_title = page_title AND page_namespace = 14) WHERE cl_to IN (SELECT cat_title FROM wiki_category WHERE cat_id = 44) AND page_namespace = 14 ORDER BY cl_sortkey LIMIT 0,11
172 - */
173 - $query_string =
174 - "SELECT {$this->select_fields} " .
175 - "FROM {$this->page_table} " .
176 - "INNER JOIN {$this->categorylinks_table} FORCE INDEX (cl_sortkey) ON cl_from = page_id " .
177 - "LEFT JOIN {$this->category_table} ON cat_title = page_title AND page_namespace = " . NS_CATEGORY . " " .
178 - "WHERE cl_to IN (" .
179 - "SELECT cat_title " .
180 - "FROM {$this->category_table} " .
181 - "WHERE cat_id = " . $this->db->addQuotes( $this->parentCatId ) .
182 - ") " . ( ( $this->ns_cond == '' ) ? '' : "AND {$this->ns_cond} " ) .
183 - "ORDER BY cl_sortkey ";
184 - $res = $this->db->query( $query_string . "LIMIT {$this->query_offset}," . ( $this->query_limit + 1 ), __METHOD__ );
185 - $this->setEntries( $res );
186 - }
187 -
188 - // returns JS function call used to navigate to the previous page of this pager
189 - function getPrevAjaxLink() {
190 - $result = (object) array(
191 - "call" => "return CategoryBrowser.{$this->js_nav_func}(this," . $this->parentCatId . "," . $this->getPrevOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
192 - "placeholders" => false
193 - );
194 - return $result;
195 - }
196 -
197 - // returns JS function call used to navigate to the next page of this pager
198 - function getNextAjaxLink() {
199 - $result = (object) array(
200 - "call" => "return CategoryBrowser.{$this->js_nav_func}(this," . $this->parentCatId . ',' . $this->getNextOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
201 - "placeholders" => false
202 - );
203 - return $result;
204 - }
205 -
206 -} /* end of CB_SubPager class */
207 -
208 -/*
209 - * creates a root category pager
210 - * TODO: gracefully set offset = 0 when too large offset was given
211 - * TODO: with $conds == '' categories aren't always sorted alphabetically
212 - */
213 -class CB_RootPager extends CB_AbstractPager {
214 -
215 -
216 - /* string paging conds aka filter (WHERE statement) */
217 - var $conds;
218 - /* _optional_ instance of CB_SqlCond object used to construct this pager
219 - * (in case it's been provided in constructor call)
220 - */
221 - var $sqlCond = null;
222 -
223 - // category name filter (LIKE)
224 - var $nameFilter = '';
225 - // category name filter case-insensetive flag (when true, tries to use insensetive LIKE COLLATE)
226 - var $nameFilterCI = false;
227 -
228 - /*
229 - * formal constructor
230 - * real instantination should be performed by calling public static methods below
231 - */
232 - function __construct( $offset, $limit ) {
233 - parent::__construct( $offset, $limit );
234 - }
235 -
236 - /*
237 - * @param $conds - instanceof CB_SqlCond (parentized condition generator)
238 - * @param $offset - SQL OFFSET
239 - * @param $limit - SQL LIMIT
240 - */
241 - public static function newFromSqlCond( CB_SqlCond $conds, $offset = 0, $limit = CB_PAGING_ROWS ) {
242 - $rp = new CB_RootPager( $offset, $limit );
243 - $rp->conds = $conds->getCond();
244 - $rp->sqlCond = &$conds;
245 - return $rp;
246 - }
247 -
248 - /*
249 - * @param $tokens - array of infix ops of sql condition
250 - * @param $offset - SQL OFFSET
251 - * @param $limit - SQL LIMIT
252 - */
253 - public static function newFromInfixTokens( $tokens, $offset = 0, $limit = CB_PAGING_ROWS ) {
254 - if ( !is_array( $tokens ) ) {
255 - return null;
256 - }
257 - try {
258 - $sqlCond = CB_SqlCond::newFromInfixTokens( $tokens );
259 - } catch ( MWException $ex ) {
260 - return null;
261 - }
262 - return self::newFromSqlCond( $sqlCond, $offset, $limit );
263 - }
264 -
265 - /*
266 - * create root pager from the largest non-empty category range
267 - * @param $ranges - array of "complete" token queues (range)
268 - * (every range is an stdobject of decoded infix queue and encoded reverse polish queue)
269 - */
270 - public static function newFromCategoryRange( $ranges ) {
271 - $rp = null;
272 - foreach ( $ranges as &$range ) {
273 - $rp = CB_RootPager::newFromInfixTokens( $range->infix_decoded );
274 - if ( is_object( $rp ) && $rp->offset != -1 ) {
275 - break;
276 - }
277 - }
278 - return $rp;
279 - }
280 -
281 - /*
282 - * filter catetories by names
283 - * @param $cat_name_filter - string category name begins from
284 - * @param $cat_name_filter_ci - boolean, true attempts to use case-insensetive search, when available
285 - */
286 - function setNameFilter( $cat_name_filter, $cat_name_filter_ci ) {
287 - $this->nameFilter = ltrim( $cat_name_filter );
288 - $this->nameFilterCI = $cat_name_filter_ci;
289 - }
290 -
291 - /*
292 - * performs range query and stores the results
293 - */
294 - function getCurrentRows() {
295 - $conds = trim( $this->conds );
296 - // use name filter, when available
297 - if ( $this->nameFilter != '' ) {
298 - if ( $conds != '' ) {
299 - $conds = "( $conds ) AND ";
300 - }
301 - $conds .= 'cat_title LIKE ' . $this->db->addQuotes( $this->nameFilter . '%' );
302 - if ( $this->nameFilterCI && CB_Setup::$cat_title_CI != '' ) {
303 - // case insensetive search is active
304 - $conds .= ' COLLATE ' . $this->db->addQuotes( CB_Setup::$cat_title_CI );
305 - }
306 - }
307 - $options = array( 'OFFSET' => $this->query_offset, 'ORDER BY' => 'cat_title', 'LIMIT' => $this->query_limit + 1 );
308 - $res = $this->db->select( 'category',
309 - array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ),
310 - $conds,
311 - __METHOD__,
312 - $options
313 - );
314 - /* set actual offset, limit, hasMoreEntries and entries */
315 - $this->setEntries( $res );
316 - }
317 -
318 - // returns JS function call used to navigate to the previous page of this pager
319 - function getPrevAjaxLink() {
320 - $result = (object) array(
321 - "call" => 'return CategoryBrowser.rootCats(\'' . Xml::escapeJsString( $this->sqlCond->getEncodedQueue( false ) ) . '\',' . $this->getPrevOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
322 - "placeholders" => true
323 - );
324 - return $result;
325 - }
326 -
327 - // returns JS function call used to navigate to the next page of this pager
328 - function getNextAjaxLink() {
329 - $result = (object) array(
330 - "call" => 'return CategoryBrowser.rootCats(\'' . Xml::escapeJsString( $this->sqlCond->getEncodedQueue( false ) ) . '\',' . $this->getNextOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
331 - "placeholders" => false
332 - );
333 - return $result;
334 - }
335 -
336 -} /* end of CB_RootPager class */
337 -
338 -/*
339 - * browsing class - both for special page and AJAX calls
340 - */
341 -class CategoryBrowser {
342 -
343 - function __construct() {
344 - CB_Setup::initUser();
345 - }
346 -
347 - /*
348 - * include stylesheets and scripts; set javascript variables
349 - * @param $outputPage - an instance of OutputPage
350 - * @param $isRTL - whether the current language is RTL
351 - * currently set: cookie prefix;
352 - * localAllOp, local1opTemplate, local2opTemplate, localDbFields, localBrackets, localBoolOps, localCmpOps
353 - */
354 - static function headScripts( &$outputPage, $isRTL ) {
355 - global $wgJsMimeType;
356 - $outputPage->addLink(
357 - array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => CB_Setup::$ScriptPath . '/category_browser.css?' . CB_Setup::$version )
358 - );
359 - if ( $isRTL ) {
360 - $outputPage->addLink(
361 - array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => CB_Setup::$ScriptPath . '/category_browser_rtl.css?' . CB_Setup::$version )
362 - );
363 - }
364 - $outputPage->addScript(
365 - '<script type="' . $wgJsMimeType . '" src="' . CB_Setup::$ScriptPath . '/category_browser.js?' . CB_Setup::$version . '"></script>
366 - <script type="' . $wgJsMimeType . '">
367 - CB_lib.setCookiePrefix("' . CB_Setup::getJsCookiePrefix() . '");
368 - CB_ConditionEditor.setLocalNames( ' .
369 - CategoryBrowser::getJsObject( 'cbLocalMessages', 'apply_button', 'all_op', 'op1_template', 'op2_template', 'ie6_warning' ) . ", \n\t\t\t" .
370 - CategoryBrowser::getJsObject( 'cbLocalEditHints', 'left', 'right', 'remove', 'copy', 'append', 'clear', 'paste', 'paste_right' ) . ", \n\t\t\t" .
371 - CategoryBrowser::getJsObject( 'cbLocalDbFields', 's', 'p', 'f' ) . ", \n\t\t\t" .
372 - CategoryBrowser::getJsObject( 'cbLocalOps', 'lbracket', 'rbracket' ) . ", \n\t\t\t" .
373 - CategoryBrowser::getJsObject( 'cbLocalOps', 'or', 'and' ) . ", \n\t\t\t" .
374 - CategoryBrowser::getJsObject( 'cbLocalOps', 'le', 'ge', 'eq' ) .
375 - ' );</script>' . "\n" );
376 - }
377 -
378 - static function getJsObject( $method_name ) {
379 - $args = func_get_args();
380 - array_shift( $args ); // remove $method_name from $args
381 - $result = '{ ';
382 - $firstElem = true;
383 - foreach ( $args as &$arg ) {
384 - if ( $firstElem ) {
385 - $firstElem = false;
386 - } else {
387 - $result .= ', ';
388 - }
389 - $result .= $arg . ': "' . Xml::escapeJsString( call_user_func( array( 'self', $method_name ), $arg ) ) . '"';
390 - }
391 - $result .= ' }';
392 - return $result;
393 - }
394 -
395 - /*
396 - * currently passed to Javascript:
397 - * localMessages, localDbFields, localBrackets, localBoolOps, localCmpOps
398 - */
399 - /*
400 - * getJsObject callback
401 - */
402 - static private function cbLocalMessages( $arg ) {
403 - return wfMsg( "cb_${arg}" );
404 - }
405 -
406 - static private function cbLocalEditHints( $arg ) {
407 - return wfMsg( "cb_edit_${arg}_hint" );
408 - }
409 -
410 - /*
411 - * getJsObject callback
412 - */
413 - static private function cbLocalOps( $arg ) {
414 - return wfMsg( "cb_${arg}_op" );
415 - }
416 -
417 - /*
418 - * getJsObject callback
419 - */
420 - static private function cbLocalDbFields( $arg ) {
421 - return wfMsg( "cb_" . CB_SqlCond::$decoded_fields[ $arg ] );
422 - }
423 -
424 - /*
425 - * generates "complete" ranges
426 - * @param $source_ranges source ranges which contain only decoded infix queue
427 - * @return "complete" ranges which contain decoded infix queue and encoded polish queue
428 - */
429 - static function generateRanges( array &$source_ranges ) {
430 - $ranges = array();
431 - foreach ( $source_ranges as $infix_queue ) {
432 - $sqlCond = CB_SqlCond::newFromInfixTokens( $infix_queue );
433 - $ranges[] = (object) array( 'infix_decoded' => $infix_queue, 'polish_encoded' => $sqlCond->getEncodedQueue( false ) );
434 - }
435 - return $ranges;
436 - }
437 -
438 - /*
439 - * add new "complete" range to "complete" ranges list
440 - * @param $ranges "complete" ranges list (decoded infix, encoded polish)
441 - * @param $sqlCond will be added to $ranges only when no such queue already exists
442 - * @modifies $ranges
443 - */
444 - static function addRange( array &$ranges, CB_SqlCond $sqlCond ) {
445 - $encPolishQueue = $sqlCond->getEncodedQueue( false );
446 - $queueExists = false;
447 - foreach ( $ranges as &$range ) {
448 - if ( $range->polish_encoded == $encPolishQueue ) {
449 - $queueExists = true;
450 - break;
451 - }
452 - }
453 - if ( !$queueExists ) {
454 - $sqlCond->getCond(); // build infix queue array
455 - $ranges[] = (object) array( 'infix_decoded' => $sqlCond->infix_queue, 'polish_encoded' => $encPolishQueue );
456 - }
457 - }
458 -
459 - /*
460 - * generates SQL condition selector html code
461 - * @param $ranges - array of "complete" (decode infix/encoded polish) token queues
462 - * @param $rootPager - root pager currently used with this selector
463 - * @return selector html code
464 - */
465 - static function generateSelector( array &$ranges, CB_RootPager $rootPager ) {
466 - # {{{ condition form/select template
467 - $condOptList = array();
468 - // do not pass current pager's limit because it's meaningless
469 - // we need MAX (default) possible limit, not the current limit
470 - // also current limit is being calculated only during the call $pager->getCurrentRows()
471 - // TODO: implement the field to select pager's default limit
472 - $js_func_call = 'return CategoryBrowser.setExpr(this,' . CB_PAGING_ROWS . ')';
473 - // FF doesn't always fire onchange, IE doesn't always fire onmouseup
474 - $condFormTpl = array (
475 - array( '__tag' => 'noscript', 'class' => 'cb_noscript', 0 => wfMsg( 'cb_requires_javascript' ) ),
476 - array( '__tag' => 'form', '__end' => "\n",
477 - array( '__tag' => 'select', 'id' => 'cb_expr_select', 'onmouseup' => $js_func_call, 'onchange' => $js_func_call, '__end' => "\n", 0 => &$condOptList )
478 - )
479 - );
480 - # }}}
481 - $queueFound = false;
482 - $selectedEncPolishQueue = $rootPager->sqlCond->getEncodedQueue( false );
483 - foreach ( $ranges as &$range ) {
484 - $condOptList[] = self::generateOption( $range, $selectedEncPolishQueue );
485 - if ( $range->polish_encoded == $selectedEncPolishQueue ) {
486 - $queueFound = true;
487 - }
488 - }
489 - if ( !$queueFound ) {
490 - throw new MWException( 'Either the selected queue was not added to ranges list via CategoryBrowser::addRange(), or wrong ranges list passed to ' . __METHOD__ );
491 - }
492 - return CB_XML::toText( $condFormTpl );
493 - }
494 -
495 - static function generateOption( $range, $selectedValue, $nodeName = 'option' ) {
496 - # {{{ condition select's option template
497 - $condOptVal = '';
498 - $condOptName = '';
499 - $condOptInfix = '';
500 - $condOptTpl =
501 - array( '__tag' => $nodeName, 'value' => &$condOptVal, 'infixexpr' => &$condOptInfix, 0 => &$condOptName, '__end' => "\n" );
502 - # }}}
503 - $le = new CB_LocalExpr( $range->infix_decoded );
504 - $condOptVal = CB_Setup::specialchars( $range->polish_encoded );
505 - $sqlCond = CB_SqlCond::newFromEncodedPolishQueue( $range->polish_encoded );
506 - $condOptInfix = CB_Setup::specialchars( $sqlCond->getEncodedQueue( true ) );
507 - if ( $range->polish_encoded == $selectedValue ) {
508 - $condOptTpl['selected'] = null;
509 - }
510 - $condOptName = CB_Setup::entities( $le->toString() );
511 - return CB_XML::toText( $condOptTpl );
512 - }
513 -
514 - function initNavTpl() {
515 - # {{{ navigation link (prev,next) template
516 - $this->nav_link = '';
517 - if ( !isset( $this->nav_link_tpl ) ) {
518 - $this->nav_link_tpl =
519 - array( '__tag' => 'div', 'class' => 'cb_cat_container', '__end' => "\n", 0 => &$this->nav_link );
520 - }
521 - # }}}
522 - }
523 -
524 - function initAjaxLinkTpl() {
525 - # {{{ ajax link template
526 - $this->ajax_onclick = '';
527 - $this->ajax_link_text = '';
528 - if ( !isset( $this->ajax_link_tpl ) ) {
529 - $this->ajax_link_tpl =
530 - array( '__tag' => 'a', 'class' => 'cb_sublink', 'href' => '', 'onclick' => &$this->ajax_onclick, 0 => &$this->ajax_link_text );
531 - }
532 - # }}}
533 - }
534 -
535 - function initPagerStatsTpl() {
536 - $this->pager_stats = '';
537 - # {{{ pager current statistics template
538 - if ( !isset( $this->pager_stats_tpl ) ) {
539 - $this->pager_stats_tpl =
540 - array( '__tag' => 'span', 'class' => 'cb_comment', 0 => &$this->pager_stats );
541 - }
542 - # }}}
543 - }
544 -
545 - function initSortkeyTpl() {
546 - # {{{ category sortkey hint template
547 - $this->sortkey_hint = '';
548 - if ( !isset( $this->sortkey_hint_tpl ) ) {
549 - $this->sortkey_hint_tpl = array( '__tag' => 'span', 'class' => 'cb_comment', 'style' => 'padding:0em 0.1em 0em 0.1em;', 0 => &$this->sortkey_hint );
550 - }
551 - # }}}
552 - }
553 -
554 - /*
555 - * previous page AJAX link
556 - * @param $pager instance of pager
557 - * @param $list, when the link is available it will be rendered then pushed to the list
558 - */
559 - function addPrevPageLink( CB_AbstractPager $pager, array &$list ) {
560 - $this->initPagerStatsTpl();
561 - $this->initNavTpl();
562 - $prev_link = '&#160;'; // &nbsp;
563 - $link_obj = $pager->getPrevAjaxLink();
564 - if ( $pager->offset != 0 ) {
565 - $this->ajax_onclick = $link_obj->call;
566 - $this->ajax_link_text = wfMsg( 'cb_previous_items_link' );
567 - $prev_offset = $pager->getPrevOffset() + 1;
568 - $this->pager_stats = wfMsg( 'cb_previous_items_stats', $prev_offset, $prev_offset + $pager->limit - 1 );
569 - $this->nav_link = wfMsg( 'cb_previous_items_line', CB_XML::toText( $this->ajax_link_tpl ), CB_XML::toText( $this->pager_stats_tpl ) );
570 - $prev_link = CB_XML::toText( $this->nav_link_tpl );
571 - }
572 - if ( $link_obj->placeholders || $this->nav_link != '' ) {
573 - $list[] = $prev_link;
574 - }
575 - }
576 -
577 - /*
578 - * next page AJAX link
579 - * @param $pager instance of pager
580 - * @param $list, when the link is available it will be rendered then pushed to the list
581 - */
582 - function addNextPageLink( CB_AbstractPager $pager, array &$list ) {
583 - $this->initPagerStatsTpl();
584 - $this->initNavTpl();
585 - $next_link = '&#160;'; // &nbsp;
586 - $link_obj = $pager->getNextAjaxLink();
587 - if ( $pager->hasMoreEntries ) {
588 - $this->ajax_onclick = $link_obj->call;
589 - $this->ajax_link_text = wfMsg( 'cb_next_items_link' );
590 - $this->pager_stats = wfMsg( 'cb_next_items_stats', $pager->getNextOffset() + 1 );
591 - $this->nav_link = wfMsg( 'cb_next_items_line', CB_XML::toText( $this->ajax_link_tpl ), CB_XML::toText( $this->pager_stats_tpl ) );
592 - $next_link = CB_XML::toText( $this->nav_link_tpl );
593 - }
594 - if ( $link_obj->placeholders || $this->nav_link != '' ) {
595 - $list[] = $next_link;
596 - }
597 - }
598 -
599 - function generateCatList( CB_AbstractPager $pager ) {
600 - if ( $pager->offset == -1 ) {
601 - return ''; // list has no entries
602 - }
603 - # {{{ one category container template
604 - $subcat_count_hint = '';
605 - $cat_expand_sign = '';
606 - $cat_link = '';
607 - $cat_tpl =
608 - array( '__tag' => 'div', 'class' => 'cb_cat_container', '__end' => "\n",
609 - array( '__tag' => 'div', 'class' => 'cb_cat_controls',
610 - array( '__tag' => 'span', 'title' => &$subcat_count_hint, 'class' => 'cb_cat_expand', 0 => &$cat_expand_sign ),
611 - array( '__tag' => 'span', 'class' => 'cb_cat_item', 0 => &$cat_link )
612 - )
613 - );
614 - # }}}
615 - $this->initAjaxLinkTpl();
616 - $this->initSortkeyTpl();
617 - # create list of categories
618 - $catlist = array();
619 - if ( $pager instanceof CB_RootPager ) {
620 - $catlist[] = array( '__tag' => 'noscript', 'class' => 'cb_noscript', 0 => wfMsg( 'cb_requires_javascript' ) );
621 - }
622 - $this->addPrevPageLink( $pager, $catlist );
623 - # generate entries list
624 - foreach ( $pager->entries as &$cat ) {
625 - // cat_title might be NULL sometimes - probably due to DB corruption?
626 - if ( ( $cat_title_str = $cat->cat_title ) == NULL ) {
627 - // weird, but occasionally may happen;
628 - if ( empty( $cat->cl_sortkey ) ) {
629 - continue;
630 - }
631 - $cat_title_str = $cat->cl_sortkey;
632 - $cat_title_obj = Title::newFromText( $cat_title_str, NS_CATEGORY );
633 - } else {
634 - $cat_title_obj = Title::makeTitle( NS_CATEGORY, $cat_title_str );
635 - }
636 - $this->ajax_link_comment = '';
637 -
638 - # calculate exact number of pages alone
639 - $cat->pages_only = intval( $cat->cat_pages ) - intval( $cat->cat_subcats ) - intval( $cat->cat_files );
640 - # generate tree "expand" sign
641 - if ( $cat->cat_subcats === NULL ) {
642 - $cat_expand_sign = 'x';
643 - $subcat_count_hint = '';
644 - } elseif ( $cat->cat_subcats > 0 ) {
645 - $this->ajax_onclick = 'return CategoryBrowser.subCatsPlus(this,' . $cat->cat_id . ')';
646 - $this->ajax_link_text = '+';
647 - $cat_expand_sign = CB_XML::toText( $this->ajax_link_tpl );
648 - $subcat_count_hint = wfMsgExt( 'cb_has_subcategories', array( 'parsemag' ), $cat->cat_subcats );
649 - } else {
650 - $cat_expand_sign = '&#160;'; // &nbsp;
651 - $subcat_count_hint = '';
652 - }
653 -
654 - # create AJAX links for viewing categories, pages, files, belonging to this category
655 - $ajax_links = '';
656 - if ( !empty( $cat->cat_id ) ) {
657 - $this->ajax_onclick = 'return CategoryBrowser.subCatsLink(this,' . $cat->cat_id . ')';
658 - $this->ajax_link_text = wfMsgExt( 'cb_has_subcategories', array( 'parsemag' ), $cat->cat_subcats );
659 - $cat_subcats = ( ( $cat->cat_subcats > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
660 -
661 - $this->ajax_onclick = 'return CategoryBrowser.pagesLink(this,' . $cat->cat_id . ')';
662 - $this->ajax_link_text = wfMsgExt( 'cb_has_pages', array( 'parsemag' ), $cat->pages_only );
663 - $cat_pages = ( ( $cat->pages_only > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
664 -
665 - $this->ajax_onclick = 'return CategoryBrowser.filesLink(this,' . $cat->cat_id . ')';
666 - $this->ajax_link_text = wfMsgExt( 'cb_has_files', array( 'parsemag' ), $cat->cat_files );
667 - $cat_files = ( ( $cat->cat_files > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
668 - $ajax_links .= $cat_subcats . $cat_pages . $cat_files;
669 - }
670 - $cat_link = CB_Setup::$skin->link( $cat_title_obj, $cat_title_obj->getText() );
671 - # show the sortkey, when it does not match title name
672 - # note that cl_sortkey is empty for CB_RootCond pager
673 - $this->sortkey_hint = '';
674 - if ( !empty( $cat->cl_sortkey ) &&
675 - $cat_title_obj->getText() != $cat->cl_sortkey ) {
676 - $this->sortkey_hint = '(' . CategoryViewer::getSubcategorySortChar( $cat_title_obj, $cat->cl_sortkey ) . ')';
677 - $cat_link .= CB_XML::toText( $this->sortkey_hint_tpl );
678 - }
679 - $cat_link .= $ajax_links;
680 - # finally add generated $cat_tpl/$cat_link to $catlist
681 - $catlist[] = CB_XML::toText( $cat_tpl );
682 - }
683 - $this->addNextPageLink( $pager, $catlist );
684 - return $catlist;
685 - }
686 -
687 - function generatePagesList( CB_SubPager $pager ) {
688 - if ( $pager->offset == -1 ) {
689 - return ''; // list has no entries
690 - }
691 - # {{{ one page container template
692 - $page_link = '';
693 - $page_tpl =
694 - array( '__tag' => 'div', 'class' => 'cb_cat_container', '__end' => "\n",
695 - array( '__tag' => 'div', 'class' => 'cb_cat_item', 0 => &$page_link )
696 - );
697 - # }}}
698 - $this->initAjaxLinkTpl();
699 - $this->initSortkeyTpl();
700 - # create list of pages
701 - $pagelist = array();
702 - $this->addPrevPageLink( $pager, $pagelist );
703 - foreach ( $pager->entries as &$page ) {
704 - $page_title = Title::makeTitle( $page->page_namespace, $page->page_title );
705 - $page_link = CB_Setup::$skin->link( $page_title, $page_title->getPrefixedText() );
706 - # show the sortkey, when it does not match title name
707 - # note that cl_sortkey is empty for CB_RootCond pager
708 - $this->sortkey_hint = '';
709 - if ( !empty( $page->cl_sortkey ) &&
710 - $page_title->getText() != $page->cl_sortkey ) {
711 - $this->sortkey_hint = '(' . CategoryViewer::getSubcategorySortChar( $page_title, $page->cl_sortkey ) . ')';
712 - $page_link .= CB_XML::toText( $this->sortkey_hint_tpl );
713 - }
714 - $pagelist[] = CB_XML::toText( $page_tpl );
715 - }
716 - $this->addNextPageLink( $pager, $pagelist );
717 - return $pagelist;
718 - }
719 -
720 - function generateFilesList( CB_SubPager $pager ) {
721 - global $wgOut, $wgCategoryMagicGallery;
722 - // unstub $wgOut, otherwise $wgOut->mNoGallery may be unavailable
723 - // strange, but calling wfDebug() instead does not unstub successfully
724 - $wgOut->getHeadItems();
725 - if ( $pager->offset == -1 ) {
726 - return ''; // list has no entries
727 - }
728 - # respect extension & core settings
729 - if ( CB_Setup::$imageGalleryPerRow < 1 || !$wgCategoryMagicGallery || $wgOut->mNoGallery ) {
730 - return $this->generatePagesList( $pager );
731 - }
732 - $this->initAjaxLinkTpl();
733 - $this->initSortkeyTpl();
734 - # {{{ gallery container template
735 - $gallery_html = '';
736 - $gallery_tpl = array( '__tag' => 'div', 'class' => 'cb_files_container', 0 => &$gallery_html );
737 - # }}}
738 - # create list of files (holder of prev/next AJAX links and generated image gallery)
739 - $filelist = array();
740 - # create image gallery
741 - $gallery = new ImageGallery();
742 - $gallery->setHideBadImages();
743 - $gallery->setPerRow( CB_Setup::$imageGalleryPerRow );
744 - $this->addPrevPageLink( $pager, $filelist );
745 - foreach ( $pager->entries as &$file ) {
746 - $file_title = Title::makeTitle( $file->page_namespace, $file->page_title );
747 - # show the sortkey, when it does not match title name
748 - # note that cl_sortkey is empty for CB_RootCond pager
749 - $this->sortkey_hint = '';
750 - if ( !empty( $file->cl_sortkey ) &&
751 - $file_title->getText() != $file->cl_sortkey ) {
752 - $this->sortkey_hint = '(' . CategoryViewer::getSubcategorySortChar( $file_title, $file->cl_sortkey ) . ')';
753 - }
754 - $gallery->add( $file_title, ( $this->sortkey_hint != '' ) ? CB_XML::toText( $this->sortkey_hint_tpl ) : '' );
755 - }
756 - if ( !$gallery->isEmpty() ) {
757 - $gallery_html = $gallery->toHTML();
758 - $filelist[] = CB_XML::toText( $gallery_tpl );
759 - }
760 - $this->addNextPageLink( $pager, $filelist );
761 - return $filelist;
762 - }
763 -
764 - /*
765 - * called via AJAX to get root list for specitied offset, limit
766 - * where condition will be read from the cookie previousely set
767 - * @param $args[0] : encoded reverse polish queue
768 - * @param $args[1] : category name filter string
769 - * @param $args[2] : category name filter case insensitive flag
770 - * @param $args[3] : offset (optional)
771 - * @param $args[4] : limit (optional)
772 - */
773 - static function getRootOffsetHtml() {
774 - wfLoadExtensionMessages( 'CategoryBrowser' );
775 - $args = func_get_args();
776 - $limit = ( count( $args ) > 4 ) ? abs( intval( $args[4] ) ) : CB_PAGING_ROWS;
777 - $offset = ( count( $args ) > 3 ) ? abs( intval( $args[3] ) ) : 0;
778 - $nameFilterCI = ( count( $args ) > 2 ) ? $args[2] == 'true' : false;
779 - $nameFilter = ( count( $args ) > 1 ) ? $args[1] : '';
780 - $encPolishQueue = ( count( $args ) > 0 ) ? $args[0] : 'all';
781 - $cb = new CategoryBrowser();
782 - $sqlCond = CB_SqlCond::newFromEncodedPolishQueue( $encPolishQueue );
783 - $rootPager = CB_RootPager::newFromSqlCond( $sqlCond, $offset, $limit );
784 - $rootPager->setNameFilter( $nameFilter, $nameFilterCI );
785 - $rootPager->getCurrentRows();
786 - $catlist = $cb->generateCatList( $rootPager );
787 - return CB_XML::toText( $catlist );
788 - }
789 -
790 - /*
791 - * called via AJAX to get list of (subcategories,pages,files) for specitied parent category id, offset, limit
792 - * @param $args[0] : type of pager ('subcats','pages','files')
793 - * @param $args[1] : parent category id
794 - * @param $args[2] : offset (optional)
795 - * @param $args[3] : limit (optional)
796 - */
797 - static function getSubOffsetHtml() {
798 - $pager_types = array(
799 - 'subcats' => array(
800 - 'js_nav_func' => "subCatsNav",
801 - 'select_fields' => "cl_sortkey, cat_id, cat_title, cat_subcats, cat_pages, cat_files",
802 - 'ns_cond' => "page_namespace = " . NS_CATEGORY
803 - ),
804 - 'pages' => array(
805 - 'js_nav_func' => "pagesNav",
806 - 'select_fields' => "page_title, page_namespace, page_len, page_is_redirect",
807 - 'ns_cond' => "NOT page_namespace IN (" . NS_FILE . "," . NS_CATEGORY . ")"
808 - ),
809 - 'files' => array(
810 - 'js_nav_func' => "filesNav",
811 - 'select_fields' => "page_title, page_namespace, page_len, page_is_redirect",
812 - 'ns_cond' => "page_namespace = " . NS_FILE
813 - )
814 - );
815 - wfLoadExtensionMessages( 'CategoryBrowser' );
816 - $args = func_get_args();
817 - if ( count( $args ) < 2 ) {
818 - return 'Too few parameters in ' . __METHOD__;
819 - }
820 - if ( !isset( $pager_types[ $args[0] ] ) ) {
821 - return 'Unknown pager type in ' . __METHOD__;
822 - }
823 - $pager_type = & $pager_types[ $args[0] ];
824 - $limit = ( count( $args ) > 3 ) ? abs( intval( $args[3] ) ) : CB_PAGING_ROWS;
825 - $offset = ( count( $args ) > 2 ) ? abs( intval( $args[2] ) ) : 0;
826 - $parentCatId = abs( intval( $args[1] ) );
827 - $cb = new CategoryBrowser();
828 - $pager = new CB_SubPager( $parentCatId, $offset, $limit,
829 - $pager_type[ 'js_nav_func' ],
830 - $pager_type[ 'select_fields' ],
831 - $pager_type[ 'ns_cond' ] );
832 - $pager->getCurrentRows();
833 - switch ( $pager->getListType() ) {
834 - case 'generateCatList' :
835 - $list = $cb->generateCatList( $pager );
836 - break;
837 - case 'generatePagesList' :
838 - $list = $cb->generatePagesList( $pager );
839 - break;
840 - case 'generateFilesList' :
841 - $list = $cb->generateFilesList( $pager );
842 - break;
843 - default :
844 - return 'Unknown list type in ' . __METHOD__;
845 - }
846 - return CB_XML::toText( $list );
847 - }
848 -
849 - /*
850 - * called via AJAX to setup custom edited expression cookie then display category root offset
851 - * @param $args[0] : encoded infix expression
852 - * @param $args[1] : category name filter string
853 - * @param $args[2] : category name filter case insensitive flag
854 - * @param $args[3] : 1 - cookie has to be set, 0 - cookie should not be set (expression is pre-defined or already was stored)
855 - * @param $args[4] : pager limit (optional)
856 - */
857 - static function applyEncodedQueue() {
858 - CB_Setup::initUser();
859 - $args = func_get_args();
860 - $limit = ( ( count( $args ) > 4 ) ? intval( $args[4] ) : CB_PAGING_ROWS );
861 - $setCookie = ( ( count( $args ) > 3 ) ? $args[3] != 0 : false );
862 - $nameFilterCI = ( count( $args ) > 2 ) ? $args[2] == 'true' : false;
863 - $nameFilter = ( count( $args ) > 1 ) ? $args[1] : '';
864 - $encInfixQueue = ( ( count( $args ) > 0 ) ? $args[0] : 'all' );
865 - $sqlCond = CB_SqlCond::newFromEncodedInfixQueue( $encInfixQueue );
866 - $encPolishQueue = $sqlCond->getEncodedQueue( false );
867 - if ( $setCookie ) {
868 - CB_Setup::setCookie( 'rootcond', $encPolishQueue, time() + 60 * 60 * 24 * 7 );
869 - }
870 - return self::getRootOffsetHtml( $encPolishQueue, $nameFilter, $nameFilterCI, 0, $limit );
871 - }
872 -
873 - /*
874 - * called via AJAX to generate new selected option when the selected rootcond is new (the rootcond cookie was set)
875 - * @param $args[0] currently selected expression in encoded infix format
876 - */
877 - static function generateSelectedOption() {
878 - wfLoadExtensionMessages( 'CategoryBrowser' );
879 - CB_Setup::initUser();
880 - $args = func_get_args();
881 - if ( count( $args ) < 1 ) {
882 - throw new MWException( 'Argument 0 is missing in ' . __METHOD__ );
883 - }
884 - $encInfixQueue = $args[0];
885 - $sqlCond = CB_SqlCond::newFromEncodedInfixQueue( $encInfixQueue );
886 - $ranges = array();
887 - self::addRange( $ranges, $sqlCond );
888 - # generate div instead of option to avoid innerHTML glitches in IE
889 - return self::generateOption( $ranges[0], $sqlCond->getEncodedQueue( false ), 'div' );
890 - }
891 -
892 -} /* end of CategoryBrowser class */
Index: trunk/extensions/CategoryBrowser/CategoryBrowserView.php
@@ -0,0 +1,308 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of CategoryBrowser.
 6+ *
 7+ * CategoryBrowser is free software; you can redistribute it and/or modify
 8+ * it under the terms of the GNU General Public License as published by
 9+ * the Free Software Foundation; either version 2 of the License, or
 10+ * (at your option) any later version.
 11+ *
 12+ * CategoryBrowser is distributed in the hope that it will be useful,
 13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15+ * GNU General Public License for more details.
 16+ *
 17+ * You should have received a copy of the GNU General Public License
 18+ * along with CategoryBrowser; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * CategoryBrowser is an AJAX-enabled category filter and browser for MediaWiki.
 24+ *
 25+ * To activate this extension :
 26+ * * Create a new directory named CategoryBrowser into the directory "extensions" of MediaWiki.
 27+ * * Place the files from the extension archive there.
 28+ * * Add this line at the end of your LocalSettings.php file :
 29+ * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
 30+ *
 31+ * @version 0.2.1
 32+ * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
 33+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 34+ * @addtogroup Extensions
 35+ */
 36+
 37+if ( !defined( 'MEDIAWIKI' ) ) {
 38+ die( "This file is a part of MediaWiki extension.\n" );
 39+}
 40+
 41+abstract class CB_AbstractPagesView {
 42+
 43+ // instance of pager (model), used to generate the view
 44+ var $pager;
 45+
 46+ function __construct( CB_AbstractPager $pager ) {
 47+ $this->pager = $pager;
 48+ }
 49+
 50+ function initNavTpl() {
 51+ # {{{ navigation link (prev,next) template
 52+ $this->nav_link = '';
 53+ if ( !isset( $this->nav_link_tpl ) ) {
 54+ $this->nav_link_tpl =
 55+ array( '__tag' => 'div', 'class' => 'cb_cat_container', '__end' => "\n", 0 => &$this->nav_link );
 56+ }
 57+ # }}}
 58+ }
 59+
 60+ function initAjaxLinkTpl() {
 61+ # {{{ ajax link template
 62+ $this->ajax_onclick = '';
 63+ $this->ajax_link_text = '';
 64+ if ( !isset( $this->ajax_link_tpl ) ) {
 65+ $this->ajax_link_tpl =
 66+ array( '__tag' => 'a', 'class' => 'cb_sublink', 'href' => '', 'onclick' => &$this->ajax_onclick, 0 => &$this->ajax_link_text );
 67+ }
 68+ # }}}
 69+ }
 70+
 71+ function initPagerStatsTpl() {
 72+ $this->pager_stats = '';
 73+ # {{{ pager current statistics template
 74+ if ( !isset( $this->pager_stats_tpl ) ) {
 75+ $this->pager_stats_tpl =
 76+ array( '__tag' => 'span', 'class' => 'cb_comment', 0 => &$this->pager_stats );
 77+ }
 78+ # }}}
 79+ }
 80+
 81+ function initSortkeyTpl() {
 82+ # {{{ category sortkey hint template
 83+ $this->sortkey_hint = '';
 84+ if ( !isset( $this->sortkey_hint_tpl ) ) {
 85+ $this->sortkey_hint_tpl = array( '__tag' => 'span', 'class' => 'cb_comment', 'style' => 'padding:0em 0.1em 0em 0.1em;', 0 => &$this->sortkey_hint );
 86+ }
 87+ # }}}
 88+ }
 89+
 90+ /*
 91+ * previous page AJAX link
 92+ * @param $list, when the link is available it will be rendered then pushed to the list
 93+ * we cannot just return rendered link, because it depends not just on pager offset,
 94+ * but also on whether an instance of pager "suggests" using placeholders (shown instead of empty links) or not
 95+ */
 96+ function addPrevPageLink( array &$list ) {
 97+ $this->initAjaxLinkTpl();
 98+ $this->initPagerStatsTpl();
 99+ $this->initNavTpl();
 100+ $prev_link = '&#160;'; // &nbsp;
 101+ $link_obj = $this->pager->getPrevAjaxLink();
 102+ if ( $this->pager->offset != 0 ) {
 103+ $this->ajax_onclick = $link_obj->call;
 104+ $this->ajax_link_text = wfMsg( 'cb_previous_items_link' );
 105+ $prev_offset = $this->pager->getPrevOffset() + 1;
 106+ $this->pager_stats = wfMsg( 'cb_previous_items_stats', $prev_offset, $prev_offset + $this->pager->limit - 1 );
 107+ $this->nav_link = wfMsg( 'cb_previous_items_line', CB_XML::toText( $this->ajax_link_tpl ), CB_XML::toText( $this->pager_stats_tpl ) );
 108+ $prev_link = CB_XML::toText( $this->nav_link_tpl );
 109+ }
 110+ if ( $link_obj->placeholders || $this->nav_link != '' ) {
 111+ $list[] = $prev_link;
 112+ }
 113+ }
 114+
 115+ /*
 116+ * next page AJAX link
 117+ * @param $list, when the link is available it will be rendered then pushed to the list
 118+ * we cannot just return rendered link, because it depends not just on pager offset,
 119+ * but also on whether an instance of pager "suggests" using placeholders (shown instead of empty links) or not
 120+ */
 121+ function addNextPageLink( array &$list ) {
 122+ $this->initAjaxLinkTpl();
 123+ $this->initPagerStatsTpl();
 124+ $this->initNavTpl();
 125+ $next_link = '&#160;'; // &nbsp;
 126+ $link_obj = $this->pager->getNextAjaxLink();
 127+ if ( $this->pager->hasMoreEntries ) {
 128+ $this->ajax_onclick = $link_obj->call;
 129+ $this->ajax_link_text = wfMsg( 'cb_next_items_link' );
 130+ $this->pager_stats = wfMsg( 'cb_next_items_stats', $this->pager->getNextOffset() + 1 );
 131+ $this->nav_link = wfMsg( 'cb_next_items_line', CB_XML::toText( $this->ajax_link_tpl ), CB_XML::toText( $this->pager_stats_tpl ) );
 132+ $next_link = CB_XML::toText( $this->nav_link_tpl );
 133+ }
 134+ if ( $link_obj->placeholders || $this->nav_link != '' ) {
 135+ $list[] = $next_link;
 136+ }
 137+ }
 138+
 139+ /*
 140+ * show the sortkey, when it does not match title name
 141+ * note that cl_sortkey is empty for CB_RootCond pager
 142+ * @return sortkey html hint, when the sortkey differs from title name, empty string otherwise
 143+ */
 144+ function addSortkey( Title $title_obj, stdClass $pager_row ) {
 145+ $this->initSortkeyTpl();
 146+ $result = '';
 147+ if ( !empty( $pager_row->cl_sortkey ) &&
 148+ $title_obj->getText() != $pager_row->cl_sortkey ) {
 149+ $this->sortkey_hint = '(' . CategoryViewer::getSubcategorySortChar( $title_obj, $pager_row->cl_sortkey ) . ')';
 150+ $result = CB_XML::toText( $this->sortkey_hint_tpl );
 151+ }
 152+ return $result;
 153+ }
 154+} /* end of CB_AbstractPagesView class */
 155+
 156+
 157+class CB_CategoriesView extends CB_AbstractPagesView {
 158+
 159+ function generateList() {
 160+ if ( $this->pager->offset == -1 ) {
 161+ return ''; // list has no entries
 162+ }
 163+ # {{{ one category container template
 164+ $subcat_count_hint = '';
 165+ $cat_expand_sign = '';
 166+ $cat_link = '';
 167+ $cat_tpl =
 168+ array( '__tag' => 'div', 'class' => 'cb_cat_container', '__end' => "\n",
 169+ array( '__tag' => 'div', 'class' => 'cb_cat_controls',
 170+ array( '__tag' => 'span', 'title' => &$subcat_count_hint, 'class' => 'cb_cat_expand', 0 => &$cat_expand_sign ),
 171+ array( '__tag' => 'span', 'class' => 'cb_cat_item', 0 => &$cat_link )
 172+ )
 173+ );
 174+ # }}}
 175+ # create list of categories
 176+ $catlist = array();
 177+ if ( $this->pager instanceof CB_RootPager ) {
 178+ $catlist[] = array( '__tag' => 'noscript', 'class' => 'cb_noscript', 0 => wfMsg( 'cb_requires_javascript' ) );
 179+ }
 180+ $this->addPrevPageLink( $catlist );
 181+ # generate entries list
 182+ foreach ( $this->pager->entries as &$cat ) {
 183+ // cat_title might be NULL sometimes - probably due to DB corruption?
 184+ if ( ( $cat_title_str = $cat->cat_title ) == NULL ) {
 185+ // weird, but occasionally may happen;
 186+ if ( empty( $cat->cl_sortkey ) ) {
 187+ continue;
 188+ }
 189+ $cat_title_str = $cat->cl_sortkey;
 190+ $cat_title_obj = Title::newFromText( $cat_title_str, NS_CATEGORY );
 191+ } else {
 192+ $cat_title_obj = Title::makeTitle( NS_CATEGORY, $cat_title_str );
 193+ }
 194+
 195+ # calculate exact number of pages alone
 196+ $cat->pages_only = intval( $cat->cat_pages ) - intval( $cat->cat_subcats ) - intval( $cat->cat_files );
 197+ # generate tree "expand" sign
 198+ $this->initAjaxLinkTpl();
 199+ if ( $cat->cat_subcats === NULL ) {
 200+ $cat_expand_sign = 'x';
 201+ $subcat_count_hint = '';
 202+ } elseif ( $cat->cat_subcats > 0 ) {
 203+ $this->ajax_onclick = 'return CategoryBrowser.subCatsPlus(this,' . $cat->cat_id . ')';
 204+ $this->ajax_link_text = '+';
 205+ $cat_expand_sign = CB_XML::toText( $this->ajax_link_tpl );
 206+ $subcat_count_hint = wfMsgExt( 'cb_has_subcategories', array( 'parsemag' ), $cat->cat_subcats );
 207+ } else {
 208+ $cat_expand_sign = '&#160;'; // &nbsp;
 209+ $subcat_count_hint = '';
 210+ }
 211+
 212+ # create AJAX links for viewing categories, pages, files, belonging to this category
 213+ $ajax_links = '';
 214+ $this->initAjaxLinkTpl();
 215+ if ( !empty( $cat->cat_id ) ) {
 216+ $this->ajax_onclick = 'return CategoryBrowser.subCatsLink(this,' . $cat->cat_id . ')';
 217+ $this->ajax_link_text = wfMsgExt( 'cb_has_subcategories', array( 'parsemag' ), $cat->cat_subcats );
 218+ $cat_subcats = ( ( $cat->cat_subcats > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
 219+
 220+ $this->ajax_onclick = 'return CategoryBrowser.pagesLink(this,' . $cat->cat_id . ')';
 221+ $this->ajax_link_text = wfMsgExt( 'cb_has_pages', array( 'parsemag' ), $cat->pages_only );
 222+ $cat_pages = ( ( $cat->pages_only > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
 223+
 224+ $this->ajax_onclick = 'return CategoryBrowser.filesLink(this,' . $cat->cat_id . ')';
 225+ $this->ajax_link_text = wfMsgExt( 'cb_has_files', array( 'parsemag' ), $cat->cat_files );
 226+ $cat_files = ( ( $cat->cat_files > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
 227+ $ajax_links .= $cat_subcats . $cat_pages . $cat_files;
 228+ }
 229+ $cat_link = CB_Setup::$skin->link( $cat_title_obj, $cat_title_obj->getText() );
 230+ # show sortkey, when it does not match title name
 231+ $cat_link .= $this->addSortkey( $cat_title_obj, $cat );
 232+ $cat_link .= $ajax_links;
 233+ # finally add generated $cat_tpl/$cat_link to $catlist
 234+ $catlist[] = CB_XML::toText( $cat_tpl );
 235+ }
 236+ $this->addNextPageLink( $catlist );
 237+ return $catlist;
 238+ }
 239+
 240+} /* end of CB_CategoriesView class */
 241+
 242+class CB_PagesView extends CB_AbstractPagesView {
 243+
 244+ function __construct( CB_SubPager $pager ) {
 245+ parent::__construct( $pager );
 246+ }
 247+
 248+ function generateList() {
 249+ if ( $this->pager->offset == -1 ) {
 250+ return ''; // list has no entries
 251+ }
 252+ # {{{ one page container template
 253+ $page_link = '';
 254+ $page_tpl =
 255+ array( '__tag' => 'div', 'class' => 'cb_cat_container', '__end' => "\n",
 256+ array( '__tag' => 'div', 'class' => 'cb_cat_item', 0 => &$page_link )
 257+ );
 258+ # }}}
 259+ # create list of pages
 260+ $pagelist = array();
 261+ $this->addPrevPageLink( $pagelist );
 262+ foreach ( $this->pager->entries as &$page ) {
 263+ $page_title = Title::makeTitle( $page->page_namespace, $page->page_title );
 264+ $page_link = CB_Setup::$skin->link( $page_title, $page_title->getPrefixedText() );
 265+ # show sortkey, when it does not match title name
 266+ $page_link .= $this->addSortkey( $page_title, $page );
 267+ $pagelist[] = CB_XML::toText( $page_tpl );
 268+ }
 269+ $this->addNextPageLink( $pagelist );
 270+ return $pagelist;
 271+ }
 272+
 273+} /* end of CB_PagesView class */
 274+
 275+class CB_FilesView extends CB_AbstractPagesView {
 276+
 277+ function __construct( CB_SubPager $pager ) {
 278+ parent::__construct( $pager );
 279+ }
 280+
 281+ function generateList() {
 282+ if ( $this->pager->offset == -1 ) {
 283+ return ''; // list has no entries
 284+ }
 285+ # {{{ gallery container template
 286+ $gallery_html = '';
 287+ $gallery_tpl = array( '__tag' => 'div', 'class' => 'cb_files_container', 0 => &$gallery_html );
 288+ # }}}
 289+ # create list of files (holder of prev/next AJAX links and generated image gallery)
 290+ $filelist = array();
 291+ # create image gallery
 292+ $gallery = new ImageGallery();
 293+ $gallery->setHideBadImages();
 294+ $gallery->setPerRow( CB_Setup::$imageGalleryPerRow );
 295+ $this->addPrevPageLink( $filelist );
 296+ foreach ( $this->pager->entries as &$file ) {
 297+ $file_title = Title::makeTitle( $file->page_namespace, $file->page_title );
 298+ # show the sortkey, when it does not match title name
 299+ $gallery->add( $file_title, $this->addSortKey( $file_title, $file ) );
 300+ }
 301+ if ( !$gallery->isEmpty() ) {
 302+ $gallery_html = $gallery->toHTML();
 303+ $filelist[] = CB_XML::toText( $gallery_tpl );
 304+ }
 305+ $this->addNextPageLink( $filelist );
 306+ return $filelist;
 307+ }
 308+
 309+} /* end of CB_FilesView class */
Property changes on: trunk/extensions/CategoryBrowser/CategoryBrowserView.php
___________________________________________________________________
Added: svn:eol-style
1310 + native
Index: trunk/extensions/CategoryBrowser/CategoryBrowserModel.php
@@ -0,0 +1,351 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of CategoryBrowser.
 6+ *
 7+ * CategoryBrowser is free software; you can redistribute it and/or modify
 8+ * it under the terms of the GNU General Public License as published by
 9+ * the Free Software Foundation; either version 2 of the License, or
 10+ * (at your option) any later version.
 11+ *
 12+ * CategoryBrowser is distributed in the hope that it will be useful,
 13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15+ * GNU General Public License for more details.
 16+ *
 17+ * You should have received a copy of the GNU General Public License
 18+ * along with CategoryBrowser; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * CategoryBrowser is an AJAX-enabled category filter and browser for MediaWiki.
 24+ *
 25+ * To activate this extension :
 26+ * * Create a new directory named CategoryBrowser into the directory "extensions" of MediaWiki.
 27+ * * Place the files from the extension archive there.
 28+ * * Add this line at the end of your LocalSettings.php file :
 29+ * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
 30+ *
 31+ * @version 0.2.1
 32+ * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
 33+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 34+ * @addtogroup Extensions
 35+ */
 36+
 37+if ( !defined( 'MEDIAWIKI' ) ) {
 38+ die( "This file is a part of MediaWiki extension.\n" );
 39+}
 40+
 41+abstract class CB_AbstractPager {
 42+
 43+ var $db;
 44+
 45+ /* pager position (actual offset)
 46+ * 0 means pager has no previous elements
 47+ * -1 means pager has no elements at all
 48+ */
 49+ var $offset = -1;
 50+ /* provided "source" offset */
 51+ var $query_offset;
 52+ /* indicates, whether the pager has further elements */
 53+ var $hasMoreEntries = false;
 54+ /* maximal number of entries per page (actual number of entries on page) */
 55+ var $limit = 0;
 56+ /* provided "source" limit */
 57+ var $query_limit;
 58+ /* array of current entries */
 59+ var $entries;
 60+
 61+ /*
 62+ * abstract query (doesn't instantinate)
 63+ * @param offset - suggested SQL offset
 64+ * @param limit - suggested SQL limit
 65+ */
 66+ function __construct( $offset, $limit ) {
 67+ $this->db = & wfGetDB( DB_SLAVE );
 68+ $this->query_limit = intval( $limit );
 69+ $this->query_offset = intval( $offset );
 70+ }
 71+
 72+ /*
 73+ *
 74+ * initializes hasMoreEntries and array of entries from DB result
 75+ */
 76+ function setEntries( &$db_result ) {
 77+ $this->hasMoreEntries = false;
 78+ $this->entries = array();
 79+ $count = $this->db->numRows( $db_result );
 80+ if ( $count < 1 ) { return; }
 81+ $this->offset = $this->query_offset;
 82+ $this->limit = $this->query_limit;
 83+ if ( $this->hasMoreEntries = $count > $this->limit ) {
 84+ // do not include "overflow" entry, it belongs to the next page
 85+ $count--;
 86+ }
 87+ // do not include last row (which was loaded only to set hasMoreEntries)
 88+ for ( $i = 0; $i < $count; $i++ ) {
 89+ $row = $this->db->fetchObject( $db_result );
 90+ $this->entries[] = $row;
 91+ }
 92+ }
 93+
 94+ // returns previous SQL select offset
 95+ function getPrevOffset() {
 96+ $prev_offset = $this->offset - $this->limit;
 97+ return ( ( $prev_offset >= 0 ) ? $prev_offset : 0 );
 98+ }
 99+
 100+ // returns next SQL select offset
 101+ function getNextOffset() {
 102+ return ( ( $this->hasMoreEntries ) ? $this->offset + $this->limit : 0 );
 103+ }
 104+
 105+ /*
 106+ * suggests, what kind of view should be used for instance of the model
 107+ * @return name of view method
 108+ * otherwise throws an error
 109+ * warning: will fail, when called before calling $this->getCurrentRows() !
 110+ * warning: $this->limit is not set properly before calling $this->getCurrentRows() !
 111+ */
 112+ function getListType() {
 113+ // it is not enough to check $this->entries[0],
 114+ // because some broken tables might have just some (not all) cat_title = NULL or page_title = NULL
 115+ foreach ( $this->entries as &$entry ) {
 116+ if ( isset( $entry->page_namespace ) && $entry->page_namespace == NS_FILE ) { return 'generateFilesList'; }
 117+ if ( isset( $entry->page_title ) ) { return 'generatePagesList'; }
 118+ if ( isset( $entry->cat_title ) ) { return 'generateCatList'; }
 119+ if ( isset( $entry->cl_sortkey ) ) { return 'generateCatList'; }
 120+ }
 121+ throw new MWException( 'Entries not initialized in ' . __METHOD__ );
 122+ }
 123+
 124+} /* end of CB_AbstractPager class */
 125+
 126+/*
 127+ * subentries (subcategories, pages, files) pager
 128+ * TODO: gracefully set offset = 0 when too large offset was given
 129+ */
 130+class CB_SubPager extends CB_AbstractPager {
 131+
 132+ var $page_table;
 133+ var $category_table;
 134+ var $categorylinks_table;
 135+ // database ID of parent category
 136+ var $parentCatId;
 137+ // database fields to query
 138+ var $select_fields;
 139+ // namespace SQL condition (WHERE part)
 140+ var $ns_cond;
 141+ // javascript function used to navigate between the pages
 142+ var $js_nav_func;
 143+
 144+ /*
 145+ * creates subcategory list pager
 146+ *
 147+ * @param $parentCatId id of parent category
 148+ * @param $offset SQL offset
 149+ * @param $limit SQL limit
 150+ *
 151+ * TODO: query count of parentCatId subcategories/pages/files in category table for progress / percentage display
 152+ */
 153+ function __construct( $parentCatId, $offset, $limit, $js_nav_func, $select_fields = '*', $ns_cond = '' ) {
 154+ parent::__construct( $offset, $limit );
 155+ $this->page_table = $this->db->tableName( 'page' );
 156+ $this->category_table = $this->db->tableName( 'category' );
 157+ $this->categorylinks_table = $this->db->tableName( 'categorylinks' );
 158+ $this->parentCatId = $parentCatId;
 159+ $this->select_fields = $select_fields;
 160+ $this->ns_cond = $ns_cond;
 161+ $this->js_nav_func = $js_nav_func;
 162+ }
 163+
 164+ /*
 165+ * set offset, limit, hasMoreEntries and entries
 166+ * @param $offset SQL offset
 167+ * @param $limit SQL limit
 168+ */
 169+ function getCurrentRows() {
 170+ /* TODO: change the query to more optimal one (no subselects)
 171+ * SELECT cl_sortkey,cat_id,cat_title,cat_subcats,cat_pages,cat_files FROM `wiki_page` INNER JOIN `wiki_categorylinks` FORCE INDEX (cl_sortkey) ON (cl_from = page_id) LEFT JOIN `wiki_category` ON (cat_title = page_title AND page_namespace = 14) WHERE cl_to IN (SELECT cat_title FROM wiki_category WHERE cat_id = 44) AND page_namespace = 14 ORDER BY cl_sortkey LIMIT 0,11
 172+ */
 173+ $query_string =
 174+ "SELECT {$this->select_fields} " .
 175+ "FROM {$this->page_table} " .
 176+ "INNER JOIN {$this->categorylinks_table} FORCE INDEX (cl_sortkey) ON cl_from = page_id " .
 177+ "LEFT JOIN {$this->category_table} ON cat_title = page_title AND page_namespace = " . NS_CATEGORY . " " .
 178+ "WHERE cl_to IN (" .
 179+ "SELECT cat_title " .
 180+ "FROM {$this->category_table} " .
 181+ "WHERE cat_id = " . $this->db->addQuotes( $this->parentCatId ) .
 182+ ") " . ( ( $this->ns_cond == '' ) ? '' : "AND {$this->ns_cond} " ) .
 183+ "ORDER BY cl_sortkey ";
 184+ $res = $this->db->query( $query_string . "LIMIT {$this->query_offset}," . ( $this->query_limit + 1 ), __METHOD__ );
 185+ $this->setEntries( $res );
 186+ }
 187+
 188+ /*
 189+ * returns JS function call used to navigate to the previous page of this pager
 190+ * when placeholders = true, empty html placeholder should be generated in pager view instead of link
 191+ * when placeholders = false, nothing ('') will be generated in pager view instead of link
 192+ */
 193+ function getPrevAjaxLink() {
 194+ $result = (object) array(
 195+ "call" => "return CategoryBrowser.{$this->js_nav_func}(this," . $this->parentCatId . "," . $this->getPrevOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
 196+ "placeholders" => false
 197+ );
 198+ return $result;
 199+ }
 200+
 201+ /*
 202+ * returns JS function call used to navigate to the next page of this pager
 203+ * when placeholders = true, empty html placeholder should be generated in pager view instead of link
 204+ * when placeholders = false, nothing ('') will be generated in pager view instead of link
 205+ */
 206+ function getNextAjaxLink() {
 207+ $result = (object) array(
 208+ "call" => "return CategoryBrowser.{$this->js_nav_func}(this," . $this->parentCatId . ',' . $this->getNextOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
 209+ "placeholders" => false
 210+ );
 211+ return $result;
 212+ }
 213+
 214+} /* end of CB_SubPager class */
 215+
 216+/*
 217+ * creates a root category pager
 218+ * TODO: gracefully set offset = 0 when too large offset was given
 219+ * TODO: with $conds == '' categories aren't always sorted alphabetically
 220+ */
 221+class CB_RootPager extends CB_AbstractPager {
 222+
 223+
 224+ /* string paging conds aka filter (WHERE statement) */
 225+ var $conds;
 226+ /* _optional_ instance of CB_SqlCond object used to construct this pager
 227+ * (in case it's been provided in constructor call)
 228+ */
 229+ var $sqlCond = null;
 230+
 231+ // category name filter (LIKE)
 232+ var $nameFilter = '';
 233+ // category name filter case-insensetive flag (when true, tries to use insensetive LIKE COLLATE)
 234+ var $nameFilterCI = false;
 235+
 236+ /*
 237+ * formal constructor
 238+ * real instantination should be performed by calling public static methods below
 239+ */
 240+ function __construct( $offset, $limit ) {
 241+ parent::__construct( $offset, $limit );
 242+ }
 243+
 244+ /*
 245+ * @param $conds - instanceof CB_SqlCond (parentized condition generator)
 246+ * @param $offset - SQL OFFSET
 247+ * @param $limit - SQL LIMIT
 248+ */
 249+ public static function newFromSqlCond( CB_SqlCond $conds, $offset = 0, $limit = CB_PAGING_ROWS ) {
 250+ $rp = new CB_RootPager( $offset, $limit );
 251+ $rp->conds = $conds->getCond();
 252+ $rp->sqlCond = &$conds;
 253+ return $rp;
 254+ }
 255+
 256+ /*
 257+ * @param $tokens - array of infix ops of sql condition
 258+ * @param $offset - SQL OFFSET
 259+ * @param $limit - SQL LIMIT
 260+ */
 261+ public static function newFromInfixTokens( $tokens, $offset = 0, $limit = CB_PAGING_ROWS ) {
 262+ if ( !is_array( $tokens ) ) {
 263+ return null;
 264+ }
 265+ try {
 266+ $sqlCond = CB_SqlCond::newFromInfixTokens( $tokens );
 267+ } catch ( MWException $ex ) {
 268+ return null;
 269+ }
 270+ return self::newFromSqlCond( $sqlCond, $offset, $limit );
 271+ }
 272+
 273+ /*
 274+ * create root pager from the largest non-empty category range
 275+ * @param $ranges - array of "complete" token queues (range)
 276+ * (every range is an stdobject of decoded infix queue and encoded reverse polish queue)
 277+ */
 278+ public static function newFromCategoryRange( $ranges ) {
 279+ $rp = null;
 280+ foreach ( $ranges as &$range ) {
 281+ $rp = CB_RootPager::newFromInfixTokens( $range->infix_decoded );
 282+ if ( is_object( $rp ) && $rp->offset != -1 ) {
 283+ break;
 284+ }
 285+ }
 286+ return $rp;
 287+ }
 288+
 289+ /*
 290+ * filter catetories by names
 291+ * @param $cat_name_filter - string category name begins from
 292+ * @param $cat_name_filter_ci - boolean, true attempts to use case-insensetive search, when available
 293+ */
 294+ function setNameFilter( $cat_name_filter, $cat_name_filter_ci ) {
 295+ $this->nameFilter = ltrim( $cat_name_filter );
 296+ $this->nameFilterCI = $cat_name_filter_ci;
 297+ }
 298+
 299+ /*
 300+ * performs range query and stores the results
 301+ */
 302+ function getCurrentRows() {
 303+ $conds = trim( $this->conds );
 304+ // use name filter, when available
 305+ if ( $this->nameFilter != '' ) {
 306+ if ( $conds != '' ) {
 307+ $conds = "( $conds ) AND ";
 308+ }
 309+ $conds .= 'cat_title LIKE ' . $this->db->addQuotes( $this->nameFilter . '%' );
 310+ if ( $this->nameFilterCI && CB_Setup::$cat_title_CI != '' ) {
 311+ // case insensetive search is active
 312+ $conds .= ' COLLATE ' . $this->db->addQuotes( CB_Setup::$cat_title_CI );
 313+ }
 314+ }
 315+ $options = array( 'OFFSET' => $this->query_offset, 'ORDER BY' => 'cat_title', 'LIMIT' => $this->query_limit + 1 );
 316+ $res = $this->db->select( 'category',
 317+ array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ),
 318+ $conds,
 319+ __METHOD__,
 320+ $options
 321+ );
 322+ /* set actual offset, limit, hasMoreEntries and entries */
 323+ $this->setEntries( $res );
 324+ }
 325+
 326+ /*
 327+ * returns JS function call used to navigate to the previous page of this pager
 328+ * when placeholders = true, empty html placeholder should be generated in pager view instead of link
 329+ * when placeholders = false, nothing ('') will be generated in pager view instead of link
 330+ */
 331+ function getPrevAjaxLink() {
 332+ $result = (object) array(
 333+ "call" => 'return CategoryBrowser.rootCats(\'' . Xml::escapeJsString( $this->sqlCond->getEncodedQueue( false ) ) . '\',' . $this->getPrevOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
 334+ "placeholders" => true
 335+ );
 336+ return $result;
 337+ }
 338+
 339+ /*
 340+ * returns JS function call used to navigate to the next page of this pager
 341+ * when placeholders = true, empty html placeholder should be generated in pager view instead of link
 342+ * when placeholders = false, nothing ('') will be generated in pager view instead of link
 343+ */
 344+ function getNextAjaxLink() {
 345+ $result = (object) array(
 346+ "call" => 'return CategoryBrowser.rootCats(\'' . Xml::escapeJsString( $this->sqlCond->getEncodedQueue( false ) ) . '\',' . $this->getNextOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
 347+ "placeholders" => false
 348+ );
 349+ return $result;
 350+ }
 351+
 352+} /* end of CB_RootPager class */
Property changes on: trunk/extensions/CategoryBrowser/CategoryBrowserModel.php
___________________________________________________________________
Added: svn:eol-style
1353 + native
Index: trunk/extensions/CategoryBrowser/CategoryBrowserBasic.php
@@ -1,674 +0,0 @@
2 -<?php
3 -/**
4 - * ***** BEGIN LICENSE BLOCK *****
5 - * This file is part of CategoryBrowser.
6 - *
7 - * CategoryBrowser is free software; you can redistribute it and/or modify
8 - * it under the terms of the GNU General Public License as published by
9 - * the Free Software Foundation; either version 2 of the License, or
10 - * (at your option) any later version.
11 - *
12 - * CategoryBrowser is distributed in the hope that it will be useful,
13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 - * GNU General Public License for more details.
16 - *
17 - * You should have received a copy of the GNU General Public License
18 - * along with CategoryBrowser; if not, write to the Free Software
19 - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 - *
21 - * ***** END LICENSE BLOCK *****
22 - *
23 - * CategoryBrowser is an AJAX-enabled category filter and browser for MediaWiki.
24 - *
25 - * To activate this extension :
26 - * * Create a new directory named CategoryBrowser into the directory "extensions" of MediaWiki.
27 - * * Place the files from the extension archive there.
28 - * * Add this line at the end of your LocalSettings.php file :
29 - * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
30 - *
31 - * @version 0.2.1
32 - * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
33 - * @author Dmitriy Sintsov <questpc@rambler.ru>
34 - * @addtogroup Extensions
35 - */
36 -
37 -if ( !defined( 'MEDIAWIKI' ) ) {
38 - die( "This file is a part of MediaWiki extension.\n" );
39 -}
40 -
41 -define( 'CB_COND_TOKEN_MATCH', '`^\s*(cat_subcats|cat_pages|cat_files)\s*(>=|<=|=)\s*(\d+)\s*$`' );
42 -define( 'CB_ENCODED_TOKEN_MATCH', '`^(ge|le|eq)(p|s|f)(\d+)$`' );
43 -
44 -/* render output data */
45 -class CB_XML {
46 - // the stucture of $tag is like this:
47 - // array( "__tag"=>"td", "class"=>"myclass", 0=>"text before li", 1=>array( "__tag"=>"li", 0=>"text inside li" ), 2=>"text after li" )
48 - // both tagged and tagless lists are supported
49 - static function toText( &$tag ) {
50 - $tag_open = "";
51 - $tag_close = "";
52 - $tag_val = null;
53 - if ( is_array( $tag ) ) {
54 - ksort( $tag );
55 - if ( array_key_exists( '__tag', $tag ) ) {
56 - # list inside of tag
57 - $tag_open .= "<" . $tag[ '__tag' ];
58 - foreach ( $tag as $attr_key => &$attr_val ) {
59 - if ( is_int( $attr_key ) ) {
60 - if ( $tag_val === null )
61 - $tag_val = "";
62 - if ( is_array( $attr_val ) ) {
63 - # recursive tags
64 - $tag_val .= self::toText( $attr_val );
65 - } else {
66 - # text
67 - $tag_val .= $attr_val;
68 - }
69 - } else {
70 - # string keys are for tag attributes
71 - if ( substr( $attr_key, 0, 2 ) != "__" ) {
72 - # include only non-reserved attributes
73 - if ( $attr_val !== null ) {
74 - $tag_open .= " $attr_key=\"" . $attr_val . "\"";
75 - } else {
76 - # null value of attribute is a special value for option selected
77 - $tag_open .= " $attr_key";
78 - }
79 - }
80 - }
81 - }
82 - if ( $tag_val !== null ) {
83 - $tag_open .= ">";
84 - $tag_close .= "</" . $tag[ '__tag' ] . ">";
85 - } else {
86 - $tag_open .= " />";
87 - }
88 - if ( array_key_exists( '__end', $tag ) ) {
89 - $tag_close .= $tag[ '__end' ];
90 - }
91 - } else {
92 - # tagless list
93 - $tag_val = "";
94 - foreach ( $tag as $attr_key => &$attr_val ) {
95 - if ( is_int( $attr_key ) ) {
96 - if ( is_array( $attr_val ) ) {
97 - # recursive tags
98 - $tag_val .= self::toText( $attr_val );
99 - } else {
100 - # text
101 - $tag_val .= $attr_val;
102 - }
103 - } else {
104 - ob_start();
105 - var_dump( $tag );
106 - $tagdump = ob_get_contents();
107 - ob_end_clean();
108 - $tag_val = "invalid argument: tagless list cannot have tag attribute values in key=$attr_key, $tagdump";
109 - }
110 - }
111 - }
112 - } else {
113 - # just a text
114 - $tag_val = $tag;
115 - }
116 - return $tag_open . $tag_val . $tag_close;
117 - }
118 -
119 - # creates one "htmlobject" row of the table
120 - # elements of $row can be either a string/number value of cell or an array( "count"=>colspannum, "attribute"=>value, 0=>html_inside_tag )
121 - # attribute maps can be like this: ("name"=>0, "count"=>colspan" )
122 - static function newRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
123 - $result = "";
124 - if ( count( $row ) > 0 ) {
125 - foreach ( $row as &$cell ) {
126 - if ( !is_array( $cell ) ) {
127 - $cell = array( 0 => $cell );
128 - }
129 - $cell[ '__tag' ] = $celltag;
130 - $cell[ '__end' ] = "\n";
131 - if ( is_array( $attribute_maps ) ) {
132 - # converts ("count"=>3) to ("colspan"=>3) in table headers - don't use frequently
133 - foreach ( $attribute_maps as $key => $val ) {
134 - if ( array_key_exists( $key, $cell ) ) {
135 - $cell[ $val ] = $cell[ $key ];
136 - unset( $cell[ $key ] );
137 - }
138 - }
139 - }
140 - }
141 - $result = array( '__tag' => 'tr', 0 => $row, '__end' => "\n" );
142 - if ( is_array( $rowattrs ) ) {
143 - $result = array_merge( $rowattrs, $result );
144 - } elseif ( $rowattrs !== "" ) {
145 - $result[0][] = __METHOD__ . ':invalid rowattrs supplied';
146 - }
147 - }
148 - return $result;
149 - }
150 -
151 - # add row to the table
152 - static function addRow( &$table, $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
153 - $table[] = self::newRow( $row, $rowattrs, $celltag, $attribute_maps );
154 - }
155 -
156 - # add column to the table
157 - static function addColumn( &$table, $column, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
158 - if ( count( $column ) > 0 ) {
159 - $row = 0;
160 - foreach ( $column as &$cell ) {
161 - if ( !is_array( $cell ) ) {
162 - $cell = array( 0 => $cell );
163 - }
164 - $cell[ '__tag' ] = $celltag;
165 - $cell[ '__end' ] = "\n";
166 - if ( is_array( $attribute_maps ) ) {
167 - # converts ("count"=>3) to ("rowspan"=>3) in table headers - don't use frequently
168 - foreach ( $attribute_maps as $key => $val ) {
169 - if ( array_key_exists( $key, $cell ) ) {
170 - $cell[ $val ] = $cell[ $key ];
171 - unset( $cell[ $key ] );
172 - }
173 - }
174 - }
175 - if ( is_array( $rowattrs ) ) {
176 - $cell = array_merge( $rowattrs, $cell );
177 - } elseif ( $rowattrs !== "" ) {
178 - $cell[ 0 ] = __METHOD__ . ':invalid rowattrs supplied';
179 - }
180 - if ( !array_key_exists( $row, $table ) ) {
181 - $table[ $row ] = array( '__tag' => 'tr', '__end' => "\n" );
182 - }
183 - $table[ $row ][] = $cell;
184 - if ( array_key_exists( 'rowspan', $cell ) ) {
185 - $row += intval( $cell[ 'rowspan' ] );
186 - } else {
187 - $row++;
188 - }
189 - }
190 - $result = array( '__tag' => 'tr', 0 => $column, '__end' => "\n" );
191 - }
192 - }
193 -
194 - static function displayRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
195 - return self::toText( self::newRow( $row, $rowattrs, $celltag, $attribute_maps ) );
196 - }
197 -
198 - // use newRow() or addColumn() to add resulting row/column to the table
199 - // if you want to use the resulting row with toText(), don't forget to apply attrs=array('__tag'=>'td')
200 - static function applyAttrsToRow( &$row, $attrs ) {
201 - if ( is_array( $attrs ) && count( $attrs > 0 ) ) {
202 - foreach ( $row as &$cell ) {
203 - if ( !is_array( $cell ) ) {
204 - $cell = array_merge( $attrs, array( $cell ) );
205 - } else {
206 - foreach ( $attrs as $attr_key => $attr_val ) {
207 - if ( !array_key_exists( $attr_key, $cell ) ) {
208 - $cell[ $attr_key ] = $attr_val;
209 - }
210 - }
211 - }
212 - }
213 - }
214 - }
215 -} /* end of CB_XML class */
216 -
217 -/*
218 - * Localization of SQL tokens list
219 - * comparsions like "a > 1" are treated like single-ops
220 - */
221 -class CB_LocalExpr {
222 -
223 - var $src_tokens;
224 - var $local_tokens;
225 -
226 - /*
227 - * @param $tokens - list of SQL condition tokens (infix or polish)
228 - * comparsions like "a > 1" are treated like single-ops
229 - */
230 - function __construct( $tokens ) {
231 - if ( is_array( $tokens ) ) {
232 - $this->src_tokens = $tokens;
233 - } else {
234 - $this->src_tokens = array( '' ); // default "all"
235 - }
236 - if ( count( $this->src_tokens ) == 1 && $this->src_tokens[0] == '' ) {
237 - $this->local_tokens = array( wfMsg( 'cb_all_op' ) ); // localized "all"
238 - return;
239 - }
240 - foreach ( $tokens as &$token ) {
241 - $field = $num = '';
242 - switch ( strtoupper( $token ) ) {
243 - case '(' : $op = 'lbracket'; break;
244 - case ')' : $op = 'rbracket'; break;
245 - case 'OR' : $op = 'or'; break;
246 - case 'AND' : $op = 'and'; break;
247 - default : // comparsion subexpression
248 - preg_match_all( CB_COND_TOKEN_MATCH, $token, $matches, PREG_SET_ORDER );
249 - if ( count( $matches ) == 1 && isset( $matches[0] ) && count( $matches[0] ) == 4 ) {
250 - list( $expr, $field, $cmp, $num ) = $matches[0];
251 - switch ( $cmp ) {
252 - case '>=' : $op = 'ge'; break;
253 - case '<=' : $op = 'le'; break;
254 - case '=' : $op = 'eq'; break;
255 - default:
256 - $this->src_tokens = array( '' ); // default "all"
257 - $this->local_tokens = array( wfMsg( 'cb_all_op' ) ); // localized default "all"
258 - throw new MWException( 'Invalid operator ' . CB_Setup::entities( $token ) . ' in ' . __METHOD__ );
259 - }
260 - } else {
261 - $this->src_tokens = array( '' ); // default "all"
262 - $this->local_tokens = array( wfMsg( 'cb_all_op' ) ); // localized default "all"
263 - throw new MWException( 'Invalid operation ' . CB_Setup::entities( $token ) . ' in ' . __METHOD__ );
264 - }
265 - }
266 - if ( $field == '' ) {
267 - $this->local_tokens[] = wfMsg( "cb_${op}_op" );
268 - } elseif ( $num == '' ) {
269 - $this->local_tokens[] = wfMsg( 'cb_op1_template', wfMsg( "cb_${op}_op" ), wfMsg( "cb_${field}" ) );
270 - } else {
271 - $this->local_tokens[] = wfMsg( 'cb_op2_template', wfMsg( "cb_${field}" ), wfMsg( "cb_${op}_op" ), $num );
272 - }
273 - }
274 - }
275 -
276 - function toString() {
277 - return implode( ' ', $this->local_tokens );
278 - }
279 -
280 -} /* end of CB_LocalExpr class */
281 -
282 -/* builds a bracketed sql condition either from the list of infix array $tokens or
283 - * from encoded reverse polish operations string $enc
284 - *
285 - * properly bracketed sql condition uses brackets to display the actual priority of expression
286 - *
287 - */
288 -class CB_SqlCond {
289 -
290 - static $decoded_fields = array( 'p' => 'cat_pages', 's' => 'cat_subcats', 'f' => 'cat_files' );
291 - static $decoded_cmps = array( 'ge' => '>=', 'le' => '<=', 'eq' => '=' );
292 -
293 - static $encoded_fields = array( 'cat_subcats' => 's', 'cat_pages' => 'p', 'cat_files' => 'f' );
294 - static $encoded_cmps = array( '>=' => 'ge', '<=' => 'le', '=' => 'eq' );
295 -
296 - # reverse polish operations queue (decoded form, every op is an element of array)
297 - # comparsions like "a > 1" are treated like single-ops
298 - # initialized in constructor (public static function)
299 - var $queue;
300 - private $queue_pos; // current position in output queue; used to generate triples
301 -
302 - # infix operations queue
303 - # comparsions like "a > 1" are treated like single-ops
304 - var $infix_queue;
305 -
306 - // used privately by decodeToken()
307 - private static $valid_logical_ops;
308 - private static $valid_bracket_ops;
309 -
310 - /*
311 - * constructor (creates an instance, initializes $this->queue, returns an instance)
312 - *
313 - * converts encoded reverse polish operations queue (string) to
314 - * decoded reverse polish operations queue (array $this->queue) (1:1)
315 - * @param $enc - string encoded reverse polish operations queue
316 - * (underscore-separated encoded polish tokens)
317 - */
318 - public static function newFromEncodedPolishQueue( $enc ) {
319 - $sc = new CB_SqlCond();
320 - self::$valid_logical_ops = array( 'and', 'or' );
321 - self::$valid_bracket_ops = array();
322 - $sc->queue = array();
323 - $q = explode( '_', $enc );
324 - # {{{ validation of expression
325 - $cmp_count = $logical_count = 0;
326 - # }}}
327 - foreach ( $q as &$token ) {
328 - $result = self::decodeToken( $token );
329 - $sc->queue[] = $result->token;
330 - if ( $result->type == 'comparsion' ) {
331 - $cmp_count++;
332 - } elseif ( $result->type == 'logical' ) {
333 - $logical_count++;
334 - } else {
335 - # tampered or bugged $enc, return default "all" instead
336 - $sc->queue = array();
337 - return $sc;
338 - }
339 - }
340 - if ( $cmp_count < 1 || $cmp_count != $logical_count + 1 ) {
341 - # tampered or bugged $enc, return default "all" instead
342 - $sc->queue = array();
343 - return $sc;
344 - }
345 - if ( $logical_count > CB_MAX_LOGICAL_OP ) {
346 - # too complex $enc (fabricated or non-realistic), return default "all" instead
347 - $sc->queue = array();
348 - return $sc;
349 - }
350 - return $sc;
351 - }
352 -
353 - /*
354 - * constructor (creates an instance, initializes $this->infix_queue, returns an instance)
355 - *
356 - * converts encoded infix operations queue (string) to
357 - * decoded infix operations queue (array $this->infix_queue) (1:1)
358 - * then fills reverse polish operations queue $this->queue
359 - * @param $enc - string encoded infix operations queue
360 - * (underscore-separated encoded infix tokens)
361 - */
362 - public static function newFromEncodedInfixQueue( $enc ) {
363 - self::$valid_logical_ops = array( 'and', 'or' );
364 - self::$valid_bracket_ops = array( '(', ')' );
365 - $infix_queue = array();
366 - $q = explode( '_', $enc );
367 - # {{{ validation of expression
368 - $brackets_level = 0; $prev_type = '';
369 - # }}}
370 - foreach ( $q as &$token ) {
371 - $result = self::decodeToken( $token );
372 - $infix_queue[] = $result->token;
373 - if ( $result->type == 'bracket' ) {
374 - if ( $result->token == '(' ) {
375 - $brackets_level++;
376 - } else {
377 - $brackets_level--;
378 - }
379 - if ( $brackets_level < 0 ) {
380 - # tampered or bugged $enc, use default "all" instead
381 - $infix_queue = array();
382 - break;
383 - }
384 - } elseif ( $result->type == 'logical' ) {
385 - if ( $prev_type == '' || $prev_type == 'logical' ) {
386 - # tampered or bugged $enc, use default "all" instead
387 - $infix_queue = array();
388 - break;
389 - }
390 - } elseif ( $result->type == 'comparsion' ) {
391 - if ( $prev_type == 'comparsion' ) {
392 - # tampered or bugged $enc, use default "all" instead
393 - $infix_queue = array();
394 - break;
395 - }
396 - } else {
397 - # tampered or bugged $enc, use default "all" instead
398 - $infix_queue = array();
399 - break;
400 - }
401 - $prev_type = $result->type;
402 - }
403 - if ( $brackets_level != 0 ) {
404 - # tampered or bugged $enc, use default "all" instead
405 - $infix_queue = array();
406 - }
407 - return self::newFromInfixTokens( $infix_queue );
408 - }
409 -
410 - private static function decodeToken( $token ) {
411 - $result = (object) array( 'type' => 'unknown', 'token' => '' );
412 - $matches = array();
413 - preg_match_all( CB_ENCODED_TOKEN_MATCH, $token, $matches, PREG_SET_ORDER );
414 - if ( count( $matches ) == 1 && isset( $matches[0] ) && count( $matches[0] ) == 4 ) {
415 - // decode comparsion op
416 - $result->token = self::$decoded_fields[ $matches[0][2] ] . ' ' . self::$decoded_cmps[ $matches[0][1] ] . ' ' . (int) $matches[0][3];
417 - $result->type = 'comparsion';
418 - return $result;
419 - }
420 - $lo_token = strtolower( $token );
421 - if ( in_array( $lo_token, self::$valid_logical_ops ) ) {
422 - // decode logical op
423 - // we store logical ops uppercase for the "prettiness"
424 - $result->token = strtoupper( $lo_token );
425 - $result->type = 'logical';
426 - return $result;
427 - }
428 - if ( in_array( $lo_token, self::$valid_bracket_ops ) ) {
429 - // decode bracket op
430 - $result->token = $lo_token;
431 - $result->type = 'bracket';
432 - return $result;
433 - }
434 - return $result;
435 - }
436 -
437 - /*
438 - * constructor (creates an instance, initializes $this->queue, returns an instance)
439 - *
440 - * fills up polish notation array $this->queue from infix $tokens provided
441 - * @param $tokens - array of infix tokens
442 - */
443 - # converts list of given infix $tokens into $this->queue of reverse polish notation
444 - # TODO: more throughout checks for invalid tokens given
445 - public static function newFromInfixTokens( array $tokens ) {
446 - $sc = new CB_SqlCond();
447 - $stack = array(); // every element is stdobject with token and prio fields
448 - $sc->queue = array();
449 - foreach ( $tokens as &$token ) {
450 - switch ( strtoupper( $token ) ) {
451 - case '(' :
452 - $prio = 0;
453 - array_push( $stack, (object) array( 'token' => $token, 'prio' => $prio ) );
454 - break;
455 - case ')' :
456 - $prio = 1;
457 - while ( $last = array_pop( $stack ) ) {
458 - if ( is_object( $last ) ) {
459 - if ( $last->token == '(' ) {
460 - break;
461 - }
462 - array_push( $sc->queue, $last->token );
463 - } else {
464 - throw new MWException( 'Open / closing brackets mismatch in ' . __METHOD__ );
465 - }
466 - }
467 - break;
468 - case 'OR' :
469 - case 'AND' :
470 - $prio = strtoupper( $token ) == 'OR' ? 2 : 3;
471 - while ( $last = array_pop( $stack ) ) {
472 - if ( is_object( $last ) && $last->prio >= $prio ) {
473 - array_push( $sc->queue, $last->token );
474 - } else {
475 - array_push( $stack, $last );
476 - break;
477 - }
478 - }
479 - array_push( $stack, (object) array( 'token' => $token, 'prio' => $prio ) );
480 - break;
481 - default : // comparsion subexpression
482 - array_push( $sc->queue, $token );
483 - }
484 - }
485 - while ( $last = array_pop( $stack ) ) {
486 - if ( !is_object( $last ) ) {
487 - break;
488 - }
489 - array_push( $sc->queue, $last->token );
490 - }
491 - return $sc;
492 - }
493 -
494 -/*
495 -src:'(', 'cat_pages > 1000', 'OR', 'cat_subcats > 10', ')', 'AND', 'cat_files > 100'
496 -dst:cat_pages > 1000;cat_subcats > 10;OR;cat_files > 100;AND
497 -
498 -('','AND','') 'AND' comes to initial empty triple (level 0)
499 -('','AND','cat_files > 100') 'cat_files > 100' becomes right param of current triple (level 0)
500 -(('','OR',''),'AND','cat_files > 100') 'OR' becomes left recursive param of current triple (level 1), because right param is already occupied
501 -(('','OR','cat_subcats > 10'),'AND','cat_files > 100') 'cat_subcats > 10' becomes right param of current triple (level 1)
502 -(('cat_pages > 1000','OR','cat_subcats > 10'),'AND','cat_files > 100') 'cat_pages > 1000' becomes right param of current triple (level 1)
503 -
504 -src:'cat_pages > 1000', 'OR', 'cat_subcats > 10', 'AND', 'cat_files > 100'
505 -dst:cat_pages > 1000;cat_subcats > 10;cat_files > 100;AND;OR
506 -
507 -('','OR','') 'OR' comes to initial empty triple (level 0)
508 -('','OR',('','AND','')) 'AND' becomes right recursive entry (level 1)
509 -('','OR',('','AND','cat_files > 100')) 'cat_files > 100' becomes right param of current triple (level 1)
510 -('','OR',('cat_subcats > 10','AND','cat_files > 100')) 'cat_subcats > 10' becomes left param of current triple, because right param is already occupied (level 1)
511 -('cat_pages > 1000','OR',('cat_subcats > 10','AND','cat_files > 100')) going level up because current triple was occupied; 'cat_pages > 1000' becomes left param of current triple
512 -
513 -1. global counter of queue position, getting elements consequtively from right to left
514 -2. operators are going to current entry, in case currept op is free, otherwise going recursively from right to left (which position is inoccupied)
515 -3. operands are going to current entry, right to left
516 -4. generating the string recursively going from left to right
517 -
518 -in actual code (null,null,null) isset() is used instead of ('','','')
519 -*/
520 - # generate triples (see example above) from $this->queue
521 - #
522 - # param &$currTriple - recursively adds new triples to $currTriple
523 - private function buildTriples( &$currTriple ) {
524 - # pre-initialize current triple
525 - # recursively feed triples with queue tokens, right to left
526 - while ( $this->queue_pos >= 0 ) {
527 - $token = $this->queue[ $this->queue_pos ];
528 - if ( preg_match( '`^AND|OR$`i', $token ) ) {
529 - // operators
530 - if ( !isset( $currTriple[1] ) ) {
531 - $currTriple[1] = $token;
532 - $this->queue_pos--;
533 - } elseif ( !isset( $currTriple[2] ) ) {
534 - $currTriple[2] = array();
535 - $this->buildTriples( $currTriple[2] );
536 - } elseif ( !isset( $currTriple[0] ) ) {
537 - $currTriple[0] = array();
538 - $this->buildTriples( $currTriple[0] );
539 - } else {
540 - return;
541 - }
542 - } else {
543 - // operands
544 - if ( !isset( $currTriple[2] ) ) {
545 - $currTriple[2] = $token;
546 - $this->queue_pos--;
547 - } elseif ( !isset( $currTriple[0] ) ) {
548 - $currTriple[0] = $token;
549 - $this->queue_pos--;
550 - } else {
551 - return;
552 - }
553 - }
554 - }
555 - }
556 -
557 - /*
558 - * build properly bracketed infix expression string
559 - * also builds $this->infix_queue array
560 - * from triples tree previousely built by CategoryFilter::buildTriples (left to right)
561 - */
562 - private $infixLevel; // used to do not include brackets at level 0
563 - private function getInfixExpr( &$out, $currTriple ) {
564 - $this->infixLevel++;
565 - if ( $this->infixLevel != 0 ) {
566 - $this->infix_queue[] = '(';
567 - $out .= '(';
568 - }
569 - if ( isset( $currTriple[0] ) ) {
570 - if ( is_array( $currTriple[0] ) ) {
571 - $this->getInfixExpr( $out, $currTriple[0] );
572 - } else {
573 - $this->infix_queue[] = $currTriple[0];
574 - $out .= $currTriple[0];
575 - }
576 - }
577 - if ( isset( $currTriple[1] ) ) {
578 - $this->infix_queue[] = $currTriple[1];
579 - $out .= ' ' . $currTriple[1] . ' ';
580 - }
581 - if ( isset( $currTriple[2] ) ) {
582 - if ( is_array( $currTriple[2] ) ) {
583 - $this->getInfixExpr( $out, $currTriple[2] );
584 - } else {
585 - $this->infix_queue[] = $currTriple[2];
586 - $out .= $currTriple[2];
587 - }
588 - }
589 - if ( $this->infixLevel != 0 ) {
590 - $this->infix_queue[] = ')';
591 - $out .= ')';
592 - }
593 - $this->infixLevel--;
594 - }
595 -
596 - /*
597 - * get SQL condition expression with full brackets (to indicate operators priority)
598 - * *** !!also builds $this->infix_queue array!! ***
599 - */
600 - function getCond() {
601 - $rootTriple = array();
602 - $this->queue_pos = count( $this->queue ) - 1;
603 - $this->buildTriples( $rootTriple );
604 - $out = '';
605 - $this->infixLevel = -1;
606 - $this->infix_queue = array();
607 - # also builds $this->infix_queue array
608 - $this->getInfixExpr( $out, $rootTriple );
609 - if ( count( $this->infix_queue ) == 0 ) {
610 - $this->infix_queue = array( '' ); // default "all"
611 - }
612 - return $out;
613 - }
614 -
615 - /*
616 - * get encoded queue to be stored in a cookie or passed from PHP AJAX handler to js callback
617 - * @param $infix set true when infix queue is decoded, otherwise brackets cause to reset to default "all"
618 - *
619 - */
620 - function getEncodedQueue( $is_infix = false ) {
621 - $result = '';
622 - if ( $is_infix ) {
623 - $valid_single_ops = array( '(', ')', 'or', 'and' );
624 - if ( !is_array( $this->infix_queue ) ) {
625 - $this->getCond();
626 - }
627 - $queue = &$this->infix_queue;
628 - } else {
629 - $valid_single_ops = array( 'or', 'and' );
630 - $queue = &$this->queue;
631 - }
632 - if ( count( $queue ) == 1 && $queue[0] == '' ) {
633 - return 'all'; // default "show all"
634 - }
635 - $firstElem = true;
636 - foreach ( $queue as &$token ) {
637 - if ( $firstElem ) {
638 - $firstElem = false;
639 - } else {
640 - $result .= '_';
641 - }
642 - $field = $num = '';
643 - if ( in_array( $lo_token = strtolower( $token ), $valid_single_ops ) ) {
644 - $op = $lo_token;
645 - } else {
646 - // comparsion subexpression ?
647 - preg_match_all( CB_COND_TOKEN_MATCH, $token, $matches, PREG_SET_ORDER );
648 - if ( count( $matches ) == 1 && isset( $matches[0] ) && count( $matches[0] ) == 4 ) {
649 - list( $expr, $field, $cmp, $num ) = $matches[0];
650 - if ( isset( self::$encoded_fields[ $field ] ) ) {
651 - $field = self::$encoded_fields[ $field ];
652 - } else {
653 - return 'all'; // default "show all"
654 - }
655 - if ( isset( self::$encoded_cmps[ $cmp ] ) ) {
656 - $op = self::$encoded_cmps[ $cmp ];
657 - } else {
658 - return 'all'; // default "show all"
659 - }
660 - } else {
661 - return 'all'; // default "show all"
662 - }
663 - }
664 - if ( $field == '' ) {
665 - $result .= $op;
666 - } elseif ( $num == '' ) {
667 - $result .= $op . $field;
668 - } else {
669 - $result .= $op . $field . $num;
670 - }
671 - }
672 - return $result;
673 - }
674 -
675 -} /* end of CB_SqlCond class */
Index: trunk/extensions/CategoryBrowser/CategoryBrowserBasic.php
@@ -0,0 +1,674 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of CategoryBrowser.
 6+ *
 7+ * CategoryBrowser is free software; you can redistribute it and/or modify
 8+ * it under the terms of the GNU General Public License as published by
 9+ * the Free Software Foundation; either version 2 of the License, or
 10+ * (at your option) any later version.
 11+ *
 12+ * CategoryBrowser is distributed in the hope that it will be useful,
 13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15+ * GNU General Public License for more details.
 16+ *
 17+ * You should have received a copy of the GNU General Public License
 18+ * along with CategoryBrowser; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * CategoryBrowser is an AJAX-enabled category filter and browser for MediaWiki.
 24+ *
 25+ * To activate this extension :
 26+ * * Create a new directory named CategoryBrowser into the directory "extensions" of MediaWiki.
 27+ * * Place the files from the extension archive there.
 28+ * * Add this line at the end of your LocalSettings.php file :
 29+ * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
 30+ *
 31+ * @version 0.2.1
 32+ * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
 33+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 34+ * @addtogroup Extensions
 35+ */
 36+
 37+if ( !defined( 'MEDIAWIKI' ) ) {
 38+ die( "This file is a part of MediaWiki extension.\n" );
 39+}
 40+
 41+define( 'CB_COND_TOKEN_MATCH', '`^\s*(cat_subcats|cat_pages|cat_files)\s*(>=|<=|=)\s*(\d+)\s*$`' );
 42+define( 'CB_ENCODED_TOKEN_MATCH', '`^(ge|le|eq)(p|s|f)(\d+)$`' );
 43+
 44+/* render output data */
 45+class CB_XML {
 46+ // the stucture of $tag is like this:
 47+ // array( "__tag"=>"td", "class"=>"myclass", 0=>"text before li", 1=>array( "__tag"=>"li", 0=>"text inside li" ), 2=>"text after li" )
 48+ // both tagged and tagless lists are supported
 49+ static function toText( &$tag ) {
 50+ $tag_open = "";
 51+ $tag_close = "";
 52+ $tag_val = null;
 53+ if ( is_array( $tag ) ) {
 54+ ksort( $tag );
 55+ if ( array_key_exists( '__tag', $tag ) ) {
 56+ # list inside of tag
 57+ $tag_open .= "<" . $tag[ '__tag' ];
 58+ foreach ( $tag as $attr_key => &$attr_val ) {
 59+ if ( is_int( $attr_key ) ) {
 60+ if ( $tag_val === null )
 61+ $tag_val = "";
 62+ if ( is_array( $attr_val ) ) {
 63+ # recursive tags
 64+ $tag_val .= self::toText( $attr_val );
 65+ } else {
 66+ # text
 67+ $tag_val .= $attr_val;
 68+ }
 69+ } else {
 70+ # string keys are for tag attributes
 71+ if ( substr( $attr_key, 0, 2 ) != "__" ) {
 72+ # include only non-reserved attributes
 73+ if ( $attr_val !== null ) {
 74+ $tag_open .= " $attr_key=\"" . $attr_val . "\"";
 75+ } else {
 76+ # null value of attribute is a special value for option selected
 77+ $tag_open .= " $attr_key";
 78+ }
 79+ }
 80+ }
 81+ }
 82+ if ( $tag_val !== null ) {
 83+ $tag_open .= ">";
 84+ $tag_close .= "</" . $tag[ '__tag' ] . ">";
 85+ } else {
 86+ $tag_open .= " />";
 87+ }
 88+ if ( array_key_exists( '__end', $tag ) ) {
 89+ $tag_close .= $tag[ '__end' ];
 90+ }
 91+ } else {
 92+ # tagless list
 93+ $tag_val = "";
 94+ foreach ( $tag as $attr_key => &$attr_val ) {
 95+ if ( is_int( $attr_key ) ) {
 96+ if ( is_array( $attr_val ) ) {
 97+ # recursive tags
 98+ $tag_val .= self::toText( $attr_val );
 99+ } else {
 100+ # text
 101+ $tag_val .= $attr_val;
 102+ }
 103+ } else {
 104+ ob_start();
 105+ var_dump( $tag );
 106+ $tagdump = ob_get_contents();
 107+ ob_end_clean();
 108+ $tag_val = "invalid argument: tagless list cannot have tag attribute values in key=$attr_key, $tagdump";
 109+ }
 110+ }
 111+ }
 112+ } else {
 113+ # just a text
 114+ $tag_val = $tag;
 115+ }
 116+ return $tag_open . $tag_val . $tag_close;
 117+ }
 118+
 119+ # creates one "htmlobject" row of the table
 120+ # elements of $row can be either a string/number value of cell or an array( "count"=>colspannum, "attribute"=>value, 0=>html_inside_tag )
 121+ # attribute maps can be like this: ("name"=>0, "count"=>colspan" )
 122+ static function newRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
 123+ $result = "";
 124+ if ( count( $row ) > 0 ) {
 125+ foreach ( $row as &$cell ) {
 126+ if ( !is_array( $cell ) ) {
 127+ $cell = array( 0 => $cell );
 128+ }
 129+ $cell[ '__tag' ] = $celltag;
 130+ $cell[ '__end' ] = "\n";
 131+ if ( is_array( $attribute_maps ) ) {
 132+ # converts ("count"=>3) to ("colspan"=>3) in table headers - don't use frequently
 133+ foreach ( $attribute_maps as $key => $val ) {
 134+ if ( array_key_exists( $key, $cell ) ) {
 135+ $cell[ $val ] = $cell[ $key ];
 136+ unset( $cell[ $key ] );
 137+ }
 138+ }
 139+ }
 140+ }
 141+ $result = array( '__tag' => 'tr', 0 => $row, '__end' => "\n" );
 142+ if ( is_array( $rowattrs ) ) {
 143+ $result = array_merge( $rowattrs, $result );
 144+ } elseif ( $rowattrs !== "" ) {
 145+ $result[0][] = __METHOD__ . ':invalid rowattrs supplied';
 146+ }
 147+ }
 148+ return $result;
 149+ }
 150+
 151+ # add row to the table
 152+ static function addRow( &$table, $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
 153+ $table[] = self::newRow( $row, $rowattrs, $celltag, $attribute_maps );
 154+ }
 155+
 156+ # add column to the table
 157+ static function addColumn( &$table, $column, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
 158+ if ( count( $column ) > 0 ) {
 159+ $row = 0;
 160+ foreach ( $column as &$cell ) {
 161+ if ( !is_array( $cell ) ) {
 162+ $cell = array( 0 => $cell );
 163+ }
 164+ $cell[ '__tag' ] = $celltag;
 165+ $cell[ '__end' ] = "\n";
 166+ if ( is_array( $attribute_maps ) ) {
 167+ # converts ("count"=>3) to ("rowspan"=>3) in table headers - don't use frequently
 168+ foreach ( $attribute_maps as $key => $val ) {
 169+ if ( array_key_exists( $key, $cell ) ) {
 170+ $cell[ $val ] = $cell[ $key ];
 171+ unset( $cell[ $key ] );
 172+ }
 173+ }
 174+ }
 175+ if ( is_array( $rowattrs ) ) {
 176+ $cell = array_merge( $rowattrs, $cell );
 177+ } elseif ( $rowattrs !== "" ) {
 178+ $cell[ 0 ] = __METHOD__ . ':invalid rowattrs supplied';
 179+ }
 180+ if ( !array_key_exists( $row, $table ) ) {
 181+ $table[ $row ] = array( '__tag' => 'tr', '__end' => "\n" );
 182+ }
 183+ $table[ $row ][] = $cell;
 184+ if ( array_key_exists( 'rowspan', $cell ) ) {
 185+ $row += intval( $cell[ 'rowspan' ] );
 186+ } else {
 187+ $row++;
 188+ }
 189+ }
 190+ $result = array( '__tag' => 'tr', 0 => $column, '__end' => "\n" );
 191+ }
 192+ }
 193+
 194+ static function displayRow( $row, $rowattrs = "", $celltag = "td", $attribute_maps = null ) {
 195+ return self::toText( self::newRow( $row, $rowattrs, $celltag, $attribute_maps ) );
 196+ }
 197+
 198+ // use newRow() or addColumn() to add resulting row/column to the table
 199+ // if you want to use the resulting row with toText(), don't forget to apply attrs=array('__tag'=>'td')
 200+ static function applyAttrsToRow( &$row, $attrs ) {
 201+ if ( is_array( $attrs ) && count( $attrs > 0 ) ) {
 202+ foreach ( $row as &$cell ) {
 203+ if ( !is_array( $cell ) ) {
 204+ $cell = array_merge( $attrs, array( $cell ) );
 205+ } else {
 206+ foreach ( $attrs as $attr_key => $attr_val ) {
 207+ if ( !array_key_exists( $attr_key, $cell ) ) {
 208+ $cell[ $attr_key ] = $attr_val;
 209+ }
 210+ }
 211+ }
 212+ }
 213+ }
 214+ }
 215+} /* end of CB_XML class */
 216+
 217+/*
 218+ * Localization of SQL tokens list
 219+ * comparsions like "a > 1" are treated like single-ops
 220+ */
 221+class CB_LocalExpr {
 222+
 223+ var $src_tokens;
 224+ var $local_tokens;
 225+
 226+ /*
 227+ * @param $tokens - list of SQL condition tokens (infix or polish)
 228+ * comparsions like "a > 1" are treated like single-ops
 229+ */
 230+ function __construct( $tokens ) {
 231+ if ( is_array( $tokens ) ) {
 232+ $this->src_tokens = $tokens;
 233+ } else {
 234+ $this->src_tokens = array( '' ); // default "all"
 235+ }
 236+ if ( count( $this->src_tokens ) == 1 && $this->src_tokens[0] == '' ) {
 237+ $this->local_tokens = array( wfMsg( 'cb_all_op' ) ); // localized "all"
 238+ return;
 239+ }
 240+ foreach ( $tokens as &$token ) {
 241+ $field = $num = '';
 242+ switch ( strtoupper( $token ) ) {
 243+ case '(' : $op = 'lbracket'; break;
 244+ case ')' : $op = 'rbracket'; break;
 245+ case 'OR' : $op = 'or'; break;
 246+ case 'AND' : $op = 'and'; break;
 247+ default : // comparsion subexpression
 248+ preg_match_all( CB_COND_TOKEN_MATCH, $token, $matches, PREG_SET_ORDER );
 249+ if ( count( $matches ) == 1 && isset( $matches[0] ) && count( $matches[0] ) == 4 ) {
 250+ list( $expr, $field, $cmp, $num ) = $matches[0];
 251+ switch ( $cmp ) {
 252+ case '>=' : $op = 'ge'; break;
 253+ case '<=' : $op = 'le'; break;
 254+ case '=' : $op = 'eq'; break;
 255+ default:
 256+ $this->src_tokens = array( '' ); // default "all"
 257+ $this->local_tokens = array( wfMsg( 'cb_all_op' ) ); // localized default "all"
 258+ throw new MWException( 'Invalid operator ' . CB_Setup::entities( $token ) . ' in ' . __METHOD__ );
 259+ }
 260+ } else {
 261+ $this->src_tokens = array( '' ); // default "all"
 262+ $this->local_tokens = array( wfMsg( 'cb_all_op' ) ); // localized default "all"
 263+ throw new MWException( 'Invalid operation ' . CB_Setup::entities( $token ) . ' in ' . __METHOD__ );
 264+ }
 265+ }
 266+ if ( $field == '' ) {
 267+ $this->local_tokens[] = wfMsg( "cb_${op}_op" );
 268+ } elseif ( $num == '' ) {
 269+ $this->local_tokens[] = wfMsg( 'cb_op1_template', wfMsg( "cb_${op}_op" ), wfMsg( "cb_${field}" ) );
 270+ } else {
 271+ $this->local_tokens[] = wfMsg( 'cb_op2_template', wfMsg( "cb_${field}" ), wfMsg( "cb_${op}_op" ), $num );
 272+ }
 273+ }
 274+ }
 275+
 276+ function toString() {
 277+ return implode( ' ', $this->local_tokens );
 278+ }
 279+
 280+} /* end of CB_LocalExpr class */
 281+
 282+/* builds a bracketed sql condition either from the list of infix array $tokens or
 283+ * from encoded reverse polish operations string $enc
 284+ *
 285+ * properly bracketed sql condition uses brackets to display the actual priority of expression
 286+ *
 287+ */
 288+class CB_SqlCond {
 289+
 290+ static $decoded_fields = array( 'p' => 'cat_pages', 's' => 'cat_subcats', 'f' => 'cat_files' );
 291+ static $decoded_cmps = array( 'ge' => '>=', 'le' => '<=', 'eq' => '=' );
 292+
 293+ static $encoded_fields = array( 'cat_subcats' => 's', 'cat_pages' => 'p', 'cat_files' => 'f' );
 294+ static $encoded_cmps = array( '>=' => 'ge', '<=' => 'le', '=' => 'eq' );
 295+
 296+ # reverse polish operations queue (decoded form, every op is an element of array)
 297+ # comparsions like "a > 1" are treated like single-ops
 298+ # initialized in constructor (public static function)
 299+ var $queue;
 300+ private $queue_pos; // current position in output queue; used to generate triples
 301+
 302+ # infix operations queue
 303+ # comparsions like "a > 1" are treated like single-ops
 304+ var $infix_queue;
 305+
 306+ // used privately by decodeToken()
 307+ private static $valid_logical_ops;
 308+ private static $valid_bracket_ops;
 309+
 310+ /*
 311+ * constructor (creates an instance, initializes $this->queue, returns an instance)
 312+ *
 313+ * converts encoded reverse polish operations queue (string) to
 314+ * decoded reverse polish operations queue (array $this->queue) (1:1)
 315+ * @param $enc - string encoded reverse polish operations queue
 316+ * (underscore-separated encoded polish tokens)
 317+ */
 318+ public static function newFromEncodedPolishQueue( $enc ) {
 319+ $sc = new CB_SqlCond();
 320+ self::$valid_logical_ops = array( 'and', 'or' );
 321+ self::$valid_bracket_ops = array();
 322+ $sc->queue = array();
 323+ $q = explode( '_', $enc );
 324+ # {{{ validation of expression
 325+ $cmp_count = $logical_count = 0;
 326+ # }}}
 327+ foreach ( $q as &$token ) {
 328+ $result = self::decodeToken( $token );
 329+ $sc->queue[] = $result->token;
 330+ if ( $result->type == 'comparsion' ) {
 331+ $cmp_count++;
 332+ } elseif ( $result->type == 'logical' ) {
 333+ $logical_count++;
 334+ } else {
 335+ # tampered or bugged $enc, return default "all" instead
 336+ $sc->queue = array();
 337+ return $sc;
 338+ }
 339+ }
 340+ if ( $cmp_count < 1 || $cmp_count != $logical_count + 1 ) {
 341+ # tampered or bugged $enc, return default "all" instead
 342+ $sc->queue = array();
 343+ return $sc;
 344+ }
 345+ if ( $logical_count > CB_MAX_LOGICAL_OP ) {
 346+ # too complex $enc (fabricated or non-realistic), return default "all" instead
 347+ $sc->queue = array();
 348+ return $sc;
 349+ }
 350+ return $sc;
 351+ }
 352+
 353+ /*
 354+ * constructor (creates an instance, initializes $this->infix_queue, returns an instance)
 355+ *
 356+ * converts encoded infix operations queue (string) to
 357+ * decoded infix operations queue (array $this->infix_queue) (1:1)
 358+ * then fills reverse polish operations queue $this->queue
 359+ * @param $enc - string encoded infix operations queue
 360+ * (underscore-separated encoded infix tokens)
 361+ */
 362+ public static function newFromEncodedInfixQueue( $enc ) {
 363+ self::$valid_logical_ops = array( 'and', 'or' );
 364+ self::$valid_bracket_ops = array( '(', ')' );
 365+ $infix_queue = array();
 366+ $q = explode( '_', $enc );
 367+ # {{{ validation of expression
 368+ $brackets_level = 0; $prev_type = '';
 369+ # }}}
 370+ foreach ( $q as &$token ) {
 371+ $result = self::decodeToken( $token );
 372+ $infix_queue[] = $result->token;
 373+ if ( $result->type == 'bracket' ) {
 374+ if ( $result->token == '(' ) {
 375+ $brackets_level++;
 376+ } else {
 377+ $brackets_level--;
 378+ }
 379+ if ( $brackets_level < 0 ) {
 380+ # tampered or bugged $enc, use default "all" instead
 381+ $infix_queue = array();
 382+ break;
 383+ }
 384+ } elseif ( $result->type == 'logical' ) {
 385+ if ( $prev_type == '' || $prev_type == 'logical' ) {
 386+ # tampered or bugged $enc, use default "all" instead
 387+ $infix_queue = array();
 388+ break;
 389+ }
 390+ } elseif ( $result->type == 'comparsion' ) {
 391+ if ( $prev_type == 'comparsion' ) {
 392+ # tampered or bugged $enc, use default "all" instead
 393+ $infix_queue = array();
 394+ break;
 395+ }
 396+ } else {
 397+ # tampered or bugged $enc, use default "all" instead
 398+ $infix_queue = array();
 399+ break;
 400+ }
 401+ $prev_type = $result->type;
 402+ }
 403+ if ( $brackets_level != 0 ) {
 404+ # tampered or bugged $enc, use default "all" instead
 405+ $infix_queue = array();
 406+ }
 407+ return self::newFromInfixTokens( $infix_queue );
 408+ }
 409+
 410+ private static function decodeToken( $token ) {
 411+ $result = (object) array( 'type' => 'unknown', 'token' => '' );
 412+ $matches = array();
 413+ preg_match_all( CB_ENCODED_TOKEN_MATCH, $token, $matches, PREG_SET_ORDER );
 414+ if ( count( $matches ) == 1 && isset( $matches[0] ) && count( $matches[0] ) == 4 ) {
 415+ // decode comparsion op
 416+ $result->token = self::$decoded_fields[ $matches[0][2] ] . ' ' . self::$decoded_cmps[ $matches[0][1] ] . ' ' . (int) $matches[0][3];
 417+ $result->type = 'comparsion';
 418+ return $result;
 419+ }
 420+ $lo_token = strtolower( $token );
 421+ if ( in_array( $lo_token, self::$valid_logical_ops ) ) {
 422+ // decode logical op
 423+ // we store logical ops uppercase for the "prettiness"
 424+ $result->token = strtoupper( $lo_token );
 425+ $result->type = 'logical';
 426+ return $result;
 427+ }
 428+ if ( in_array( $lo_token, self::$valid_bracket_ops ) ) {
 429+ // decode bracket op
 430+ $result->token = $lo_token;
 431+ $result->type = 'bracket';
 432+ return $result;
 433+ }
 434+ return $result;
 435+ }
 436+
 437+ /*
 438+ * constructor (creates an instance, initializes $this->queue, returns an instance)
 439+ *
 440+ * fills up polish notation array $this->queue from infix $tokens provided
 441+ * @param $tokens - array of infix tokens
 442+ */
 443+ # converts list of given infix $tokens into $this->queue of reverse polish notation
 444+ # TODO: more throughout checks for invalid tokens given
 445+ public static function newFromInfixTokens( array $tokens ) {
 446+ $sc = new CB_SqlCond();
 447+ $stack = array(); // every element is stdobject with token and prio fields
 448+ $sc->queue = array();
 449+ foreach ( $tokens as &$token ) {
 450+ switch ( strtoupper( $token ) ) {
 451+ case '(' :
 452+ $prio = 0;
 453+ array_push( $stack, (object) array( 'token' => $token, 'prio' => $prio ) );
 454+ break;
 455+ case ')' :
 456+ $prio = 1;
 457+ while ( $last = array_pop( $stack ) ) {
 458+ if ( is_object( $last ) ) {
 459+ if ( $last->token == '(' ) {
 460+ break;
 461+ }
 462+ array_push( $sc->queue, $last->token );
 463+ } else {
 464+ throw new MWException( 'Open / closing brackets mismatch in ' . __METHOD__ );
 465+ }
 466+ }
 467+ break;
 468+ case 'OR' :
 469+ case 'AND' :
 470+ $prio = strtoupper( $token ) == 'OR' ? 2 : 3;
 471+ while ( $last = array_pop( $stack ) ) {
 472+ if ( is_object( $last ) && $last->prio >= $prio ) {
 473+ array_push( $sc->queue, $last->token );
 474+ } else {
 475+ array_push( $stack, $last );
 476+ break;
 477+ }
 478+ }
 479+ array_push( $stack, (object) array( 'token' => $token, 'prio' => $prio ) );
 480+ break;
 481+ default : // comparsion subexpression
 482+ array_push( $sc->queue, $token );
 483+ }
 484+ }
 485+ while ( $last = array_pop( $stack ) ) {
 486+ if ( !is_object( $last ) ) {
 487+ break;
 488+ }
 489+ array_push( $sc->queue, $last->token );
 490+ }
 491+ return $sc;
 492+ }
 493+
 494+/*
 495+src:'(', 'cat_pages > 1000', 'OR', 'cat_subcats > 10', ')', 'AND', 'cat_files > 100'
 496+dst:cat_pages > 1000;cat_subcats > 10;OR;cat_files > 100;AND
 497+
 498+('','AND','') 'AND' comes to initial empty triple (level 0)
 499+('','AND','cat_files > 100') 'cat_files > 100' becomes right param of current triple (level 0)
 500+(('','OR',''),'AND','cat_files > 100') 'OR' becomes left recursive param of current triple (level 1), because right param is already occupied
 501+(('','OR','cat_subcats > 10'),'AND','cat_files > 100') 'cat_subcats > 10' becomes right param of current triple (level 1)
 502+(('cat_pages > 1000','OR','cat_subcats > 10'),'AND','cat_files > 100') 'cat_pages > 1000' becomes right param of current triple (level 1)
 503+
 504+src:'cat_pages > 1000', 'OR', 'cat_subcats > 10', 'AND', 'cat_files > 100'
 505+dst:cat_pages > 1000;cat_subcats > 10;cat_files > 100;AND;OR
 506+
 507+('','OR','') 'OR' comes to initial empty triple (level 0)
 508+('','OR',('','AND','')) 'AND' becomes right recursive entry (level 1)
 509+('','OR',('','AND','cat_files > 100')) 'cat_files > 100' becomes right param of current triple (level 1)
 510+('','OR',('cat_subcats > 10','AND','cat_files > 100')) 'cat_subcats > 10' becomes left param of current triple, because right param is already occupied (level 1)
 511+('cat_pages > 1000','OR',('cat_subcats > 10','AND','cat_files > 100')) going level up because current triple was occupied; 'cat_pages > 1000' becomes left param of current triple
 512+
 513+1. global counter of queue position, getting elements consequtively from right to left
 514+2. operators are going to current entry, in case currept op is free, otherwise going recursively from right to left (which position is inoccupied)
 515+3. operands are going to current entry, right to left
 516+4. generating the string recursively going from left to right
 517+
 518+in actual code (null,null,null) isset() is used instead of ('','','')
 519+*/
 520+ # generate triples (see example above) from $this->queue
 521+ #
 522+ # param &$currTriple - recursively adds new triples to $currTriple
 523+ private function buildTriples( &$currTriple ) {
 524+ # pre-initialize current triple
 525+ # recursively feed triples with queue tokens, right to left
 526+ while ( $this->queue_pos >= 0 ) {
 527+ $token = $this->queue[ $this->queue_pos ];
 528+ if ( preg_match( '`^AND|OR$`i', $token ) ) {
 529+ // operators
 530+ if ( !isset( $currTriple[1] ) ) {
 531+ $currTriple[1] = $token;
 532+ $this->queue_pos--;
 533+ } elseif ( !isset( $currTriple[2] ) ) {
 534+ $currTriple[2] = array();
 535+ $this->buildTriples( $currTriple[2] );
 536+ } elseif ( !isset( $currTriple[0] ) ) {
 537+ $currTriple[0] = array();
 538+ $this->buildTriples( $currTriple[0] );
 539+ } else {
 540+ return;
 541+ }
 542+ } else {
 543+ // operands
 544+ if ( !isset( $currTriple[2] ) ) {
 545+ $currTriple[2] = $token;
 546+ $this->queue_pos--;
 547+ } elseif ( !isset( $currTriple[0] ) ) {
 548+ $currTriple[0] = $token;
 549+ $this->queue_pos--;
 550+ } else {
 551+ return;
 552+ }
 553+ }
 554+ }
 555+ }
 556+
 557+ /*
 558+ * build properly bracketed infix expression string
 559+ * also builds $this->infix_queue array
 560+ * from triples tree previousely built by CategoryFilter::buildTriples (left to right)
 561+ */
 562+ private $infixLevel; // used to do not include brackets at level 0
 563+ private function getInfixExpr( &$out, $currTriple ) {
 564+ $this->infixLevel++;
 565+ if ( $this->infixLevel != 0 ) {
 566+ $this->infix_queue[] = '(';
 567+ $out .= '(';
 568+ }
 569+ if ( isset( $currTriple[0] ) ) {
 570+ if ( is_array( $currTriple[0] ) ) {
 571+ $this->getInfixExpr( $out, $currTriple[0] );
 572+ } else {
 573+ $this->infix_queue[] = $currTriple[0];
 574+ $out .= $currTriple[0];
 575+ }
 576+ }
 577+ if ( isset( $currTriple[1] ) ) {
 578+ $this->infix_queue[] = $currTriple[1];
 579+ $out .= ' ' . $currTriple[1] . ' ';
 580+ }
 581+ if ( isset( $currTriple[2] ) ) {
 582+ if ( is_array( $currTriple[2] ) ) {
 583+ $this->getInfixExpr( $out, $currTriple[2] );
 584+ } else {
 585+ $this->infix_queue[] = $currTriple[2];
 586+ $out .= $currTriple[2];
 587+ }
 588+ }
 589+ if ( $this->infixLevel != 0 ) {
 590+ $this->infix_queue[] = ')';
 591+ $out .= ')';
 592+ }
 593+ $this->infixLevel--;
 594+ }
 595+
 596+ /*
 597+ * get SQL condition expression with full brackets (to indicate operators priority)
 598+ * *** !!also builds $this->infix_queue array!! ***
 599+ */
 600+ function getCond() {
 601+ $rootTriple = array();
 602+ $this->queue_pos = count( $this->queue ) - 1;
 603+ $this->buildTriples( $rootTriple );
 604+ $out = '';
 605+ $this->infixLevel = -1;
 606+ $this->infix_queue = array();
 607+ # also builds $this->infix_queue array
 608+ $this->getInfixExpr( $out, $rootTriple );
 609+ if ( count( $this->infix_queue ) == 0 ) {
 610+ $this->infix_queue = array( '' ); // default "all"
 611+ }
 612+ return $out;
 613+ }
 614+
 615+ /*
 616+ * get encoded queue to be stored in a cookie or passed from PHP AJAX handler to js callback
 617+ * @param $infix set true when infix queue is decoded, otherwise brackets cause to reset to default "all"
 618+ *
 619+ */
 620+ function getEncodedQueue( $is_infix = false ) {
 621+ $result = '';
 622+ if ( $is_infix ) {
 623+ $valid_single_ops = array( '(', ')', 'or', 'and' );
 624+ if ( !is_array( $this->infix_queue ) ) {
 625+ $this->getCond();
 626+ }
 627+ $queue = &$this->infix_queue;
 628+ } else {
 629+ $valid_single_ops = array( 'or', 'and' );
 630+ $queue = &$this->queue;
 631+ }
 632+ if ( count( $queue ) == 1 && $queue[0] == '' ) {
 633+ return 'all'; // default "show all"
 634+ }
 635+ $firstElem = true;
 636+ foreach ( $queue as &$token ) {
 637+ if ( $firstElem ) {
 638+ $firstElem = false;
 639+ } else {
 640+ $result .= '_';
 641+ }
 642+ $field = $num = '';
 643+ if ( in_array( $lo_token = strtolower( $token ), $valid_single_ops ) ) {
 644+ $op = $lo_token;
 645+ } else {
 646+ // comparsion subexpression ?
 647+ preg_match_all( CB_COND_TOKEN_MATCH, $token, $matches, PREG_SET_ORDER );
 648+ if ( count( $matches ) == 1 && isset( $matches[0] ) && count( $matches[0] ) == 4 ) {
 649+ list( $expr, $field, $cmp, $num ) = $matches[0];
 650+ if ( isset( self::$encoded_fields[ $field ] ) ) {
 651+ $field = self::$encoded_fields[ $field ];
 652+ } else {
 653+ return 'all'; // default "show all"
 654+ }
 655+ if ( isset( self::$encoded_cmps[ $cmp ] ) ) {
 656+ $op = self::$encoded_cmps[ $cmp ];
 657+ } else {
 658+ return 'all'; // default "show all"
 659+ }
 660+ } else {
 661+ return 'all'; // default "show all"
 662+ }
 663+ }
 664+ if ( $field == '' ) {
 665+ $result .= $op;
 666+ } elseif ( $num == '' ) {
 667+ $result .= $op . $field;
 668+ } else {
 669+ $result .= $op . $field . $num;
 670+ }
 671+ }
 672+ return $result;
 673+ }
 674+
 675+} /* end of CB_SqlCond class */
Property changes on: trunk/extensions/CategoryBrowser/CategoryBrowserBasic.php
___________________________________________________________________
Added: svn:eol-style
1676 + native
Index: trunk/extensions/CategoryBrowser/CategoryBrowserCtrl.php
@@ -0,0 +1,358 @@
 2+<?php
 3+/**
 4+ * ***** BEGIN LICENSE BLOCK *****
 5+ * This file is part of CategoryBrowser.
 6+ *
 7+ * CategoryBrowser is free software; you can redistribute it and/or modify
 8+ * it under the terms of the GNU General Public License as published by
 9+ * the Free Software Foundation; either version 2 of the License, or
 10+ * (at your option) any later version.
 11+ *
 12+ * CategoryBrowser is distributed in the hope that it will be useful,
 13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15+ * GNU General Public License for more details.
 16+ *
 17+ * You should have received a copy of the GNU General Public License
 18+ * along with CategoryBrowser; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * ***** END LICENSE BLOCK *****
 22+ *
 23+ * CategoryBrowser is an AJAX-enabled category filter and browser for MediaWiki.
 24+ *
 25+ * To activate this extension :
 26+ * * Create a new directory named CategoryBrowser into the directory "extensions" of MediaWiki.
 27+ * * Place the files from the extension archive there.
 28+ * * Add this line at the end of your LocalSettings.php file :
 29+ * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
 30+ *
 31+ * @version 0.2.1
 32+ * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
 33+ * @author Dmitriy Sintsov <questpc@rambler.ru>
 34+ * @addtogroup Extensions
 35+ */
 36+
 37+if ( !defined( 'MEDIAWIKI' ) ) {
 38+ die( "This file is a part of MediaWiki extension.\n" );
 39+}
 40+
 41+/*
 42+ * browsing class - both for special page and AJAX calls
 43+ */
 44+class CategoryBrowser {
 45+
 46+ function __construct() {
 47+ CB_Setup::initUser();
 48+ }
 49+
 50+ /*
 51+ * include stylesheets and scripts; set javascript variables
 52+ * @param $outputPage - an instance of OutputPage
 53+ * @param $isRTL - whether the current language is RTL
 54+ * currently set: cookie prefix;
 55+ * localAllOp, local1opTemplate, local2opTemplate, localDbFields, localBrackets, localBoolOps, localCmpOps
 56+ */
 57+ static function headScripts( &$outputPage, $isRTL ) {
 58+ global $wgJsMimeType;
 59+ $outputPage->addLink(
 60+ array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => CB_Setup::$ScriptPath . '/category_browser.css?' . CB_Setup::$version )
 61+ );
 62+ if ( $isRTL ) {
 63+ $outputPage->addLink(
 64+ array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => CB_Setup::$ScriptPath . '/category_browser_rtl.css?' . CB_Setup::$version )
 65+ );
 66+ }
 67+ $outputPage->addScript(
 68+ '<script type="' . $wgJsMimeType . '" src="' . CB_Setup::$ScriptPath . '/category_browser.js?' . CB_Setup::$version . '"></script>
 69+ <script type="' . $wgJsMimeType . '">
 70+ CB_lib.setCookiePrefix("' . CB_Setup::getJsCookiePrefix() . '");
 71+ CB_ConditionEditor.setLocalNames( ' .
 72+ CategoryBrowser::getJsObject( 'cbLocalMessages', 'apply_button', 'all_op', 'op1_template', 'op2_template', 'ie6_warning' ) . ", \n\t\t\t" .
 73+ CategoryBrowser::getJsObject( 'cbLocalEditHints', 'left', 'right', 'remove', 'copy', 'append', 'clear', 'paste', 'paste_right' ) . ", \n\t\t\t" .
 74+ CategoryBrowser::getJsObject( 'cbLocalDbFields', 's', 'p', 'f' ) . ", \n\t\t\t" .
 75+ CategoryBrowser::getJsObject( 'cbLocalOps', 'lbracket', 'rbracket' ) . ", \n\t\t\t" .
 76+ CategoryBrowser::getJsObject( 'cbLocalOps', 'or', 'and' ) . ", \n\t\t\t" .
 77+ CategoryBrowser::getJsObject( 'cbLocalOps', 'le', 'ge', 'eq' ) .
 78+ ' );</script>' . "\n" );
 79+ }
 80+
 81+ static function getJsObject( $method_name ) {
 82+ $args = func_get_args();
 83+ array_shift( $args ); // remove $method_name from $args
 84+ $result = '{ ';
 85+ $firstElem = true;
 86+ foreach ( $args as &$arg ) {
 87+ if ( $firstElem ) {
 88+ $firstElem = false;
 89+ } else {
 90+ $result .= ', ';
 91+ }
 92+ $result .= $arg . ': "' . Xml::escapeJsString( call_user_func( array( 'self', $method_name ), $arg ) ) . '"';
 93+ }
 94+ $result .= ' }';
 95+ return $result;
 96+ }
 97+
 98+ /*
 99+ * currently passed to Javascript:
 100+ * localMessages, localDbFields, localBrackets, localBoolOps, localCmpOps
 101+ */
 102+ /*
 103+ * getJsObject callback
 104+ */
 105+ static private function cbLocalMessages( $arg ) {
 106+ return wfMsg( "cb_${arg}" );
 107+ }
 108+
 109+ static private function cbLocalEditHints( $arg ) {
 110+ return wfMsg( "cb_edit_${arg}_hint" );
 111+ }
 112+
 113+ /*
 114+ * getJsObject callback
 115+ */
 116+ static private function cbLocalOps( $arg ) {
 117+ return wfMsg( "cb_${arg}_op" );
 118+ }
 119+
 120+ /*
 121+ * getJsObject callback
 122+ */
 123+ static private function cbLocalDbFields( $arg ) {
 124+ return wfMsg( "cb_" . CB_SqlCond::$decoded_fields[ $arg ] );
 125+ }
 126+
 127+ /*
 128+ * generates "complete" ranges
 129+ * @param $source_ranges source ranges which contain only decoded infix queue
 130+ * @return "complete" ranges which contain decoded infix queue and encoded polish queue
 131+ */
 132+ static function generateRanges( array &$source_ranges ) {
 133+ $ranges = array();
 134+ foreach ( $source_ranges as $infix_queue ) {
 135+ $sqlCond = CB_SqlCond::newFromInfixTokens( $infix_queue );
 136+ $ranges[] = (object) array( 'infix_decoded' => $infix_queue, 'polish_encoded' => $sqlCond->getEncodedQueue( false ) );
 137+ }
 138+ return $ranges;
 139+ }
 140+
 141+ /*
 142+ * add new "complete" range to "complete" ranges list
 143+ * @param $ranges "complete" ranges list (decoded infix, encoded polish)
 144+ * @param $sqlCond will be added to $ranges only when no such queue already exists
 145+ * @modifies $ranges
 146+ */
 147+ static function addRange( array &$ranges, CB_SqlCond $sqlCond ) {
 148+ $encPolishQueue = $sqlCond->getEncodedQueue( false );
 149+ $queueExists = false;
 150+ foreach ( $ranges as &$range ) {
 151+ if ( $range->polish_encoded == $encPolishQueue ) {
 152+ $queueExists = true;
 153+ break;
 154+ }
 155+ }
 156+ if ( !$queueExists ) {
 157+ $sqlCond->getCond(); // build infix queue array
 158+ $ranges[] = (object) array( 'infix_decoded' => $sqlCond->infix_queue, 'polish_encoded' => $encPolishQueue );
 159+ }
 160+ }
 161+
 162+ /*
 163+ * generates SQL condition selector html code
 164+ * @param $ranges - array of "complete" (decode infix/encoded polish) token queues
 165+ * @param $rootPager - root pager currently used with this selector
 166+ * @return selector html code
 167+ */
 168+ static function generateSelector( array &$ranges, CB_RootPager $rootPager ) {
 169+ # {{{ condition form/select template
 170+ $condOptList = array();
 171+ // do not pass current pager's limit because it's meaningless
 172+ // we need MAX (default) possible limit, not the current limit
 173+ // also current limit is being calculated only during the call $pager->getCurrentRows()
 174+ // TODO: implement the field to select pager's default limit
 175+ $js_func_call = 'return CategoryBrowser.setExpr(this,' . CB_PAGING_ROWS . ')';
 176+ // FF doesn't always fire onchange, IE doesn't always fire onmouseup
 177+ $condFormTpl = array (
 178+ array( '__tag' => 'noscript', 'class' => 'cb_noscript', 0 => wfMsg( 'cb_requires_javascript' ) ),
 179+ array( '__tag' => 'form', '__end' => "\n",
 180+ array( '__tag' => 'select', 'id' => 'cb_expr_select', 'onmouseup' => $js_func_call, 'onchange' => $js_func_call, '__end' => "\n", 0 => &$condOptList )
 181+ )
 182+ );
 183+ # }}}
 184+ $queueFound = false;
 185+ $selectedEncPolishQueue = $rootPager->sqlCond->getEncodedQueue( false );
 186+ foreach ( $ranges as &$range ) {
 187+ $condOptList[] = self::generateOption( $range, $selectedEncPolishQueue );
 188+ if ( $range->polish_encoded == $selectedEncPolishQueue ) {
 189+ $queueFound = true;
 190+ }
 191+ }
 192+ if ( !$queueFound ) {
 193+ throw new MWException( 'Either the selected queue was not added to ranges list via CategoryBrowser::addRange(), or wrong ranges list passed to ' . __METHOD__ );
 194+ }
 195+ return CB_XML::toText( $condFormTpl );
 196+ }
 197+
 198+ static function generateOption( $range, $selectedValue, $nodeName = 'option' ) {
 199+ # {{{ condition select's option template
 200+ $condOptVal = '';
 201+ $condOptName = '';
 202+ $condOptInfix = '';
 203+ $condOptTpl =
 204+ array( '__tag' => $nodeName, 'value' => &$condOptVal, 'infixexpr' => &$condOptInfix, 0 => &$condOptName, '__end' => "\n" );
 205+ # }}}
 206+ $le = new CB_LocalExpr( $range->infix_decoded );
 207+ $condOptVal = CB_Setup::specialchars( $range->polish_encoded );
 208+ $sqlCond = CB_SqlCond::newFromEncodedPolishQueue( $range->polish_encoded );
 209+ $condOptInfix = CB_Setup::specialchars( $sqlCond->getEncodedQueue( true ) );
 210+ if ( $range->polish_encoded == $selectedValue ) {
 211+ $condOptTpl['selected'] = null;
 212+ }
 213+ $condOptName = CB_Setup::entities( $le->toString() );
 214+ return CB_XML::toText( $condOptTpl );
 215+ }
 216+
 217+ /*
 218+ * called via AJAX to get root list for specitied offset, limit
 219+ * where condition will be read from the cookie previousely set
 220+ * @param $args[0] : encoded reverse polish queue
 221+ * @param $args[1] : category name filter string
 222+ * @param $args[2] : category name filter case insensitive flag
 223+ * @param $args[3] : offset (optional)
 224+ * @param $args[4] : limit (optional)
 225+ */
 226+ static function getRootOffsetHtml() {
 227+ wfLoadExtensionMessages( 'CategoryBrowser' );
 228+ $args = func_get_args();
 229+ $limit = ( count( $args ) > 4 ) ? abs( intval( $args[4] ) ) : CB_PAGING_ROWS;
 230+ $offset = ( count( $args ) > 3 ) ? abs( intval( $args[3] ) ) : 0;
 231+ $nameFilterCI = ( count( $args ) > 2 ) ? $args[2] == 'true' : false;
 232+ $nameFilter = ( count( $args ) > 1 ) ? $args[1] : '';
 233+ $encPolishQueue = ( count( $args ) > 0 ) ? $args[0] : 'all';
 234+ $cb = new CategoryBrowser();
 235+ $sqlCond = CB_SqlCond::newFromEncodedPolishQueue( $encPolishQueue );
 236+ $rootPager = CB_RootPager::newFromSqlCond( $sqlCond, $offset, $limit );
 237+ $rootPager->setNameFilter( $nameFilter, $nameFilterCI );
 238+ $rootPager->getCurrentRows();
 239+ $catView = new CB_CategoriesView( $rootPager );
 240+ $catlist = $catView->generateList();
 241+ return CB_XML::toText( $catlist );
 242+ }
 243+
 244+ /*
 245+ * called via AJAX to get list of (subcategories,pages,files) for specitied parent category id, offset, limit
 246+ * @param $args[0] : type of pager ('subcats','pages','files')
 247+ * @param $args[1] : parent category id
 248+ * @param $args[2] : offset (optional)
 249+ * @param $args[3] : limit (optional)
 250+ */
 251+ static function getSubOffsetHtml() {
 252+ $pager_types = array(
 253+ 'subcats' => array(
 254+ 'js_nav_func' => "subCatsNav",
 255+ 'select_fields' => "cl_sortkey, cat_id, cat_title, cat_subcats, cat_pages, cat_files",
 256+ 'ns_cond' => "page_namespace = " . NS_CATEGORY,
 257+ 'default_limit' => CB_Setup::$categoriesLimit
 258+ ),
 259+ 'pages' => array(
 260+ 'js_nav_func' => "pagesNav",
 261+ 'select_fields' => "page_title, page_namespace, page_len, page_is_redirect",
 262+ 'ns_cond' => "NOT page_namespace IN (" . NS_FILE . "," . NS_CATEGORY . ")",
 263+ 'default_limit' => CB_Setup::$pagesLimit
 264+ ),
 265+ 'files' => array(
 266+ 'js_nav_func' => "filesNav",
 267+ 'select_fields' => "page_title, page_namespace, page_len, page_is_redirect",
 268+ 'ns_cond' => "page_namespace = " . NS_FILE,
 269+ 'default_limit' => CB_Setup::$filesLimit * CB_Setup::$imageGalleryPerRow
 270+ )
 271+ );
 272+ wfLoadExtensionMessages( 'CategoryBrowser' );
 273+ $args = func_get_args();
 274+ if ( count( $args ) < 2 ) {
 275+ return 'Too few parameters in ' . __METHOD__;
 276+ }
 277+ if ( !isset( $pager_types[ $args[0] ] ) ) {
 278+ return 'Unknown pager type in ' . __METHOD__;
 279+ }
 280+ $pager_type = & $pager_types[ $args[0] ];
 281+ $limit = ( count( $args ) > 3 ) ? abs( intval( $args[3] ) ) : $pager_type[ 'default_limit' ];
 282+ $offset = ( count( $args ) > 2 ) ? abs( intval( $args[2] ) ) : 0;
 283+ $parentCatId = abs( intval( $args[1] ) );
 284+ $cb = new CategoryBrowser();
 285+ $pager = new CB_SubPager( $parentCatId, $offset, $limit,
 286+ $pager_type[ 'js_nav_func' ],
 287+ $pager_type[ 'select_fields' ],
 288+ $pager_type[ 'ns_cond' ] );
 289+ $pager->getCurrentRows();
 290+ switch ( $pager->getListType() ) {
 291+ case 'generateCatList' :
 292+ $view = new CB_CategoriesView( $pager );
 293+ break;
 294+ case 'generatePagesList' :
 295+ $view = new CB_PagesView( $pager );
 296+ break;
 297+ case 'generateFilesList' :
 298+ # respect extension & core settings
 299+ global $wgOut, $wgCategoryMagicGallery;
 300+ // unstub $wgOut, otherwise $wgOut->mNoGallery may be unavailable
 301+ // strange, but calling wfDebug() instead does not unstub $wgOut successfully
 302+ $wgOut->getHeadItems();
 303+ if ( CB_Setup::$imageGalleryPerRow < 1 || !$wgCategoryMagicGallery || $wgOut->mNoGallery ) {
 304+ $view = new CB_PagesView( $pager );
 305+ } else {
 306+ $view = new CB_FilesView( $pager );
 307+ }
 308+ break;
 309+ default :
 310+ return 'Unknown list type in ' . __METHOD__;
 311+ }
 312+ $list = $view->generateList();
 313+ return CB_XML::toText( $list );
 314+ }
 315+
 316+ /*
 317+ * called via AJAX to setup custom edited expression cookie then display category root offset
 318+ * @param $args[0] : encoded infix expression
 319+ * @param $args[1] : category name filter string
 320+ * @param $args[2] : category name filter case insensitive flag
 321+ * @param $args[3] : 1 - cookie has to be set, 0 - cookie should not be set (expression is pre-defined or already was stored)
 322+ * @param $args[4] : pager limit (optional)
 323+ */
 324+ static function applyEncodedQueue() {
 325+ CB_Setup::initUser();
 326+ $args = func_get_args();
 327+ $limit = ( ( count( $args ) > 4 ) ? intval( $args[4] ) : CB_PAGING_ROWS );
 328+ $setCookie = ( ( count( $args ) > 3 ) ? $args[3] != 0 : false );
 329+ $nameFilterCI = ( count( $args ) > 2 ) ? $args[2] == 'true' : false;
 330+ $nameFilter = ( count( $args ) > 1 ) ? $args[1] : '';
 331+ $encInfixQueue = ( ( count( $args ) > 0 ) ? $args[0] : 'all' );
 332+ $sqlCond = CB_SqlCond::newFromEncodedInfixQueue( $encInfixQueue );
 333+ $encPolishQueue = $sqlCond->getEncodedQueue( false );
 334+ if ( $setCookie ) {
 335+ CB_Setup::setCookie( 'rootcond', $encPolishQueue, time() + 60 * 60 * 24 * 7 );
 336+ }
 337+ return self::getRootOffsetHtml( $encPolishQueue, $nameFilter, $nameFilterCI, 0, $limit );
 338+ }
 339+
 340+ /*
 341+ * called via AJAX to generate new selected option when the selected rootcond is new (the rootcond cookie was set)
 342+ * @param $args[0] currently selected expression in encoded infix format
 343+ */
 344+ static function generateSelectedOption() {
 345+ wfLoadExtensionMessages( 'CategoryBrowser' );
 346+ CB_Setup::initUser();
 347+ $args = func_get_args();
 348+ if ( count( $args ) < 1 ) {
 349+ throw new MWException( 'Argument 0 is missing in ' . __METHOD__ );
 350+ }
 351+ $encInfixQueue = $args[0];
 352+ $sqlCond = CB_SqlCond::newFromEncodedInfixQueue( $encInfixQueue );
 353+ $ranges = array();
 354+ self::addRange( $ranges, $sqlCond );
 355+ # generate div instead of option to avoid innerHTML glitches in IE
 356+ return self::generateOption( $ranges[0], $sqlCond->getEncodedQueue( false ), 'div' );
 357+ }
 358+
 359+} /* end of CategoryBrowser class */
Property changes on: trunk/extensions/CategoryBrowser/CategoryBrowserCtrl.php
___________________________________________________________________
Added: svn:eol-style
1360 + native

Status & tagging log