r72293 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r72292‎ | r72293 | r72294 >
Date:16:16, 3 September 2010
Author:questpc
Status:deferred
Tags:
Comment:
Many bugfixes. Optional browsing of parent categories (reverse browsing). Optional selection of categories which has no parents.
Modified paths:
  • /trunk/extensions/CategoryBrowser/CategoryBrowser.php (modified) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserBasic.php (modified) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserCtrl.php (modified) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserModel.php (modified) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserPage.php (modified) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowserView.php (modified) (history)
  • /trunk/extensions/CategoryBrowser/CategoryBrowser_i18n.php (modified) (history)
  • /trunk/extensions/CategoryBrowser/INSTALL (modified) (history)
  • /trunk/extensions/CategoryBrowser/README (modified) (history)
  • /trunk/extensions/CategoryBrowser/category_browser.css (modified) (history)
  • /trunk/extensions/CategoryBrowser/category_browser.js (modified) (history)
  • /trunk/extensions/CategoryBrowser/category_browser_rtl.css (modified) (history)

Diff [purge]

Index: trunk/extensions/CategoryBrowser/CategoryBrowserView.php
@@ -27,7 +27,7 @@
2828 * * Add this line at the end of your LocalSettings.php file :
2929 * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
3030 *
31 - * @version 0.2.1
 31+ * @version 0.3.1
3232 * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
3333 * @author Dmitriy Sintsov <questpc@rambler.ru>
3434 * @addtogroup Extensions
@@ -60,9 +60,10 @@
6161 # {{{ ajax link template
6262 $this->ajax_onclick = '';
6363 $this->ajax_link_text = '';
 64+ $this->ajax_title_attr = '';
