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 = ' '; // |
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 = ' '; // |
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 = ' '; // |
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 = ' '; // |
| 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 = ' '; // |
| 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 = ' '; // |
| 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 |
1 | 310 | + 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 |
1 | 353 | + 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 |
1 | 676 | + 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 |
1 | 360 | + native |