PageRenderTime 186ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/src/administrator/components/com_users/models/users.php

https://bitbucket.org/ke2083/transfans.co.uk-website
PHP | 523 lines | 333 code | 77 blank | 113 comment | 29 complexity | b9970a83265e6fd799c41bab1656c625 MD5 | raw file
  1. <?php
  2. /**
  3. * @package Joomla.Administrator
  4. * @subpackage com_users
  5. *
  6. * @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE.txt
  8. */
  9. defined('_JEXEC') or die;
  10. use Joomla\Utilities\ArrayHelper;
  11. /**
  12. * Methods supporting a list of user records.
  13. *
  14. * @since 1.6
  15. */
  16. class UsersModelUsers extends JModelList
  17. {
  18. /**
  19. * Constructor.
  20. *
  21. * @param array $config An optional associative array of configuration settings.
  22. *
  23. * @see JController
  24. * @since 1.6
  25. */
  26. public function __construct($config = array())
  27. {
  28. if (empty($config['filter_fields']))
  29. {
  30. $config['filter_fields'] = array(
  31. 'id', 'a.id',
  32. 'name', 'a.name',
  33. 'username', 'a.username',
  34. 'email', 'a.email',
  35. 'block', 'a.block',
  36. 'sendEmail', 'a.sendEmail',
  37. 'registerDate', 'a.registerDate',
  38. 'lastvisitDate', 'a.lastvisitDate',
  39. 'activation', 'a.activation',
  40. 'active',
  41. 'group_id',
  42. 'range',
  43. 'lastvisitrange',
  44. 'state',
  45. );
  46. }
  47. parent::__construct($config);
  48. }
  49. /**
  50. * Method to auto-populate the model state.
  51. *
  52. * Note. Calling getState in this method will result in recursion.
  53. *
  54. * @param string $ordering An optional ordering field.
  55. * @param string $direction An optional direction (asc|desc).
  56. *
  57. * @return void
  58. *
  59. * @since 1.6
  60. */
  61. protected function populateState($ordering = 'a.name', $direction = 'asc')
  62. {
  63. $app = JFactory::getApplication('administrator');
  64. // Adjust the context to support modal layouts.
  65. if ($layout = $app->input->get('layout', 'default', 'cmd'))
  66. {
  67. $this->context .= '.' . $layout;
  68. }
  69. // Load the filter state.
  70. $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
  71. $this->setState('filter.active', $this->getUserStateFromRequest($this->context . '.filter.active', 'filter_active', '', 'cmd'));
  72. $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'cmd'));
  73. $this->setState('filter.group_id', $this->getUserStateFromRequest($this->context . '.filter.group_id', 'filter_group_id', null, 'int'));
  74. $this->setState('filter.range', $this->getUserStateFromRequest($this->context . '.filter.range', 'filter_range', '', 'cmd'));
  75. $this->setState(
  76. 'filter.lastvisitrange', $this->getUserStateFromRequest($this->context . '.filter.lastvisitrange', 'filter_lastvisitrange', '', 'cmd')
  77. );
  78. $groups = json_decode(base64_decode($app->input->get('groups', '', 'BASE64')));
  79. if (isset($groups))
  80. {
  81. $groups = ArrayHelper::toInteger($groups);
  82. }
  83. $this->setState('filter.groups', $groups);
  84. $excluded = json_decode(base64_decode($app->input->get('excluded', '', 'BASE64')));
  85. if (isset($excluded))
  86. {
  87. $excluded = ArrayHelper::toInteger($excluded);
  88. }
  89. $this->setState('filter.excluded', $excluded);
  90. // Load the parameters.
  91. $params = JComponentHelper::getParams('com_users');
  92. $this->setState('params', $params);
  93. // List state information.
  94. parent::populateState($ordering, $direction);
  95. }
  96. /**
  97. * Method to get a store id based on model configuration state.
  98. *
  99. * This is necessary because the model is used by the component and
  100. * different modules that might need different sets of data or different
  101. * ordering requirements.
  102. *
  103. * @param string $id A prefix for the store id.
  104. *
  105. * @return string A store id.
  106. *
  107. * @since 1.6
  108. */
  109. protected function getStoreId($id = '')
  110. {
  111. // Compile the store id.
  112. $id .= ':' . $this->getState('filter.search');
  113. $id .= ':' . $this->getState('filter.active');
  114. $id .= ':' . $this->getState('filter.state');
  115. $id .= ':' . $this->getState('filter.group_id');
  116. $id .= ':' . $this->getState('filter.range');
  117. return parent::getStoreId($id);
  118. }
  119. /**
  120. * Gets the list of users and adds expensive joins to the result set.
  121. *
  122. * @return mixed An array of data items on success, false on failure.
  123. *
  124. * @since 1.6
  125. */
  126. public function getItems()
  127. {
  128. // Get a storage key.
  129. $store = $this->getStoreId();
  130. // Try to load the data from internal storage.
  131. if (empty($this->cache[$store]))
  132. {
  133. $groups = $this->getState('filter.groups');
  134. $groupId = $this->getState('filter.group_id');
  135. if (isset($groups) && (empty($groups) || $groupId && !in_array($groupId, $groups)))
  136. {
  137. $items = array();
  138. }
  139. else
  140. {
  141. $items = parent::getItems();
  142. }
  143. // Bail out on an error or empty list.
  144. if (empty($items))
  145. {
  146. $this->cache[$store] = $items;
  147. return $items;
  148. }
  149. // Joining the groups with the main query is a performance hog.
  150. // Find the information only on the result set.
  151. // First pass: get list of the user id's and reset the counts.
  152. $userIds = array();
  153. foreach ($items as $item)
  154. {
  155. $userIds[] = (int) $item->id;
  156. $item->group_count = 0;
  157. $item->group_names = '';
  158. $item->note_count = 0;
  159. }
  160. // Get the counts from the database only for the users in the list.
  161. $db = $this->getDbo();
  162. $query = $db->getQuery(true);
  163. // Join over the group mapping table.
  164. $query->select('map.user_id, COUNT(map.group_id) AS group_count')
  165. ->from('#__user_usergroup_map AS map')
  166. ->where('map.user_id IN (' . implode(',', $userIds) . ')')
  167. ->group('map.user_id')
  168. // Join over the user groups table.
  169. ->join('LEFT', '#__usergroups AS g2 ON g2.id = map.group_id');
  170. $db->setQuery($query);
  171. // Load the counts into an array indexed on the user id field.
  172. try
  173. {
  174. $userGroups = $db->loadObjectList('user_id');
  175. }
  176. catch (RuntimeException $e)
  177. {
  178. $this->setError($e->getMessage());
  179. return false;
  180. }
  181. $query->clear()
  182. ->select('n.user_id, COUNT(n.id) As note_count')
  183. ->from('#__user_notes AS n')
  184. ->where('n.user_id IN (' . implode(',', $userIds) . ')')
  185. ->where('n.state >= 0')
  186. ->group('n.user_id');
  187. $db->setQuery($query);
  188. // Load the counts into an array indexed on the aro.value field (the user id).
  189. try
  190. {
  191. $userNotes = $db->loadObjectList('user_id');
  192. }
  193. catch (RuntimeException $e)
  194. {
  195. $this->setError($e->getMessage());
  196. return false;
  197. }
  198. // Second pass: collect the group counts into the master items array.
  199. foreach ($items as &$item)
  200. {
  201. if (isset($userGroups[$item->id]))
  202. {
  203. $item->group_count = $userGroups[$item->id]->group_count;
  204. // Group_concat in other databases is not supported
  205. $item->group_names = $this->_getUserDisplayedGroups($item->id);
  206. }
  207. if (isset($userNotes[$item->id]))
  208. {
  209. $item->note_count = $userNotes[$item->id]->note_count;
  210. }
  211. }
  212. // Add the items to the internal cache.
  213. $this->cache[$store] = $items;
  214. }
  215. return $this->cache[$store];
  216. }
  217. /**
  218. * Build an SQL query to load the list data.
  219. *
  220. * @return JDatabaseQuery
  221. *
  222. * @since 1.6
  223. */
  224. protected function getListQuery()
  225. {
  226. // Create a new query object.
  227. $db = $this->getDbo();
  228. $query = $db->getQuery(true);
  229. // Select the required fields from the table.
  230. $query->select(
  231. $this->getState(
  232. 'list.select',
  233. 'a.*'
  234. )
  235. );
  236. $query->from($db->quoteName('#__users') . ' AS a');
  237. // If the model is set to check item state, add to the query.
  238. $state = $this->getState('filter.state');
  239. if (is_numeric($state))
  240. {
  241. $query->where('a.block = ' . (int) $state);
  242. }
  243. // If the model is set to check the activated state, add to the query.
  244. $active = $this->getState('filter.active');
  245. if (is_numeric($active))
  246. {
  247. if ($active == '0')
  248. {
  249. $query->where('a.activation IN (' . $db->quote('') . ', ' . $db->quote('0') . ')');
  250. }
  251. elseif ($active == '1')
  252. {
  253. $query->where($query->length('a.activation') . ' > 1');
  254. }
  255. }
  256. // Filter the items over the group id if set.
  257. $groupId = $this->getState('filter.group_id');
  258. $groups = $this->getState('filter.groups');
  259. if ($groupId || isset($groups))
  260. {
  261. $query->join('LEFT', '#__user_usergroup_map AS map2 ON map2.user_id = a.id')
  262. ->group(
  263. $db->quoteName(
  264. array(
  265. 'a.id',
  266. 'a.name',
  267. 'a.username',
  268. 'a.password',
  269. 'a.block',
  270. 'a.sendEmail',
  271. 'a.registerDate',
  272. 'a.lastvisitDate',
  273. 'a.activation',
  274. 'a.params',
  275. 'a.email'
  276. )
  277. )
  278. );
  279. if ($groupId)
  280. {
  281. $query->where('map2.group_id = ' . (int) $groupId);
  282. }
  283. if (isset($groups))
  284. {
  285. $query->where('map2.group_id IN (' . implode(',', $groups) . ')');
  286. }
  287. }
  288. // Filter the items over the search string if set.
  289. $search = $this->getState('filter.search');
  290. if (!empty($search))
  291. {
  292. if (stripos($search, 'id:') === 0)
  293. {
  294. $query->where('a.id = ' . (int) substr($search, 3));
  295. }
  296. elseif (stripos($search, 'username:') === 0)
  297. {
  298. $search = $db->quote('%' . $db->escape(substr($search, 9), true) . '%');
  299. $query->where('a.username LIKE ' . $search);
  300. }
  301. else
  302. {
  303. // Escape the search token.
  304. $search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%'));
  305. // Compile the different search clauses.
  306. $searches = array();
  307. $searches[] = 'a.name LIKE ' . $search;
  308. $searches[] = 'a.username LIKE ' . $search;
  309. $searches[] = 'a.email LIKE ' . $search;
  310. // Add the clauses to the query.
  311. $query->where('(' . implode(' OR ', $searches) . ')');
  312. }
  313. }
  314. // Add filter for registration ranges select list
  315. $range = $this->getState('filter.range');
  316. // Apply the range filter.
  317. if ($range)
  318. {
  319. $dates = $this->buildDateRange($range);
  320. if ($dates['dNow'] === false)
  321. {
  322. $query->where(
  323. $db->qn('a.registerDate') . ' < ' . $db->quote($dates['dStart']->format('Y-m-d H:i:s'))
  324. );
  325. }
  326. else
  327. {
  328. $query->where(
  329. $db->qn('a.registerDate') . ' >= ' . $db->quote($dates['dStart']->format('Y-m-d H:i:s')) .
  330. ' AND ' . $db->qn('a.registerDate') . ' <= ' . $db->quote($dates['dNow']->format('Y-m-d H:i:s'))
  331. );
  332. }
  333. }
  334. // Add filter for registration ranges select list
  335. $lastvisitrange = $this->getState('filter.lastvisitrange');
  336. // Apply the range filter.
  337. if ($lastvisitrange)
  338. {
  339. $dates = $this->buildDateRange($lastvisitrange);
  340. if (is_string($dates['dStart']))
  341. {
  342. $query->where(
  343. $db->qn('a.lastvisitDate') . ' = ' . $db->quote($dates['dStart'])
  344. );
  345. }
  346. elseif ($dates['dNow'] === false)
  347. {
  348. $query->where(
  349. $db->qn('a.lastvisitDate') . ' < ' . $db->quote($dates['dStart']->format('Y-m-d H:i:s'))
  350. );
  351. }
  352. else
  353. {
  354. $query->where(
  355. $db->qn('a.lastvisitDate') . ' >= ' . $db->quote($dates['dStart']->format('Y-m-d H:i:s')) .
  356. ' AND ' . $db->qn('a.lastvisitDate') . ' <= ' . $db->quote($dates['dNow']->format('Y-m-d H:i:s'))
  357. );
  358. }
  359. }
  360. // Filter by excluded users
  361. $excluded = $this->getState('filter.excluded');
  362. if (!empty($excluded))
  363. {
  364. $query->where('id NOT IN (' . implode(',', $excluded) . ')');
  365. }
  366. // Add the list ordering clause.
  367. $query->order($db->qn($db->escape($this->getState('list.ordering', 'a.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
  368. return $query;
  369. }
  370. /**
  371. * Construct the date range to filter on.
  372. *
  373. * @param string $range The textual range to construct the filter for.
  374. *
  375. * @return string The date range to filter on.
  376. *
  377. * @since 3.6.0
  378. */
  379. private function buildDateRange($range)
  380. {
  381. // Get UTC for now.
  382. $dNow = new JDate;
  383. $dStart = clone $dNow;
  384. switch ($range)
  385. {
  386. case 'past_week':
  387. $dStart->modify('-7 day');
  388. break;
  389. case 'past_1month':
  390. $dStart->modify('-1 month');
  391. break;
  392. case 'past_3month':
  393. $dStart->modify('-3 month');
  394. break;
  395. case 'past_6month':
  396. $dStart->modify('-6 month');
  397. break;
  398. case 'post_year':
  399. $dNow = false;
  400. case 'past_year':
  401. $dStart->modify('-1 year');
  402. break;
  403. case 'today':
  404. // Ranges that need to align with local 'days' need special treatment.
  405. $app = JFactory::getApplication();
  406. $offset = $app->get('offset');
  407. // Reset the start time to be the beginning of today, local time.
  408. $dStart = new JDate('now', $offset);
  409. $dStart->setTime(0, 0, 0);
  410. // Now change the timezone back to UTC.
  411. $tz = new DateTimeZone('GMT');
  412. $dStart->setTimezone($tz);
  413. break;
  414. case 'never':
  415. $dNow = false;
  416. $dStart = $this->_db->getNullDate();
  417. break;
  418. }
  419. return array('dNow' => $dNow, 'dStart' => $dStart);
  420. }
  421. /**
  422. * SQL server change
  423. *
  424. * @param integer $user_id User identifier
  425. *
  426. * @return string Groups titles imploded :$
  427. */
  428. protected function _getUserDisplayedGroups($user_id)
  429. {
  430. $db = $this->getDbo();
  431. $query = $db->getQuery(true)
  432. ->select($db->qn('title'))
  433. ->from($db->qn('#__usergroups', 'ug'))
  434. ->join('LEFT', $db->qn('#__user_usergroup_map', 'map') . ' ON (ug.id = map.group_id)')
  435. ->where($db->qn('map.user_id') . ' = ' . (int) $user_id);
  436. try
  437. {
  438. $result = $db->setQuery($query)->loadColumn();
  439. }
  440. catch (RunTimeException $e)
  441. {
  442. $result = array();
  443. }
  444. return implode("\n", $result);
  445. }
  446. }