6465 if ( !isset( $this->ajax_link_tpl ) ) {
6566 $this->ajax_link_tpl =
66 - array( '__tag' => 'a', 'class' => 'cb_sublink', 'href' => '', 'onclick' => &$this->ajax_onclick, 0 => &$this->ajax_link_text );
 67+ array( '__tag' => 'a', 'class' => 'cb_sublink', 'href' => '', 'onclick' => &$this->ajax_onclick, 'title' => &$this->ajax_title_attr, 0 => &$this->ajax_link_text );
6768 }
6869 # }}}
6970 }
@@ -152,7 +153,6 @@
153154 }
154155 } /* end of CB_AbstractPagesView class */
155156
156 -
157157 class CB_CategoriesView extends CB_AbstractPagesView {
158158
159159 function generateList() {
@@ -190,16 +190,15 @@
191191 } else {
192192 $cat_title_obj = Title::makeTitle( NS_CATEGORY, $cat_title_str );
193193 }
 194+ $js_cat_string = "'" . Xml::escapeJsString( $cat_title_str ) . "'";
194195
195 - # calculate exact number of pages alone
196 - $cat->pages_only = intval( $cat->cat_pages ) - intval( $cat->cat_subcats ) - intval( $cat->cat_files );
197196 # generate tree "expand" sign
198197 $this->initAjaxLinkTpl();
199198 if ( $cat->cat_subcats === NULL ) {
200199 $cat_expand_sign = 'x';
201200 $subcat_count_hint = '';
202201 } elseif ( $cat->cat_subcats > 0 ) {
203 - $this->ajax_onclick = 'return CategoryBrowser.subCatsPlus(this,' . $cat->cat_id . ')';
 202+ $this->ajax_onclick = 'return CategoryBrowser.subCatsPlus(this,' . $js_cat_string . ')';
204203 $this->ajax_link_text = '+';
205204 $cat_expand_sign = CB_XML::toText( $this->ajax_link_tpl );
206205 $subcat_count_hint = wfMsgExt( 'cb_has_subcategories', array( 'parsemag' ), $cat->cat_subcats );
@@ -211,20 +210,30 @@
212211 # create AJAX links for viewing categories, pages, files, belonging to this category
213212 $ajax_links = '';
214213 $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 ) : '' );
219214
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 ) : '' );
 215+ $this->ajax_onclick = 'return CategoryBrowser.subCatsLink(this,' . $js_cat_string . ')';
 216+ $this->ajax_link_text = wfMsgExt( 'cb_has_subcategories', array( 'parsemag' ), $cat->cat_subcats );
 217+ $cat_subcats = ( ( $cat->cat_subcats > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
223218
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;
 219+ $this->ajax_onclick = 'return CategoryBrowser.pagesLink(this,' . $js_cat_string . ')';
 220+ $this->ajax_link_text = wfMsgExt( 'cb_has_pages', array( 'parsemag' ), $cat->cat_pages_only );
 221+ $cat_pages = ( ( $cat->cat_pages_only > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
 222+
 223+ $this->ajax_onclick = 'return CategoryBrowser.filesLink(this,' . $js_cat_string . ')';
 224+ $this->ajax_link_text = wfMsgExt( 'cb_has_files', array( 'parsemag' ), $cat->cat_files );
 225+ $cat_files = ( ( $cat->cat_files > 0 ) ? ' | ' . CB_XML::toText( $this->ajax_link_tpl ) : '' );
 226+
 227+ if ( CB_Setup::$allowNestedParents ) {
 228+ $this->ajax_onclick = 'return CategoryBrowser.parentCatsLink(this,' . $js_cat_string . ')';
 229+ $this->ajax_link_text = '↑';
 230+ $this->ajax_title_attr = wfMsg( 'cb_has_parentcategories' );
 231+ $cat_parentcats = ' | ' . CB_XML::toText( $this->ajax_link_tpl );
 232+ } else {
 233+ $cat_parentcats = '';
228234 }
 235+
 236+ $ajax_links .= $cat_subcats . $cat_pages . $cat_files . $cat_parentcats;
 237+
229238 $cat_link = CB_Setup::$skin->link( $cat_title_obj, $cat_title_obj->getText() );
230239 # show sortkey, when it does not match title name
231240 $cat_link .= $this->addSortkey( $cat_title_obj, $cat );
@@ -252,7 +261,8 @@
253262 $page_link = '';
254263 $page_tpl =
255264 array( '__tag' => 'div', 'class' => 'cb_cat_container', '__end' => "\n",
256 - array( '__tag' => 'div', 'class' => 'cb_cat_item', 0 => &$page_link )
 265+ array( '__tag' => 'span', 'class' => 'cb_cat_expand', 0 => '' ), // empty cb_cat_expand makes line height matching to CB_CategoriesView
 266+ array( '__tag' => 'span', 'class' => 'cb_cat_item', 0 => &$page_link )
257267 );
258268 # }}}
259269 # create list of pages
@@ -271,12 +281,8 @@
272282
273283 } /* end of CB_PagesView class */
274284
275 -class CB_FilesView extends CB_AbstractPagesView {
 285+class CB_FilesView extends CB_PagesView {
276286
277 - function __construct( CB_SubPager $pager ) {
278 - parent::__construct( $pager );
279 - }
280 -
281287 function generateList() {
282288 if ( $this->pager->offset == -1 ) {
283289 return ''; // list has no entries
Index: trunk/extensions/CategoryBrowser/CategoryBrowserPage.php
@@ -27,7 +27,7 @@
2828 * * Add this line at the end of your LocalSettings.php file :
2929 * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
3030 *
31 - * @version 0.2.1
 31+ * @version 0.3.1
3232 * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
3333 * @author Dmitriy Sintsov <questpc@rambler.ru>
3434 * @addtogroup Extensions
@@ -53,24 +53,23 @@
5454
5555 var $source_ranges =
5656 array(
57 -// start of test entries
58 -// NOTE: do not forget that only '>=', '<=', '=' comparsions are allowed, otherwise the bug check will be "triggered"
59 -// array( '(', 'cat_pages >= 1000', 'OR', 'cat_subcats >= 10', ')', 'AND', 'cat_files >= 100' ),
60 -// array( 'cat_pages >= 1000', 'OR', 'cat_subcats >= 10', 'AND', 'cat_files >= 100' ),
61 -// array( 'cat_pages >= 100', 'OR', 'cat_subcats >= 10' ),
62 -// array( '(', 'cat_pages <= 100', 'AND', 'cat_pages >= 10', ')', 'OR', '(', 'cat_subcats >= 0', 'AND', 'cat_subcats <= 10', ')' ),
63 -// end of test entries
64 - array( '' ), // default value "all",
65 - array( 'cat_pages >= 100', 'OR', 'cat_subcats >= 1', 'OR', 'cat_files >= 10' ),
66 - array( 'cat_pages >= 1000', 'OR', 'cat_subcats >= 10', 'OR', 'cat_files >= 100' ),
67 - array( 'cat_pages >= 10000', 'OR', 'cat_subcats >= 100', 'OR', 'cat_files >= 1000' ),
68 - array( 'cat_subcats >= 1' ),
69 - array( 'cat_pages >= 1' ),
70 - array( 'cat_files >= 1' ),
71 - array( 'cat_subcats = 0' ),
72 - array( 'cat_pages = 0' ),
73 - array( 'cat_files = 0' ),
74 - );
 57+ // start of test entries
 58+/* '(_gep1000_or_ges10_)_and_gef100',
 59+ 'gep1000_or_ges10_and_gef100',
 60+ 'gep100_or_ges10',
 61+ '(_lep100_and_gep10_)_or_(_ges0_and_les10_)', */
 62+ // end of test entries
 63+ '', // default value "all",
 64+ 'gep10000_or_ges100_or_gef1000',
 65+ 'gep1000_or_ges10_or_gef100',
 66+ 'gep100_or_ges1_or_gef10',
 67+ 'ges1',
 68+ 'gep1',
 69+ 'gef1',
 70+ 'eqs0',
 71+ 'eqp0',
 72+ 'eqf0'
 73+ );
7574 var $ranges;
7675
7776 function execute( $param ) {
@@ -105,15 +104,20 @@
106105 # {{{ top template
107106 $condSelector = '';
108107 $catlist = array();
109 - $js_setNameFilter = 'CategoryBrowser.setNameFilter( this )';
110 - $nameFilterFields = array(
111 - array( '__tag' => 'input', 'type' => 'text', 'onkeyup' => $js_setNameFilter, 'onchange' => $js_setNameFilter, 'id' => 'cb_cat_name_filter' )
112 - );
 108+ $js_setFilter = 'CategoryBrowser.setFilter()';
 109+ $filterFields = array();
113110 if ( CB_Setup::$cat_title_CI != '' ) {
114111 // case insensitive search is possible
115 - $nameFilterFields[] = wfMsg( 'cb_cat_name_filter_ci' );
116 - $nameFilterFields[] = array( '__tag' => 'input', 'type' => 'checkbox', 'onchange' => $js_setNameFilter, 'id' => 'cb_cat_name_filter_ci', 'checked' => null );
 112+ $filterFields[] = array( '__tag' => 'span', 0 => wfMsg( 'cb_cat_name_filter_ci' ) );
 113+ $filterFields[] = array( '__tag' => 'input', 'class' => 'cb_filter_field', 'type' => 'checkbox', 'onchange' => $js_setFilter, 'id' => 'cb_cat_name_filter_ci', 'title' => wfMsg( 'cb_cat_name_filter_ci' ), 'checked' => null );
117114 }
 115+ if ( CB_Setup::$allowNoParentsOnly ) {
 116+ $filterFields[] =
 117+ array(
 118+ array( '__tag' => 'span', 'class' => 'cb_filter_field', 0 => wfMsg( 'cb_show_no_parents_only' ) ),
 119+ array( '__tag' => 'input', 'class' => 'cb_filter_field', 'type' => 'checkbox', 'onchange' => $js_setFilter, 'onclick' => $js_setFilter, 'title' => wfMsg( 'cb_show_no_parents_only' ), 'id' =>'cb_cat_no_parents_only' )
 120+ );
 121+ }
118122 $top_tpl =
119123 array( '__tag' => 'table', 'class' => 'cb_top_container', '__end' => "\n",
120124 array( '__tag' => 'tr', '__end' => "\n",
@@ -121,8 +125,14 @@
122126 ),
123127 array( '__tag' => 'tr', '__end' => "\n",
124128 array( '__tag' => 'td', 'class' => 'cb_toolbox_bottom', '__end' => "\n",
125 - array( wfMsg( 'cb_cat_name_filter' ) ),
126 - &$nameFilterFields,
 129+ array( '__tag' => 'div', 'class' => 'cb_filter_container',
 130+ wfMsg( 'cb_cat_name_filter' ),
 131+ array( '__tag' => 'input', 'type' => 'text', 'onkeyup' => $js_setFilter, 'onchange' => $js_setFilter, 'id' => 'cb_cat_name_filter' ),
 132+ array( '__tag' => 'input', 'type' => 'button', 'class' => 'cb_filter_field', 'onclick' => 'CategoryBrowser.clearNameFilter(this)', 'title' => wfMsg( 'cb_cat_name_filter_clear' ), 'value' =>'&#8251;' )
 133+ ),
 134+ array( '__tag' => 'div', 'class' => 'cb_filter_container',
 135+ &$filterFields
 136+ )
127137 )
128138 ),
129139 array( '__tag' => 'tr', '__end' => "\n",
Index: trunk/extensions/CategoryBrowser/CategoryBrowser_i18n.php
@@ -27,7 +27,7 @@
2828 * * Add this line at the end of your LocalSettings.php file :
2929 * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
3030 *
31 - * @version 0.2.1
 31+ * @version 0.3.1
3232 * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
3333 * @author Dmitriy Sintsov <questpc@rambler.ru>
3434 * @addtogroup Extensions
@@ -49,12 +49,15 @@
5050 'cb_ie6_warning' => 'The condition editor does not work in Internet Explorer 6.0 or earlier versions.
5151 However, browsing of pre-defined conditions should work normally.
5252 Please change or upgrade your browser, if possible.',
 53+ 'cb_show_no_parents_only' => 'Show only categories which has no parents',
5354 'cb_cat_name_filter' => 'Search for category by name:',
54 - 'cb_cat_name_filter_ci' => '(case insensitive)',
 55+ 'cb_cat_name_filter_clear' => 'Press to clear category name filter',
 56+ 'cb_cat_name_filter_ci' => 'Case insensitive',
5557 'cb_copy_line_hint' => 'Use the [+] and [>+] buttons to copy and paste operators into the selected expression',
5658 'cb_has_subcategories' => '$1 {{PLURAL:$1|subcategory|subcategories}}',
5759 'cb_has_pages' => '$1 {{PLURAL:$1|page|pages}}',
5860 'cb_has_files' => '$1 {{PLURAL:$1|file|files}}',
 61+ 'cb_has_parentcategories' => 'parent categories (if any)',
5962 'cb_previous_items_link' => 'Previous',
6063 'cb_previous_items_stats' => ' ($1 - $2)',
6164 'cb_previous_items_line' => '$1 $2',
@@ -112,12 +115,15 @@
113116 'cb_ie6_warning' => 'Редактор выражений не поддерживается в Internet Explorer версии 6.0 или более ранних.
114117 Возможен лишь просмотр предопределенных выражений.
115118 Пожалуйста поменяйте или обновите ваш браузер.',
 119+ 'cb_show_no_parents_only' => 'Показывать только категории без родителей',
116120 'cb_cat_name_filter' => 'Поиск категории по имени:',
117 - 'cb_cat_name_filter_ci' => '(без учёта регистра)',
 121+ 'cb_cat_name_filter_clear' => 'Нажмите здесь для очистки поля поиска категории по имени',
 122+ 'cb_cat_name_filter_ci' => 'Без учёта регистра',
118123 'cb_copy_line_hint' => 'Используйте кнопки [+] и [>+] для копирования оператора в выбранное выражение',
119124 'cb_has_subcategories' => '$1 {{PLURAL:$1|подкатегория|подкатегории|подкатегорий}}',
120125 'cb_has_pages' => '$1 {{PLURAL:$1|страница|страницы|страниц}}',
121126 'cb_has_files' => '$1 {{PLURAL:$1|файл|файла|файлов}}',
 127+ 'cb_has_parentcategories' => 'родительские категории (если есть)',
122128 'cb_previous_items_link' => 'Предыдущие',
123129 'cb_next_items_link' => 'Следующие',
124130 'cb_next_items_stats' => ' (начиная с $1)',
Index: trunk/extensions/CategoryBrowser/INSTALL
@@ -1,4 +1,4 @@
2 -MediaWiki extension CategoryBrowser, version 0.2.1
 2+MediaWiki extension CategoryBrowser, version 0.3.1
33
44 * download the latest available version and extract it to your wiki extension directory.
55 * add the following line to LocalSettings.php
Index: trunk/extensions/CategoryBrowser/CategoryBrowser.php
@@ -27,7 +27,7 @@
2828 * * Add this line at the end of your LocalSettings.php file :
2929 * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
3030 *
31 - * @version 0.2.1
 31+ * @version 0.3.1
3232 * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
3333 * @author Dmitriy Sintsov <questpc@rambler.ru>
3434 * @addtogroup Extensions
@@ -48,7 +48,7 @@
4949
5050 class CB_Setup {
5151
52 - static $version = '0.2.1';
 52+ static $version = '0.3.1';
5353 static $ExtDir; // filesys path with windows path fix
5454 static $ScriptPath; // apache virtual path
5555 static $cat_pages_ranges; // ???
@@ -58,9 +58,12 @@
5959 static $response;
6060 static $cookie_prefix;
6161
62 - // case insensitive collation of category table 'cat_title' field
63 - static $cat_title_CI = '';
 62+ // by default, enable browsing of category parents (reverse browsing)
 63+ static $allowNestedParents = true;
6464
 65+ // by default, allow optional selecting of categories which has no parents only
 66+ static $allowNoParentsOnly = true;
 67+
6568 // number of files to show in gallery row
6669 static $imageGalleryPerRow = 4;
6770
@@ -69,7 +72,11 @@
7073 static $pagesLimit = CB_PAGING_ROWS;
7174 static $filesLimit = null;
7275 static $filesMaxRows = CB_FILES_MAX_ROWS;
 76+ static $parentsLimit = CB_PAGING_ROWS;
7377
 78+ // case insensitive collation of category table 'cat_title' field
 79+ static $cat_title_CI = '';
 80+
7481 /**
7582 * Add this extension to the mediawiki's extensions list.
7683 */
@@ -91,9 +98,12 @@
9299 $wgAutoloadClasses['CB_XML'] =
93100 $wgAutoloadClasses['CB_SqlCond'] = self::$ExtDir . '/CategoryBrowserBasic.php';
94101
 102+ $wgAutoloadClasses['CB_AbstractPager'] =
95103 $wgAutoloadClasses['CB_RootPager'] =
96 - $wgAutoloadClasses['CB_SubPager'] = self::$ExtDir . '/CategoryBrowserModel.php';
 104+ $wgAutoloadClasses['CB_SubPager'] =
 105+ $wgAutoloadClasses['CB_ParentPager'] = self::$ExtDir . '/CategoryBrowserModel.php';
97106
 107+ $wgAutoloadClasses['CB_AbstractPagesView'] =
98108 $wgAutoloadClasses['CB_CategoriesView'] =
99109 $wgAutoloadClasses['CB_PagesView'] =
100110 $wgAutoloadClasses['CB_FilesView'] = self::$ExtDir . '/CategoryBrowserView.php';
Index: trunk/extensions/CategoryBrowser/category_browser.css
@@ -35,10 +35,18 @@
3636 }
3737
3838 input#cb_cat_name_filter {
39 - margin: 0 1em 0 1em;
40 - width: 20em;
 39+ margin: 0 0 0 0.5em;
 40+ width: 16em;
4141 }
4242
 43+div.cb_filter_container {
 44+ margin: 0 0 0.5em 0;
 45+}
 46+
 47+.cb_filter_field {
 48+ margin: 0 0 0 0.5em;
 49+}
 50+
4351 div#cb_root_container {
4452 width: 100%;
4553 height: 100%;
Index: trunk/extensions/CategoryBrowser/CategoryBrowserModel.php
@@ -27,7 +27,7 @@
2828 * * Add this line at the end of your LocalSettings.php file :
2929 * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
3030 *
31 - * @version 0.2.1
 31+ * @version 0.3.1
3232 * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
3333 * @author Dmitriy Sintsov <questpc@rambler.ru>
3434 * @addtogroup Extensions
@@ -40,6 +40,11 @@
4141 abstract class CB_AbstractPager {
4242
4343 var $db;
 44+ var $page_table;
 45+ var $category_table;
 46+ var $categorylinks_table;
 47+ // select pages number without number of categories or files
 48+ static $cat_pages_only = '(cat_pages - cat_subcats - cat_files) as cat_pages_only';
4449
4550 /* pager position (actual offset)
4651 * 0 means pager has no previous elements
@@ -50,12 +55,15 @@
5156 var $query_offset;
5257 /* indicates, whether the pager has further elements */
5358 var $hasMoreEntries = false;
54 - /* maximal number of entries per page (actual number of entries on page) */
 59+ /*
 60+ * maximal number of entries per page (actual number of entries on page)
 61+ * warning: is not initialized until $this->getCurrentRows() call
 62+ */
5563 var $limit = 0;
5664 /* provided "source" limit */
5765 var $query_limit;
5866 /* array of current entries */
59 - var $entries;
 67+ var $entries = array();
6068
6169 /*
6270 * abstract query (doesn't instantinate)
@@ -64,6 +72,9 @@
6573 */
6674 function __construct( $offset, $limit ) {
6775 $this->db = & wfGetDB( DB_SLAVE );
 76+ $this->page_table = $this->db->tableName( 'page' );
 77+ $this->category_table = $this->db->tableName( 'category' );
 78+ $this->categorylinks_table = $this->db->tableName( 'categorylinks' );
6879 $this->query_limit = intval( $limit );
6980 $this->query_offset = intval( $offset );
7081 }
@@ -101,39 +112,79 @@
102113 return ( ( $this->hasMoreEntries ) ? $this->offset + $this->limit : 0 );
103114 }
104115
 116+} /* end of CB_AbstractPager class */
 117+
 118+/*
 119+ * parent categories pager
 120+ */
 121+class CB_ParentPager extends CB_AbstractPager {
 122+
 123+ // database cat_title of children category (with underscores, dbkey-like)
 124+ var $childCatName;
 125+
 126+ function __construct( $childCatName, $offset, $limit ) {
 127+ parent::__construct( $offset, $limit );
 128+ $this->childCatName = $childCatName;
 129+ }
 130+
105131 /*
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 - * todo: unused anymore; keep because might be useful in some cases
 132+ * set offset, limit, hasMoreEntries and entries
 133+ * @param $offset SQL offset
 134+ * @param $limit SQL limit
112135 */
113 - function getListType() {
114 - // it is not enough to check $this->entries[0],
115 - // because some broken tables might have just some (not all) cat_title = NULL or page_title = NULL
116 - foreach ( $this->entries as &$entry ) {
117 - if ( isset( $entry->page_namespace ) && $entry->page_namespace == NS_FILE ) { return 'generateFilesList'; }
118 - if ( isset( $entry->page_title ) ) { return 'generatePagesList'; }
119 - if ( isset( $entry->cat_title ) ) { return 'generateCatList'; }
120 - if ( isset( $entry->cl_sortkey ) ) { return 'generateCatList'; }
 136+ function getCurrentRows() {
 137+ if ( !CB_Setup::$allowNestedParents ) {
 138+ return;
121139 }
122 - throw new MWException( 'Entries not initialized in ' . __METHOD__ );
 140+ /*
 141+ SELECT null cl_sortkey,cat_id,cat_title,cat_subcats,cat_pages,cat_files FROM `wiki_page` INNER JOIN `wiki_categorylinks` ON (cl_from = page_id AND page_namespace=14 AND page_title='Жизненные_ценности' ) INNER JOIN `wiki_category` ON (cat_title = cl_to) ORDER BY cat_title
 142+ */
 143+ $query_string =
 144+ "SELECT null cl_sortkey,cat_id,cat_title,cat_subcats," . self::$cat_pages_only . ",cat_files " .
 145+ "FROM {$this->page_table} " .
 146+ "INNER JOIN {$this->categorylinks_table} ON (cl_from = page_id AND page_namespace=14 AND page_title=" . $this->db->addQuotes( $this->childCatName ) . ") " .
 147+ "INNER JOIN {$this->category_table} ON (cat_title = cl_to) " .
 148+ "ORDER BY cat_title ";
 149+ $res = $this->db->query( $query_string . "LIMIT {$this->query_offset}," . ( $this->query_limit + 1 ), __METHOD__ );
 150+ $this->setEntries( $res );
123151 }
124152
125 -} /* end of CB_AbstractPager class */
 153+ /*
 154+ * returns JS function call used to navigate to the previous page of this pager
 155+ * when placeholders = true, empty html placeholder should be generated in pager view instead of link
 156+ * when placeholders = false, nothing ('') will be generated in pager view instead of link
 157+ */
 158+ function getPrevAjaxLink() {
 159+ $result = (object) array(
 160+ "call" => "return CategoryBrowser.parentCatsNav(this,'" . Xml::escapeJsString( $this->childCatName ) . "'," . $this->getPrevOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
 161+ "placeholders" => false
 162+ );
 163+ return $result;
 164+ }
126165
 166+ /*
 167+ * returns JS function call used to navigate to the next page of this pager
 168+ * when placeholders = true, empty html placeholder should be generated in pager view instead of link
 169+ * when placeholders = false, nothing ('') will be generated in pager view instead of link
 170+ */
 171+ function getNextAjaxLink() {
 172+ $result = (object) array(
 173+ "call" => "return CategoryBrowser.parentCatsNav(this,'" . Xml::escapeJsString( $this->childCatName ) . "'," . $this->getNextOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
 174+ "placeholders" => false
 175+ );
 176+ return $result;
 177+ }
 178+
 179+} /* end of CB_ParentPager class */
 180+
127181 /*
128182 * subentries (subcategories, pages, files) pager
129183 * TODO: gracefully set offset = 0 when too large offset was given
130184 */
131185 class CB_SubPager extends CB_AbstractPager {
132186
133 - var $page_table;
134 - var $category_table;
135 - var $categorylinks_table;
136 - // database ID of parent category
137 - var $parentCatId;
 187+ // database cat_title of parent category (with underscores, dbkey-like)
 188+ var $parentCatName;
138189 // database fields to query
139190 var $select_fields;
140191 // namespace SQL condition (WHERE part)
@@ -144,18 +195,15 @@
145196 /*
146197 * creates subcategory list pager
147198 *
148 - * @param $parentCatId id of parent category
 199+ * @param $parentCatName cat_title of parent category
149200 * @param $offset SQL offset
150201 * @param $limit SQL limit
151202 *
152 - * TODO: query count of parentCatId subcategories/pages/files in category table for progress / percentage display
 203+ * TODO: query count of parent subcategories/pages/files in category table for progress / percentage display
153204 */
154 - function __construct( $parentCatId, $offset, $limit, $js_nav_func, $select_fields = '*', $ns_cond = '' ) {
 205+ function __construct( $parentCatName, $offset, $limit, $js_nav_func, $select_fields = '*', $ns_cond = '' ) {
155206 parent::__construct( $offset, $limit );
156 - $this->page_table = $this->db->tableName( 'page' );
157 - $this->category_table = $this->db->tableName( 'category' );
158 - $this->categorylinks_table = $this->db->tableName( 'categorylinks' );
159 - $this->parentCatId = $parentCatId;
 207+ $this->parentCatName = $parentCatName;
160208 $this->select_fields = $select_fields;
161209 $this->ns_cond = $ns_cond;
162210 $this->js_nav_func = $js_nav_func;
@@ -167,19 +215,16 @@
168216 * @param $limit SQL limit
169217 */
170218 function getCurrentRows() {
171 - /* TODO: change the query to more optimal one (no subselects)
172 - * 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
 219+ /*
 220+ * 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 = 'parent category name' AND page_namespace = 14 ORDER BY cl_sortkey LIMIT 0,11
173221 */
174222 $query_string =
175223 "SELECT {$this->select_fields} " .
176224 "FROM {$this->page_table} " .
177 - "INNER JOIN {$this->categorylinks_table} FORCE INDEX (cl_sortkey) ON cl_from = page_id " .
178 - "LEFT JOIN {$this->category_table} ON cat_title = page_title AND page_namespace = " . NS_CATEGORY . " " .
179 - "WHERE cl_to IN (" .
180 - "SELECT cat_title " .
181 - "FROM {$this->category_table} " .
182 - "WHERE cat_id = " . $this->db->addQuotes( $this->parentCatId ) .
183 - ") " . ( ( $this->ns_cond == '' ) ? '' : "AND {$this->ns_cond} " ) .
 225+ "INNER JOIN {$this->categorylinks_table} FORCE INDEX (cl_sortkey) ON (cl_from = page_id) " .
 226+ "LEFT JOIN {$this->category_table} ON (cat_title = page_title AND page_namespace = " . NS_CATEGORY . ") " .
 227+ "WHERE cl_to = " . $this->db->addQuotes( $this->parentCatName ) .
 228+ ( ( $this->ns_cond == '' ) ? ' ' : "AND {$this->ns_cond} " ) .
184229 "ORDER BY cl_sortkey ";
185230 $res = $this->db->query( $query_string . "LIMIT {$this->query_offset}," . ( $this->query_limit + 1 ), __METHOD__ );
186231 $this->setEntries( $res );
@@ -192,7 +237,7 @@
193238 */
194239 function getPrevAjaxLink() {
195240 $result = (object) array(
196 - "call" => "return CategoryBrowser.{$this->js_nav_func}(this," . $this->parentCatId . "," . $this->getPrevOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
 241+ "call" => "return CategoryBrowser.{$this->js_nav_func}(this,'" . Xml::escapeJsString( $this->parentCatName ) . "'," . $this->getPrevOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
197242 "placeholders" => false
198243 );
199244 return $result;
@@ -205,7 +250,7 @@
206251 */
207252 function getNextAjaxLink() {
208253 $result = (object) array(
209 - "call" => "return CategoryBrowser.{$this->js_nav_func}(this," . $this->parentCatId . ',' . $this->getNextOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
 254+ "call" => "return CategoryBrowser.{$this->js_nav_func}(this,'" . Xml::escapeJsString( $this->parentCatName ) . "'," . $this->getNextOffset() . ( ( $this->limit == CB_PAGING_ROWS ) ? '' : ',' . $this->limit ) . ')',
210255 "placeholders" => false
211256 );
212257 return $result;
@@ -228,6 +273,9 @@
229274 */
230275 var $sqlCond = null;
231276
 277+ // by default, query for all categories, not just these which have no parents
 278+ var $noParentsOnly = false;
 279+
232280 // category name filter (LIKE)
233281 var $nameFilter = '';
234282 // category name filter case-insensetive flag (when true, tries to use insensetive LIKE COLLATE)
@@ -278,7 +326,7 @@
279327 public static function newFromCategoryRange( $ranges ) {
280328 $rp = null;
281329 foreach ( $ranges as &$range ) {
282 - $rp = CB_RootPager::newFromInfixTokens( $range->infix_decoded );
 330+ $rp = CB_RootPager::newFromInfixTokens( $range->infix_tokens );
283331 if ( is_object( $rp ) && $rp->offset != -1 ) {
284332 break;
285333 }
@@ -287,12 +335,21 @@
288336 }
289337
290338 /*
 339+ * setup query only for categories, which does not have the parents
 340+ * @param: $noParentsOnly - boolean flag
 341+ * true - query only for categories, which does not have the parents
 342+ */
 343+ function setNoParentsOnly( $noParentsOnly ) {
 344+ $this->noParentsOnly = (boolean) $noParentsOnly;
 345+ }
 346+
 347+ /*
291348 * filter catetories by names
292349 * @param $cat_name_filter - string category name begins from
293350 * @param $cat_name_filter_ci - boolean, true attempts to use case-insensetive search, when available
294351 */
295352 function setNameFilter( $cat_name_filter, $cat_name_filter_ci ) {
296 - $this->nameFilter = ltrim( $cat_name_filter );
 353+ $this->nameFilter = str_replace( ' ', '_', ltrim( $cat_name_filter ) );
297354 $this->nameFilterCI = $cat_name_filter_ci;
298355 }
299356
@@ -312,13 +369,26 @@
313370 $conds .= ' COLLATE ' . $this->db->addQuotes( CB_Setup::$cat_title_CI );
314371 }
315372 }
316 - $options = array( 'OFFSET' => $this->query_offset, 'ORDER BY' => 'cat_title', 'LIMIT' => $this->query_limit + 1 );
317 - $res = $this->db->select( 'category',
318 - array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ),
319 - $conds,
320 - __METHOD__,
321 - $options
322 - );
 373+ if ( CB_Setup::$allowNoParentsOnly && $this->noParentsOnly ) {
 374+ /*
 375+ SELECT * FROM `wiki_category` INNER JOIN `wiki_categorylinks` AS childs ON (cat_title = cl_to) LEFT JOIN `wiki_page` ON ( childs.cl_to = page_title AND page_namespace = 14 ) LEFT JOIN `wiki_categorylinks` AS parents ON ( page_id = parents.cl_from ) WHERE parents.cl_from IS NULL GROUP BY childs.cl_to ORDER BY childs.cl_to
 376+ */
 377+ $conds = "parents.cl_from IS NULL" . ( ($conds == '') ? '' : " AND ${conds}" );
 378+ $query_string =
 379+ "SELECT cat_id, cat_title, " . self::$cat_pages_only . ", cat_subcats, cat_files FROM {$this->category_table} " .
 380+ "INNER JOIN {$this->categorylinks_table} AS childs ON (cat_title = cl_to) " .
 381+ "LEFT JOIN {$this->page_table} ON (childs.cl_to = page_title AND page_namespace = 14) " .
 382+ "LEFT JOIN {$this->categorylinks_table} AS parents ON (page_id = parents.cl_from) " .
 383+ "WHERE ${conds} GROUP BY childs.cl_to ORDER BY childs.cl_to ";
 384+ $res = $this->db->query( $query_string . "LIMIT {$this->query_offset}," . ( $this->query_limit + 1 ), __METHOD__ );
 385+ } else {
 386+ $res = $this->db->select( 'category',
 387+ array( 'cat_id', 'cat_title', self::$cat_pages_only, 'cat_subcats', 'cat_files' ),
 388+ $conds,
 389+ __METHOD__,
 390+ array( 'OFFSET' => $this->query_offset, 'ORDER BY' => 'cat_title', 'LIMIT' => $this->query_limit + 1 )
 391+ );
 392+ }
323393 /* set actual offset, limit, hasMoreEntries and entries */
324394 $this->setEntries( $res );
325395 }
Index: trunk/extensions/CategoryBrowser/category_browser_rtl.css
@@ -36,10 +36,18 @@
3737 }
3838
3939 input#cb_cat_name_filter {
40 - margin: 0 1em 0 1em;
41 - width: 20em;
 40+ margin: 0 0.5em 0 0;
 41+ width: 16em;
4242 }
4343
 44+div.cb_filter_container {
 45+ margin: 0 0 0.5em 0;
 46+}
 47+
 48+.cb_filter_field {
 49+ margin: 0 0.5em 0 0;
 50+}
 51+
4452 div#cb_root_container {
4553 width: 100%;
4654 height: 100%;
Index: trunk/extensions/CategoryBrowser/CategoryBrowserBasic.php
@@ -27,7 +27,7 @@
2828 * * Add this line at the end of your LocalSettings.php file :
2929 * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
3030 *
31 - * @version 0.2.1
 31+ * @version 0.3.1
3232 * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
3333 * @author Dmitriy Sintsov <questpc@rambler.ru>
3434 * @addtogroup Extensions
@@ -37,7 +37,6 @@
3838 die( "This file is a part of MediaWiki extension.\n" );
3939 }
4040
41 -define( 'CB_COND_TOKEN_MATCH', '`^\s*(cat_subcats|cat_pages|cat_files)\s*(>=|<=|=)\s*(\d+)\s*$`' );
4241 define( 'CB_ENCODED_TOKEN_MATCH', '`^(ge|le|eq)(p|s|f)(\d+)$`' );
4342
4443 /* render output data */
@@ -223,45 +222,48 @@
224223 var $local_tokens;
225224
226225 /*
227 - * @param $tokens - list of SQL condition tokens (infix or polish)
228 - * comparsions like "a > 1" are treated like single-ops
 226+ * @param $tokens - list of encoded SQL condition tokens
 227+ * (infix or RPN - doesn't matter for localization)
 228+ * comparsions are single-ops
229229 */
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"
 230+ function __construct( array $tokens ) {
 231+ $this->src_tokens = $tokens;
 232+ if ( count( $this->src_tokens ) < 1 ) {
 233+ $this->local_tokens = array( wfMsg( 'cb_all_op' ) ); // localized default "all"
238234 return;
239235 }
240236 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 );
 237+ $op = $field = $num = '';
 238+ switch ( $token->type ) {
 239+ case 'bracket' :
 240+ if ( $token->value == '(' ) {
 241+ $op = 'lbracket';
 242+ } else { // $token->value == ')'
 243+ $op = 'rbracket';
 244+ }
 245+ break;
 246+ case 'logical' :
 247+ if ( $token->value == 'or' ) {
 248+ $op = 'or';
 249+ } else { // $token->value == 'and'
 250+ $op = 'and';
 251+ }
 252+ break;
 253+ case 'comparsion' : // comparsion subexpression
 254+ $matches = array();
 255+ preg_match_all( CB_ENCODED_TOKEN_MATCH, $token->value, $matches, PREG_SET_ORDER );
249256 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__ );
 257+ // localize comparsion op
 258+ list( $expr, $cmp, $field, $num ) = $matches[0];
 259+ $op = $cmp;
 260+ $field = CB_SqlCond::$decoded_fields[ $field ];
264261 }
 262+ break;
265263 }
 264+ if ( $op == '' ) {
 265+ $this->src_tokens = array(); // default "all"
 266+ $this->local_tokens = array( wfMsg( 'cb_all_op' ) ); // localized default "all"
 267+ throw new MWException( 'Invalid operation ' . CB_Setup::entities( $token ) . ' in ' . __METHOD__ ); }
266268 if ( $field == '' ) {
267269 $this->local_tokens[] = wfMsg( "cb_${op}_op" );
268270 } elseif ( $num == '' ) {
@@ -286,190 +288,182 @@
287289 */
288290 class CB_SqlCond {
289291
 292+ # general terminology : queue is a string of underscore separated operations
 293+ # token arrays (RPN or infix) are an arrays of token objects
 294+ # each is corresponding to queue's operations and vice versa
 295+
 296+ # decoded_fields and sql_fields differ because in current scheme ( MW <= 1.16 )
 297+ # cat_pages include cat_subcats and cat_fields count as well
 298+ # while we need to query against actual number of pages only
 299+
 300+ # opcodes mapping for i18n
290301 static $decoded_fields = array( 'p' => 'cat_pages', 's' => 'cat_subcats', 'f' => 'cat_files' );
291 - static $decoded_cmps = array( 'ge' => '>=', 'le' => '<=', 'eq' => '=' );
292 -
 302+ # opcodes mapping for SQL
 303+ static $sql_fields = array( 'p' => '(cat_pages - cat_subcats - cat_files)', 's' => 'cat_subcats', 'f' => 'cat_files' );
 304+ static $sql_cmps = array( 'ge' => '>=', 'le' => '<=', 'eq' => '=' );
 305+ # opcodes mapping for encoded queues (JS & cookie)
293306 static $encoded_fields = array( 'cat_subcats' => 's', 'cat_pages' => 'p', 'cat_files' => 'f' );
294307 static $encoded_cmps = array( '>=' => 'ge', '<=' => 'le', '=' => 'eq' );
295308
296 - # reverse polish operations queue (decoded form, every op is an element of array)
 309+ # RPN array (encoded form, every token is an element of array)
297310 # 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
 311+ # initialized in constructor (public static functions)
 312+ var $rpn_tokens;
 313+ private $rpn_tokens_pos; // current position in rpn_tokens; used to generate triples
301314
302 - # infix operations queue
 315+ # infix operations array (encoded form, every token is an element of array)
303316 # comparsions like "a > 1" are treated like single-ops
304 - var $infix_queue;
 317+ # initialized in constructor (public static functions)
 318+ var $infix_tokens;
305319
306 - // used privately by decodeToken()
 320+ // used privately by parseEncodedToken(), getSqlToken()
307321 private static $valid_logical_ops;
308322 private static $valid_bracket_ops;
309323
310324 /*
311 - * constructor (creates an instance, initializes $this->queue, returns an instance)
 325+ * constructor (creates an instance, initializes $this->rpn_tokens, returns an instance)
312326 *
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
 327+ * converts encoded RPN queue (string) to RPN tokens array ($this->rpn_tokens) (1:1)
 328+ * @param $enc - string encoded RPN queue
316329 * (underscore-separated encoded polish tokens)
317330 */
318331 public static function newFromEncodedPolishQueue( $enc ) {
 332+ if ( !is_string( $enc ) ) {
 333+ throw new MWException( 'First argument should be a string in ' . __METHOD__ );
 334+ }
319335 $sc = new CB_SqlCond();
 336+ # parseEncodedToken(), getSqlToken() in RPN mode
320337 self::$valid_logical_ops = array( 'and', 'or' );
321338 self::$valid_bracket_ops = array();
322 - $sc->queue = array();
 339+ $sc->rpn_tokens = array();
323340 $q = explode( '_', $enc );
324341 # {{{ validation of expression
325342 $cmp_count = $logical_count = 0;
326343 # }}}
327344 foreach ( $q as &$token ) {
328 - $result = self::decodeToken( $token );
329 - $sc->queue[] = $result->token;
 345+ $result = self::parseEncodedToken( $token );
 346+ $sc->rpn_tokens[] = $result;
330347 if ( $result->type == 'comparsion' ) {
331348 $cmp_count++;
332349 } elseif ( $result->type == 'logical' ) {
333350 $logical_count++;
334351 } else {
335352 # tampered or bugged $enc, return default "all" instead
336 - $sc->queue = array();
 353+ $sc->rpn_tokens = array();
337354 return $sc;
338355 }
339356 }
340357 if ( $cmp_count < 1 || $cmp_count != $logical_count + 1 ) {
341358 # tampered or bugged $enc, return default "all" instead
342 - $sc->queue = array();
 359+ $sc->rpn_tokens = array();
343360 return $sc;
344361 }
345362 if ( $logical_count > CB_MAX_LOGICAL_OP ) {
346363 # too complex $enc (fabricated or non-realistic), return default "all" instead
347 - $sc->queue = array();
 364+ $sc->rpn_tokens = array();
348365 return $sc;
349366 }
350367 return $sc;
351368 }
352369
353370 /*
354 - * constructor (creates an instance, initializes $this->infix_queue, returns an instance)
 371+ * constructor (creates an instance, initializes $this->infix_tokens, returns an instance)
355372 *
356373 * 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
 374+ * decoded infix operations array $this->infix_tokens (1:1)
 375+ * then fills RPN tokens array $this->rpn_tokens
359376 * @param $enc - string encoded infix operations queue
360377 * (underscore-separated encoded infix tokens)
361378 */
362379 public static function newFromEncodedInfixQueue( $enc ) {
 380+ if ( !is_string( $enc ) ) {
 381+ throw new MWException( 'First argument should be a string in ' . __METHOD__ );
 382+ }
 383+ # parseEncodedToken(), getSqlToken() in infix mode
363384 self::$valid_logical_ops = array( 'and', 'or' );
364385 self::$valid_bracket_ops = array( '(', ')' );
365 - $infix_queue = array();
 386+ $infix_tokens = array();
366387 $q = explode( '_', $enc );
367388 # {{{ validation of expression
368389 $brackets_level = 0; $prev_type = '';
369390 # }}}
370391 foreach ( $q as &$token ) {
371 - $result = self::decodeToken( $token );
372 - $infix_queue[] = $result->token;
 392+ $result = self::parseEncodedToken( $token );
 393+ $infix_tokens[] = $result;
373394 if ( $result->type == 'bracket' ) {
374 - if ( $result->token == '(' ) {
 395+ if ( $result->value == '(' ) {
375396 $brackets_level++;
376397 } else {
377398 $brackets_level--;
378399 }
379400 if ( $brackets_level < 0 ) {
380401 # tampered or bugged $enc, use default "all" instead
381 - $infix_queue = array();
 402+ $infix_tokens = array();
382403 break;
383404 }
384405 } elseif ( $result->type == 'logical' ) {
385406 if ( $prev_type == '' || $prev_type == 'logical' ) {
386407 # tampered or bugged $enc, use default "all" instead
387 - $infix_queue = array();
 408+ $infix_tokens = array();
388409 break;
389410 }
390411 } elseif ( $result->type == 'comparsion' ) {
391412 if ( $prev_type == 'comparsion' ) {
392413 # tampered or bugged $enc, use default "all" instead
393 - $infix_queue = array();
 414+ $infix_tokens = array();
394415 break;
395416 }
396417 } else {
397418 # tampered or bugged $enc, use default "all" instead
398 - $infix_queue = array();
 419+ $infix_tokens = array();
399420 break;
400421 }
401422 $prev_type = $result->type;
402423 }
403424 if ( $brackets_level != 0 ) {
404425 # tampered or bugged $enc, use default "all" instead
405 - $infix_queue = array();
 426+ $infix_tokens = array();
406427 }
407 - return self::newFromInfixTokens( $infix_queue );
 428+ return self::newFromInfixTokens( $infix_tokens );
408429 }
409430
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 -
437431 /*
438 - * constructor (creates an instance, initializes $this->queue, returns an instance)
 432+ * constructor (creates an instance, initializes $this->rpn_tokens, returns an instance)
439433 *
440 - * fills up polish notation array $this->queue from infix $tokens provided
441 - * @param $tokens - array of infix tokens
 434+ * fills up RPN array $this->rpn_tokens from infix $tokens array provided
 435+ * @param $tokens - array of encoded infix tokens
442436 */
443 - # converts list of given infix $tokens into $this->queue of reverse polish notation
444 - # TODO: more throughout checks for invalid tokens given
445437 public static function newFromInfixTokens( array $tokens ) {
446438 $sc = new CB_SqlCond();
447 - $stack = array(); // every element is stdobject with token and prio fields
448 - $sc->queue = array();
 439+ $stack = array(); // every element is stdClass with stdClass(token) or stdClass(stdClass(token),prio) fields
 440+ $sc->rpn_tokens = array();
449441 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;
 442+ switch ( $token->type ) {
 443+ case 'bracket' :
 444+ if ( $token->value == '(' ) {
 445+ $prio = 0;
 446+ array_push( $stack, (object) array( 'token' => $token, 'prio' => $prio ) );
 447+ } else { // $token->value == ')'
 448+ $prio = 1;
 449+ while ( $last = array_pop( $stack ) ) {
 450+ if ( isset( $last->prio ) ) {
 451+ # last element in stack is an container with 'prio' property (different array_push'es)
 452+ if ( $last->token->value == '(' ) {
 453+ break;
 454+ }
 455+ array_push( $sc->rpn_tokens, $last->token );
 456+ } else {
 457+ # last element in stack is just a token object
 458+ throw new MWException( 'Open / closing brackets mismatch in ' . __METHOD__ );
461459 }
462 - array_push( $sc->queue, $last->token );
463 - } else {
464 - throw new MWException( 'Open / closing brackets mismatch in ' . __METHOD__ );
465460 }
466461 }
467462 break;
468 - case 'OR' :
469 - case 'AND' :
470 - $prio = strtoupper( $token ) == 'OR' ? 2 : 3;
 463+ case 'logical' :
 464+ $prio = $token->value == 'or' ? 2 : 3;
471465 while ( $last = array_pop( $stack ) ) {
472 - if ( is_object( $last ) && $last->prio >= $prio ) {
473 - array_push( $sc->queue, $last->token );
 466+ if ( isset( $last->prio ) && $last->prio >= $prio ) {
 467+ array_push( $sc->rpn_tokens, $last->token );
474468 } else {
475469 array_push( $stack, $last );
476470 break;
@@ -478,18 +472,81 @@
479473 array_push( $stack, (object) array( 'token' => $token, 'prio' => $prio ) );
480474 break;
481475 default : // comparsion subexpression
482 - array_push( $sc->queue, $token );
 476+ array_push( $sc->rpn_tokens, $token );
483477 }
484478 }
485479 while ( $last = array_pop( $stack ) ) {
486 - if ( !is_object( $last ) ) {
 480+ if ( !isset( $last->prio ) ) {
487481 break;
488482 }
489 - array_push( $sc->queue, $last->token );
 483+ array_push( $sc->rpn_tokens, $last->token );
490484 }
491485 return $sc;
492486 }
493487
 488+ /*
 489+ * @param $value - encoded token value
 490+ * @return encoded token object
 491+ */
 492+ private static function parseEncodedToken( $value ) {
 493+ if ( !is_string( $value ) ) {
 494+ throw new MWException( 'First argument should be a string in ' . __METHOD__ );
 495+ }
 496+ $result = (object) array( 'type' => 'unknown', 'value' => '' );
 497+ $matches = array();
 498+ preg_match_all( CB_ENCODED_TOKEN_MATCH, $value, $matches, PREG_SET_ORDER );
 499+ if ( count( $matches ) == 1 && isset( $matches[0] ) && count( $matches[0] ) == 4 ) {
 500+ // decode comparsion op
 501+ $result->value = $matches[0][1] . $matches[0][2] . intval( $matches[0][3] );
 502+ $result->type = 'comparsion';
 503+ return $result;
 504+ }
 505+ $lo_value = strtolower( $value );
 506+ if ( in_array( $lo_value, self::$valid_logical_ops ) ) {
 507+ // decode logical op
 508+ $result->value = $lo_value;
 509+ $result->type = 'logical';
 510+ return $result;
 511+ }
 512+ if ( ($opcode = array_search( $value, self::$valid_bracket_ops )) !== false ) {
 513+ // decode bracket op
 514+ $result->value = $opcode;
 515+ $result->type = 'bracket';
 516+ return $result;
 517+ }
 518+ return $result;
 519+ }
 520+
 521+ /*
 522+ * @param $token - encoded token object
 523+ * @result - SQL token
 524+ */
 525+ private static function getSqlToken( stdClass $token ) {
 526+ switch ( $token->type ) {
 527+ case 'comparsion' :
 528+ $matches = array();
 529+ preg_match_all( CB_ENCODED_TOKEN_MATCH, $token->value, $matches, PREG_SET_ORDER );
 530+ if ( count( $matches ) == 1 && isset( $matches[0] ) && count( $matches[0] ) == 4 ) {
 531+ // decode comparsion op
 532+ return self::$sql_fields[ $matches[0][2] ] . ' ' . self::$sql_cmps[ $matches[0][1] ] . ' ' . (int) $matches[0][3];
 533+ }
 534+ break;
 535+ case 'logical' :
 536+ if ( in_array( $token->value, self::$valid_logical_ops ) ) {
 537+ // decode logical op
 538+ // we store logical ops uppercase for the "prettiness"
 539+ return strtoupper( $token->value );
 540+ }
 541+ break;
 542+ case 'bracket' :
 543+ if ( ($opcode = array_search( $token->value, self::$valid_bracket_ops )) !== false ) {
 544+ // decode bracket op
 545+ return self::$valid_bracket_ops[ $opcode ];
 546+ }
 547+ }
 548+ throw new MWException( 'Invalid operation type=' . CB_Setup::specialchars( $token->type ) . ' value=' . CB_Setup::specialchars( $token->value ) . ' in ' . __METHOD__ );
 549+ }
 550+
494551 /*
495552 src:'(', 'cat_pages > 1000', 'OR', 'cat_subcats > 10', ')', 'AND', 'cat_files > 100'
496553 dst:cat_pages > 1000;cat_subcats > 10;OR;cat_files > 100;AND
@@ -509,26 +566,26 @@
510567 ('','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)
511568 ('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
512569
513 -1. global counter of queue position, getting elements consequtively from right to left
 570+1. global counter of rpn_tokens position, getting elements consequtively from right to left
514571 2. operators are going to current entry, in case currept op is free, otherwise going recursively from right to left (which position is inoccupied)
515572 3. operands are going to current entry, right to left
516573 4. generating the string recursively going from left to right
517574
518575 in actual code (null,null,null) isset() is used instead of ('','','')
519576 */
520 - # generate triples (see example above) from $this->queue
 577+ # generate triples (see example above) from $this->rpn_tokens
521578 #
522579 # param &$currTriple - recursively adds new triples to $currTriple
523 - private function buildTriples( &$currTriple ) {
 580+ private function buildTriples( array &$currTriple ) {
524581 # 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
 582+ # recursively feed triples with rpn_tokens tokens, right to left
 583+ while ( $this->rpn_tokens_pos >= 0 ) {
 584+ $token = $this->rpn_tokens[ $this->rpn_tokens_pos ];
 585+ if ( $token->type == 'logical' ) {
 586+ // our subset of polish notation has only logical operators
530587 if ( !isset( $currTriple[1] ) ) {
531588 $currTriple[1] = $token;
532 - $this->queue_pos--;
 589+ $this->rpn_tokens_pos--;
533590 } elseif ( !isset( $currTriple[2] ) ) {
534591 $currTriple[2] = array();
535592 $this->buildTriples( $currTriple[2] );
@@ -539,13 +596,13 @@
540597 return;
541598 }
542599 } else {
543 - // operands
 600+ // comparsions are treated as operands
544601 if ( !isset( $currTriple[2] ) ) {
545602 $currTriple[2] = $token;
546 - $this->queue_pos--;
 603+ $this->rpn_tokens_pos--;
547604 } elseif ( !isset( $currTriple[0] ) ) {
548605 $currTriple[0] = $token;
549 - $this->queue_pos--;
 606+ $this->rpn_tokens_pos--;
550607 } else {
551608 return;
552609 }
@@ -555,38 +612,41 @@
556613
557614 /*
558615 * build properly bracketed infix expression string
559 - * also builds $this->infix_queue array
 616+ * also builds $this->infix_tokens array
560617 * from triples tree previousely built by CategoryFilter::buildTriples (left to right)
561618 */
562619 private $infixLevel; // used to do not include brackets at level 0
563 - private function getInfixExpr( &$out, $currTriple ) {
 620+ private function getInfixExpr( &$out, array $currTriple ) {
 621+ # parseEncodedToken(), getSqlToken() in infix mode
 622+ self::$valid_logical_ops = array( 'and', 'or' );
 623+ self::$valid_bracket_ops = array( '(', ')' );
564624 $this->infixLevel++;
565625 if ( $this->infixLevel != 0 ) {
566 - $this->infix_queue[] = '(';
 626+ $this->infix_tokens[] = (object) array( 'type' => 'bracket', 'value' => '(' );
567627 $out .= '(';
568628 }
569629 if ( isset( $currTriple[0] ) ) {
570630 if ( is_array( $currTriple[0] ) ) {
571631 $this->getInfixExpr( $out, $currTriple[0] );
572632 } else {
573 - $this->infix_queue[] = $currTriple[0];
574 - $out .= $currTriple[0];
 633+ $this->infix_tokens[] = $currTriple[0];
 634+ $out .= $this->getSqlToken( $currTriple[0] );
575635 }
576636 }
577637 if ( isset( $currTriple[1] ) ) {
578 - $this->infix_queue[] = $currTriple[1];
579 - $out .= ' ' . $currTriple[1] . ' ';
 638+ $this->infix_tokens[] = $currTriple[1];
 639+ $out .= ' ' . $this->getSqlToken( $currTriple[1] ) . ' ';
580640 }
581641 if ( isset( $currTriple[2] ) ) {
582642 if ( is_array( $currTriple[2] ) ) {
583643 $this->getInfixExpr( $out, $currTriple[2] );
584644 } else {
585 - $this->infix_queue[] = $currTriple[2];
586 - $out .= $currTriple[2];
 645+ $this->infix_tokens[] = $currTriple[2];
 646+ $out .= $this->getSqlToken( $currTriple[2] );
587647 }
588648 }
589649 if ( $this->infixLevel != 0 ) {
590 - $this->infix_queue[] = ')';
 650+ $this->infix_tokens[] = (object) array( 'type' => 'bracket', 'value' => ')' );
591651 $out .= ')';
592652 }
593653 $this->infixLevel--;
@@ -594,79 +654,49 @@
595655
596656 /*
597657 * get SQL condition expression with full brackets (to indicate operators priority)
598 - * *** !!also builds $this->infix_queue array!! ***
 658+ * *** !!also builds $this->infix_tokens array!! ***
599659 */
600660 function getCond() {
601661 $rootTriple = array();
602 - $this->queue_pos = count( $this->queue ) - 1;
 662+ $this->rpn_tokens_pos = count( $this->rpn_tokens ) - 1;
603663 $this->buildTriples( $rootTriple );
604664 $out = '';
605665 $this->infixLevel = -1;
606 - $this->infix_queue = array();
607 - # also builds $this->infix_queue array
 666+ $this->infix_tokens = array();
 667+ # also builds $this->infix_tokens array
608668 $this->getInfixExpr( $out, $rootTriple );
609 - if ( count( $this->infix_queue ) == 0 ) {
610 - $this->infix_queue = array( '' ); // default "all"
 669+ if ( count( $this->infix_tokens ) == 0 ) {
 670+ $this->infix_tokens = array(); // default "all"
611671 }
612672 return $out;
613673 }
614674
615675 /*
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"
 676+ * get encoded queue string to be stored in a cookie or passed from PHP AJAX handler to js callback
 677+ * @param $infix set true when infix queue is decoded, otherwise brackets will cause to reset to default "all"
618678 *
619679 */
620680 function getEncodedQueue( $is_infix = false ) {
621681 $result = '';
622682 if ( $is_infix ) {
623 - $valid_single_ops = array( '(', ')', 'or', 'and' );
624 - if ( !is_array( $this->infix_queue ) ) {
 683+ if ( !is_array( $this->infix_tokens ) ) {
625684 $this->getCond();
626685 }
627 - $queue = &$this->infix_queue;
 686+ $tokens = &$this->infix_tokens;
628687 } else {
629 - $valid_single_ops = array( 'or', 'and' );
630 - $queue = &$this->queue;
 688+ $tokens = &$this->rpn_tokens;
631689 }
632 - if ( count( $queue ) == 1 && $queue[0] == '' ) {
 690+ if ( count( $tokens ) < 1 ) {
633691 return 'all'; // default "show all"
634692 }
635693 $firstElem = true;
636 - foreach ( $queue as &$token ) {
 694+ foreach ( $tokens as &$token ) {
637695 if ( $firstElem ) {
638696 $firstElem = false;
639697 } else {
640698 $result .= '_';
641699 }
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 - }
 700+ $result .= $token->value;
671701 }
672702 return $result;
673703 }
Index: trunk/extensions/CategoryBrowser/category_browser.js
@@ -26,7 +26,7 @@
2727 * * Add this line at the end of your LocalSettings.php file :
2828 * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
2929 *
30 - * @version 0.2.1
 30+ * @version 0.3.1
3131 * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
3232 * @author Dmitriy Sintsov <questpc@rambler.ru>
3333 * @addtogroup Extensions
@@ -122,6 +122,15 @@
123123 return null;
124124 },
125125
 126+ findParentByClassName : function( obj, className ) {
 127+ for ( var parentObj = obj.parentNode; parentObj != null; parentObj = parentObj.parentNode ) {
 128+ if ( parentObj.nodeType == 1 && parentObj.className == className ) {
 129+ return parentObj;
 130+ }
 131+ }
 132+ return parentObj;
 133+ },
 134+
126135 /*
127136 * TODO: unused, remove
128137 * usage example: CB_lib.setCookie( 'rootcond', eventObj.value, 24 * 60 * 60, '/' );
@@ -233,7 +242,7 @@
234243 'error': { 'color': 'red', 'bgr': 'aquamarine' },
235244 'copy': { 'color': 'white', 'bgr': 'blue' }
236245 },
237 - // delay in seconds before performing ajax call to reduce server load
 246+ // delay in milliseconds before performing ajax call to reduce server load
238247 'ajaxPollTimeout' : 2000
239248 } /* end of CB_Setup */
240249
@@ -243,12 +252,17 @@
244253 * AJAX category tree section
245254 */
246255
247 - // currently selected encoded reverse polish queue
 256+ // {{{ root query condition parameters
 257+ // currently selected encoded reverse polish queue (constructed in CB_ConditionEditor)
248258 rootCond : null,
 259+ // query only categories which has no parents
 260+ noParentsOnly : false,
249261 // category name filter (LIKE)
250262 nameFilter : '',
251263 // category name filter case-insensetive flag (when true, queries use LIKE COLLATE)
252264 nameFilterCI : false,
 265+ // }}}
 266+
253267 // limit of pager query (also passed further via AJAX call)
254268 pagerLimit : null,
255269 // nested container object
@@ -262,7 +276,7 @@
263277 */
264278 rootCats : function( rootCond, offset, limit ) {
265279 this.rootCond = rootCond;
266 - var param = [this.rootCond, this.nameFilter, this.nameFilterCI, offset];
 280+ var param = [this.rootCond, this.nameFilter, this.nameFilterCI, this.noParentsOnly, offset];
267281 if ( limit ) {
268282 param.push( limit );
269283 } else {
@@ -291,6 +305,7 @@
292306 /*
293307 * create / get nested DOM container of the specified category DOM node
294308 * @param node - category DOM node
 309+ * @return DOM node of current nested container
295310 */
296311 getNestedContainer : function( node ) {
297312 var container = node.lastChild;
@@ -302,64 +317,88 @@
303318 container.className = 'cb_nested_container';
304319 node.appendChild( container );
305320 }
306 - this.container = container;
 321+ return container;
307322 },
308323
309324 /*
310325 * switch visibility of container
311 - * @param: id - set id attribute of nested container (used in setContainerVisibility() method)
 326+ * @param: id - set id attribute of nested container (indicates the type of content placed into container)
312327 */
313 - setContainerVisibility : function( id ) {
314 - if ( this.container.style.display == 'none' ||
315 - this.container.getAttribute( 'id' ) != id ) {
 328+ setContainerVisibility : function( id, isVisible ) {
 329+ if ( isVisible ) {
316330 this.container.style.display = 'block';
317331 } else {
 332+ this.container.innerHTML = '';
318333 this.container.style.display = 'none';
319334 }
320335 this.container.setAttribute( 'id', id );
321336 },
322337
323 - subCatsNav : function( eventObj, catId, offset, limit ) {
324 - var param = ['subcats', catId];
325 - if ( offset ) {
326 - param.push( offset );
327 - if ( limit ) {
328 - param.push( limit );
329 - }
 338+ subLink : function( eventObj, catName, pagerType, plusMode ) {
 339+ eventObj.blur();
 340+ // find 'cat_expand_sign' container
 341+ var expandSign = CB_lib.findParentByClassName( eventObj, 'cb_cat_controls' );
 342+ if ( expandSign === null ||
 343+ (expandSign = expandSign.firstChild) === null ||
 344+ (expandSign = expandSign.firstChild) === null ) {
 345+ alert( 'Cannot find expand sign container in CategoryBrowser.subCatsLink() ' );
330346 }
331 - this.subOffset( eventObj, param );
 347+ var treeNode = this.findNode( eventObj );
 348+ if ( treeNode == null ) {
 349+ alert( 'Cannot find DOM node object of event click object in CategoryBrowser.subCatsLink()' );
 350+ return;
 351+ }
 352+ this.container = this.getNestedContainer( treeNode );
 353+ // when clicking an expanded (opened) node "sign", force collapsing (close node)
 354+ var forceCollapsing = typeof plusMode !== 'undefined' && expandSign.innerHTML == '-';
 355+ if ( forceCollapsing ||
 356+ ( this.container.style.display != 'none' &&
 357+ this.container.getAttribute( 'id' ) == 'cb_nested_'+pagerType ) ) {
 358+ // collapsing
 359+ expandSign.innerHTML = '+';
 360+ this.setContainerVisibility( '', false );
 361+ } else {
 362+ // expanding
 363+ expandSign.innerHTML = '-';
 364+ var param = [pagerType, catName];
 365+ sajax_do_call( "CategoryBrowser::getSubOffsetHtml", param, this.container );
 366+ this.setContainerVisibility( 'cb_nested_'+pagerType, true );
 367+ }
332368 return false;
333369 },
334370
335 - subCatsPlus : function( eventObj, catId ) {
336 - eventObj.blur();
337 - eventObj.innerHTML = ( eventObj.innerHTML == '+' ) ? '-' : '+';
338 - var param = ['subcats', catId];
339 - this.subOffset( eventObj, param );
340 - this.setContainerVisibility( 'cb_nested_subcats' );
341 - return false;
 371+ subCatsPlus : function( eventObj, catName ) {
 372+ return this.subLink( eventObj, catName, 'subcats', true );
342373 },
343374
344 - subCatsLink : function( eventObj, catId ) {
345 - eventObj.blur();
346 - var param = ['subcats', catId];
347 - this.subOffset( eventObj, param );
348 - this.setContainerVisibility( 'cb_nested_subcats' );
349 - return false;
 375+ subCatsLink : function( eventObj, catName ) {
 376+ return this.subLink( eventObj, catName, 'subcats' );
350377 },
351378
352 - subOffset : function( eventObj, param ) {
 379+ pagesLink : function( eventObj, catName ) {
 380+ return this.subLink( eventObj, catName, 'pages' );
 381+ },
 382+
 383+ filesLink : function( eventObj, catName ) {
 384+ return this.subLink( eventObj, catName, 'files' );
 385+ },
 386+
 387+ parentCatsLink : function( eventObj, catName ) {
 388+ return this.subLink( eventObj, catName, 'parents' );
 389+ },
 390+
 391+ subNav : function( eventObj, param ) {
353392 var treeNode = this.findNode( eventObj );
354393 if ( treeNode == null ) {
355 - alert( 'Cannot find DOM node object of event click object in CategoryBrowser.subOffset()' );
 394+ alert( 'Cannot find DOM node object of event click object in CategoryBrowser.subNav()' );
356395 return;
357396 }
358 - this.getNestedContainer( treeNode );
 397+ this.container = this.getNestedContainer( treeNode );
359398 sajax_do_call( "CategoryBrowser::getSubOffsetHtml", param, this.container );
360399 },
361400
362 - pagesNav : function( eventObj, catId, offset, limit ) {
363 - var param = ['pages', catId];
 401+ subCatsNav : function( eventObj, catName, offset, limit ) {
 402+ var param = ['subcats', catName];
364403 if ( offset ) {
365404 param.push( offset );
366405 if ( limit ) {
@@ -367,20 +406,25 @@
368407 }
369408 }
370409 // used .parentNode to skip self container
371 - this.subOffset( eventObj.parentNode, param );
 410+ this.subNav( eventObj.parentNode, param );
372411 return false;
373412 },
374413
375 - pagesLink : function( eventObj, catId ) {
376 - eventObj.blur();
377 - var param = ['pages', catId];
378 - this.subOffset( eventObj, param );
379 - this.setContainerVisibility( 'cb_nested_pages' );
 414+ pagesNav : function( eventObj, catName, offset, limit ) {
 415+ var param = ['pages', catName];
 416+ if ( offset ) {
 417+ param.push( offset );
 418+ if ( limit ) {
 419+ param.push( limit );
 420+ }
 421+ }
 422+ // used .parentNode to skip self container
 423+ this.subNav( eventObj.parentNode, param );
380424 return false;
381425 },
382426
383 - filesNav : function( eventObj, catId, offset, limit ) {
384 - var param = ['files', catId];
 427+ filesNav : function( eventObj, catName, offset, limit ) {
 428+ var param = ['files', catName];
385429 if ( offset ) {
386430 param.push( offset );
387431 if ( limit ) {
@@ -388,63 +432,71 @@
389433 }
390434 }
391435 // used .parentNode to skip self container
392 - this.subOffset( eventObj.parentNode, param );
 436+ this.subNav( eventObj.parentNode, param );
393437 return false;
394438 },
395439
396 - filesLink : function( eventObj, catId ) {
 440+ parentCatsNav : function( eventObj, catName, offset, limit ) {
 441+ var param = ['parents', catName];
 442+ if ( offset ) {
 443+ param.push( offset );
 444+ if ( limit ) {
 445+ param.push( limit );
 446+ }
 447+ }
 448+ // used .parentNode to skip self container
 449+ this.subNav( eventObj.parentNode, param );
 450+ return false;
 451+ },
 452+
 453+ searchForRoot : function( eventObj, catName ) {
397454 eventObj.blur();
398 - var param = ['files', catId];
399 - this.subOffset( eventObj, param );
400 - this.setContainerVisibility( 'cb_nested_files' );
 455+ document.getElementById( 'cb_cat_name_filter' ).value = catName;
 456+ this.setFilter();
401457 return false;
402458 },
403459
404 - setNameFilter : function( eventObj ) {
405 - var id = eventObj.getAttribute( 'id' );
 460+ setFilter : function() {
406461 if ( this.rootCond === null ) {
407462 this.rootCond = document.getElementById( 'cb_expr_select' ).value;
408463 if ( this.rootCond === null ) {
409 - alert( 'Cannot find selected rootCond option in CategoryBrowser.setNameFilter' );
 464+ alert( 'Cannot find selected rootCond option in CategoryBrowser.setFilter' );
410465 }
411466 }
412 - switch ( id ) {
413 - case 'cb_cat_name_filter' :
414 - /* more reliable to check in nameFilterCall
415 - this.nameFilter = eventObj.value;
416 - CB_lib.log( 'setNameFilter nameFilter='+this.nameFilter ); */
417 - break;
418 - case 'cb_cat_name_filter_ci' :
419 - /* more reliable to check in nameFilterCall
420 - this.nameFilterCI = eventObj.checked; */
421 - break;
422 - default : alert( 'CategoryBrowser.setNameFilter was called with unknown event object id='+id );
423 - }
424 - window.setTimeout( function() { CategoryBrowser.nameFilterCall(); }, CB_Setup.ajaxPollTimeout );
 467+ window.setTimeout( function() { CategoryBrowser.filterPoll(); }, CB_Setup.ajaxPollTimeout );
425468 return true;
426469 },
427470
428 - nameFilterCall : function() {
 471+ clearNameFilter : function( eventObj ) {
 472+ this.searchForRoot( eventObj, '' );
 473+ document.getElementById( 'cb_cat_name_filter' ).focus();
 474+ return false;
 475+ },
 476+
 477+ filterPoll : function() {
429478 if ( this.rootCond === null ) {
430479 this.rootCond = document.getElementById( 'cb_expr_select' ).value;
431480 }
432481 var nameFilter = document.getElementById( 'cb_cat_name_filter' ).value;
433482 var CIcheckbox = document.getElementById( 'cb_cat_name_filter_ci' );
434 - if ( CIcheckbox !== null ) {
435 - var nameFilterCI = CIcheckbox.checked;
436 - }
 483+ var nameFilterCI = CIcheckbox !== null ? CIcheckbox.checked : false;
 484+ var noParentsCheckbox = document.getElementById( 'cb_cat_no_parents_only' );
 485+ var noParentsOnly = noParentsCheckbox !== null ? noParentsCheckbox.checked : false;
437486 if ( this.rootCond !== null &&
438 - ( this.nameFilter != nameFilter || this.nameFilterCI != nameFilterCI ) ) {
 487+ ( this.nameFilter != nameFilter ||
 488+ this.nameFilterCI != nameFilterCI ||
 489+ this.noParentsOnly != noParentsOnly ) ) {
439490 // in case nameFilter field was changed, update the root pager
440491 this.nameFilter = nameFilter;
441492 this.nameFilterCI = nameFilterCI;
442 - var param = [this.rootCond, this.nameFilter, this.nameFilterCI, 0];
 493+ this.noParentsOnly = noParentsOnly;
 494+ var param = [this.rootCond, this.nameFilter, this.nameFilterCI, this.noParentsOnly, 0];
443495 if ( this.pagerLimit !== null ) {
444496 param.push( this.pagerLimit );
445497 }
446498 sajax_do_call( "CategoryBrowser::getRootOffsetHtml", param, document.getElementById( "cb_root_container" ) );
447499 }
448 - window.setTimeout( function() { CategoryBrowser.nameFilterCall(); }, CB_Setup.ajaxPollTimeout );
 500+ window.setTimeout( function() { CategoryBrowser.filterPoll(); }, CB_Setup.ajaxPollTimeout );
449501 },
450502
451503 /*
@@ -461,7 +513,7 @@
462514 CB_lib.log('setExpr selectedEncInfixQueue='+selectedEncInfixQueue);
463515 this.pagerLimit = pagerLimit;
464516 CB_lib.log( 'setExpr refreshing with value='+eventObj.value );
465 - sajax_do_call( "CategoryBrowser::getRootOffsetHtml", [this.rootCond, this.nameFilter, this.nameFilterCI, 0, this.pagerLimit], document.getElementById( "cb_root_container" ) );
 517+ sajax_do_call( "CategoryBrowser::getRootOffsetHtml", [this.rootCond, this.nameFilter, this.nameFilterCI, this.noParentsOnly, 0, this.pagerLimit], document.getElementById( "cb_root_container" ) );
466518 CB_ConditionEditor.createExpr( selectedEncInfixQueue );
467519 return true;
468520 }
@@ -1621,7 +1673,7 @@
16221674 var encInfixQueue = this.conditionLine.getEncodedExpr();
16231675 var appliedOption = CB_lib.getSelectOption( document.getElementById( 'cb_expr_select' ), encInfixQueue, 'infixexpr' );
16241676 var setCookie = appliedOption == null ? 1 : 0;
1625 - var param = [ encInfixQueue, CategoryBrowser.nameFilter, CategoryBrowser.nameFilterCI, setCookie ];
 1677+ var param = [ encInfixQueue, CategoryBrowser.nameFilter, CategoryBrowser.nameFilterCI, CategoryBrowser.noParentsOnly, setCookie ];
16261678 if ( CategoryBrowser.pagerLimit !== null ) {
16271679 param.push( CategoryBrowser.pagerLimit );
16281680 }
Index: trunk/extensions/CategoryBrowser/CategoryBrowserCtrl.php
@@ -27,7 +27,7 @@
2828 * * Add this line at the end of your LocalSettings.php file :
2929 * require_once "$IP/extensions/CategoryBrowser/CategoryBrowser.php";
3030 *
31 - * @version 0.2.1
 31+ * @version 0.3.1
3232 * @link http://www.mediawiki.org/wiki/Extension:CategoryBrowser
3333 * @author Dmitriy Sintsov <questpc@rambler.ru>
3434 * @addtogroup Extensions
@@ -131,8 +131,9 @@
132132 static function generateRanges( array &$source_ranges ) {
133133 $ranges = array();
134134 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 ) );
 135+ $sqlCond = CB_SqlCond::newFromEncodedInfixQueue( $infix_queue );
 136+ $sqlCond->getCond(); // build $sqlCond->infix_tokens
 137+ $ranges[] = (object) array( 'infix_tokens' => $sqlCond->infix_tokens, 'rpn_queue' => $sqlCond->getEncodedQueue( false ) );
137138 }
138139 return $ranges;
139140 }
@@ -147,14 +148,14 @@
148149 $encPolishQueue = $sqlCond->getEncodedQueue( false );
149150 $queueExists = false;
150151 foreach ( $ranges as &$range ) {
151 - if ( $range->polish_encoded == $encPolishQueue ) {
 152+ if ( $range->rpn_queue == $encPolishQueue ) {
152153 $queueExists = true;
153154 break;
154155 }
155156 }
156157 if ( !$queueExists ) {
157 - $sqlCond->getCond(); // build infix queue array
158 - $ranges[] = (object) array( 'infix_decoded' => $sqlCond->infix_queue, 'polish_encoded' => $encPolishQueue );
 158+ $sqlCond->getCond(); // build $sqlCond->infix_tokens array
 159+ $ranges[] = (object) array( 'infix_tokens' => $sqlCond->infix_tokens, 'rpn_queue' => $encPolishQueue );
159160 }
160161 }
161162
@@ -184,7 +185,7 @@
185186 $selectedEncPolishQueue = $rootPager->sqlCond->getEncodedQueue( false );
186187 foreach ( $ranges as &$range ) {
187188 $condOptList[] = self::generateOption( $range, $selectedEncPolishQueue );
188 - if ( $range->polish_encoded == $selectedEncPolishQueue ) {
 189+ if ( $range->rpn_queue == $selectedEncPolishQueue ) {
189190 $queueFound = true;
190191 }
191192 }
@@ -202,11 +203,11 @@
203204 $condOptTpl =
204205 array( '__tag' => $nodeName, 'value' => &$condOptVal, 'infixexpr' => &$condOptInfix, 0 => &$condOptName, '__end' => "\n" );
205206 # }}}
206 - $le = new CB_LocalExpr( $range->infix_decoded );
207 - $condOptVal = CB_Setup::specialchars( $range->polish_encoded );
208 - $sqlCond = CB_SqlCond::newFromEncodedPolishQueue( $range->polish_encoded );
 207+ $le = new CB_LocalExpr( $range->infix_tokens );
 208+ $condOptVal = CB_Setup::specialchars( $range->rpn_queue );
 209+ $sqlCond = CB_SqlCond::newFromEncodedPolishQueue( $range->rpn_queue );
209210 $condOptInfix = CB_Setup::specialchars( $sqlCond->getEncodedQueue( true ) );
210 - if ( $range->polish_encoded == $selectedValue ) {
 211+ if ( $range->rpn_queue == $selectedValue ) {
211212 $condOptTpl['selected'] = null;
212213 }
213214 $condOptName = CB_Setup::entities( $le->toString() );
@@ -219,20 +220,23 @@
220221 * @param $args[0] : encoded reverse polish queue
221222 * @param $args[1] : category name filter string
222223 * @param $args[2] : category name filter case insensitive flag
223 - * @param $args[3] : offset (optional)
224 - * @param $args[4] : limit (optional)
 224+ * @param $args[3] : browse only categories which has no parents flag (by default, false)
 225+ * @param $args[4] : offset (optional)
 226+ * @param $args[5] : limit (optional)
225227 */
226228 static function getRootOffsetHtml() {
227229 wfLoadExtensionMessages( 'CategoryBrowser' );
228230 $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+ $limit = ( count( $args ) > 5 ) ? abs( intval( $args[5] ) ) : CB_PAGING_ROWS;
 232+ $offset = ( count( $args ) > 4 ) ? abs( intval( $args[4] ) ) : 0;
 233+ $noParentsOnly = ( count( $args ) > 3 ) ? $args[3] == 'true' : false;
231234 $nameFilterCI = ( count( $args ) > 2 ) ? $args[2] == 'true' : false;
232235 $nameFilter = ( count( $args ) > 1 ) ? $args[1] : '';
233236 $encPolishQueue = ( count( $args ) > 0 ) ? $args[0] : 'all';
234237 $cb = new CategoryBrowser();
235238 $sqlCond = CB_SqlCond::newFromEncodedPolishQueue( $encPolishQueue );
236239 $rootPager = CB_RootPager::newFromSqlCond( $sqlCond, $offset, $limit );
 240+ $rootPager->setNoParentsOnly( $noParentsOnly );
237241 $rootPager->setNameFilter( $nameFilter, $nameFilterCI );
238242 $rootPager->getCurrentRows();
239243 $catView = new CB_CategoriesView( $rootPager );
@@ -241,9 +245,35 @@
242246 }
243247
244248 /*
 249+ * called via AJAX to setup custom edited expression cookie then display category root offset
 250+ * @param $args[0] : encoded infix expression
 251+ * @param $args[1] : category name filter string
 252+ * @param $args[2] : category name filter case insensitive flag
 253+ * @param $args[3] : browse only categories which has no parents flag (by default, false)
 254+ * @param $args[4] : 1 - cookie has to be set, 0 - cookie should not be set (expression is pre-defined or already was stored)
 255+ * @param $args[5] : pager limit (optional)
 256+ */
 257+ static function applyEncodedQueue() {
 258+ CB_Setup::initUser();
 259+ $args = func_get_args();
 260+ $limit = ( ( count( $args ) > 5 ) ? intval( $args[5] ) : CB_PAGING_ROWS );
 261+ $setCookie = ( ( count( $args ) > 4 ) ? $args[4] != 0 : false );
 262+ $noParentsOnly = ( count( $args ) > 3 ) ? $args[3] == 'true' : false;
 263+ $nameFilterCI = ( count( $args ) > 2 ) ? $args[2] == 'true' : false;
 264+ $nameFilter = ( count( $args ) > 1 ) ? $args[1] : '';
 265+ $encInfixQueue = ( ( count( $args ) > 0 ) ? $args[0] : 'all' );
 266+ $sqlCond = CB_SqlCond::newFromEncodedInfixQueue( $encInfixQueue );
 267+ $encPolishQueue = $sqlCond->getEncodedQueue( false );
 268+ if ( $setCookie ) {
 269+ CB_Setup::setCookie( 'rootcond', $encPolishQueue, time() + 60 * 60 * 24 * 7 );
 270+ }
 271+ return self::getRootOffsetHtml( $encPolishQueue, $nameFilter, $nameFilterCI, $noParentsOnly, 0, $limit );
 272+ }
 273+
 274+ /*
245275 * called via AJAX to get list of (subcategories,pages,files) for specitied parent category id, offset, limit
246276 * @param $args[0] : type of pager ('subcats','pages','files')
247 - * @param $args[1] : parent category id
 277+ * @param $args[1] : parent category name
248278 * @param $args[2] : offset (optional)
249279 * @param $args[3] : limit (optional)
250280 */
@@ -251,7 +281,7 @@
252282 $pager_types = array(
253283 'subcats' => array(
254284 'js_nav_func' => "subCatsNav",
255 - 'select_fields' => "cl_sortkey, cat_id, cat_title, cat_subcats, cat_pages, cat_files",
 285+ 'select_fields' => "cl_sortkey, cat_id, cat_title, cat_subcats, " . CB_AbstractPager::$cat_pages_only . ", cat_files",
256286 'ns_cond' => "page_namespace = " . NS_CATEGORY,
257287 'default_limit' => CB_Setup::$categoriesLimit
258288 ),
@@ -266,6 +296,9 @@
267297 'select_fields' => "page_title, page_namespace, page_len, page_is_redirect",
268298 'ns_cond' => "page_namespace = " . NS_FILE,
269299 'default_limit' => CB_Setup::$filesLimit
 300+ ),
 301+ 'parents' => array(
 302+ 'default_limit' => CB_Setup::$parentsLimit
270303 )
271304 );
272305 wfLoadExtensionMessages( 'CategoryBrowser' );
@@ -277,18 +310,23 @@
278311 if ( !isset( $pager_types[ $pager_type ] ) ) {
279312 return 'Unknown pager type=' . CB_Setup::specialchars( $pager_type ) . ' in ' . __METHOD__;
280313 }
281 - $pager_setup = & $pager_types[ $args[0] ];
 314+ $pager_setup = & $pager_types[ $pager_type ];
282315 $limit = ( count( $args ) > 3 ) ? abs( intval( $args[3] ) ) : $pager_setup[ 'default_limit' ];
283316 $offset = ( count( $args ) > 2 ) ? abs( intval( $args[2] ) ) : 0;
284 - $parentCatId = abs( intval( $args[1] ) );
 317+ $parentCatName = $args[1];
285318 $cb = new CategoryBrowser();
286 - $pager = new CB_SubPager( $parentCatId, $offset, $limit,
287 - $pager_setup[ 'js_nav_func' ],
288 - $pager_setup[ 'select_fields' ],
289 - $pager_setup[ 'ns_cond' ] );
 319+ if ( $pager_type == 'parents' ) {
 320+ $pager = new CB_ParentPager( $parentCatName, $offset, $limit );
 321+ } else {
 322+ $pager = new CB_SubPager( $parentCatName, $offset, $limit,
 323+ $pager_setup[ 'js_nav_func' ],
 324+ $pager_setup[ 'select_fields' ],
 325+ $pager_setup[ 'ns_cond' ] );
 326+ }
290327 $pager->getCurrentRows();
291328 switch ( $pager_type ) {
292329 case 'subcats' :
 330+ case 'parents' :
293331 $view = new CB_CategoriesView( $pager );
294332 break;
295333 case 'pages' :
@@ -314,30 +352,6 @@
315353 }
316354
317355 /*
318 - * called via AJAX to setup custom edited expression cookie then display category root offset
319 - * @param $args[0] : encoded infix expression
320 - * @param $args[1] : category name filter string
321 - * @param $args[2] : category name filter case insensitive flag
322 - * @param $args[3] : 1 - cookie has to be set, 0 - cookie should not be set (expression is pre-defined or already was stored)
323 - * @param $args[4] : pager limit (optional)
324 - */
325 - static function applyEncodedQueue() {
326 - CB_Setup::initUser();
327 - $args = func_get_args();
328 - $limit = ( ( count( $args ) > 4 ) ? intval( $args[4] ) : CB_PAGING_ROWS );
329 - $setCookie = ( ( count( $args ) > 3 ) ? $args[3] != 0 : false );
330 - $nameFilterCI = ( count( $args ) > 2 ) ? $args[2] == 'true' : false;
331 - $nameFilter = ( count( $args ) > 1 ) ? $args[1] : '';
332 - $encInfixQueue = ( ( count( $args ) > 0 ) ? $args[0] : 'all' );
333 - $sqlCond = CB_SqlCond::newFromEncodedInfixQueue( $encInfixQueue );
334 - $encPolishQueue = $sqlCond->getEncodedQueue( false );
335 - if ( $setCookie ) {
336 - CB_Setup::setCookie( 'rootcond', $encPolishQueue, time() + 60 * 60 * 24 * 7 );
337 - }
338 - return self::getRootOffsetHtml( $encPolishQueue, $nameFilter, $nameFilterCI, 0, $limit );
339 - }
340 -
341 - /*
342356 * called via AJAX to generate new selected option when the selected rootcond is new (the rootcond cookie was set)
343357 * @param $args[0] currently selected expression in encoded infix format
344358 */
Index: trunk/extensions/CategoryBrowser/README
@@ -1,4 +1,4 @@
2 -MediaWiki extension CategoryBrowser, version 0.2.1
 2+MediaWiki extension CategoryBrowser, version 0.3.1
33
44 CategoryBrowser is an AJAX-enabled category filter and browser for MediaWiki.
55 Categories can be filtered with pre-defined and also with used-defined conditions.

Status & tagging log