PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/administrator/components/com_menus/models/item.php

https://github.com/J2MTecnologia/joomla-3.x
PHP | 1524 lines | 962 code | 197 blank | 365 comment | 151 complexity | a30f8a45509a6d1edd3a31a8f1420cff MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * @package Joomla.Administrator
  4. * @subpackage com_menus
  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.txt
  8. */
  9. defined('_JEXEC') or die;
  10. jimport('joomla.filesystem.path');
  11. require_once JPATH_COMPONENT . '/helpers/menus.php';
  12. /**
  13. * Menu Item Model for Menus.
  14. *
  15. * @package Joomla.Administrator
  16. * @subpackage com_menus
  17. * @since 1.6
  18. */
  19. class MenusModelItem extends JModelAdmin
  20. {
  21. /**
  22. * @var string The prefix to use with controller messages.
  23. * @since 1.6
  24. */
  25. protected $text_prefix = 'COM_MENUS_ITEM';
  26. /**
  27. * @var string The help screen key for the menu item.
  28. * @since 1.6
  29. */
  30. protected $helpKey = 'JHELP_MENUS_MENU_ITEM_MANAGER_EDIT';
  31. /**
  32. * @var string The help screen base URL for the menu item.
  33. * @since 1.6
  34. */
  35. protected $helpURL;
  36. /**
  37. * @var boolean True to use local lookup for the help screen.
  38. * @since 1.6
  39. */
  40. protected $helpLocal = false;
  41. /**
  42. * Method to test whether a record can be deleted.
  43. *
  44. * @param object A record object.
  45. *
  46. * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
  47. * @since 1.6
  48. */
  49. protected function canDelete($record)
  50. {
  51. if (!empty($record->id))
  52. {
  53. if ($record->published != -2)
  54. {
  55. return;
  56. }
  57. $user = JFactory::getUser();
  58. return $user->authorise('core.delete', 'com_menus.item.' . (int) $record->id);
  59. }
  60. }
  61. /**
  62. * Method to test whether a record can have its state edited.
  63. *
  64. * @param object A record object.
  65. *
  66. * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
  67. * @since 1.6
  68. */
  69. protected function canEditState($record)
  70. {
  71. $user = JFactory::getUser();
  72. if (!empty($record->id))
  73. {
  74. return $user->authorise('core.edit.state', 'com_menus.item.' . (int) $record->id);
  75. }
  76. // Default to component settings if menu item not known.
  77. else
  78. {
  79. return parent::canEditState($record);
  80. }
  81. }
  82. /**
  83. * Method to perform batch operations on an item or a set of items.
  84. *
  85. * @param array $commands An array of commands to perform.
  86. * @param array $pks An array of item ids.
  87. * @param array $contexts An array of item contexts.
  88. *
  89. * @return boolean Returns true on success, false on failure.
  90. *
  91. * @since 1.6
  92. */
  93. public function batch($commands, $pks, $contexts)
  94. {
  95. // Sanitize user ids.
  96. $pks = array_unique($pks);
  97. JArrayHelper::toInteger($pks);
  98. // Remove any values of zero.
  99. if (array_search(0, $pks, true))
  100. {
  101. unset($pks[array_search(0, $pks, true)]);
  102. }
  103. if (empty($pks))
  104. {
  105. $this->setError(JText::_('COM_MENUS_NO_ITEM_SELECTED'));
  106. return false;
  107. }
  108. $done = false;
  109. if (!empty($commands['menu_id']))
  110. {
  111. $cmd = JArrayHelper::getValue($commands, 'move_copy', 'c');
  112. if ($cmd == 'c')
  113. {
  114. $result = $this->batchCopy($commands['menu_id'], $pks, $contexts);
  115. if (is_array($result))
  116. {
  117. $pks = $result;
  118. }
  119. else
  120. {
  121. return false;
  122. }
  123. }
  124. elseif ($cmd == 'm' && !$this->batchMove($commands['menu_id'], $pks, $contexts))
  125. {
  126. return false;
  127. }
  128. $done = true;
  129. }
  130. if (!empty($commands['assetgroup_id']))
  131. {
  132. if (!$this->batchAccess($commands['assetgroup_id'], $pks, $contexts))
  133. {
  134. return false;
  135. }
  136. $done = true;
  137. }
  138. if (!empty($commands['language_id']))
  139. {
  140. if (!$this->batchLanguage($commands['language_id'], $pks, $contexts))
  141. {
  142. return false;
  143. }
  144. $done = true;
  145. }
  146. if (!$done)
  147. {
  148. $this->setError(JText::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION'));
  149. return false;
  150. }
  151. return true;
  152. }
  153. /**
  154. * Batch copy menu items to a new menu or parent.
  155. *
  156. * @param integer $value The new menu or sub-item.
  157. * @param array $pks An array of row IDs.
  158. * @param array $contexts An array of item contexts.
  159. *
  160. * @return mixed An array of new IDs on success, boolean false on failure.
  161. *
  162. * @since 1.6
  163. */
  164. protected function batchCopy($value, $pks, $contexts)
  165. {
  166. // $value comes as {menutype}.{parent_id}
  167. $parts = explode('.', $value);
  168. $menuType = $parts[0];
  169. $parentId = (int) JArrayHelper::getValue($parts, 1, 0);
  170. $table = $this->getTable();
  171. $db = $this->getDbo();
  172. $query = $db->getQuery(true);
  173. $i = 0;
  174. // Check that the parent exists
  175. if ($parentId)
  176. {
  177. if (!$table->load($parentId))
  178. {
  179. if ($error = $table->getError())
  180. {
  181. // Fatal error
  182. $this->setError($error);
  183. return false;
  184. }
  185. else
  186. {
  187. // Non-fatal error
  188. $this->setError(JText::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
  189. $parentId = 0;
  190. }
  191. }
  192. }
  193. // If the parent is 0, set it to the ID of the root item in the tree
  194. if (empty($parentId))
  195. {
  196. if (!$parentId = $table->getRootId())
  197. {
  198. $this->setError($db->getErrorMsg());
  199. return false;
  200. }
  201. }
  202. // Check that user has create permission for menus
  203. $user = JFactory::getUser();
  204. if (!$user->authorise('core.create', 'com_menus'))
  205. {
  206. $this->setError(JText::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE'));
  207. return false;
  208. }
  209. // We need to log the parent ID
  210. $parents = array();
  211. // Calculate the emergency stop count as a precaution against a runaway loop bug
  212. $query->select('COUNT(id)')
  213. ->from($db->quoteName('#__menu'));
  214. $db->setQuery($query);
  215. try
  216. {
  217. $count = $db->loadResult();
  218. }
  219. catch (RuntimeException $e)
  220. {
  221. $this->setError($e->getMessage());
  222. return false;
  223. }
  224. // Parent exists so we let's proceed
  225. while (!empty($pks) && $count > 0)
  226. {
  227. // Pop the first id off the stack
  228. $pk = array_shift($pks);
  229. $table->reset();
  230. // Check that the row actually exists
  231. if (!$table->load($pk))
  232. {
  233. if ($error = $table->getError())
  234. {
  235. // Fatal error
  236. $this->setError($error);
  237. return false;
  238. }
  239. else
  240. {
  241. // Not fatal error
  242. $this->setError(JText::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
  243. continue;
  244. }
  245. }
  246. // Copy is a bit tricky, because we also need to copy the children
  247. $query->clear()
  248. ->select('id')
  249. ->from($db->quoteName('#__menu'))
  250. ->where('lft > ' . (int) $table->lft)
  251. ->where('rgt < ' . (int) $table->rgt);
  252. $db->setQuery($query);
  253. $childIds = $db->loadColumn();
  254. // Add child ID's to the array only if they aren't already there.
  255. foreach ($childIds as $childId)
  256. {
  257. if (!in_array($childId, $pks))
  258. {
  259. array_push($pks, $childId);
  260. }
  261. }
  262. // Make a copy of the old ID and Parent ID
  263. $oldId = $table->id;
  264. $oldParentId = $table->parent_id;
  265. // Reset the id because we are making a copy.
  266. $table->id = 0;
  267. // If we a copying children, the Old ID will turn up in the parents list
  268. // otherwise it's a new top level item
  269. $table->parent_id = isset($parents[$oldParentId]) ? $parents[$oldParentId] : $parentId;
  270. $table->menutype = $menuType;
  271. // Set the new location in the tree for the node.
  272. $table->setLocation($table->parent_id, 'last-child');
  273. // TODO: Deal with ordering?
  274. //$table->ordering = 1;
  275. $table->level = null;
  276. $table->lft = null;
  277. $table->rgt = null;
  278. $table->home = 0;
  279. // Alter the title & alias
  280. list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title);
  281. $table->title = $title;
  282. $table->alias = $alias;
  283. // Check the row.
  284. if (!$table->check())
  285. {
  286. $this->setError($table->getError());
  287. return false;
  288. }
  289. // Store the row.
  290. if (!$table->store())
  291. {
  292. $this->setError($table->getError());
  293. return false;
  294. }
  295. // Get the new item ID
  296. $newId = $table->get('id');
  297. // Add the new ID to the array
  298. $newIds[$i] = $newId;
  299. $i++;
  300. // Now we log the old 'parent' to the new 'parent'
  301. $parents[$oldId] = $table->id;
  302. $count--;
  303. }
  304. // Rebuild the hierarchy.
  305. if (!$table->rebuild())
  306. {
  307. $this->setError($table->getError());
  308. return false;
  309. }
  310. // Rebuild the tree path.
  311. if (!$table->rebuildPath($table->id))
  312. {
  313. $this->setError($table->getError());
  314. return false;
  315. }
  316. // Clean the cache
  317. $this->cleanCache();
  318. return $newIds;
  319. }
  320. /**
  321. * Batch move menu items to a new menu or parent.
  322. *
  323. * @param integer $value The new menu or sub-item.
  324. * @param array $pks An array of row IDs.
  325. * @param array $contexts An array of item contexts.
  326. *
  327. * @return boolean True on success.
  328. *
  329. * @since 1.6
  330. */
  331. protected function batchMove($value, $pks, $contexts)
  332. {
  333. // $value comes as {menutype}.{parent_id}
  334. $parts = explode('.', $value);
  335. $menuType = $parts[0];
  336. $parentId = (int) JArrayHelper::getValue($parts, 1, 0);
  337. $table = $this->getTable();
  338. $db = $this->getDbo();
  339. $query = $db->getQuery(true);
  340. // Check that the parent exists.
  341. if ($parentId)
  342. {
  343. if (!$table->load($parentId))
  344. {
  345. if ($error = $table->getError())
  346. {
  347. // Fatal error
  348. $this->setError($error);
  349. return false;
  350. }
  351. else
  352. {
  353. // Non-fatal error
  354. $this->setError(JText::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
  355. $parentId = 0;
  356. }
  357. }
  358. }
  359. // Check that user has create and edit permission for menus
  360. $user = JFactory::getUser();
  361. if (!$user->authorise('core.create', 'com_menus'))
  362. {
  363. $this->setError(JText::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE'));
  364. return false;
  365. }
  366. if (!$user->authorise('core.edit', 'com_menus'))
  367. {
  368. $this->setError(JText::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_EDIT'));
  369. return false;
  370. }
  371. // We are going to store all the children and just moved the menutype
  372. $children = array();
  373. // Parent exists so we let's proceed
  374. foreach ($pks as $pk)
  375. {
  376. // Check that the row actually exists
  377. if (!$table->load($pk))
  378. {
  379. if ($error = $table->getError())
  380. {
  381. // Fatal error
  382. $this->setError($error);
  383. return false;
  384. }
  385. else
  386. {
  387. // Not fatal error
  388. $this->setError(JText::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
  389. continue;
  390. }
  391. }
  392. // Set the new location in the tree for the node.
  393. $table->setLocation($parentId, 'last-child');
  394. // Set the new Parent Id
  395. $table->parent_id = $parentId;
  396. // Check if we are moving to a different menu
  397. if ($menuType != $table->menutype)
  398. {
  399. // Add the child node ids to the children array.
  400. $query->clear()
  401. ->select($db->quoteName('id'))
  402. ->from($db->quoteName('#__menu'))
  403. ->where($db->quoteName('lft') . ' BETWEEN ' . (int) $table->lft . ' AND ' . (int) $table->rgt);
  404. $db->setQuery($query);
  405. $children = array_merge($children, (array) $db->loadColumn());
  406. }
  407. // Check the row.
  408. if (!$table->check())
  409. {
  410. $this->setError($table->getError());
  411. return false;
  412. }
  413. // Store the row.
  414. if (!$table->store())
  415. {
  416. $this->setError($table->getError());
  417. return false;
  418. }
  419. // Rebuild the tree path.
  420. if (!$table->rebuildPath())
  421. {
  422. $this->setError($table->getError());
  423. return false;
  424. }
  425. }
  426. // Process the child rows
  427. if (!empty($children))
  428. {
  429. // Remove any duplicates and sanitize ids.
  430. $children = array_unique($children);
  431. JArrayHelper::toInteger($children);
  432. // Update the menutype field in all nodes where necessary.
  433. $query->clear()
  434. ->update($db->quoteName('#__menu'))
  435. ->set($db->quoteName('menutype') . ' = ' . $db->quote($menuType))
  436. ->where($db->quoteName('id') . ' IN (' . implode(',', $children) . ')');
  437. $db->setQuery($query);
  438. try
  439. {
  440. $db->execute();
  441. }
  442. catch (RuntimeException $e)
  443. {
  444. $this->setError($e->getMessage());
  445. return false;
  446. }
  447. }
  448. // Clean the cache
  449. $this->cleanCache();
  450. return true;
  451. }
  452. /**
  453. * Method to check if you can save a record.
  454. *
  455. * @param array $data An array of input data.
  456. * @param string $key The name of the key for the primary key.
  457. *
  458. * @return boolean
  459. * @since 1.6
  460. */
  461. protected function canSave($data = array(), $key = 'id')
  462. {
  463. return JFactory::getUser()->authorise('core.edit', $this->option);
  464. }
  465. /**
  466. * Method to get the row form.
  467. *
  468. * @param array $data Data for the form.
  469. * @param boolean $loadData True if the form is to load its own data (default case), false if not.
  470. * @return mixed A JForm object on success, false on failure
  471. * @since 1.6
  472. */
  473. public function getForm($data = array(), $loadData = true)
  474. {
  475. // The folder and element vars are passed when saving the form.
  476. if (empty($data))
  477. {
  478. $item = $this->getItem();
  479. $this->setState('item.link', $item->link);
  480. // The type should already be set.
  481. }
  482. else
  483. {
  484. $this->setState('item.link', JArrayHelper::getValue($data, 'link'));
  485. $this->setState('item.type', JArrayHelper::getValue($data, 'type'));
  486. }
  487. // Get the form.
  488. $form = $this->loadForm('com_menus.item', 'item', array('control' => 'jform', 'load_data' => $loadData), true);
  489. if (empty($form))
  490. {
  491. return false;
  492. }
  493. // Modify the form based on access controls.
  494. if (!$this->canEditState((object) $data))
  495. {
  496. // Disable fields for display.
  497. $form->setFieldAttribute('menuordering', 'disabled', 'true');
  498. $form->setFieldAttribute('published', 'disabled', 'true');
  499. // Disable fields while saving.
  500. // The controller has already verified this is an article you can edit.
  501. $form->setFieldAttribute('menuordering', 'filter', 'unset');
  502. $form->setFieldAttribute('published', 'filter', 'unset');
  503. }
  504. return $form;
  505. }
  506. /**
  507. * Method to get the data that should be injected in the form.
  508. *
  509. * @return mixed The data for the form.
  510. * @since 1.6
  511. */
  512. protected function loadFormData()
  513. {
  514. // Check the session for previously entered form data.
  515. $data = array_merge((array) $this->getItem(), (array) JFactory::getApplication()->getUserState('com_menus.edit.item.data', array()));
  516. $this->preprocessData('com_menus.item', $data);
  517. return $data;
  518. }
  519. /**
  520. * Get the necessary data to load an item help screen.
  521. *
  522. * @return object An object with key, url, and local properties for loading the item help screen.
  523. * @since 1.6
  524. */
  525. public function getHelp()
  526. {
  527. return (object) array('key' => $this->helpKey, 'url' => $this->helpURL, 'local' => $this->helpLocal);
  528. }
  529. /**
  530. * Method to get a menu item.
  531. *
  532. * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used.
  533. *
  534. * @return mixed Menu item data object on success, false on failure.
  535. * @since 1.6
  536. */
  537. public function getItem($pk = null)
  538. {
  539. $pk = (!empty($pk)) ? $pk : (int) $this->getState('item.id');
  540. // Get a level row instance.
  541. $table = $this->getTable();
  542. // Attempt to load the row.
  543. $table->load($pk);
  544. // Check for a table object error.
  545. if ($error = $table->getError())
  546. {
  547. $this->setError($error);
  548. return false;
  549. }
  550. // Prime required properties.
  551. if ($type = $this->getState('item.type'))
  552. {
  553. $table->type = $type;
  554. }
  555. if (empty($table->id))
  556. {
  557. $table->parent_id = $this->getState('item.parent_id');
  558. $table->menutype = $this->getState('item.menutype');
  559. $table->params = '{}';
  560. }
  561. // If the link has been set in the state, possibly changing link type.
  562. if ($link = $this->getState('item.link'))
  563. {
  564. // Check if we are changing away from the actual link type.
  565. if (MenusHelper::getLinkKey($table->link) != MenusHelper::getLinkKey($link))
  566. {
  567. $table->link = $link;
  568. }
  569. }
  570. switch ($table->type)
  571. {
  572. case 'alias':
  573. $table->component_id = 0;
  574. $args = array();
  575. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  576. break;
  577. case 'separator':
  578. case 'heading':
  579. $table->link = '';
  580. $table->component_id = 0;
  581. break;
  582. case 'url':
  583. $table->component_id = 0;
  584. parse_str(parse_url($table->link, PHP_URL_QUERY));
  585. break;
  586. case 'component':
  587. default:
  588. // Enforce a valid type.
  589. $table->type = 'component';
  590. // Ensure the integrity of the component_id field is maintained, particularly when changing the menu item type.
  591. $args = array();
  592. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  593. if (isset($args['option']))
  594. {
  595. // Load the language file for the component.
  596. $lang = JFactory::getLanguage();
  597. $lang->load($args['option'], JPATH_ADMINISTRATOR, null, false, true)
  598. || $lang->load($args['option'], JPATH_ADMINISTRATOR . '/components/' . $args['option'], null, false, true);
  599. // Determine the component id.
  600. $component = JComponentHelper::getComponent($args['option']);
  601. if (isset($component->id))
  602. {
  603. $table->component_id = $component->id;
  604. }
  605. }
  606. break;
  607. }
  608. // We have a valid type, inject it into the state for forms to use.
  609. $this->setState('item.type', $table->type);
  610. // Convert to the JObject before adding the params.
  611. $properties = $table->getProperties(1);
  612. $result = JArrayHelper::toObject($properties);
  613. // Convert the params field to an array.
  614. $registry = new JRegistry;
  615. $registry->loadString($table->params);
  616. $result->params = $registry->toArray();
  617. // Merge the request arguments in to the params for a component.
  618. if ($table->type == 'component')
  619. {
  620. // Note that all request arguments become reserved parameter names.
  621. $result->request = $args;
  622. $result->params = array_merge($result->params, $args);
  623. }
  624. if ($table->type == 'alias')
  625. {
  626. // Note that all request arguments become reserved parameter names.
  627. $args = array();
  628. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  629. $result->params = array_merge($result->params, $args);
  630. }
  631. if ($table->type == 'url')
  632. {
  633. // Note that all request arguments become reserved parameter names.
  634. $args = array();
  635. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  636. $result->params = array_merge($result->params, $args);
  637. }
  638. // Load associated menu items
  639. $app = JFactory::getApplication();
  640. $assoc = JLanguageAssociations::isEnabled();
  641. if ($assoc)
  642. {
  643. if ($pk != null)
  644. {
  645. $result->associations = MenusHelper::getAssociations($pk);
  646. }
  647. else
  648. {
  649. $result->associations = array();
  650. }
  651. }
  652. $result->menuordering = $pk;
  653. return $result;
  654. }
  655. /**
  656. * Get the list of modules not in trash.
  657. *
  658. * @return mixed An array of module records (id, title, position), or false on error.
  659. * @since 1.6
  660. */
  661. public function getModules()
  662. {
  663. $db = $this->getDbo();
  664. $query = $db->getQuery(true);
  665. // Join on the module-to-menu mapping table.
  666. // We are only interested if the module is displayed on ALL or THIS menu item (or the inverse ID number).
  667. //sqlsrv changes for modulelink to menu manager
  668. $query->select('a.id, a.title, a.position, a.published, map.menuid')
  669. ->from('#__modules AS a')
  670. ->join('LEFT', sprintf('#__modules_menu AS map ON map.moduleid = a.id AND map.menuid IN (0, %1$d, -%1$d)', $this->getState('item.id')))
  671. ->select('(SELECT COUNT(*) FROM #__modules_menu WHERE moduleid = a.id AND menuid < 0) AS ' . $db->quoteName('except'));
  672. // Join on the asset groups table.
  673. $query->select('ag.title AS access_title')
  674. ->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access')
  675. ->where('a.published >= 0')
  676. ->where('a.client_id = 0')
  677. ->order('a.position, a.ordering');
  678. $db->setQuery($query);
  679. try
  680. {
  681. $result = $db->loadObjectList();
  682. }
  683. catch (RuntimeException $e)
  684. {
  685. $this->setError($e->getMessage());
  686. return false;
  687. }
  688. return $result;
  689. }
  690. /**
  691. * A protected method to get the where clause for the reorder
  692. * This ensures that the row will be moved relative to a row with the same menutype
  693. *
  694. * @param JTableMenu $table instance
  695. *
  696. * @return array An array of conditions to add to add to ordering queries.
  697. * @since 1.6
  698. */
  699. protected function getReorderConditions($table)
  700. {
  701. return 'menutype = ' . $this->_db->quote($table->menutype);
  702. }
  703. /**
  704. * Returns a Table object, always creating it
  705. *
  706. * @param type $type The table type to instantiate
  707. * @param string $prefix A prefix for the table class name. Optional.
  708. * @param array $config Configuration array for model. Optional.
  709. *
  710. * @return JTable A database object
  711. * @since 1.6
  712. */
  713. public function getTable($type = 'Menu', $prefix = 'MenusTable', $config = array())
  714. {
  715. return JTable::getInstance($type, $prefix, $config);
  716. }
  717. /**
  718. * Auto-populate the model state.
  719. *
  720. * Note. Calling getState in this method will result in recursion.
  721. *
  722. * @return void
  723. * @since 1.6
  724. */
  725. protected function populateState()
  726. {
  727. $app = JFactory::getApplication('administrator');
  728. // Load the User state.
  729. $pk = $app->input->getInt('id');
  730. $this->setState('item.id', $pk);
  731. if (!($parentId = $app->getUserState('com_menus.edit.item.parent_id')))
  732. {
  733. $parentId = $app->input->getInt('parent_id');
  734. }
  735. $this->setState('item.parent_id', $parentId);
  736. $menuType = $app->getUserState('com_menus.edit.item.menutype');
  737. if ($app->input->getString('menutype', false))
  738. {
  739. $menuType = $app->input->getString('menutype', 'mainmenu');
  740. }
  741. $this->setState('item.menutype', $menuType);
  742. if (!($type = $app->getUserState('com_menus.edit.item.type')))
  743. {
  744. $type = $app->input->get('type');
  745. // Note a new menu item will have no field type.
  746. // The field is required so the user has to change it.
  747. }
  748. $this->setState('item.type', $type);
  749. if ($link = $app->getUserState('com_menus.edit.item.link'))
  750. {
  751. $this->setState('item.link', $link);
  752. }
  753. // Load the parameters.
  754. $params = JComponentHelper::getParams('com_menus');
  755. $this->setState('params', $params);
  756. }
  757. /**
  758. * @param object $form A form object.
  759. * @param mixed $data The data expected for the form.
  760. *
  761. * @return void
  762. * @since 1.6
  763. * @throws Exception if there is an error in the form event.
  764. */
  765. protected function preprocessForm(JForm $form, $data, $group = 'content')
  766. {
  767. $link = $this->getState('item.link');
  768. $type = $this->getState('item.type');
  769. $formFile = false;
  770. // Initialise form with component view params if available.
  771. if ($type == 'component')
  772. {
  773. $link = htmlspecialchars_decode($link);
  774. // Parse the link arguments.
  775. $args = array();
  776. parse_str(parse_url(htmlspecialchars_decode($link), PHP_URL_QUERY), $args);
  777. // Confirm that the option is defined.
  778. $option = '';
  779. $base = '';
  780. if (isset($args['option']))
  781. {
  782. // The option determines the base path to work with.
  783. $option = $args['option'];
  784. $base = JPATH_SITE . '/components/' . $option;
  785. }
  786. // Confirm a view is defined.
  787. $formFile = false;
  788. if (isset($args['view']))
  789. {
  790. $view = $args['view'];
  791. // Determine the layout to search for.
  792. if (isset($args['layout']))
  793. {
  794. $layout = $args['layout'];
  795. }
  796. else
  797. {
  798. $layout = 'default';
  799. }
  800. $formFile = false;
  801. // Check for the layout XML file. Use standard xml file if it exists.
  802. $tplFolders = array(
  803. $base . '/views/' . $view . '/tmpl',
  804. $base . '/view/' . $view . '/tmpl'
  805. );
  806. $path = JPath::find($tplFolders, $layout . '.xml');
  807. if (is_file($path))
  808. {
  809. $formFile = $path;
  810. }
  811. // If custom layout, get the xml file from the template folder
  812. // template folder is first part of file name -- template:folder
  813. if (!$formFile && (strpos($layout, ':') > 0))
  814. {
  815. $temp = explode(':', $layout);
  816. $templatePath = JPATH::clean(JPATH_SITE . '/templates/' . $temp[0] . '/html/' . $option . '/' . $view . '/' . $temp[1] . '.xml');
  817. if (is_file($templatePath))
  818. {
  819. $formFile = $templatePath;
  820. }
  821. }
  822. }
  823. // Now check for a view manifest file
  824. if (!$formFile)
  825. {
  826. if (isset($view))
  827. {
  828. $metadataFolders = array(
  829. $base . '/view/' . $view,
  830. $base . '/views/' . $view
  831. );
  832. $metaPath = JPath::find($metadataFolders, 'metadata.xml');
  833. if (is_file($path = JPath::clean($metaPath)))
  834. {
  835. $formFile = $path;
  836. }
  837. }
  838. else
  839. {
  840. // Now check for a component manifest file
  841. $path = JPath::clean($base . '/metadata.xml');
  842. if (is_file($path))
  843. {
  844. $formFile = $path;
  845. }
  846. }
  847. }
  848. }
  849. if ($formFile)
  850. {
  851. // If an XML file was found in the component, load it first.
  852. // We need to qualify the full path to avoid collisions with component file names.
  853. if ($form->loadFile($formFile, true, '/metadata') == false)
  854. {
  855. throw new Exception(JText::_('JERROR_LOADFILE_FAILED'));
  856. }
  857. // Attempt to load the xml file.
  858. if (!$xml = simplexml_load_file($formFile))
  859. {
  860. throw new Exception(JText::_('JERROR_LOADFILE_FAILED'));
  861. }
  862. // Get the help data from the XML file if present.
  863. $help = $xml->xpath('/metadata/layout/help');
  864. }
  865. else
  866. {
  867. // We don't have a component. Load the form XML to get the help path
  868. $xmlFile = JPath::find(JPATH_ROOT . '/administrator/components/com_menus/models/forms', 'item_' . $type . '.xml');
  869. // Attempt to load the xml file.
  870. if ($xmlFile && !$xml = simplexml_load_file($xmlFile))
  871. {
  872. throw new Exception(JText::_('JERROR_LOADFILE_FAILED'));
  873. }
  874. // Get the help data from the XML file if present.
  875. $help = $xml->xpath('/form/help');
  876. }
  877. if (!empty($help))
  878. {
  879. $helpKey = trim((string) $help[0]['key']);
  880. $helpURL = trim((string) $help[0]['url']);
  881. $helpLoc = trim((string) $help[0]['local']);
  882. $this->helpKey = $helpKey ? $helpKey : $this->helpKey;
  883. $this->helpURL = $helpURL ? $helpURL : $this->helpURL;
  884. $this->helpLocal = (($helpLoc == 'true') || ($helpLoc == '1') || ($helpLoc == 'local')) ? true : false;
  885. }
  886. // Now load the component params.
  887. // TODO: Work out why 'fixing' this breaks JForm
  888. if ($isNew = false)
  889. {
  890. $path = JPath::clean(JPATH_ADMINISTRATOR . '/components/' . $option . '/config.xml');
  891. }
  892. else
  893. {
  894. $path = 'null';
  895. }
  896. if (is_file($path))
  897. {
  898. // Add the component params last of all to the existing form.
  899. if (!$form->load($path, true, '/config'))
  900. {
  901. throw new Exception(JText::_('JERROR_LOADFILE_FAILED'));
  902. }
  903. }
  904. // Load the specific type file
  905. if (!$form->loadFile('item_' . $type, false, false))
  906. {
  907. throw new Exception(JText::_('JERROR_LOADFILE_FAILED'));
  908. }
  909. // Association menu items
  910. $app = JFactory::getApplication();
  911. $assoc = JLanguageAssociations::isEnabled();
  912. if ($assoc)
  913. {
  914. $languages = JLanguageHelper::getLanguages('lang_code');
  915. $addform = new SimpleXMLElement('<form />');
  916. $fields = $addform->addChild('fields');
  917. $fields->addAttribute('name', 'associations');
  918. $fieldset = $fields->addChild('fieldset');
  919. $fieldset->addAttribute('name', 'item_associations');
  920. $fieldset->addAttribute('description', 'COM_MENUS_ITEM_ASSOCIATIONS_FIELDSET_DESC');
  921. $add = false;
  922. foreach ($languages as $tag => $language)
  923. {
  924. if ($tag != $data['language'])
  925. {
  926. $add = true;
  927. $field = $fieldset->addChild('field');
  928. $field->addAttribute('name', $tag);
  929. $field->addAttribute('type', 'menuitem');
  930. $field->addAttribute('language', $tag);
  931. $field->addAttribute('label', $language->title);
  932. $field->addAttribute('translate_label', 'false');
  933. $option = $field->addChild('option', 'COM_MENUS_ITEM_FIELD_ASSOCIATION_NO_VALUE');
  934. $option->addAttribute('value', '');
  935. }
  936. }
  937. if ($add)
  938. {
  939. $form->load($addform, false);
  940. }
  941. }
  942. // Trigger the default form events.
  943. parent::preprocessForm($form, $data, $group);
  944. }
  945. /**
  946. * Method rebuild the entire nested set tree.
  947. *
  948. * @return boolean False on failure or error, true otherwise.
  949. * @since 1.6
  950. */
  951. public function rebuild()
  952. {
  953. // Initialiase variables.
  954. $db = $this->getDbo();
  955. $query = $db->getQuery(true);
  956. $table = $this->getTable();
  957. if (!$table->rebuild())
  958. {
  959. $this->setError($table->getError());
  960. return false;
  961. }
  962. $query->select('id, params')
  963. ->from('#__menu')
  964. ->where('params NOT LIKE ' . $db->quote('{%'))
  965. ->where('params <> ' . $db->quote(''));
  966. $db->setQuery($query);
  967. try
  968. {
  969. $items = $db->loadObjectList();
  970. }
  971. catch (RuntimeException $e)
  972. {
  973. return JError::raiseWarning(500, $e->getMessage());
  974. }
  975. foreach ($items as &$item)
  976. {
  977. $registry = new JRegistry;
  978. $registry->loadString($item->params);
  979. $params = (string) $registry;
  980. $query->clear();
  981. $query->update('#__menu')
  982. ->set('params = ' . $db->quote($params))
  983. ->where('id = ' . $item->id);
  984. try
  985. {
  986. $db->setQuery($query)->execute();
  987. }
  988. catch (RuntimeException $e)
  989. {
  990. return JError::raiseWarning(500, $e->getMessage());
  991. }
  992. unset($registry);
  993. }
  994. // Clean the cache
  995. $this->cleanCache();
  996. return true;
  997. }
  998. /**
  999. * Method to save the form data.
  1000. *
  1001. * @param array $data The form data.
  1002. *
  1003. * @return boolean True on success.
  1004. * @since 1.6
  1005. */
  1006. public function save($data)
  1007. {
  1008. $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('item.id');
  1009. $isNew = true;
  1010. $table = $this->getTable();
  1011. // Load the row if saving an existing item.
  1012. if ($pk > 0)
  1013. {
  1014. $table->load($pk);
  1015. $isNew = false;
  1016. }
  1017. if (!$isNew)
  1018. {
  1019. if ($table->parent_id == $data['parent_id'])
  1020. {
  1021. // If first is chosen make the item the first child of the selected parent.
  1022. if ($data['menuordering'] == -1)
  1023. {
  1024. $table->setLocation($data['parent_id'], 'first-child');
  1025. }
  1026. // If last is chosen make it the last child of the selected parent.
  1027. elseif ($data['menuordering'] == -2)
  1028. {
  1029. $table->setLocation($data['parent_id'], 'last-child');
  1030. }
  1031. // Don't try to put an item after itself. All other ones put after the selected item.
  1032. // $data['id'] is empty means it's a save as copy
  1033. elseif ($data['menuordering'] && $table->id != $data['menuordering'] || empty($data['id']))
  1034. {
  1035. $table->setLocation($data['menuordering'], 'after');
  1036. }
  1037. // Just leave it where it is if no change is made.
  1038. elseif ($data['menuordering'] && $table->id == $data['menuordering'])
  1039. {
  1040. unset($data['menuordering']);
  1041. }
  1042. }
  1043. // Set the new parent id if parent id not matched and put in last position
  1044. else
  1045. {
  1046. $table->setLocation($data['parent_id'], 'last-child');
  1047. }
  1048. }
  1049. // We have a new item, so it is not a change.
  1050. else
  1051. {
  1052. $table->setLocation($data['parent_id'], 'last-child');
  1053. }
  1054. // Bind the data.
  1055. if (!$table->bind($data))
  1056. {
  1057. $this->setError($table->getError());
  1058. return false;
  1059. }
  1060. // Alter the title & alias for save as copy. Also, unset the home record.
  1061. if (!$isNew && $data['id'] == 0)
  1062. {
  1063. list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title);
  1064. $table->title = $title;
  1065. $table->alias = $alias;
  1066. $table->published = 0;
  1067. $table->home = 0;
  1068. }
  1069. // Check the data.
  1070. if (!$table->check())
  1071. {
  1072. $this->setError($table->getError());
  1073. return false;
  1074. }
  1075. // Store the data.
  1076. if (!$table->store())
  1077. {
  1078. $this->setError($table->getError());
  1079. return false;
  1080. }
  1081. // Rebuild the tree path.
  1082. if (!$table->rebuildPath($table->id))
  1083. {
  1084. $this->setError($table->getError());
  1085. return false;
  1086. }
  1087. $this->setState('item.id', $table->id);
  1088. $this->setState('item.menutype', $table->menutype);
  1089. // Load associated menu items
  1090. $app = JFactory::getApplication();
  1091. $assoc = JLanguageAssociations::isEnabled();
  1092. if ($assoc)
  1093. {
  1094. // Adding self to the association
  1095. $associations = $data['associations'];
  1096. foreach ($associations as $tag => $id)
  1097. {
  1098. if (empty($id))
  1099. {
  1100. unset($associations[$tag]);
  1101. }
  1102. }
  1103. // Detecting all item menus
  1104. $all_language = $table->language == '*';
  1105. if ($all_language && !empty($associations))
  1106. {
  1107. JError::raiseNotice(403, JText::_('COM_MENUS_ERROR_ALL_LANGUAGE_ASSOCIATED'));
  1108. }
  1109. $associations[$table->language] = $table->id;
  1110. // Deleting old association for these items
  1111. $db = JFactory::getDbo();
  1112. $query = $db->getQuery(true)
  1113. ->delete('#__associations')
  1114. ->where('context=' . $db->quote('com_menus.item'))
  1115. ->where('id IN (' . implode(',', $associations) . ')');
  1116. $db->setQuery($query);
  1117. try
  1118. {
  1119. $db->execute();
  1120. }
  1121. catch (RuntimeException $e)
  1122. {
  1123. $this->setError($e->getMessage());
  1124. return false;
  1125. }
  1126. if (!$all_language && count($associations) > 1)
  1127. {
  1128. // Adding new association for these items
  1129. $key = md5(json_encode($associations));
  1130. $query->clear()
  1131. ->insert('#__associations');
  1132. foreach ($associations as $id)
  1133. {
  1134. $query->values($id . ',' . $db->quote('com_menus.item') . ',' . $db->quote($key));
  1135. }
  1136. $db->setQuery($query);
  1137. try
  1138. {
  1139. $db->execute();
  1140. }
  1141. catch (RuntimeException $e)
  1142. {
  1143. $this->setError($e->getMessage());
  1144. return false;
  1145. }
  1146. }
  1147. }
  1148. // Clean the cache
  1149. $this->cleanCache();
  1150. if (isset($data['link']))
  1151. {
  1152. $base = JUri::base();
  1153. $juri = JUri::getInstance($base . $data['link']);
  1154. $option = $juri->getVar('option');
  1155. // Clean the cache
  1156. parent::cleanCache($option);
  1157. }
  1158. return true;
  1159. }
  1160. /**
  1161. * Method to save the reordered nested set tree.
  1162. * First we save the new order values in the lft values of the changed ids.
  1163. * Then we invoke the table rebuild to implement the new ordering.
  1164. *
  1165. * @param array $idArray id's of rows to be reordered
  1166. * @param array $lft_array lft values of rows to be reordered
  1167. *
  1168. * @return boolean false on failuer or error, true otherwise
  1169. * @since 1.6
  1170. */
  1171. public function saveorder($idArray = null, $lft_array = null)
  1172. {
  1173. // Get an instance of the table object.
  1174. $table = $this->getTable();
  1175. if (!$table->saveorder($idArray, $lft_array))
  1176. {
  1177. $this->setError($table->getError());
  1178. return false;
  1179. }
  1180. // Clean the cache
  1181. $this->cleanCache();
  1182. return true;
  1183. }
  1184. /**
  1185. * Method to change the home state of one or more items.
  1186. *
  1187. * @param array $pks A list of the primary keys to change.
  1188. * @param integer $value The value of the home state.
  1189. *
  1190. * @return boolean True on success.
  1191. * @since 1.6
  1192. */
  1193. public function setHome(&$pks, $value = 1)
  1194. {
  1195. $table = $this->getTable();
  1196. $pks = (array) $pks;
  1197. $languages = array();
  1198. $onehome = false;
  1199. // Remember that we can set a home page for different languages,
  1200. // so we need to loop through the primary key array.
  1201. foreach ($pks as $i => $pk)
  1202. {
  1203. if ($table->load($pk))
  1204. {
  1205. if (!array_key_exists($table->language, $languages))
  1206. {
  1207. $languages[$table->language] = true;
  1208. if ($table->home == $value)
  1209. {
  1210. unset($pks[$i]);
  1211. JError::raiseNotice(403, JText::_('COM_MENUS_ERROR_ALREADY_HOME'));
  1212. }
  1213. else
  1214. {
  1215. $table->home = $value;
  1216. if ($table->language == '*')
  1217. {
  1218. $table->published = 1;
  1219. }
  1220. if (!$this->canSave($table))
  1221. {
  1222. // Prune items that you can't change.
  1223. unset($pks[$i]);
  1224. JError::raiseWarning(403, JText::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
  1225. }
  1226. elseif (!$table->check())
  1227. {
  1228. // Prune the items that failed pre-save checks.
  1229. unset($pks[$i]);
  1230. JError::raiseWarning(403, $table->getError());
  1231. }
  1232. elseif (!$table->store())
  1233. {
  1234. // Prune the items that could not be stored.
  1235. unset($pks[$i]);
  1236. JError::raiseWarning(403, $table->getError());
  1237. }
  1238. }
  1239. }
  1240. else
  1241. {
  1242. unset($pks[$i]);
  1243. if (!$onehome)
  1244. {
  1245. $onehome = true;
  1246. JError::raiseNotice(403, JText::sprintf('COM_MENUS_ERROR_ONE_HOME'));
  1247. }
  1248. }
  1249. }
  1250. }
  1251. // Clean the cache
  1252. $this->cleanCache();
  1253. return true;
  1254. }
  1255. /**
  1256. * Method to change the published state of one or more records.
  1257. *
  1258. * @param array &$pks A list of the primary keys to change.
  1259. * @param integer $value The value of the published state.
  1260. *
  1261. * @return boolean True on success.
  1262. *
  1263. * @since 1.6
  1264. */
  1265. public function publish(&$pks, $value = 1)
  1266. {
  1267. $table = $this->getTable();
  1268. $pks = (array) $pks;
  1269. // Default menu item existence checks.
  1270. if ($value != 1)
  1271. {
  1272. foreach ($pks as $i => $pk)
  1273. {
  1274. if ($table->load($pk) && $table->home && $table->language == '*')
  1275. {
  1276. // Prune items that you can't change.
  1277. JError::raiseWarning(403, JText::_('JLIB_DATABASE_ERROR_MENU_UNPUBLISH_DEFAULT_HOME'));
  1278. unset($pks[$i]);
  1279. break;
  1280. }
  1281. }
  1282. }
  1283. // Clean the cache
  1284. $this->cleanCache();
  1285. // Ensure that previous checks doesn't empty the array
  1286. if (empty($pks))
  1287. {
  1288. return true;
  1289. }
  1290. return parent::publish($pks, $value);
  1291. }
  1292. /**
  1293. * Method to change the title & alias.
  1294. *
  1295. * @param integer $parent_id The id of the parent.
  1296. * @param string $alias The alias.
  1297. * @param string $title The title.
  1298. *
  1299. * @return array Contains the modified title and alias.
  1300. *
  1301. * @since 1.6
  1302. */
  1303. protected function generateNewTitle($parent_id, $alias, $title)
  1304. {
  1305. // Alter the title & alias
  1306. $table = $this->getTable();
  1307. while ($table->load(array('alias' => $alias, 'parent_id' => $parent_id)))
  1308. {
  1309. if ($title == $table->title)
  1310. {
  1311. $title = JString::increment($title);
  1312. }
  1313. $alias = JString::increment($alias, 'dash');
  1314. }
  1315. return array($title, $alias);
  1316. }
  1317. /**
  1318. * Custom clean cache method
  1319. *
  1320. * @since 1.6
  1321. */
  1322. protected function cleanCache($group = null, $client_id = 0)
  1323. {
  1324. parent::cleanCache('com_modules');
  1325. parent::cleanCache('mod_menu');
  1326. }
  1327. }