PageRenderTime 43ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 1ms

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

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