PageRenderTime 42ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/blocks/manage/column.php

https://github.com/Icybee/Icybee
PHP | 472 lines | 272 code | 71 blank | 129 comment | 24 complexity | 07a921063d1c7de2727d1e5700aa8154 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /*
  3. * This file is part of the Icybee package.
  4. *
  5. * (c) Olivier Laviale <olivier.laviale@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Icybee\ManageBlock;
  11. use ICanBoogie\ActiveRecord\Query;
  12. use Brickrouge\DropdownMenu;
  13. use Brickrouge\Element;
  14. /**
  15. * Representation of a column of the manager element.
  16. *
  17. * This is the base class for all the columns.
  18. *
  19. * @property-read mixed $filter_value The value used by the column to filter the records.
  20. * @property-read bool $is_filtering `true` if the column is currently filtering the records.
  21. * `false` otherwise.
  22. */
  23. class Column extends \ICanBoogie\Object
  24. {
  25. const ORDER_ASC = 1;
  26. const ORDER_DESC = -1;
  27. public $id;
  28. public $title;
  29. public $class;
  30. public $filters;
  31. public $reset;
  32. public $orderable = false;
  33. public $order;
  34. public $default_order = self::ORDER_ASC;
  35. public $discreet = true;
  36. protected $header_renderer = 'Icybee\ManageBlock\HeaderRenderer';
  37. protected $cell_renderer = 'Icybee\ManageBlock\CellRenderer';
  38. public $manager;
  39. public function __construct(\Icybee\ManageBlock $manager, $id, array $options=array())
  40. {
  41. // TODO-20130627
  42. if (method_exists($this, 'update_filters'))
  43. {
  44. throw new \Exception("The <q>update_filters()</q> method is deprecated, please use the <q>alter_filters()</q> method.");
  45. }
  46. if (method_exists($this, 'alter_query'))
  47. {
  48. throw new \Exception("The <q>alter_query()</q> method is deprecated, please use the <q>alter_query_with_filter()</q> method.");
  49. }
  50. // /
  51. $this->manager = $manager;
  52. $this->id = $id;
  53. $this->modify_options($options + $this->resolve_default_values());
  54. }
  55. // TODO-20130627: remove this compat method
  56. protected function set_filtering()
  57. {
  58. throw new \InvalidArgumentException("The <q>filtering</q> property is deprecated. Use <q>is_filtering</q> or <q>filter_value</q>");
  59. }
  60. protected function set_label($value)
  61. {
  62. trigger_error("The <q>label</q> property is deprecated, use <q>title</q> instead.");
  63. $this->title = $value;
  64. }
  65. protected function get_label()
  66. {
  67. trigger_error("The <q>label</q> property is deprecated, use <q>title</q> instead.");
  68. return $this->title;
  69. }
  70. /**
  71. * Returns `true` if the column is filtering the records.
  72. *
  73. * @return boolean
  74. */
  75. protected function get_is_filtering()
  76. {
  77. return $this->manager->is_filtering($this->id);
  78. }
  79. /**
  80. * Returns the value used by the column to filter the records.
  81. *
  82. * @return mixed|null
  83. */
  84. protected function get_filter_value()
  85. {
  86. return $this->is_filtering ? $this->manager->options->filters[$this->id] : null;
  87. }
  88. /**
  89. * Translates and formats the specified string.
  90. *
  91. * @param string $native
  92. * @param array $args
  93. * @param array $options
  94. *
  95. * @return string
  96. */
  97. public function t($native, array $args=array(), array $options=array())
  98. {
  99. return $this->manager->t($native, $args, $options);
  100. }
  101. /**
  102. * Returns the default values for the column initialization.
  103. *
  104. * @return array
  105. */
  106. protected function resolve_default_values()
  107. {
  108. $id = $this->id;
  109. $fields = $this->manager->model->extended_schema['fields'];
  110. $field = isset($fields[$id]) ? $fields[$id] : null;
  111. $orderable = true;
  112. $default_order = 1;
  113. $discreet = false;
  114. if ($field)
  115. {
  116. if (($field['type'] == 'integer' && (!empty($field['primary']) || !empty($field['indexed']))) || $field['type'] == 'boolean')
  117. {
  118. $orderable = false;
  119. if (!empty($field['indexed']))
  120. {
  121. $discreet = true;
  122. }
  123. }
  124. if (in_array($field['type'], array('date', 'datetime', 'timestamp')))
  125. {
  126. $default_order = -1;
  127. }
  128. }
  129. else
  130. {
  131. $orderable = false;
  132. }
  133. return array
  134. (
  135. 'title' => $id,
  136. 'reset' => "?$id=",
  137. 'orderable' => $orderable,
  138. 'default_order' => $default_order
  139. );
  140. }
  141. /**
  142. * Modifies the options of the column.
  143. *
  144. * @param array $options
  145. *
  146. * @return \Icybee\ManageBlock\Column
  147. */
  148. public function modify_options(array $options)
  149. {
  150. foreach ($options as $option => $value)
  151. {
  152. switch ($option)
  153. {
  154. case 'label':
  155. case 'title':
  156. case 'class':
  157. case 'filters':
  158. case 'reset':
  159. case 'orderable':
  160. case 'order':
  161. case 'default_order':
  162. case 'discreet':
  163. case 'filtering':
  164. case 'header_renderer':
  165. case 'cell_renderer':
  166. $this->$option = $value;
  167. break;
  168. case 'hook':
  169. // var_dump($value);
  170. break;
  171. }
  172. }
  173. return $this;
  174. }
  175. /**
  176. * Updates the filters for the records according to the specified modifiers.
  177. *
  178. * Note: The filters are returned as is, subclasses shoudl override the method according to
  179. * their needs.
  180. *
  181. * @param array $filters
  182. * @param array $modifiers
  183. *
  184. * @return array The updated filters.
  185. */
  186. public function alter_filters(array $filters, array $modifiers)
  187. {
  188. return $filters;
  189. }
  190. /**
  191. * Alters the query according to the filter value specified.
  192. *
  193. * The method does a simple `{$this->id} = {$filter_value}`, subclasses might want to override
  194. * the method according to the kind of filter they provide.
  195. *
  196. * @param Query $query
  197. * @param mixed $filter_value
  198. *
  199. * @return Query
  200. */
  201. public function alter_query_with_filter(Query $query, $filter_value)
  202. {
  203. if ($filter_value)
  204. {
  205. $query->and(array($this->id => $filter_value));
  206. }
  207. return $query;
  208. }
  209. /**
  210. * Alters the ORDER clause of the query according to the column identifier and the order
  211. * direction.
  212. *
  213. * The implementation of the method is simple, subclasses might want to override the method
  214. * to support complexer ordering.
  215. *
  216. * @param Query $query
  217. * @param int $order_direction
  218. *
  219. * @return Query
  220. */
  221. public function alter_query_with_order(Query $query, $order_direction)
  222. {
  223. return $query->order("`$this->id` " . ($order_direction < 0 ? 'desc' : 'asc'));
  224. }
  225. /**
  226. * Alters the records.
  227. *
  228. * Note: The records are returned as is, subclasses might override the method according to
  229. * their needs.
  230. *
  231. * @param array $records
  232. *
  233. * @return array[]ActiveRecord
  234. */
  235. public function alter_records(array $records)
  236. {
  237. return $records;
  238. }
  239. /**
  240. * Returns the options available for the filter.
  241. *
  242. * @return array|null
  243. */
  244. protected function get_options()
  245. {
  246. if (empty($this->filters['options']))
  247. {
  248. return;
  249. }
  250. $options = array();
  251. foreach ($this->filters['options'] as $qs => $label)
  252. {
  253. if ($qs[0] == '=')
  254. {
  255. $qs = $this->id . $qs;
  256. }
  257. $options['?' . $qs] = $this->manager->t($label);
  258. }
  259. return $options;
  260. }
  261. /**
  262. * Renders the column's options.
  263. */
  264. public function render_options()
  265. {
  266. $options = $this->get_options();
  267. if (!$options)
  268. {
  269. return;
  270. }
  271. if ($this->is_filtering)
  272. {
  273. $options = array_merge
  274. (
  275. array
  276. (
  277. $this->reset => $this->t('Display all'),
  278. false
  279. ),
  280. $options
  281. );
  282. }
  283. $menu = new DropdownMenu
  284. (
  285. array
  286. (
  287. DropdownMenu::OPTIONS => $options,
  288. 'value' => $this->filter_value
  289. )
  290. );
  291. return <<<EOT
  292. <div class="dropdown navbar"><a href="#" data-toggle="dropdown"><i class="icon-cog"></i></a>$menu</div>
  293. EOT;
  294. }
  295. /**
  296. * Renders the column's header.
  297. *
  298. * @return string
  299. */
  300. public function render_header()
  301. {
  302. $renderer = $this->header_renderer;
  303. if (!($renderer instanceof HeaderRenderer))
  304. {
  305. $this->header_renderer = $renderer = new $renderer($this);
  306. }
  307. return $renderer();
  308. }
  309. /**
  310. * Renders a column cell.
  311. *
  312. * @param mixed $record
  313. *
  314. * @return string
  315. */
  316. public function render_cell($record)
  317. {
  318. $renderer = $this->cell_renderer;
  319. if (!($renderer instanceof CellRenderer))
  320. {
  321. $this->cell_renderer = $renderer = new $renderer($this);
  322. }
  323. return $renderer($record, $this->id);
  324. }
  325. /**
  326. * Adds assets to the document.
  327. *
  328. * Subclasses might implement this method to add assets to the document.
  329. *
  330. * @param \Brickrouge\Document $document
  331. */
  332. public function add_assets(\Brickrouge\Document $document)
  333. {
  334. }
  335. }
  336. /**
  337. * Default header renderer.
  338. */
  339. class HeaderRenderer
  340. {
  341. protected $column;
  342. public function __construct(Column $column)
  343. {
  344. $this->column = $column;
  345. }
  346. public function __invoke()
  347. {
  348. $column = $this->column;
  349. $module = $this->column->manager->module;
  350. $id = $column->id;
  351. $title = $column->title;
  352. $t = $this->column->manager->t;
  353. if ($title)
  354. {
  355. $title = $t($id, array(), array('scope' => 'column', 'default' => $title));
  356. }
  357. if ($column->is_filtering)
  358. {
  359. $a_title = $t('View all');
  360. $title = $title ?: '&nbsp;';
  361. return <<<EOT
  362. <a href="{$column->reset}" title="{$a_title}"><span class="title">{$title}</span></a>
  363. EOT;
  364. }
  365. if ($title && $column->orderable)
  366. {
  367. $order = $column->order;
  368. $order_reverse = ($order === null) ? $column->default_order : -$order;
  369. return new Element
  370. (
  371. 'a', array
  372. (
  373. Element::INNER_HTML => '<span class="title">' . $title . '</span>',
  374. 'title' => $t('Sort by: :identifier', array(':identifier' => $title)),
  375. 'href' => "?order=$id:" . ($order_reverse < 0 ? 'desc' : 'asc'),
  376. 'class' => $order ? ($order < 0 ? 'desc' : 'asc') : null
  377. )
  378. );
  379. }
  380. return $title;
  381. }
  382. }
  383. /**
  384. * Default cell renderer.
  385. */
  386. class CellRenderer
  387. {
  388. protected $column;
  389. public function __construct(Column $column)
  390. {
  391. $this->column = $column;
  392. }
  393. public function __invoke($record, $property)
  394. {
  395. return \Brickrouge\escape($record->$property);
  396. }
  397. public function t($str, array $args=array(), array $options=array())
  398. {
  399. return $this->column->t($str, $args, $options);
  400. }
  401. }