PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/legacy/model/list.php

https://github.com/J2MTecnologia/joomla-3.x
PHP | 682 lines | 333 code | 100 blank | 249 comment | 46 complexity | cc1ea609996e8edd6660e615a8140a38 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * @package Joomla.Legacy
  4. * @subpackage Model
  5. *
  6. * @copyright Copyright (C) 2005 - 2014 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE
  8. */
  9. defined('JPATH_PLATFORM') or die;
  10. /**
  11. * Model class for handling lists of items.
  12. *
  13. * @package Joomla.Legacy
  14. * @subpackage Model
  15. * @since 12.2
  16. */
  17. class JModelList extends JModelLegacy
  18. {
  19. /**
  20. * Internal memory based cache array of data.
  21. *
  22. * @var array
  23. * @since 12.2
  24. */
  25. protected $cache = array();
  26. /**
  27. * Context string for the model type. This is used to handle uniqueness
  28. * when dealing with the getStoreId() method and caching data structures.
  29. *
  30. * @var string
  31. * @since 12.2
  32. */
  33. protected $context = null;
  34. /**
  35. * Valid filter fields or ordering.
  36. *
  37. * @var array
  38. * @since 12.2
  39. */
  40. protected $filter_fields = array();
  41. /**
  42. * An internal cache for the last query used.
  43. *
  44. * @var JDatabaseQuery
  45. * @since 12.2
  46. */
  47. protected $query = array();
  48. /**
  49. * Name of the filter form to load
  50. *
  51. * @var string
  52. * @since 3.2
  53. */
  54. protected $filterFormName = null;
  55. /**
  56. * Associated HTML form
  57. *
  58. * @var string
  59. */
  60. protected $htmlFormName = 'adminForm';
  61. /**
  62. * Constructor.
  63. *
  64. * @param array $config An optional associative array of configuration settings.
  65. *
  66. * @see JModelLegacy
  67. * @since 12.2
  68. */
  69. public function __construct($config = array())
  70. {
  71. parent::__construct($config);
  72. // Add the ordering filtering fields white list.
  73. if (isset($config['filter_fields']))
  74. {
  75. $this->filter_fields = $config['filter_fields'];
  76. }
  77. // Guess the context as Option.ModelName.
  78. if (empty($this->context))
  79. {
  80. $this->context = strtolower($this->option . '.' . $this->getName());
  81. }
  82. }
  83. /**
  84. * Method to cache the last query constructed.
  85. *
  86. * This method ensures that the query is constructed only once for a given state of the model.
  87. *
  88. * @return JDatabaseQuery A JDatabaseQuery object
  89. *
  90. * @since 12.2
  91. */
  92. protected function _getListQuery()
  93. {
  94. // Capture the last store id used.
  95. static $lastStoreId;
  96. // Compute the current store id.
  97. $currentStoreId = $this->getStoreId();
  98. // If the last store id is different from the current, refresh the query.
  99. if ($lastStoreId != $currentStoreId || empty($this->query))
  100. {
  101. $lastStoreId = $currentStoreId;
  102. $this->query = $this->getListQuery();
  103. }
  104. return $this->query;
  105. }
  106. /**
  107. * Function to get the active filters
  108. *
  109. * @return array Associative array in the format: array('filter_published' => 0)
  110. *
  111. * @since 3.2
  112. */
  113. public function getActiveFilters()
  114. {
  115. $activeFilters = array();
  116. if (!empty($this->filter_fields))
  117. {
  118. foreach ($this->filter_fields as $filter)
  119. {
  120. $filterName = 'filter.' . $filter;
  121. if (property_exists($this->state, $filterName) && (!empty($this->state->{$filterName}) || is_numeric($this->state->{$filterName})))
  122. {
  123. $activeFilters[$filter] = $this->state->get($filterName);
  124. }
  125. }
  126. }
  127. return $activeFilters;
  128. }
  129. /**
  130. * Method to get an array of data items.
  131. *
  132. * @return mixed An array of data items on success, false on failure.
  133. *
  134. * @since 12.2
  135. */
  136. public function getItems()
  137. {
  138. // Get a storage key.
  139. $store = $this->getStoreId();
  140. // Try to load the data from internal storage.
  141. if (isset($this->cache[$store]))
  142. {
  143. return $this->cache[$store];
  144. }
  145. // Load the list items.
  146. $query = $this->_getListQuery();
  147. try
  148. {
  149. $items = $this->_getList($query, $this->getStart(), $this->getState('list.limit'));
  150. }
  151. catch (RuntimeException $e)
  152. {
  153. $this->setError($e->getMessage());
  154. return false;
  155. }
  156. // Add the items to the internal cache.
  157. $this->cache[$store] = $items;
  158. return $this->cache[$store];
  159. }
  160. /**
  161. * Method to get a JDatabaseQuery object for retrieving the data set from a database.
  162. *
  163. * @return JDatabaseQuery A JDatabaseQuery object to retrieve the data set.
  164. *
  165. * @since 12.2
  166. */
  167. protected function getListQuery()
  168. {
  169. $db = $this->getDbo();
  170. $query = $db->getQuery(true);
  171. return $query;
  172. }
  173. /**
  174. * Method to get a JPagination object for the data set.
  175. *
  176. * @return JPagination A JPagination object for the data set.
  177. *
  178. * @since 12.2
  179. */
  180. public function getPagination()
  181. {
  182. // Get a storage key.
  183. $store = $this->getStoreId('getPagination');
  184. // Try to load the data from internal storage.
  185. if (isset($this->cache[$store]))
  186. {
  187. return $this->cache[$store];
  188. }
  189. // Create the pagination object.
  190. $limit = (int) $this->getState('list.limit') - (int) $this->getState('list.links');
  191. $page = new JPagination($this->getTotal(), $this->getStart(), $limit);
  192. // Add the object to the internal cache.
  193. $this->cache[$store] = $page;
  194. return $this->cache[$store];
  195. }
  196. /**
  197. * Method to get a store id based on the model configuration state.
  198. *
  199. * This is necessary because the model is used by the component and
  200. * different modules that might need different sets of data or different
  201. * ordering requirements.
  202. *
  203. * @param string $id An identifier string to generate the store id.
  204. *
  205. * @return string A store id.
  206. *
  207. * @since 12.2
  208. */
  209. protected function getStoreId($id = '')
  210. {
  211. // Add the list state to the store id.
  212. $id .= ':' . $this->getState('list.start');
  213. $id .= ':' . $this->getState('list.limit');
  214. $id .= ':' . $this->getState('list.ordering');
  215. $id .= ':' . $this->getState('list.direction');
  216. return md5($this->context . ':' . $id);
  217. }
  218. /**
  219. * Method to get the total number of items for the data set.
  220. *
  221. * @return integer The total number of items available in the data set.
  222. *
  223. * @since 12.2
  224. */
  225. public function getTotal()
  226. {
  227. // Get a storage key.
  228. $store = $this->getStoreId('getTotal');
  229. // Try to load the data from internal storage.
  230. if (isset($this->cache[$store]))
  231. {
  232. return $this->cache[$store];
  233. }
  234. // Load the total.
  235. $query = $this->_getListQuery();
  236. try
  237. {
  238. $total = (int) $this->_getListCount($query);
  239. }
  240. catch (RuntimeException $e)
  241. {
  242. $this->setError($e->getMessage());
  243. return false;
  244. }
  245. // Add the total to the internal cache.
  246. $this->cache[$store] = $total;
  247. return $this->cache[$store];
  248. }
  249. /**
  250. * Method to get the starting number of items for the data set.
  251. *
  252. * @return integer The starting number of items available in the data set.
  253. *
  254. * @since 12.2
  255. */
  256. public function getStart()
  257. {
  258. $store = $this->getStoreId('getstart');
  259. // Try to load the data from internal storage.
  260. if (isset($this->cache[$store]))
  261. {
  262. return $this->cache[$store];
  263. }
  264. $start = $this->getState('list.start');
  265. $limit = $this->getState('list.limit');
  266. $total = $this->getTotal();
  267. if ($start > $total - $limit)
  268. {
  269. $start = max(0, (int) (ceil($total / $limit) - 1) * $limit);
  270. }
  271. // Add the total to the internal cache.
  272. $this->cache[$store] = $start;
  273. return $this->cache[$store];
  274. }
  275. /**
  276. * Get the filter form
  277. *
  278. * @param array $data data
  279. * @param boolean $loadData load current data
  280. *
  281. * @return JForm/false the JForm object or false
  282. *
  283. * @since 3.2
  284. */
  285. public function getFilterForm($data = array(), $loadData = true)
  286. {
  287. $form = null;
  288. // Try to locate the filter form automatically. Example: ContentModelArticles => "filter_articles"
  289. if (empty($this->filterFormName))
  290. {
  291. $classNameParts = explode('Model', get_called_class());
  292. if (count($classNameParts) == 2)
  293. {
  294. $this->filterFormName = 'filter_' . strtolower($classNameParts[1]);
  295. }
  296. }
  297. if (!empty($this->filterFormName))
  298. {
  299. // Get the form.
  300. $form = $this->loadForm($this->context . '.filter', $this->filterFormName, array('control' => '', 'load_data' => $loadData));
  301. }
  302. return $form;
  303. }
  304. /**
  305. * Method to get a form object.
  306. *
  307. * @param string $name The name of the form.
  308. * @param string $source The form source. Can be XML string if file flag is set to false.
  309. * @param array $options Optional array of options for the form creation.
  310. * @param boolean $clear Optional argument to force load a new form.
  311. * @param string $xpath An optional xpath to search for the fields.
  312. *
  313. * @return mixed JForm object on success, False on error.
  314. *
  315. * @see JForm
  316. * @since 3.2
  317. */
  318. protected function loadForm($name, $source = null, $options = array(), $clear = false, $xpath = false)
  319. {
  320. // Handle the optional arguments.
  321. $options['control'] = JArrayHelper::getValue($options, 'control', false);
  322. // Create a signature hash.
  323. $hash = md5($source . serialize($options));
  324. // Check if we can use a previously loaded form.
  325. if (isset($this->_forms[$hash]) && !$clear)
  326. {
  327. return $this->_forms[$hash];
  328. }
  329. // Get the form.
  330. JForm::addFormPath(JPATH_COMPONENT . '/models/forms');
  331. JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
  332. try
  333. {
  334. $form = JForm::getInstance($name, $source, $options, false, $xpath);
  335. if (isset($options['load_data']) && $options['load_data'])
  336. {
  337. // Get the data for the form.
  338. $data = $this->loadFormData();
  339. }
  340. else
  341. {
  342. $data = array();
  343. }
  344. // Allow for additional modification of the form, and events to be triggered.
  345. // We pass the data because plugins may require it.
  346. $this->preprocessForm($form, $data);
  347. // Load the data into the form after the plugins have operated.
  348. $form->bind($data);
  349. }
  350. catch (Exception $e)
  351. {
  352. $this->setError($e->getMessage());
  353. return false;
  354. }
  355. // Store the form for later.
  356. $this->_forms[$hash] = $form;
  357. return $form;
  358. }
  359. /**
  360. * Method to get the data that should be injected in the form.
  361. *
  362. * @return mixed The data for the form.
  363. *
  364. * @since 3.2
  365. */
  366. protected function loadFormData()
  367. {
  368. // Check the session for previously entered form data.
  369. $data = JFactory::getApplication()->getUserState($this->context, new stdClass);
  370. // Pre-fill the list options
  371. if (!property_exists($data, 'list'))
  372. {
  373. $data->list = array(
  374. 'direction' => $this->state->{'list.direction'},
  375. 'limit' => $this->state->{'list.limit'},
  376. 'ordering' => $this->state->{'list.ordering'},
  377. 'start' => $this->state->{'list.start'}
  378. );
  379. }
  380. return $data;
  381. }
  382. /**
  383. * Method to auto-populate the model state.
  384. *
  385. * This method should only be called once per instantiation and is designed
  386. * to be called on the first call to the getState() method unless the model
  387. * configuration flag to ignore the request is set.
  388. *
  389. * Note. Calling getState in this method will result in recursion.
  390. *
  391. * @param string $ordering An optional ordering field.
  392. * @param string $direction An optional direction (asc|desc).
  393. *
  394. * @return void
  395. *
  396. * @since 12.2
  397. */
  398. protected function populateState($ordering = null, $direction = null)
  399. {
  400. // If the context is set, assume that stateful lists are used.
  401. if ($this->context)
  402. {
  403. $app = JFactory::getApplication();
  404. // Receive & set filters
  405. if ($filters = $app->getUserStateFromRequest($this->context . '.filter', 'filter', array(), 'array'))
  406. {
  407. foreach ($filters as $name => $value)
  408. {
  409. $this->setState('filter.' . $name, $value);
  410. }
  411. }
  412. $limit = 0;
  413. // Receive & set list options
  414. if ($list = $app->getUserStateFromRequest($this->context . '.list', 'list', array(), 'array'))
  415. {
  416. foreach ($list as $name => $value)
  417. {
  418. // Extra validations
  419. switch ($name)
  420. {
  421. case 'fullordering':
  422. $orderingParts = explode(' ', $value);
  423. if (count($orderingParts) >= 2)
  424. {
  425. // Latest part will be considered the direction
  426. $fullDirection = end($orderingParts);
  427. if (in_array(strtoupper($fullDirection), array('ASC', 'DESC', '')))
  428. {
  429. $this->setState('list.direction', $fullDirection);
  430. }
  431. unset($orderingParts[count($orderingParts) - 1]);
  432. // The rest will be the ordering
  433. $fullOrdering = implode(' ', $orderingParts);
  434. if (in_array($fullOrdering, $this->filter_fields))
  435. {
  436. $this->setState('list.ordering', $fullOrdering);
  437. }
  438. }
  439. else
  440. {
  441. $this->setState('list.ordering', $ordering);
  442. $this->setState('list.direction', $direction);
  443. }
  444. break;
  445. case 'ordering':
  446. if (!in_array($value, $this->filter_fields))
  447. {
  448. $value = $ordering;
  449. }
  450. break;
  451. case 'direction':
  452. if (!in_array(strtoupper($value), array('ASC', 'DESC', '')))
  453. {
  454. $value = $direction;
  455. }
  456. break;
  457. case 'limit':
  458. $limit = $value;
  459. break;
  460. // Just to keep the default case
  461. default:
  462. $value = $value;
  463. break;
  464. }
  465. $this->setState('list.' . $name, $value);
  466. }
  467. }
  468. else
  469. // Keep B/C for components previous to jform forms for filters
  470. {
  471. // Pre-fill the limits
  472. $limit = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->getCfg('list_limit'), 'uint');
  473. $this->setState('list.limit', $limit);
  474. // Check if the ordering field is in the white list, otherwise use the incoming value.
  475. $value = $app->getUserStateFromRequest($this->context . '.ordercol', 'filter_order', $ordering);
  476. if (!in_array($value, $this->filter_fields))
  477. {
  478. $value = $ordering;
  479. $app->setUserState($this->context . '.ordercol', $value);
  480. }
  481. $this->setState('list.ordering', $value);
  482. // Check if the ordering direction is valid, otherwise use the incoming value.
  483. $value = $app->getUserStateFromRequest($this->context . '.orderdirn', 'filter_order_Dir', $direction);
  484. if (!in_array(strtoupper($value), array('ASC', 'DESC', '')))
  485. {
  486. $value = $direction;
  487. $app->setUserState($this->context . '.orderdirn', $value);
  488. }
  489. $this->setState('list.direction', $value);
  490. }
  491. // Support old ordering field
  492. $oldOrdering = $app->input->get('filter_order');
  493. if (!empty($oldOrdering) && in_array($value, $this->filter_fields))
  494. {
  495. $this->setState('list.ordering', $oldOrdering);
  496. }
  497. // Support old direction field
  498. $oldDirection = $app->input->get('filter_order_Dir');
  499. if (!empty($oldDirection) && in_array(strtoupper($oldDirection), array('ASC', 'DESC', '')))
  500. {
  501. $this->setState('list.direction', $oldDirection);
  502. }
  503. $value = $app->getUserStateFromRequest($this->context . '.limitstart', 'limitstart', 0);
  504. $limitstart = ($limit != 0 ? (floor($value / $limit) * $limit) : 0);
  505. $this->setState('list.start', $limitstart);
  506. }
  507. else
  508. {
  509. $this->setState('list.start', 0);
  510. $this->setState('list.limit', 0);
  511. }
  512. }
  513. /**
  514. * Method to allow derived classes to preprocess the form.
  515. *
  516. * @param JForm $form A JForm object.
  517. * @param mixed $data The data expected for the form.
  518. * @param string $group The name of the plugin group to import (defaults to "content").
  519. *
  520. * @return void
  521. *
  522. * @since 3.2
  523. * @throws Exception if there is an error in the form event.
  524. */
  525. protected function preprocessForm(JForm $form, $data, $group = 'content')
  526. {
  527. // Import the appropriate plugin group.
  528. JPluginHelper::importPlugin($group);
  529. // Get the dispatcher.
  530. $dispatcher = JDispatcher::getInstance();
  531. // Trigger the form preparation event.
  532. $results = $dispatcher->trigger('onContentPrepareForm', array($form, $data));
  533. // Check for errors encountered while preparing the form.
  534. if (count($results) && in_array(false, $results, true))
  535. {
  536. // Get the last error.
  537. $error = $dispatcher->getError();
  538. if (!($error instanceof Exception))
  539. {
  540. throw new Exception($error);
  541. }
  542. }
  543. }
  544. /**
  545. * Gets the value of a user state variable and sets it in the session
  546. *
  547. * This is the same as the method in JApplication except that this also can optionally
  548. * force you back to the first page when a filter has changed
  549. *
  550. * @param string $key The key of the user state variable.
  551. * @param string $request The name of the variable passed in a request.
  552. * @param string $default The default value for the variable if not found. Optional.
  553. * @param string $type Filter for the variable, for valid values see {@link JFilterInput::clean()}. Optional.
  554. * @param boolean $resetPage If true, the limitstart in request is set to zero
  555. *
  556. * @return The request user state.
  557. *
  558. * @since 12.2
  559. */
  560. public function getUserStateFromRequest($key, $request, $default = null, $type = 'none', $resetPage = true)
  561. {
  562. $app = JFactory::getApplication();
  563. $input = $app->input;
  564. $old_state = $app->getUserState($key);
  565. $cur_state = (!is_null($old_state)) ? $old_state : $default;
  566. $new_state = $input->get($request, null, $type);
  567. if (($cur_state != $new_state) && ($resetPage))
  568. {
  569. $input->set('limitstart', 0);
  570. }
  571. // Save the new value only if it is set in this request.
  572. if ($new_state !== null)
  573. {
  574. $app->setUserState($key, $new_state);
  575. }
  576. else
  577. {
  578. $new_state = $cur_state;
  579. }
  580. return $new_state;
  581. }
  582. }