PageRenderTime 59ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/gforge/plugins/wiki/www/lib/PageList.php

https://github.com/neymanna/fusionforge
PHP | 1734 lines | 1100 code | 115 blank | 519 comment | 223 complexity | b2c7fa863c1e18c54d082b991150a8c1 MD5 | raw file
Possible License(s): GPL-2.0, MPL-2.0-no-copyleft-exception

Large files files are truncated, but you can click here to view the full file

  1. <?php rcs_id('$Id: PageList.php,v 1.135 2005/09/14 05:59:03 rurban Exp $');
  2. /**
  3. * List a number of pagenames, optionally as table with various columns.
  4. * This library relieves some work for these plugins:
  5. *
  6. * AllPages, BackLinks, LikePages, MostPopular, TitleSearch, WikiAdmin* and more
  7. *
  8. * It also allows dynamic expansion of those plugins to include more
  9. * columns in their output.
  10. *
  11. * Column 'info=' arguments:
  12. *
  13. * 'pagename' _("Page Name")
  14. * 'mtime' _("Last Modified")
  15. * 'hits' _("Hits")
  16. * 'summary' _("Last Summary")
  17. * 'version' _("Version")),
  18. * 'author' _("Last Author")),
  19. * 'locked' _("Locked"), _("locked")
  20. * 'minor' _("Minor Edit"), _("minor")
  21. * 'markup' _("Markup")
  22. * 'size' _("Size")
  23. * 'creator' _("Creator")
  24. * 'owner' _("Owner")
  25. * 'checkbox' selectable checkbox at the left.
  26. * 'content'
  27. *
  28. * Special, custom columns: Either theme or plugin (WikiAdmin*) specific.
  29. * 'remove' _("Remove")
  30. * 'perm' _("Permission Mask")
  31. * 'acl' _("ACL")
  32. * 'renamed_pagename' _("Rename to")
  33. * 'ratingwidget', ... wikilens theme specific.
  34. * 'custom' See plugin/_WikiTranslation
  35. *
  36. * Symbolic 'info=' arguments:
  37. * 'all' All columns except the special columns
  38. * 'most' pagename, mtime, author, size, hits, ...
  39. * 'some' pagename, mtime, author
  40. *
  41. * FIXME: In this refactoring I (Jeff) have un-implemented _ctime, _cauthor, and
  42. * number-of-revision. Note the _ctime and _cauthor as they were implemented
  43. * were somewhat flawed: revision 1 of a page doesn't have to exist in the
  44. * database. If lots of revisions have been made to a page, it's more than likely
  45. * that some older revisions (include revision 1) have been cleaned (deleted).
  46. *
  47. * DONE:
  48. * paging support: limit, offset args
  49. * check PagePerm "list" access-type,
  50. * all columns are sortable (Thanks to the wikilens team).
  51. * cols > 1, comma, azhead, ordered (OL lists)
  52. * ->supportedArgs() which arguments are supported, so that the plugin
  53. * doesn't explictly need to declare it
  54. *
  55. * FIXED:
  56. * fix memory exhaustion on large pagelists with old --memory-limit php's only.
  57. * Status: improved 2004-06-25 16:19:36 rurban
  58. * but needs further testing.
  59. */
  60. class _PageList_Column_base {
  61. var $_tdattr = array();
  62. function _PageList_Column_base ($default_heading, $align = false) {
  63. $this->_heading = $default_heading;
  64. if ($align) {
  65. // align="char" isn't supported by any browsers yet :(
  66. //if (is_array($align))
  67. // $this->_tdattr = $align;
  68. //else
  69. $this->_tdattr['align'] = $align;
  70. }
  71. }
  72. function format ($pagelist, $page_handle, &$revision_handle) {
  73. return HTML::td($this->_tdattr,
  74. HTML::raw('&nbsp;'),
  75. $this->_getValue($page_handle, $revision_handle),
  76. HTML::raw('&nbsp;'));
  77. }
  78. function getHeading () {
  79. return $this->_heading;
  80. }
  81. function setHeading ($heading) {
  82. $this->_heading = $heading;
  83. }
  84. // old-style heading
  85. function heading () {
  86. // allow sorting?
  87. if (1 /* or in_array($this->_field, PageList::sortable_columns())*/) {
  88. // multiple comma-delimited sortby args: "+hits,+pagename"
  89. // asc or desc: +pagename, -pagename
  90. $sortby = PageList::sortby($this->_field, 'flip_order');
  91. //Fixme: pass all also other GET args along. (limit, p[])
  92. //TODO: support GET and POST
  93. $s = HTML::a(array('href' =>
  94. $GLOBALS['request']->GetURLtoSelf(array('sortby' => $sortby,
  95. 'nocache' => '1')),
  96. 'class' => 'pagetitle',
  97. 'title' => sprintf(_("Sort by %s"), $this->_field)),
  98. HTML::raw('&nbsp;'), HTML::u($this->_heading), HTML::raw('&nbsp;'));
  99. } else {
  100. $s = HTML(HTML::raw('&nbsp;'), HTML::u($this->_heading), HTML::raw('&nbsp;'));
  101. }
  102. return HTML::th(array('align' => 'center'),$s);
  103. }
  104. // new grid-style sortable heading
  105. // see activeui.js
  106. function button_heading ($pagelist, $colNum) {
  107. global $WikiTheme, $request;
  108. // allow sorting?
  109. if (1 /* or in_array($this->_field, PageList::sortable_columns()) */) {
  110. // multiple comma-delimited sortby args: "+hits,+pagename"
  111. $src = false;
  112. $noimg_src = $WikiTheme->getButtonURL('no_order');
  113. if ($noimg_src)
  114. $noimg = HTML::img(array('src' => $noimg_src,
  115. 'width' => '7',
  116. 'height' => '7',
  117. 'border' => 0,
  118. 'alt' => '.'));
  119. else
  120. $noimg = HTML::raw('&nbsp;');
  121. if ($request->getArg('sortby')) {
  122. if ($pagelist->sortby($colNum, 'check')) { // show icon?
  123. $sortby = $pagelist->sortby($request->getArg('sortby'), 'flip_order');
  124. //$request->setArg('sortby', $sortby);
  125. $desc = (substr($sortby,0,1) == '-'); // asc or desc? (+pagename, -pagename)
  126. $src = $WikiTheme->getButtonURL($desc ? 'asc_order' : 'desc_order');
  127. } else {
  128. $sortby = $pagelist->sortby($colNum, 'init');
  129. }
  130. } else {
  131. $sortby = $pagelist->sortby($colNum, 'init');
  132. }
  133. if (!$src) {
  134. $img = $noimg;
  135. //$img->setAttr('alt', _("Click to sort"));
  136. } else {
  137. $img = HTML::img(array('src' => $src,
  138. 'width' => '7',
  139. 'height' => '7',
  140. 'border' => 0,
  141. 'alt' => _("Click to reverse sort order")));
  142. }
  143. $s = HTML::a(array('href' =>
  144. //Fixme: pass all also other GET args along. (limit is ok, p[])
  145. //Fixme: convert to POST submit[sortby]
  146. $request->GetURLtoSelf(array('sortby' => $sortby,
  147. /*'nocache' => '1'*/)),
  148. 'class' => 'gridbutton',
  149. 'title' => sprintf(_("Click to sort by %s"), $this->_field)),
  150. HTML::raw('&nbsp;'),
  151. $noimg,
  152. HTML::raw('&nbsp;'),
  153. $this->_heading,
  154. HTML::raw('&nbsp;'),
  155. $img,
  156. HTML::raw('&nbsp;'));
  157. } else {
  158. $s = HTML(HTML::raw('&nbsp;'), $this->_heading, HTML::raw('&nbsp;'));
  159. }
  160. return HTML::th(array('align' => 'center', 'valign' => 'middle',
  161. 'class' => 'gridbutton'), $s);
  162. }
  163. /**
  164. * Take two columns of this type and compare them.
  165. * An undefined value is defined to be < than the smallest defined value.
  166. * This base class _compare only works if the value is simple (e.g., a number).
  167. *
  168. * @param $colvala $this->_getValue() of column a
  169. * @param $colvalb $this->_getValue() of column b
  170. *
  171. * @return -1 if $a < $b, 1 if $a > $b, 0 otherwise.
  172. */
  173. function _compare($colvala, $colvalb) {
  174. if (is_string($colvala))
  175. return strcmp($colvala,$colvalb);
  176. $ret = 0;
  177. if (($colvala === $colvalb) || (!isset($colvala) && !isset($colvalb))) {
  178. ;
  179. } else {
  180. $ret = (!isset($colvala) || ($colvala < $colvalb)) ? -1 : 1;
  181. }
  182. return $ret;
  183. }
  184. };
  185. class _PageList_Column extends _PageList_Column_base {
  186. function _PageList_Column ($field, $default_heading, $align = false) {
  187. $this->_PageList_Column_base($default_heading, $align);
  188. $this->_need_rev = substr($field, 0, 4) == 'rev:';
  189. $this->_iscustom = substr($field, 0, 7) == 'custom:';
  190. if ($this->_iscustom) {
  191. $this->_field = substr($field, 7);
  192. }
  193. elseif ($this->_need_rev)
  194. $this->_field = substr($field, 4);
  195. else
  196. $this->_field = $field;
  197. }
  198. function _getValue ($page_handle, &$revision_handle) {
  199. if ($this->_need_rev) {
  200. if (!$revision_handle)
  201. // columns which need the %content should override this. (size, hi_content)
  202. $revision_handle = $page_handle->getCurrentRevision(false);
  203. return $revision_handle->get($this->_field);
  204. }
  205. else {
  206. return $page_handle->get($this->_field);
  207. }
  208. }
  209. function _getSortableValue ($page_handle, &$revision_handle) {
  210. $val = $this->_getValue($page_handle, $revision_handle);
  211. if ($this->_field == 'hits')
  212. return (int) $val;
  213. elseif (is_object($val))
  214. return $val->asString();
  215. else
  216. return (string) $val;
  217. }
  218. };
  219. /* overcome a call_user_func limitation by not being able to do:
  220. * call_user_func_array(array(&$class, $class_name), $params);
  221. * So we need $class = new $classname($params);
  222. * And we add a 4th param to get at the parent $pagelist object
  223. */
  224. class _PageList_Column_custom extends _PageList_Column {
  225. function _PageList_Column_custom($params) {
  226. $this->_pagelist =& $params[3];
  227. $this->_PageList_Column($params[0], $params[1], $params[2]);
  228. }
  229. }
  230. class _PageList_Column_size extends _PageList_Column {
  231. function format (&$pagelist, $page_handle, &$revision_handle) {
  232. return HTML::td($this->_tdattr,
  233. HTML::raw('&nbsp;'),
  234. $this->_getValue($pagelist, $page_handle, $revision_handle),
  235. HTML::raw('&nbsp;'));
  236. }
  237. function _getValue (&$pagelist, $page_handle, &$revision_handle) {
  238. if (!$revision_handle or (!$revision_handle->_data['%content']
  239. or $revision_handle->_data['%content'] === true)) {
  240. $revision_handle = $page_handle->getCurrentRevision(true);
  241. unset($revision_handle->_data['%pagedata']['_cached_html']);
  242. }
  243. $size = $this->_getSize($revision_handle);
  244. // we can safely purge the content when it is not sortable
  245. if (empty($pagelist->_sortby[$this->_field]))
  246. unset($revision_handle->_data['%content']);
  247. return $size;
  248. }
  249. function _getSortableValue ($page_handle, &$revision_handle) {
  250. if (!$revision_handle)
  251. $revision_handle = $page_handle->getCurrentRevision(true);
  252. return (empty($revision_handle->_data['%content']))
  253. ? 0 : strlen($revision_handle->_data['%content']);
  254. }
  255. function _getSize($revision_handle) {
  256. $bytes = @strlen($revision_handle->_data['%content']);
  257. return ByteFormatter($bytes);
  258. }
  259. }
  260. class _PageList_Column_bool extends _PageList_Column {
  261. function _PageList_Column_bool ($field, $default_heading, $text = 'yes') {
  262. $this->_PageList_Column($field, $default_heading, 'center');
  263. $this->_textIfTrue = $text;
  264. $this->_textIfFalse = new RawXml('&#8212;'); //mdash
  265. }
  266. function _getValue ($page_handle, &$revision_handle) {
  267. //FIXME: check if $this is available in the parent (->need_rev)
  268. $val = _PageList_Column::_getValue($page_handle, $revision_handle);
  269. return $val ? $this->_textIfTrue : $this->_textIfFalse;
  270. }
  271. };
  272. class _PageList_Column_checkbox extends _PageList_Column {
  273. function _PageList_Column_checkbox ($field, $default_heading, $name='p') {
  274. $this->_name = $name;
  275. $heading = HTML::input(array('type' => 'button',
  276. 'title' => _("Click to de-/select all pages"),
  277. //'width' => '100%',
  278. 'name' => $default_heading,
  279. 'value' => $default_heading,
  280. 'onclick' => "flipAll(this.form)"
  281. ));
  282. $this->_PageList_Column($field, $heading, 'center');
  283. }
  284. function _getValue ($pagelist, $page_handle, &$revision_handle) {
  285. $pagename = $page_handle->getName();
  286. $selected = !empty($pagelist->_selected[$pagename]);
  287. if (strstr($pagename,'[') or strstr($pagename,']')) {
  288. $pagename = str_replace(array('[',']'),array('%5B','%5D'),$pagename);
  289. }
  290. if ($selected) {
  291. return HTML::input(array('type' => 'checkbox',
  292. 'name' => $this->_name . "[$pagename]",
  293. 'value' => 1,
  294. 'checked' => 'checked'));
  295. } else {
  296. return HTML::input(array('type' => 'checkbox',
  297. 'name' => $this->_name . "[$pagename]",
  298. 'value' => 1));
  299. }
  300. }
  301. function format ($pagelist, $page_handle, &$revision_handle) {
  302. return HTML::td($this->_tdattr,
  303. HTML::raw('&nbsp;'),
  304. $this->_getValue($pagelist, $page_handle, $revision_handle),
  305. HTML::raw('&nbsp;'));
  306. }
  307. // don't sort this javascript button
  308. function button_heading ($pagelist, $colNum) {
  309. $s = HTML(HTML::raw('&nbsp;'), $this->_heading, HTML::raw('&nbsp;'));
  310. return HTML::th(array('align' => 'center', 'valign' => 'middle',
  311. 'class' => 'gridbutton'), $s);
  312. }
  313. };
  314. class _PageList_Column_time extends _PageList_Column {
  315. function _PageList_Column_time ($field, $default_heading) {
  316. $this->_PageList_Column($field, $default_heading, 'right');
  317. global $WikiTheme;
  318. $this->Theme = &$WikiTheme;
  319. }
  320. function _getValue ($page_handle, &$revision_handle) {
  321. $time = _PageList_Column::_getValue($page_handle, $revision_handle);
  322. return $this->Theme->formatDateTime($time);
  323. }
  324. };
  325. class _PageList_Column_version extends _PageList_Column {
  326. function _getValue ($page_handle, &$revision_handle) {
  327. if (!$revision_handle)
  328. $revision_handle = $page_handle->getCurrentRevision();
  329. return $revision_handle->getVersion();
  330. }
  331. };
  332. // Output is hardcoded to limit of first 50 bytes. Otherwise
  333. // on very large Wikis this will fail if used with AllPages
  334. // (PHP memory limit exceeded)
  335. class _PageList_Column_content extends _PageList_Column {
  336. function _PageList_Column_content ($field, $default_heading, $align = false) {
  337. $this->_PageList_Column($field, $default_heading, $align);
  338. $this->bytes = 50;
  339. if ($field == 'content') {
  340. $this->_heading .= sprintf(_(" ... first %d bytes"),
  341. $this->bytes);
  342. } elseif ($field == 'hi_content') {
  343. global $HTTP_POST_VARS;
  344. if (!empty($HTTP_POST_VARS['admin_replace'])) {
  345. $search = $HTTP_POST_VARS['admin_replace']['from'];
  346. $this->_heading .= sprintf(_(" ... around %s"),
  347. '»'.$search.'«');
  348. }
  349. }
  350. }
  351. function _getValue ($page_handle, &$revision_handle) {
  352. if (!$revision_handle or (!$revision_handle->_data['%content']
  353. or $revision_handle->_data['%content'] === true)) {
  354. $revision_handle = $page_handle->getCurrentRevision(true);
  355. }
  356. // Not sure why implode is needed here, I thought
  357. // getContent() already did this, but it seems necessary.
  358. $c = implode("\n", $revision_handle->getContent());
  359. if (empty($pagelist->_sortby[$this->_field]))
  360. unset($revision_handle->_data['%content']);
  361. if ($this->_field == 'hi_content') {
  362. global $HTTP_POST_VARS;
  363. unset($revision_handle->_data['%pagedata']['_cached_html']);
  364. $search = $HTTP_POST_VARS['admin_replace']['from'];
  365. if ($search and ($i = strpos($c,$search))) {
  366. $l = strlen($search);
  367. $j = max(0,$i - ($this->bytes / 2));
  368. return HTML::div(array('style' => 'font-size:x-small'),
  369. HTML::div(array('class' => 'transclusion'),
  370. HTML::span(substr($c, $j, ($this->bytes / 2))),
  371. HTML::span(array("style"=>"background:yellow"),$search),
  372. HTML::span(substr($c, $i+$l, ($this->bytes / 2))))
  373. );
  374. } else {
  375. $c = sprintf(_("%s not found"),
  376. '»'.$search.'«');
  377. return HTML::div(array('style' => 'font-size:x-small','align'=>'center'),
  378. $c);
  379. }
  380. } elseif (($len = strlen($c)) > $this->bytes) {
  381. $c = substr($c, 0, $this->bytes);
  382. }
  383. include_once('lib/BlockParser.php');
  384. // false --> don't bother processing hrefs for embedded WikiLinks
  385. $ct = TransformText($c, $revision_handle->get('markup'), false);
  386. if (empty($pagelist->_sortby[$this->_field]))
  387. unset($revision_handle->_data['%pagedata']['_cached_html']);
  388. return HTML::div(array('style' => 'font-size:x-small'),
  389. HTML::div(array('class' => 'transclusion'), $ct),
  390. // Don't show bytes here if size column present too
  391. ($this->parent->_columns_seen['size'] or !$len) ? "" :
  392. ByteFormatter($len, /*$longformat = */true));
  393. }
  394. function _getSortableValue ($page_handle, &$revision_handle) {
  395. return substr(_PageList_Column::_getValue($page_handle, $revision_handle),0,50);
  396. }
  397. };
  398. class _PageList_Column_author extends _PageList_Column {
  399. function _PageList_Column_author ($field, $default_heading, $align = false) {
  400. _PageList_Column::_PageList_Column($field, $default_heading, $align);
  401. $this->dbi =& $GLOBALS['request']->getDbh();
  402. }
  403. function _getValue ($page_handle, &$revision_handle) {
  404. $author = _PageList_Column::_getValue($page_handle, $revision_handle);
  405. if (isWikiWord($author) && $this->dbi->isWikiPage($author))
  406. return WikiLink($author);
  407. else
  408. return $author;
  409. }
  410. };
  411. class _PageList_Column_owner extends _PageList_Column_author {
  412. function _getValue ($page_handle, &$revision_handle) {
  413. $author = $page_handle->getOwner();
  414. if (isWikiWord($author) && $this->dbi->isWikiPage($author))
  415. return WikiLink($author);
  416. else
  417. return $author;
  418. }
  419. };
  420. class _PageList_Column_creator extends _PageList_Column_author {
  421. function _getValue ($page_handle, &$revision_handle) {
  422. $author = $page_handle->getCreator();
  423. if (isWikiWord($author) && $this->dbi->isWikiPage($author))
  424. return WikiLink($author);
  425. else
  426. return $author;
  427. }
  428. };
  429. class _PageList_Column_pagename extends _PageList_Column_base {
  430. var $_field = 'pagename';
  431. function _PageList_Column_pagename () {
  432. $this->_PageList_Column_base(_("Page Name"));
  433. global $request;
  434. $this->dbi = &$request->getDbh();
  435. }
  436. function _getValue ($page_handle, &$revision_handle) {
  437. if ($this->dbi->isWikiPage($page_handle->getName()))
  438. return WikiLink($page_handle, 'known');
  439. else
  440. return WikiLink($page_handle, 'unknown');
  441. }
  442. function _getSortableValue ($page_handle, &$revision_handle) {
  443. return $page_handle->getName();
  444. }
  445. /**
  446. * Compare two pagenames for sorting. See _PageList_Column::_compare.
  447. **/
  448. function _compare($colvala, $colvalb) {
  449. return strcmp($colvala, $colvalb);
  450. }
  451. };
  452. class PageList {
  453. var $_group_rows = 3;
  454. var $_columns = array();
  455. var $_columnsMap = array(); // Maps column name to column number.
  456. var $_excluded_pages = array();
  457. var $_pages = array();
  458. var $_caption = "";
  459. var $_pagename_seen = false;
  460. var $_types = array();
  461. var $_options = array();
  462. var $_selected = array();
  463. var $_sortby = array();
  464. var $_maxlen = 0;
  465. function PageList ($columns = false, $exclude = false, $options = false) {
  466. if ($options)
  467. $this->_options = $options;
  468. // let plugins predefine only certain objects, such its own custom pagelist columns
  469. if (!empty($this->_options['types'])) {
  470. $this->_types = $this->_options['types'];
  471. unset($this->_options['types']);
  472. }
  473. $this->_initAvailableColumns();
  474. $symbolic_columns =
  475. array(
  476. 'all' => array_diff(array_keys($this->_types), // all but...
  477. array('checkbox','remove','renamed_pagename',
  478. 'content','hi_content','perm','acl')),
  479. 'most' => array('pagename','mtime','author','hits'),
  480. 'some' => array('pagename','mtime','author')
  481. );
  482. if ($columns) {
  483. if (!is_array($columns))
  484. $columns = explode(',', $columns);
  485. // expand symbolic columns:
  486. foreach ($symbolic_columns as $symbol => $cols) {
  487. if (in_array($symbol,$columns)) { // e.g. 'checkbox,all'
  488. $columns = array_diff(array_merge($columns,$cols),array($symbol));
  489. }
  490. }
  491. if (!in_array('pagename',$columns))
  492. $this->_addColumn('pagename');
  493. foreach ($columns as $col) {
  494. $this->_addColumn($col);
  495. }
  496. }
  497. // If 'pagename' is already present, _addColumn() will not add it again
  498. $this->_addColumn('pagename');
  499. foreach (array('sortby','limit','paging','count','dosort') as $key) {
  500. if (!empty($options) and !empty($options[$key])) {
  501. $this->_options[$key] = $options[$key];
  502. } else {
  503. $this->_options[$key] = $GLOBALS['request']->getArg($key);
  504. }
  505. }
  506. $this->_options['sortby'] = $this->sortby($this->_options['sortby'], 'init');
  507. if ($exclude) {
  508. if (is_string($exclude) and !is_array($exclude))
  509. $exclude = $this->explodePageList($exclude, false,
  510. $this->_options['sortby'],
  511. $this->_options['limit']);
  512. $this->_excluded_pages = $exclude;
  513. }
  514. $this->_messageIfEmpty = _("<no matches>");
  515. }
  516. // Currently PageList takes these arguments:
  517. // 1: info, 2: exclude, 3: hash of options
  518. // Here we declare which options are supported, so that
  519. // the calling plugin may simply merge this with its own default arguments
  520. function supportedArgs () {
  521. return array(// Currently supported options:
  522. /* what columns, what pages */
  523. 'info' => 'pagename',
  524. 'exclude' => '', // also wildcards, comma-seperated lists
  525. // and <!plugin-list !> arrays
  526. /* select pages by meta-data: */
  527. 'author' => false, // current user by []
  528. 'owner' => false, // current user by []
  529. 'creator' => false, // current user by []
  530. /* for the sort buttons in <th> */
  531. 'sortby' => '', // same as for WikiDB::getAllPages
  532. // (unsorted is faster)
  533. /* PageList pager options:
  534. * These options may also be given to _generate(List|Table) later
  535. * But limit and offset might help the query WikiDB::getAllPages()
  536. */
  537. 'limit' => 0, // number of rows (pagesize)
  538. 'paging' => 'auto', // 'auto' top + bottom rows if applicable
  539. // // 'top' top only if applicable
  540. // // 'bottom' bottom only if applicable
  541. // // 'none' don't page at all
  542. // (TODO: clarify what if $paging==false ?)
  543. /* list-style options (with single pagename column only so far) */
  544. 'cols' => 1, // side-by-side display of list (1-3)
  545. 'azhead' => 0, // 1: group by initials
  546. // 2: provide shortcut links to initials also
  547. 'comma' => 0, // condensed comma-seperated list,
  548. // 1 if without links, 2 if with
  549. 'commasep' => false, // Default: ', '
  550. 'ordered' => false, // OL or just UL lists (ignored for comma)
  551. );
  552. }
  553. function setCaption ($caption_string) {
  554. $this->_caption = $caption_string;
  555. }
  556. function getCaption () {
  557. // put the total into the caption if needed
  558. if (is_string($this->_caption) && strstr($this->_caption, '%d'))
  559. return sprintf($this->_caption, $this->getTotal());
  560. return $this->_caption;
  561. }
  562. function setMessageIfEmpty ($msg) {
  563. $this->_messageIfEmpty = $msg;
  564. }
  565. function getTotal () {
  566. return !empty($this->_options['count'])
  567. ? (integer) $this->_options['count'] : count($this->_pages);
  568. }
  569. function isEmpty () {
  570. return empty($this->_pages);
  571. }
  572. function addPage($page_handle) {
  573. if (!empty($this->_excluded_pages)) {
  574. if (!in_array((is_string($page_handle) ? $page_handle : $page_handle->getName()),
  575. $this->_excluded_pages))
  576. $this->_pages[] = $page_handle;
  577. } else {
  578. $this->_pages[] = $page_handle;
  579. }
  580. }
  581. function pageNames() {
  582. $pages = array();
  583. foreach ($this->_pages as $page_handle) {
  584. $pages[] = $page_handle->getName();
  585. }
  586. return $pages;
  587. }
  588. function _getPageFromHandle($page_handle) {
  589. if (is_string($page_handle)) {
  590. if (empty($page_handle)) return $page_handle;
  591. //$dbi = $GLOBALS['request']->getDbh(); // no, safe memory!
  592. $page_handle = $GLOBALS['request']->_dbi->getPage($page_handle);
  593. }
  594. return $page_handle;
  595. }
  596. /**
  597. * Take a PageList_Page object, and return an HTML object to display
  598. * it in a table or list row.
  599. */
  600. function _renderPageRow (&$page_handle, $i = 0) {
  601. $page_handle = $this->_getPageFromHandle($page_handle);
  602. //FIXME. only on sf.net
  603. if (!is_object($page_handle)) {
  604. trigger_error("PageList: Invalid page_handle $page_handle", E_USER_WARNING);
  605. return;
  606. }
  607. if (!isset($page_handle)
  608. or empty($page_handle)
  609. or (!empty($this->_excluded_pages)
  610. and in_array($page_handle->getName(), $this->_excluded_pages)))
  611. return; // exclude page.
  612. // enforce view permission
  613. if (!mayAccessPage('view', $page_handle->getName()))
  614. return;
  615. $group = (int)($i / $this->_group_rows);
  616. $class = ($group % 2) ? 'oddrow' : 'evenrow';
  617. $revision_handle = false;
  618. $this->_maxlen = max($this->_maxlen, strlen($page_handle->getName()));
  619. if (count($this->_columns) > 1) {
  620. $row = HTML::tr(array('class' => $class));
  621. foreach ($this->_columns as $col)
  622. $row->pushContent($col->format($this, $page_handle, $revision_handle));
  623. } else {
  624. $col = $this->_columns[0];
  625. $row = $col->_getValue($page_handle, $revision_handle);
  626. }
  627. return $row;
  628. }
  629. function addPages ($page_iter) {
  630. //Todo: if limit check max(strlen(pagename))
  631. while ($page = $page_iter->next()) {
  632. $this->addPage($page);
  633. }
  634. }
  635. function addPageList (&$list) {
  636. if (empty($list)) return; // Protect reset from a null arg
  637. foreach ($list as $page) {
  638. if (is_object($page))
  639. $page = $page->_pagename;
  640. $this->addPage((string)$page);
  641. }
  642. }
  643. function maxLen() {
  644. global $request;
  645. $dbi =& $request->getDbh();
  646. if (isa($dbi,'WikiDB_SQL')) {
  647. extract($dbi->_backend->_table_names);
  648. $res = $dbi->_backend->_dbh->getOne("SELECT max(length(pagename)) FROM $page_tbl");
  649. if (DB::isError($res) || empty($res)) return false;
  650. else return $res;
  651. } elseif (isa($dbi,'WikiDB_ADODB')) {
  652. extract($dbi->_backend->_table_names);
  653. $row = $dbi->_backend->_dbh->getRow("SELECT max(length(pagename)) FROM $page_tbl");
  654. return $row ? $row[0] : false;
  655. } else
  656. return false;
  657. }
  658. function getContent() {
  659. // Note that the <caption> element wants inline content.
  660. $caption = $this->getCaption();
  661. if ($this->isEmpty())
  662. return $this->_emptyList($caption);
  663. elseif (count($this->_columns) == 1)
  664. return $this->_generateList($caption);
  665. else
  666. return $this->_generateTable($caption);
  667. }
  668. function printXML() {
  669. PrintXML($this->getContent());
  670. }
  671. function asXML() {
  672. return AsXML($this->getContent());
  673. }
  674. /**
  675. * Handle sortby requests for the DB iterator and table header links.
  676. * Prefix the column with + or - like "+pagename","-mtime", ...
  677. *
  678. * Supported actions:
  679. * 'init' : unify with predefined order. "pagename" => "+pagename"
  680. * 'flip_order' : "mtime" => "+mtime" => "-mtime" ...
  681. * 'db' : "-pagename" => "pagename DESC"
  682. * 'check' :
  683. *
  684. * Now all columns are sortable. (patch by DanFr)
  685. * Some columns have native DB backend methods, some not.
  686. */
  687. function sortby ($column, $action, $valid_fields=false) {
  688. global $request;
  689. if (empty($column)) return '';
  690. if (is_int($column)) {
  691. $column = $this->_columns[$column - 1]->_field;
  692. //$column = $col->_field;
  693. }
  694. //if (!is_string($column)) return '';
  695. // support multiple comma-delimited sortby args: "+hits,+pagename"
  696. // recursive concat
  697. if (strstr($column, ',')) {
  698. $result = ($action == 'check') ? true : array();
  699. foreach (explode(',', $column) as $col) {
  700. if ($action == 'check')
  701. $result = $result && $this->sortby($col, $action, $valid_fields);
  702. else
  703. $result[] = $this->sortby($col, $action, $valid_fields);
  704. }
  705. // 'check' returns true/false for every col. return true if all are true.
  706. // i.e. the unsupported 'every' operator in functional languages.
  707. if ($action == 'check')
  708. return $result;
  709. else
  710. return join(",", $result);
  711. }
  712. if (substr($column,0,1) == '+') {
  713. $order = '+'; $column = substr($column,1);
  714. } elseif (substr($column,0,1) == '-') {
  715. $order = '-'; $column = substr($column,1);
  716. }
  717. // default initial order: +pagename, -mtime, -hits
  718. if (empty($order))
  719. if (in_array($column, array('mtime','hits')))
  720. $order = '-';
  721. else
  722. $order = '+';
  723. if ($action == 'flip_order') {
  724. return ($order == '+' ? '-' : '+') . $column;
  725. } elseif ($action == 'init') {
  726. $this->_sortby[$column] = $order;
  727. return $order . $column;
  728. } elseif ($action == 'check') {
  729. return (!empty($this->_sortby[$column])
  730. or ($request->getArg('sortby')
  731. and strstr($request->getArg('sortby'),$column)));
  732. } elseif ($action == 'db') {
  733. // Performance enhancement: use native DB sort if possible.
  734. if (($valid_fields and in_array($column, $valid_fields))
  735. or (method_exists($request->_dbi->_backend, 'sortable_columns')
  736. and (in_array($column, $request->_dbi->_backend->sortable_columns())))) {
  737. // omit this sort method from the _sortPages call at rendering
  738. // asc or desc: +pagename, -pagename
  739. return $column . ($order == '+' ? ' ASC' : ' DESC');
  740. } else {
  741. return '';
  742. }
  743. }
  744. return '';
  745. }
  746. // echo implode(":",explodeList("Test*",array("xx","Test1","Test2")));
  747. function explodePageList($input, $include_empty=false, $sortby=false,
  748. $limit=false, $exclude=false)
  749. {
  750. if (empty($input)) return array();
  751. // expand wildcards from list of all pages
  752. if (preg_match('/[\?\*]/', $input)) {
  753. include_once("lib/TextSearchQuery.php");
  754. $search = new TextSearchQuery(str_replace(",", " ", $input), true, 'glob');
  755. $dbi = $GLOBALS['request']->getDbh();
  756. $iter = $dbi->titleSearch($search, $sortby, $limit, $exclude);
  757. $pages = array();
  758. while ($pagehandle = $iter->next()) {
  759. $pages[] = $pagehandle->getName();
  760. }
  761. return $pages;
  762. /*
  763. //TODO: need an SQL optimization here
  764. $allPagehandles = $dbi->getAllPages($include_empty, $sortby, $limit,
  765. $exclude);
  766. while ($pagehandle = $allPagehandles->next()) {
  767. $allPages[] = $pagehandle->getName();
  768. }
  769. return explodeList($input, $allPages);
  770. */
  771. } else {
  772. //TODO: do the sorting, normally not needed if used for exclude only
  773. return explode(',', $input);
  774. }
  775. }
  776. function allPagesByAuthor($wildcard, $include_empty=false, $sortby=false,
  777. $limit=false, $exclude=false) {
  778. $dbi = $GLOBALS['request']->getDbh();
  779. $allPagehandles = $dbi->getAllPages($include_empty, $sortby, $limit, $exclude);
  780. $allPages = array();
  781. if ($wildcard === '[]') {
  782. $wildcard = $GLOBALS['request']->_user->getAuthenticatedId();
  783. if (!$wildcard) return $allPages;
  784. }
  785. $do_glob = preg_match('/[\?\*]/', $wildcard);
  786. while ($pagehandle = $allPagehandles->next()) {
  787. $name = $pagehandle->getName();
  788. $author = $pagehandle->getAuthor();
  789. if ($author) {
  790. if ($do_glob) {
  791. if (glob_match($wildcard, $author))
  792. $allPages[] = $name;
  793. } elseif ($wildcard == $author) {
  794. $allPages[] = $name;
  795. }
  796. }
  797. // TODO: purge versiondata_cache
  798. }
  799. return $allPages;
  800. }
  801. function allPagesByOwner($wildcard, $include_empty=false, $sortby=false,
  802. $limit=false, $exclude=false) {
  803. $dbi = $GLOBALS['request']->getDbh();
  804. $allPagehandles = $dbi->getAllPages($include_empty, $sortby, $limit, $exclude);
  805. $allPages = array();
  806. if ($wildcard === '[]') {
  807. $wildcard = $GLOBALS['request']->_user->getAuthenticatedId();
  808. if (!$wildcard) return $allPages;
  809. }
  810. $do_glob = preg_match('/[\?\*]/', $wildcard);
  811. while ($pagehandle = $allPagehandles->next()) {
  812. $name = $pagehandle->getName();
  813. $owner = $pagehandle->getOwner();
  814. if ($owner) {
  815. if ($do_glob) {
  816. if (glob_match($wildcard, $owner))
  817. $allPages[] = $name;
  818. } elseif ($wildcard == $owner) {
  819. $allPages[] = $name;
  820. }
  821. }
  822. }
  823. return $allPages;
  824. }
  825. function allPagesByCreator($wildcard, $include_empty=false, $sortby=false,
  826. $limit=false, $exclude=false) {
  827. $dbi = $GLOBALS['request']->getDbh();
  828. $allPagehandles = $dbi->getAllPages($include_empty, $sortby, $limit, $exclude);
  829. $allPages = array();
  830. if ($wildcard === '[]') {
  831. $wildcard = $GLOBALS['request']->_user->getAuthenticatedId();
  832. if (!$wildcard) return $allPages;
  833. }
  834. $do_glob = preg_match('/[\?\*]/', $wildcard);
  835. while ($pagehandle = $allPagehandles->next()) {
  836. $name = $pagehandle->getName();
  837. $creator = $pagehandle->getCreator();
  838. if ($creator) {
  839. if ($do_glob) {
  840. if (glob_match($wildcard, $creator))
  841. $allPages[] = $name;
  842. } elseif ($wildcard == $creator) {
  843. $allPages[] = $name;
  844. }
  845. }
  846. }
  847. return $allPages;
  848. }
  849. ////////////////////
  850. // private
  851. ////////////////////
  852. /** Plugin and theme hooks:
  853. * If the pageList is initialized with $options['types'] these types are also initialized,
  854. * overriding the standard types.
  855. */
  856. function _initAvailableColumns() {
  857. global $customPageListColumns;
  858. $standard_types =
  859. array(
  860. 'content'
  861. => new _PageList_Column_content('rev:content', _("Content")),
  862. // new: plugin specific column types initialised by the relevant plugins
  863. /*
  864. 'hi_content' // with highlighted search for SearchReplace
  865. => new _PageList_Column_content('rev:hi_content', _("Content")),
  866. 'remove'
  867. => new _PageList_Column_remove('remove', _("Remove")),
  868. // initialised by the plugin
  869. 'renamed_pagename'
  870. => new _PageList_Column_renamed_pagename('rename', _("Rename to")),
  871. 'perm'
  872. => new _PageList_Column_perm('perm', _("Permission")),
  873. 'acl'
  874. => new _PageList_Column_acl('acl', _("ACL")),
  875. */
  876. 'checkbox'
  877. => new _PageList_Column_checkbox('p', _("Select")),
  878. 'pagename'
  879. => new _PageList_Column_pagename,
  880. 'mtime'
  881. => new _PageList_Column_time('rev:mtime', _("Last Modified")),
  882. 'hits'
  883. => new _PageList_Column('hits', _("Hits"), 'right'),
  884. 'size'
  885. => new _PageList_Column_size('rev:size', _("Size"), 'right'),
  886. /*array('align' => 'char', 'char' => ' ')*/
  887. 'summary'
  888. => new _PageList_Column('rev:summary', _("Last Summary")),
  889. 'version'
  890. => new _PageList_Column_version('rev:version', _("Version"),
  891. 'right'),
  892. 'author'
  893. => new _PageList_Column_author('rev:author', _("Last Author")),
  894. 'owner'
  895. => new _PageList_Column_owner('author_id', _("Owner")),
  896. 'creator'
  897. => new _PageList_Column_creator('author_id', _("Creator")),
  898. /*
  899. 'group'
  900. => new _PageList_Column_author('group', _("Group")),
  901. */
  902. 'locked'
  903. => new _PageList_Column_bool('locked', _("Locked"),
  904. _("locked")),
  905. 'minor'
  906. => new _PageList_Column_bool('rev:is_minor_edit',
  907. _("Minor Edit"), _("minor")),
  908. 'markup'
  909. => new _PageList_Column('rev:markup', _("Markup")),
  910. // 'rating' initialised by the wikilens theme hook: addPageListColumn
  911. /*
  912. 'rating'
  913. => new _PageList_Column_rating('rating', _("Rate")),
  914. */
  915. );
  916. if (empty($this->_types))
  917. $this->_types = array();
  918. // add plugin specific pageList columns, initialized by $options['types']
  919. $this->_types = array_merge($standard_types, $this->_types);
  920. // add theme custom specific pageList columns:
  921. // set the 4th param as the current pagelist object.
  922. if (!empty($customPageListColumns)) {
  923. foreach ($customPageListColumns as $column => $params) {
  924. $class_name = array_shift($params);
  925. $params[3] =& $this;
  926. $class = new $class_name($params);
  927. $this->_types[$column] =& $class;
  928. }
  929. }
  930. }
  931. function getOption($option) {
  932. if (array_key_exists($option, $this->_options)) {
  933. return $this->_options[$option];
  934. }
  935. else {
  936. return null;
  937. }
  938. }
  939. /**
  940. * Add a column to this PageList, given a column name.
  941. * The name is a type, and optionally has a : and a label. Examples:
  942. *
  943. * pagename
  944. * pagename:This page
  945. * mtime
  946. * mtime:Last modified
  947. *
  948. * If this function is called multiple times for the same type, the
  949. * column will only be added the first time, and ignored the succeeding times.
  950. * If you wish to add multiple columns of the same type, use addColumnObject().
  951. *
  952. * @param column name
  953. * @return true if column is added, false otherwise
  954. */
  955. function _addColumn ($column) {
  956. if (isset($this->_columns_seen[$column]))
  957. return false; // Already have this one.
  958. if (!isset($this->_types[$column]))
  959. $this->_initAvailableColumns();
  960. $this->_columns_seen[$column] = true;
  961. if (strstr($column, ':'))
  962. list ($column, $heading) = explode(':', $column, 2);
  963. // FIXME: these column types have hooks (objects) elsewhere
  964. // Omitting this warning should be overridable by the extension
  965. if (!isset($this->_types[$column])) {
  966. $silently_ignore = array('numbacklinks',
  967. 'rating',/*'ratingwidget',*/
  968. 'coagreement', 'minmisery',
  969. /*'prediction',*/
  970. 'averagerating', 'top3recs');
  971. if (!in_array($column, $silently_ignore))
  972. trigger_error(sprintf("%s: Bad column", $column), E_USER_NOTICE);
  973. return false;
  974. }
  975. // FIXME: anon users might rate and see ratings also.
  976. // Defer this logic to the plugin.
  977. if ($column == 'rating' and !$GLOBALS['request']->_user->isSignedIn())
  978. return false;
  979. $this->addColumnObject($this->_types[$column]);
  980. return true;
  981. }
  982. /**
  983. * Add a column to this PageList, given a column object.
  984. *
  985. * @param $col object An object derived from _PageList_Column.
  986. **/
  987. function addColumnObject($col) {
  988. if (is_array($col)) {// custom column object
  989. $params =& $col;
  990. $class_name = array_shift($params);
  991. $params[3] =& $this;
  992. $col = new $class_name($params);
  993. }
  994. $heading = $col->getHeading();
  995. if (!empty($heading))
  996. $col->setHeading($heading);
  997. $this->_columns[] = $col;
  998. $this->_columnsMap[$col->_field] = count($this->_columns); // start with 1
  999. }
  1000. /**
  1001. * Compare _PageList_Page objects.
  1002. **/
  1003. function _pageCompare(&$a, &$b) {
  1004. if (empty($this->_sortby) or count($this->_sortby) == 0) {
  1005. // No columns to sort by
  1006. return 0;
  1007. }
  1008. else {
  1009. $pagea = $this->_getPageFromHandle($a); // If a string, convert to page
  1010. assert(isa($pagea, 'WikiDB_Page'));
  1011. $pageb = $this->_getPageFromHandle($b); // If a string, convert to page
  1012. assert(isa($pageb, 'WikiDB_Page'));
  1013. foreach ($this->_sortby as $colNum => $direction) {
  1014. if (!is_int($colNum)) // or column fieldname
  1015. $colNum = $this->_columnsMap[$colNum];
  1016. $col = $this->_columns[$colNum - 1];
  1017. assert(isset($col));
  1018. $revision_handle = false;
  1019. $aval = $col->_getSortableValue($pagea, $revision_handle);
  1020. $bval = $col->_getSortableValue($pageb, $revision_handle);
  1021. $cmp = $col->_compare($aval, $bval);
  1022. if ($direction === "-") // Reverse the sense of the comparison
  1023. $cmp *= -1;
  1024. if ($cmp !== 0)
  1025. // This is the first comparison that is not equal-- go with it
  1026. return $cmp;
  1027. }
  1028. return 0;
  1029. }
  1030. }
  1031. /**
  1032. * Put pages in order according to the sortby arg, if given
  1033. * If the sortby cols are already sorted by the DB call, don't do usort.
  1034. * TODO: optimize for multiple sortable cols
  1035. */
  1036. function _sortPages() {
  1037. if (count($this->_sortby) > 0) {
  1038. $need_sort = $this->_options['dosort'];
  1039. foreach ($this->_sortby as $col => $dir) {
  1040. if (! $this->sortby($col, 'db'))
  1041. $need_sort = true;
  1042. }
  1043. if ($need_sort) { // There are some columns to sort by
  1044. usort($this->_pages, array($this, '_pageCompare'));
  1045. }
  1046. }
  1047. //unset($GLOBALS['PhpWiki_pagelist']);
  1048. }
  1049. function limit($limit) {
  1050. if (is_array($limit)) return $limit;
  1051. if (strstr($limit, ','))
  1052. return split(',', $limit);
  1053. else
  1054. return array(0, $limit);
  1055. }
  1056. function pagingTokens($numrows = false, $ncolumns = false, $limit = false) {
  1057. if ($numrows === false)
  1058. $numrows = $this->getTotal();
  1059. if ($limit === false)
  1060. $limit = $this->_options['limit'];
  1061. if ($ncolumns === false)
  1062. $ncolumns = count($this->_columns);
  1063. list($offset, $pagesize) = $this->limit($limit);
  1064. if (!$pagesize or
  1065. (!$offset and $numrows <= $pagesize) or
  1066. ($offset + $pagesize < 0))
  1067. return false;
  1068. $request = &$GLOBALS['request'];
  1069. $pagename = $request->getArg('pagename');
  1070. $defargs = $request->args;
  1071. if (USE_PATH_INFO) unset($defargs['pagename']);
  1072. if ($defargs['action'] == 'browse') unset($defargs['action']);
  1073. $prev = $defargs;
  1074. $tokens = array();
  1075. $tokens['PREV'] = false; $tokens['PREV_LINK'] = "";
  1076. $tokens['COLS'] = count($this->_columns);
  1077. $tokens['COUNT'] = $numrows;
  1078. $tokens['OFFSET'] = $offset;
  1079. $tokens['SIZE'] = $pagesize;
  1080. $tokens['NUMPAGES'] = (int)($numrows / $pagesize)+1;
  1081. $tokens['ACTPAGE'] = (int) (($offset+1) / $pagesize)+1;
  1082. if ($offset > 0) {
  1083. $prev['limit'] = max(0, $offset - $pagesize) . ",$pagesize";
  1084. $prev['count'] = $numrows;
  1085. $tokens['LIMIT'] = $prev['limit'];
  1086. $tokens['PREV'] = true;
  1087. $tokens['PREV_LINK'] = WikiURL($pagename, $prev);
  1088. $prev['limit'] = "0,$pagesize";
  1089. $tokens['FIRST_LINK'] = WikiURL($pagename, $prev);
  1090. }
  1091. $next = $defargs;
  1092. $tokens['NEXT'] = false; $tokens['NEXT_LINK'] = "";
  1093. if ($offset + $pagesize < $numrows) {
  1094. $next['limit'] = min($offset + $pagesize, $numrows - $pagesize) . ",$pagesize";
  1095. $next['count'] = $numrows;
  1096. $tokens['LIMIT'] = $next['limit'];
  1097. $tokens['NEXT'] = true;
  1098. $tokens['NEXT_LINK'] = WikiURL($pagename, $next);
  1099. $next['limit'] = $numrows - $pagesize . ",$pagesize";
  1100. $tokens['LAST_LINK'] = WikiURL($pagename, $next);
  1101. }
  1102. return $tokens;
  1103. }
  1104. // make a table given the caption
  1105. function _generateTable($caption) {
  1106. if (count($this->_sortby) > 0) $this->_sortPages();
  1107. $rows = array(); $i = 0;
  1108. foreach ($this->_pages as $pagenum => $page) {
  1109. $rows[] = $this->_renderPageRow($page, $i++);
  1110. }
  1111. $table = HTML::table(array('cellpadding' => 0,
  1112. 'cellspacing' => 1,
  1113. 'border' => 0,
  1114. 'class' => 'pagelist'));
  1115. if ($caption)
  1116. $table->pushContent(HTML::caption(array('align'=>'top'), $caption));
  1117. //Warning: This is quite fragile. It depends solely on a private variable
  1118. // in ->_addColumn()
  1119. if (!empty($this->_columns_seen['checkbox'])) {
  1120. $table->pushContent($this->_jsFlipAll());
  1121. }
  1122. $do_paging = ( isset($this->_options['paging'])
  1123. and !empty($this->_options['limit'])
  1124. and $this->getTotal()
  1125. and $this->_options['paging'] != 'none' );
  1126. $row = HTML::tr();
  1127. $table_summary = array();
  1128. $i = 1; // start with 1!
  1129. foreach ($this->_columns as $col) {
  1130. $heading = $col->button_heading($this, $i);
  1131. if ( $do_paging
  1132. and isset($col->_field)
  1133. and $col->_field == 'pagename'
  1134. and ($maxlen = $this->maxLe…

Large files files are truncated, but you can click here to view the full file