1<?php
2/**
3 * @package     Joomla.Libraries
4 * @subpackage  HTML
5 *
6 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
7 * @license     GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10defined('JPATH_PLATFORM') or die;
11
12/**
13 * Utility class working with menu select lists
14 *
15 * @since  1.5
16 */
17abstract class JHtmlMenu
18{
19	/**
20	 * Cached array of the menus.
21	 *
22	 * @var    array
23	 * @since  1.6
24	 */
25	protected static $menus = array();
26
27	/**
28	 * Cached array of the menus items.
29	 *
30	 * @var    array
31	 * @since  1.6
32	 */
33	protected static $items = array();
34
35	/**
36	 * Get a list of the available menus.
37	 *
38	 * @param   int  $clientId  The client id
39	 *
40	 * @return  array
41	 *
42	 * @since   1.6
43	 */
44	public static function menus($clientId = 0)
45	{
46		$key = serialize($clientId);
47
48		if (!isset(static::$menus[$key]))
49		{
50			$db = JFactory::getDbo();
51
52			$query = $db->getQuery(true)
53				->select($db->qn(array('id', 'menutype', 'title', 'client_id'), array('id', 'value', 'text', 'client_id')))
54				->from($db->quoteName('#__menu_types'))
55				->order('client_id, title');
56
57			if (isset($clientId))
58			{
59				$query->where('client_id = ' . (int) $clientId);
60			}
61
62			static::$menus[$key] = $db->setQuery($query)->loadObjectList();
63		}
64
65		return static::$menus[$key];
66	}
67
68	/**
69	 * Returns an array of menu items grouped by menu.
70	 *
71	 * @param   array  $config  An array of configuration options [published, checkacl, clientid].
72	 *
73	 * @return  array
74	 *
75	 * @since   1.6
76	 */
77	public static function menuItems($config = array())
78	{
79		$key = serialize($config);
80
81		if (empty(static::$items[$key]))
82		{
83			// B/C - not passed  = 0, null can be passed for both clients
84			$clientId = array_key_exists('clientid', $config) ? $config['clientid'] : 0;
85			$menus    = static::menus($clientId);
86
87			$db    = JFactory::getDbo();
88			$query = $db->getQuery(true)
89				->select('a.id AS value, a.title AS text, a.level, a.menutype, a.client_id')
90				->from('#__menu AS a')
91				->where('a.parent_id > 0');
92
93			// Filter on the client id
94			if (isset($clientId))
95			{
96				$query->where('a.client_id = ' . (int) $clientId);
97			}
98
99			// Filter on the published state
100			if (isset($config['published']))
101			{
102				if (is_numeric($config['published']))
103				{
104					$query->where('a.published = ' . (int) $config['published']);
105				}
106				elseif ($config['published'] === '')
107				{
108					$query->where('a.published IN (0,1)');
109				}
110			}
111
112			$query->order('a.lft');
113
114			$db->setQuery($query);
115			$items = $db->loadObjectList();
116
117			// Collate menu items based on menutype
118			$lookup = array();
119
120			foreach ($items as &$item)
121			{
122				if (!isset($lookup[$item->menutype]))
123				{
124					$lookup[$item->menutype] = array();
125				}
126
127				$lookup[$item->menutype][] = &$item;
128
129				// Translate the menu item title when client is administrator
130				if ($clientId === 1)
131				{
132					$item->text = JText::_($item->text);
133				}
134
135				$item->text = str_repeat('- ', $item->level) . $item->text;
136			}
137
138			static::$items[$key] = array();
139
140			$user = JFactory::getUser();
141
142			$aclcheck = !empty($config['checkacl']) ? (int) $config['checkacl'] : 0;
143
144			foreach ($menus as &$menu)
145			{
146				if ($aclcheck)
147				{
148					$action = $aclcheck == $menu->id ? 'edit' : 'create';
149
150					if (!$user->authorise('core.' . $action, 'com_menus.menu.' . $menu->id))
151					{
152						continue;
153					}
154				}
155
156				// Start group:
157				static::$items[$key][] = JHtml::_('select.optgroup', $menu->text);
158
159				// Special "Add to this Menu" option:
160				static::$items[$key][] = JHtml::_('select.option', $menu->value . '.1', JText::_('JLIB_HTML_ADD_TO_THIS_MENU'));
161
162				// Menu items:
163				if (isset($lookup[$menu->value]))
164				{
165					foreach ($lookup[$menu->value] as &$item)
166					{
167						static::$items[$key][] = JHtml::_('select.option', $menu->value . '.' . $item->value, $item->text);
168					}
169				}
170
171				// Finish group:
172				static::$items[$key][] = JHtml::_('select.optgroup', $menu->text);
173			}
174		}
175
176		return static::$items[$key];
177	}
178
179	/**
180	 * Displays an HTML select list of menu items.
181	 *
182	 * @param   string  $name      The name of the control.
183	 * @param   string  $selected  The value of the selected option.
184	 * @param   string  $attribs   Attributes for the control.
185	 * @param   array   $config    An array of options for the control [id, published, checkacl, clientid].
186	 *
187	 * @return  string
188	 *
189	 * @since   1.6
190	 */
191	public static function menuItemList($name, $selected = null, $attribs = null, $config = array())
192	{
193		static $count;
194
195		$options = static::menuItems($config);
196
197		return JHtml::_(
198			'select.genericlist', $options, $name,
199			array(
200				'id'             => isset($config['id']) ? $config['id'] : 'assetgroups_' . (++$count),
201				'list.attr'      => $attribs === null ? 'class="inputbox" size="1"' : $attribs,
202				'list.select'    => (int) $selected,
203				'list.translate' => false,
204			)
205		);
206	}
207
208	/**
209	 * Build the select list for Menu Ordering
210	 *
211	 * @param   object   &$row  The row object
212	 * @param   integer  $id    The id for the row. Must exist to enable menu ordering
213	 *
214	 * @return  string
215	 *
216	 * @since   1.5
217	 */
218	public static function ordering(&$row, $id)
219	{
220		if ($id)
221		{
222			$db = JFactory::getDbo();
223			$query = $db->getQuery(true)
224				->select('ordering AS value, title AS text')
225				->from($db->quoteName('#__menu'))
226				->where($db->quoteName('menutype') . ' = ' . $db->quote($row->menutype))
227				->where($db->quoteName('parent_id') . ' = ' . (int) $row->parent_id)
228				->where($db->quoteName('published') . ' != -2')
229				->order('ordering');
230			$order = JHtml::_('list.genericordering', $query);
231			$ordering = JHtml::_(
232				'select.genericlist', $order, 'ordering',
233				array('list.attr' => 'class="inputbox" size="1"', 'list.select' => (int) $row->ordering)
234			);
235		}
236		else
237		{
238			$ordering = '<input type="hidden" name="ordering" value="' . $row->ordering . '" />' . JText::_('JGLOBAL_NEWITEMSLAST_DESC');
239		}
240
241		return $ordering;
242	}
243
244	/**
245	 * Build the multiple select list for Menu Links/Pages
246	 *
247	 * @param   boolean  $all         True if all can be selected
248	 * @param   boolean  $unassigned  True if unassigned can be selected
249	 * @param   int      $clientId    The client id
250	 *
251	 * @return  string
252	 *
253	 * @since   1.5
254	 */
255	public static function linkOptions($all = false, $unassigned = false, $clientId = 0)
256	{
257		$db = JFactory::getDbo();
258
259		// Get a list of the menu items
260		$query = $db->getQuery(true)
261			->select('m.id, m.parent_id, m.title, m.menutype, m.client_id')
262			->from($db->quoteName('#__menu') . ' AS m')
263			->where($db->quoteName('m.published') . ' = 1')
264			->order('m.client_id, m.menutype, m.parent_id');
265
266		if (isset($clientId))
267		{
268			$query->where('m.client_id = ' . (int) $clientId);
269		}
270
271		$db->setQuery($query);
272
273		$mitems = $db->loadObjectList();
274
275		if (!$mitems)
276		{
277			$mitems = array();
278		}
279
280		// Establish the hierarchy of the menu
281		$children = array();
282
283		// First pass - collect children
284		foreach ($mitems as $v)
285		{
286			$pt            = $v->parent_id;
287			$list          = @$children[$pt] ? $children[$pt] : array();
288			$list[]        = $v;
289			$children[$pt] = $list;
290		}
291
292		// Second pass - get an indent list of the items
293		$list = static::treerecurse((int) $mitems[0]->parent_id, '', array(), $children, 9999, 0, 0);
294
295		// Code that adds menu name to Display of Page(s)
296		$mitems = array();
297
298		if ($all | $unassigned)
299		{
300			$mitems[] = JHtml::_('select.option', '<OPTGROUP>', JText::_('JOPTION_MENUS'));
301
302			if ($all)
303			{
304				$mitems[] = JHtml::_('select.option', 0, JText::_('JALL'));
305			}
306
307			if ($unassigned)
308			{
309				$mitems[] = JHtml::_('select.option', -1, JText::_('JOPTION_UNASSIGNED'));
310			}
311
312			$mitems[] = JHtml::_('select.option', '</OPTGROUP>');
313		}
314
315		$lastMenuType = null;
316		$tmpMenuType  = null;
317
318		foreach ($list as $list_a)
319		{
320			if ($list_a->menutype != $lastMenuType)
321			{
322				if ($tmpMenuType)
323				{
324					$mitems[] = JHtml::_('select.option', '</OPTGROUP>');
325				}
326
327				$mitems[]     = JHtml::_('select.option', '<OPTGROUP>', $list_a->menutype);
328				$lastMenuType = $list_a->menutype;
329				$tmpMenuType  = $list_a->menutype;
330			}
331
332			$mitems[] = JHtml::_('select.option', $list_a->id, $list_a->title);
333		}
334
335		if ($lastMenuType !== null)
336		{
337			$mitems[] = JHtml::_('select.option', '</OPTGROUP>');
338		}
339
340		return $mitems;
341	}
342
343	/**
344	 * Build the list representing the menu tree
345	 *
346	 * @param   integer  $id         Id of the menu item
347	 * @param   string   $indent     The indentation string
348	 * @param   array    $list       The list to process
349	 * @param   array    &$children  The children of the current item
350	 * @param   integer  $maxlevel   The maximum number of levels in the tree
351	 * @param   integer  $level      The starting level
352	 * @param   int      $type       Set the type of spacer to use. Use 1 for |_ or 0 for -
353	 *
354	 * @return  array
355	 *
356	 * @since   1.5
357	 */
358	public static function treerecurse($id, $indent, $list, &$children, $maxlevel = 9999, $level = 0, $type = 1)
359	{
360		if ($level <= $maxlevel && isset($children[$id]) && is_array($children[$id]))
361		{
362			if ($type)
363			{
364				$pre    = '<sup>|_</sup>&#160;';
365				$spacer = '.&#160;&#160;&#160;&#160;&#160;&#160;';
366			}
367			else
368			{
369				$pre    = '- ';
370				$spacer = '&#160;&#160;';
371			}
372
373			foreach ($children[$id] as $v)
374			{
375				$id = $v->id;
376
377				if ($v->parent_id == 0)
378				{
379					$txt = $v->title;
380				}
381				else
382				{
383					$txt = $pre . $v->title;
384				}
385
386				$list[$id]           = $v;
387				$list[$id]->treename = $indent . $txt;
388
389				if (isset($children[$id]) && is_array($children[$id]))
390				{
391					$list[$id]->children = count($children[$id]);
392					$list                = static::treerecurse($id, $indent . $spacer, $list, $children, $maxlevel, $level + 1, $type);
393				}
394				else
395				{
396					$list[$id]->children = 0;
397				}
398			}
399		}
400
401		return $list;
402	}
403}
404