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

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

https://github.com/cosmocommerce/joomla
PHP | 1057 lines | 604 code | 171 blank | 282 comment | 127 complexity | df399c56b56446013fbb8364f0486f10 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @version $Id$
  4. * @copyright Copyright (C) 2005 - 2010 Open Source Matters, Inc. All rights reserved.
  5. * @license GNU General Public License version 2 or later; see LICENSE.txt
  6. */
  7. // No direct access
  8. defined('_JEXEC') or die;
  9. // Include dependancies.
  10. jimport('joomla.application.component.modelform');
  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. * @version 1.6
  18. */
  19. class MenusModelItem extends JModelForm
  20. {
  21. /**
  22. * Model context string.
  23. *
  24. * @var string
  25. */
  26. protected $_context = 'com_menus.item';
  27. /**
  28. * Returns a Table object, always creating it
  29. *
  30. * @param type The table type to instantiate
  31. * @param string A prefix for the table class name. Optional.
  32. * @param array Configuration array for model. Optional.
  33. * @return JTable A database object
  34. */
  35. public function getTable($type = 'Menu', $prefix = 'JTable', $config = array())
  36. {
  37. return JTable::getInstance($type, $prefix, $config);
  38. }
  39. /**
  40. * Auto-populate the model state.
  41. *
  42. * @return void
  43. */
  44. protected function _populateState()
  45. {
  46. $app = JFactory::getApplication('administrator');
  47. // Load the User state.
  48. if (!($pk = (int) $app->getUserState('com_menus.edit.item.id'))) {
  49. $pk = (int) JRequest::getInt('item_id');
  50. }
  51. $this->setState('item.id', $pk);
  52. if (!($parentId = $app->getUserState('com_menus.edit.item.parent_id'))) {
  53. $parentId = JRequest::getInt('parent_id');
  54. }
  55. $this->setState('item.parent_id', $parentId);
  56. if (!($menuType = $app->getUserState('com_menus.edit.item.menutype'))) {
  57. $menuType = JRequest::getCmd('menutype', 'mainmenu');
  58. }
  59. $this->setState('item.menutype', $menuType);
  60. if ($type = $app->getUserState('com_menus.edit.item.type')){
  61. // $type = JRequest::getCmd('type', 'url');
  62. }
  63. $this->setState('item.type', $type);
  64. if ($link = $app->getUserState('com_menus.edit.item.link')) {
  65. $this->setState('item.link', $link);
  66. }
  67. // Load the parameters.
  68. $params = &JComponentHelper::getParams('com_menus');
  69. $this->setState('params', $params);
  70. }
  71. /**
  72. * Method to get a menu item.
  73. *
  74. * @param integer An optional id of the object to get, otherwise the id from the model state is used.
  75. *
  76. * @return mixed Menu item data object on success, false on failure.
  77. */
  78. public function &getItem($pk = null)
  79. {
  80. // Initialise variables.
  81. $pk = (!empty($pk)) ? $pk : (int)$this->getState('item.id');
  82. // Get a level row instance.
  83. $table = &$this->getTable();
  84. // Attempt to load the row.
  85. $table->load($pk);
  86. // Check for a table object error.
  87. if ($error = $table->getError()) {
  88. $this->setError($error);
  89. $false = false;
  90. return $false;
  91. }
  92. // Prime required properties.
  93. if (empty($table->id)) {
  94. $table->parent_id = $this->getState('item.parent_id');
  95. $table->menutype = $this->getState('item.menutype');
  96. $table->type = $this->getState('item.type');
  97. $table->params = '{}';
  98. }
  99. // If the link has been set in the state, possibly changing link type.
  100. if ($link = $this->getState('item.link')) {
  101. // Check if we are changing away from the actual link type.
  102. if (MenusHelper::getLinkKey($table->link) != MenusHelper::getLinkKey($link)) {
  103. $table->link = $link;
  104. }
  105. }
  106. switch ($table->type)
  107. {
  108. case 'alias':
  109. $table->component_id = 0;
  110. $args = array();
  111. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  112. break;
  113. case 'separator':
  114. $table->link = '';
  115. $table->component_id = 0;
  116. break;
  117. case 'url':
  118. $table->component_id = 0;
  119. parse_str(parse_url($table->link, PHP_URL_QUERY));
  120. break;
  121. case 'component':
  122. default:
  123. // Enforce a valid type.
  124. $table->type = 'component';
  125. // Ensure the integrity of the component_id field is maintained, particularly when changing the menu item type.
  126. $args = array();
  127. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  128. if (isset($args['option'])) {
  129. // Load the language file for the component.
  130. $lang = &JFactory::getLanguage();
  131. $lang->load($args['option'], JPATH_ADMINISTRATOR, null, false, false)
  132. || $lang->load($args['option'], JPATH_ADMINISTRATOR.'/components/'.$args['option'], null, false, false)
  133. || $lang->load($args['option'], JPATH_ADMINISTRATOR, $lang->getDefault(), false, false)
  134. || $lang->load($args['option'], JPATH_ADMINISTRATOR.'/components/'.$args['option'], $lang->getDefault(), false, false);
  135. // Determine the component id.
  136. $component = JComponentHelper::getComponent($args['option']);
  137. if (isset($component->id)) {
  138. $table->component_id = $component->id;
  139. }
  140. }
  141. break;
  142. }
  143. // We have a valid type, inject it into the state for forms to use.
  144. $this->setState('item.type', $table->type);
  145. // Convert to the JObject before adding the params.
  146. $result = JArrayHelper::toObject($table->getProperties(1), 'JObject');
  147. // Convert the params field to an array.
  148. $registry = new JRegistry;
  149. $registry->loadJSON($table->params);
  150. $result->params = $registry->toArray();
  151. // Merge the request arguments in to the params for a component.
  152. if ($table->type == 'component') {
  153. // Note that all request arguments become reserved parameter names.
  154. $args = array();
  155. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  156. $result->params = array_merge($result->params, $args);
  157. }
  158. if ($table->type == 'alias') {
  159. // Note that all request arguments become reserved parameter names.
  160. $args = array();
  161. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  162. $result->params = array_merge($result->params, $args);
  163. }
  164. if ($table->type == 'url') {
  165. // Note that all request arguments become reserved parameter names.
  166. $args = array();
  167. parse_str(parse_url($table->link, PHP_URL_QUERY), $args);
  168. $result->params = array_merge($result->params, $args);
  169. }
  170. return $result;
  171. }
  172. /**
  173. * Method to get the row form.
  174. *
  175. * @return mixed JForm object on success, false on failure.
  176. * @since 1.6
  177. */
  178. public function getForm()
  179. {
  180. // Initialise variables.
  181. $app = &JFactory::getApplication();
  182. // Get the form.
  183. $form = parent::getForm('item', 'com_menus.item', array('array' => 'jform', 'event' => 'onPrepareForm'), true);
  184. // Check for an error.
  185. if (JError::isError($form)) {
  186. $this->setError($form->getMessage());
  187. return false;
  188. }
  189. // Check the session for previously entered form data.
  190. $data = $app->getUserState('com_menus.edit.item.data', array());
  191. // Bind the form data if present.
  192. if (!empty($data)) {
  193. $form->bind($data);
  194. }
  195. return $form;
  196. }
  197. /**
  198. * Method to get a form object for the menu item params.
  199. *
  200. * This is one of the most complicated aspects of the menu item.
  201. * If it's an alias, a separator or URL, we just need a simple form for the extra params.
  202. * If it's a component, we need to look first for layout, view and component form data.
  203. * Then we need to add the component configuration if it's available. Note that hidden fieldset groups
  204. * with not display in the menu edit form.
  205. *
  206. * @param string An optional type (component, url, alias or separator).
  207. * @param string An optional link
  208. *
  209. * @return mixed A JForm object on success, false on failure.
  210. * @since 1.6
  211. */
  212. public function getParamsForm($type = null, $link = null)
  213. {
  214. jimport('joomla.filesystem.file');
  215. jimport('joomla.filesystem.folder');
  216. // Initialise variables.
  217. $form = null;
  218. $formFile = null;
  219. $formName = 'com_menus.item.params';
  220. $formOptions = array('array' => 'jformparams', 'event' => 'onPrepareForm');
  221. // Determine the link type.
  222. if (empty($type)) {
  223. $type = $this->getState('item.type');
  224. }
  225. if (empty($link)) {
  226. // If the link not supplied, try to load it.
  227. if ($item = &$this->getItem()) {
  228. $link = htmlspecialchars_decode($item->link);
  229. }
  230. }
  231. // Initialise form with component view params if available.
  232. if ($type == 'component') {
  233. // Parse the link arguments.
  234. $args = array();
  235. parse_str(parse_url(htmlspecialchars_decode($link), PHP_URL_QUERY), $args);
  236. // Confirm that the option is defined.
  237. $option = '';
  238. if (isset($args['option'])) {
  239. // The option determines the base path to work with.
  240. $option = $args['option'];
  241. $base = JPATH_SITE.DS.'components'.DS.$option;
  242. }
  243. // Confirm a view is defined.
  244. if (isset($args['view'])) {
  245. $view = $args['view'];
  246. // Determine the layout to search for.
  247. if (isset($args['layout'])) {
  248. $layout = $args['layout'];
  249. } else {
  250. $layout = 'default';
  251. }
  252. $formFile = false;
  253. // Check for the layout XML file. Use standard xml file if it exists.
  254. $path = JPath::clean($base.DS.'views'.DS.$view.DS.'tmpl'.DS.$layout.'.xml');
  255. if (JFile::exists($path)) {
  256. $formFile = $path;
  257. }
  258. // if custom layout, get the xml file from the template folder
  259. // TODO: only look in the template folder for the menu item's template
  260. if (!$formFile) {
  261. $folders = JFolder::folders(JPATH_SITE.DS.'templates','',false,true);
  262. foreach($folders as $folder)
  263. {
  264. if (JFile::exists($folder.DS.'html'.DS.$option.DS.$view.DS.$layout.'.xml')) {
  265. $formFile = $folder.DS.'html'.DS.$option.DS.$view.DS.$layout.'.xml';
  266. break;
  267. }
  268. }
  269. }
  270. // }
  271. // TODO: Now check for a view manifest file
  272. // TODO: Now check for a component manifest file
  273. }
  274. if ($formFile) {
  275. // If an XML file was found in the component, load it first.
  276. // We need to qualify the full path to avoid collisions with component file names.
  277. $form = parent::getForm($formFile, $formName, $formOptions, true);
  278. // Check for an error.
  279. if (JError::isError($form)) {
  280. $this->setError($form->getMessage());
  281. return false;
  282. }
  283. }
  284. // Now load the component params.
  285. if ($isNew = false) {
  286. $path = JPath::clean(JPATH_ADMINISTRATOR.DS.'components'.DS. $option.DS.'config.xml');
  287. } else {
  288. $path='null';
  289. }
  290. if (JFile::exists($path)) {
  291. if (empty($form)) {
  292. // It's possible the form hasn't been defined yet.
  293. $form = parent::getForm($path, $formName, $formOptions, true);
  294. // Check for an error.
  295. if (JError::isError($form)) {
  296. $this->setError($form->getMessage());
  297. return false;
  298. }
  299. } else {
  300. // Add the component params last of all to the existing form.
  301. $form->load($path, true, false);
  302. }
  303. }
  304. }
  305. // If no component file found, or not a component, create the form.
  306. if (empty($form)) {
  307. $form = parent::getForm('item_'.$type, $formName, $formOptions, true);
  308. // Check for an error.
  309. if (JError::isError($form)) {
  310. $this->setError($form->getMessage());
  311. return false;
  312. }
  313. } else {
  314. $form->load('item_'.$type, true, false);
  315. }
  316. return $form;
  317. }
  318. /**
  319. * Get the list of modules not in trash.
  320. *
  321. * @return mixed An array of module records (id, title, position), or false on error.
  322. */
  323. public function getModules()
  324. {
  325. $db = $this->getDbo();
  326. $query = $db->getQuery(true);
  327. $query->select('a.id, a.title, a.position, a.published');
  328. $query->from('#__modules AS a');
  329. // Join on the module-to-menu mapping table.
  330. // We are only interested if the module is displayed on ALL or THIS menu item (or the inverse ID number).
  331. $query->select('map.menuid');
  332. $query->join('LEFT', '#__modules_menu AS map ON map.moduleid = a.id AND (map.menuid = 0 OR ABS(map.menuid) = '.(int) $this->getState('item.id').')');
  333. // Join on the asset groups table.
  334. $query->select('ag.title AS access_title');
  335. $query->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access');
  336. $query->where('a.published >= 0');
  337. $query->where('a.client_id = 0');
  338. $query->order('a.position, a.ordering');
  339. $db->setQuery($query);
  340. $result = $db->loadObjectList();
  341. if ($error = $db->getError()) {
  342. $this->setError($error);
  343. return false;
  344. }
  345. return $result;
  346. }
  347. /**
  348. * Method to checkin a row.
  349. *
  350. * @param integer $pk The numeric id of a row
  351. * @return boolean False on failure or error, true otherwise.
  352. */
  353. public function checkin($pk = null)
  354. {
  355. // Initialise variables.
  356. $pk = (!empty($pk)) ? $pk : (int) $this->getState('item.id');
  357. // Only attempt to check the row in if it exists.
  358. if ($pk) {
  359. $user = &JFactory::getUser();
  360. // Get an instance of the row to checkin.
  361. $table = &$this->getTable();
  362. if (!$table->load($pk)) {
  363. $this->setError($table->getError());
  364. return false;
  365. }
  366. // Check if this is the user having previously checked out the row.
  367. if ($table->checked_out > 0 && $table->checked_out != $user->get('id')) {
  368. $this->setError(JText::_('JError_Checkin_user_mismatch'));
  369. return false;
  370. }
  371. // Attempt to check the row in.
  372. if (!$table->checkin($pk)) {
  373. $this->setError($table->getError());
  374. return false;
  375. }
  376. }
  377. return true;
  378. }
  379. /**
  380. * Method to check-out a row for editing.
  381. *
  382. * @param int $pk The numeric id of the row to check-out.
  383. *
  384. * @return boolean False on failure or error, true otherwise.
  385. */
  386. public function checkout($pk = null)
  387. {
  388. // Initialise variables.
  389. $pk = (!empty($pk)) ? $pk : (int) $this->getState('item.id');
  390. // Only attempt to check the row in if it exists.
  391. if ($pk) {
  392. // Get a row instance.
  393. $table = &$this->getTable();
  394. // Get the current user object.
  395. $user = &JFactory::getUser();
  396. // Attempt to check the row out.
  397. if (!$table->checkout($user->get('id'), $pk)) {
  398. $this->setError($table->getError());
  399. return false;
  400. }
  401. }
  402. return true;
  403. }
  404. /**
  405. * Method to save the form data.
  406. *
  407. * @param array The form data.
  408. * @return boolean True on success.
  409. * @since 1.6
  410. */
  411. public function save($data)
  412. {
  413. // Initialise variables.
  414. $pk = (!empty($data['id'])) ? $data['id'] : (int)$this->getState('item.id');
  415. $isNew = true;
  416. $db = $this->getDbo();
  417. $table = $this->getTable();
  418. // Load the row if saving an existing item.
  419. if ($pk > 0) {
  420. $table->load($pk);
  421. $isNew = false;
  422. }
  423. // Set the new parent id if set.
  424. if ($table->parent_id != $data['parent_id']) {
  425. $table->setLocation($data['parent_id'], 'last-child');
  426. }
  427. // Bind the data.
  428. if (!$table->bind($data)) {
  429. $this->setError(JText::sprintf('JTable_Error_Bind_failed', $table->getError()));
  430. return false;
  431. }
  432. // Check the data.
  433. if (!$table->check()) {
  434. $this->setError($table->getError());
  435. return false;
  436. }
  437. // Store the data.
  438. if (!$table->store()) {
  439. $this->setError($table->getError());
  440. return false;
  441. }
  442. // Rebuild the tree path.
  443. if (!$table->rebuildPath($table->id)) {
  444. $this->setError($table->getError());
  445. return false;
  446. }
  447. $this->setState('item.id', $table->id);
  448. // So this is where things get a little bit insane ...
  449. // If menuid is 0, there is only one entry in the mapping table.
  450. // If menuid is nothing, there could be other entries in the mapping table, but not be zero.
  451. // If menuid is positive or negative, there could also be other entries in the table.
  452. $map = JArrayHelper::getValue($data, 'map', array(), 'array');
  453. $drops = array();
  454. $adds = array();
  455. foreach ($map as $moduleId => $menuId) {
  456. $moduleId = (int) $moduleId;
  457. // Check that we have a module id.
  458. if (empty($moduleId)) {
  459. continue;
  460. }
  461. // Check if the menuid is set to ALL
  462. if (is_numeric($menuId) && (int) $menuId == 0) {
  463. // Drop all other maps for this module.
  464. $drops[] = '(moduleid = '.$moduleId.')';
  465. // Add the map for this module to show on all pages.
  466. $adds[] = '('.$moduleId.', 0)';
  467. } else {
  468. // Drop all other maps for this module to ALL pages.
  469. $drops[] = '(moduleid = '.$moduleId.' AND menuid = 0)';
  470. if ($menuId == 1 || $menuId == -1) {
  471. // Add the map for this module to show/hide on this page.
  472. $adds[] = '('.$moduleId.', '.(int) $table->id * $menuId.')';
  473. }
  474. }
  475. }
  476. // Preform the drops.
  477. if (!empty($drops)) {
  478. $db->setQuery(
  479. 'DELETE FROM #__modules_menu' .
  480. ' WHERE '.implode(' OR ', $drops)
  481. );
  482. if (!$db->query()) {
  483. $this->setError($db->getErrorMsg());
  484. return false;
  485. }
  486. }
  487. // Perform the inserts.
  488. if (!empty($adds)) {
  489. $db->setQuery(
  490. 'INSERT INTO #__modules_menu (moduleid, menuid)' .
  491. ' VALUES '.implode(',', $adds)
  492. );
  493. if (!$db->query()) {
  494. $this->setError($db->getErrorMsg());
  495. return false;
  496. }
  497. }
  498. return true;
  499. }
  500. /**
  501. * Method to delete rows.
  502. *
  503. * @param array An array of item ids.
  504. *
  505. * @return boolean Returns true on success, false on failure.
  506. */
  507. public function delete($pks)
  508. {
  509. $pks = (array) $pks;
  510. // Get a row instance.
  511. $table = &$this->getTable();
  512. // Iterate the items to delete each one.
  513. foreach ($pks as $pk) {
  514. if (!$table->delete((int) $pk)) {
  515. $this->setError($table->getError());
  516. return false;
  517. }
  518. }
  519. return true;
  520. }
  521. /**
  522. * Method to publish
  523. *
  524. * @param array The ids of the items to publish.
  525. * @param int The value of the published state
  526. *
  527. * @return boolean True on success.
  528. */
  529. function publish($pks, $value = 1)
  530. {
  531. $pks = (array) $pks;
  532. // Get the current user object.
  533. $user = &JFactory::getUser();
  534. // Get an instance of the table row.
  535. $table = &$this->getTable();
  536. // Attempt to publish the items.
  537. if (!$table->publish($pks, $value, $user->get('id'))) {
  538. $this->setError($table->getError());
  539. return false;
  540. }
  541. return true;
  542. }
  543. /**
  544. * Method to adjust the ordering of a row.
  545. *
  546. * @param int The numeric id of the row to move.
  547. * @param integer Increment, usually +1 or -1
  548. * @return boolean False on failure or error, true otherwise.
  549. */
  550. public function ordering($pk, $direction = 0)
  551. {
  552. // Sanitize the id and adjustment.
  553. $pk = (!empty($pk)) ? $pk : (int) $this->getState('item.id');
  554. // If the ordering direction is 0 then we aren't moving anything.
  555. if ($direction == 0) {
  556. return true;
  557. }
  558. // Get a row instance.
  559. $table = &$this->getTable();
  560. // Move the row down in the ordering.
  561. if ($direction > 0) {
  562. if (!$table->orderDown($pk)) {
  563. $this->setError($table->getError());
  564. return false;
  565. }
  566. } else {
  567. // Move the row up in the ordering.
  568. if (!$table->orderUp($pk)) {
  569. $this->setError($table->getError());
  570. return false;
  571. }
  572. }
  573. return true;
  574. }
  575. /**
  576. * Method rebuild the entire nested set tree.
  577. *
  578. * @return boolean False on failure or error, true otherwise.
  579. */
  580. public function rebuild()
  581. {
  582. // Initialiase variables.
  583. $db = $this->getDbo();
  584. $table = &$this->getTable();
  585. if (!$table->rebuild()) {
  586. $this->setError($table->getError());
  587. return false;
  588. }
  589. // Convert the parameters not in JSON format.
  590. $db->setQuery(
  591. 'SELECT id, params' .
  592. ' FROM #__menu' .
  593. ' WHERE params NOT LIKE '.$db->quote('{%') .
  594. ' AND params <> '.$db->quote('')
  595. );
  596. $items = $db->loadObjectList();
  597. if ($error = $db->getErrorMsg()) {
  598. $this->setError($error);
  599. return false;
  600. }
  601. foreach ($items as &$item) {
  602. $registry = new JRegistry;
  603. $registry->loadJSON($item->params);
  604. $params = (string)$registry;
  605. $db->setQuery(
  606. 'UPDATE #__menu' .
  607. ' SET params = '.$db->quote($params).
  608. ' WHERE id = '.(int) $item->id
  609. );
  610. if (!$db->query()) {
  611. $this->setError($error);
  612. return false;
  613. }
  614. unset($registry);
  615. }
  616. return true;
  617. }
  618. /**
  619. * Method to perform batch operations on an item or a set of items.
  620. *
  621. * @param array An array of commands to perform.
  622. * @param array An array of category ids.
  623. *
  624. * @return boolean Returns true on success, false on failure.
  625. */
  626. function batch($commands, $pks)
  627. {
  628. // Sanitize user ids.
  629. $pks = array_unique($pks);
  630. JArrayHelper::toInteger($pks);
  631. // Remove any values of zero.
  632. if (array_search(0, $pks, true)) {
  633. unset($pks[array_search(0, $pks, true)]);
  634. }
  635. if (empty($pks)) {
  636. $this->setError(JText::_('JError_No_items_selected'));
  637. return false;
  638. }
  639. $done = false;
  640. if (!empty($commands['assetgroup_id'])) {
  641. if (!$this->_batchAccess($commands['assetgroup_id'], $pks)) {
  642. return false;
  643. }
  644. $done = true;
  645. }
  646. if (!empty($commands['menu_id'])) {
  647. $cmd = JArrayHelper::getValue($commands, 'move_copy', 'c');
  648. if ($cmd == 'c' && !$this->_batchCopy($commands['menu_id'], $pks)) {
  649. return false;
  650. } else if ($cmd == 'm' && !$this->_batchMove($commands['menu_id'], $pks)) {
  651. return false;
  652. }
  653. $done = true;
  654. }
  655. if (!$done) {
  656. $this->setError('Menus_Error_Insufficient_batch_information');
  657. return false;
  658. }
  659. return true;
  660. }
  661. /**
  662. * Batch access level changes for a group of rows.
  663. *
  664. * @param int The new value matching an Asset Group ID.
  665. * @param array An array of row IDs.
  666. *
  667. * @return booelan True if successful, false otherwise and internal error is set.
  668. */
  669. protected function _batchAccess($value, $pks)
  670. {
  671. $table = $this->getTable();
  672. foreach ($pks as $pk) {
  673. $table->reset();
  674. $table->load($pk);
  675. $table->access = (int) $value;
  676. if (!$table->store()) {
  677. $this->setError($table->getError());
  678. return false;
  679. }
  680. }
  681. return true;
  682. }
  683. /**
  684. * Batch move menu items to a new menu or parent.
  685. *
  686. * @param int The new menu or sub-item.
  687. * @param array An array of row IDs.
  688. *
  689. * @return booelan True if successful, false otherwise and internal error is set.
  690. */
  691. protected function _batchMove($value, $pks)
  692. {
  693. // $value comes as {menutype}.{parent_id}
  694. $parts = explode('.', $value);
  695. $menuType = $parts[0];
  696. $parentId = (int) JArrayHelper::getValue($parts, 1, 0);
  697. $table = &$this->getTable();
  698. $db = &$this->getDbo();
  699. // Check that the parent exists.
  700. if ($parentId) {
  701. if (!$table->load($parentId)) {
  702. if ($error = $table->getError()) {
  703. // Fatal error
  704. $this->setError($error);
  705. return false;
  706. } else {
  707. // Non-fatal error
  708. $this->setError(JText::_('Menus_Batch_Move_parent_not_found'));
  709. $parentId = 0;
  710. }
  711. }
  712. }
  713. // We are going to store all the children and just moved the menutype
  714. $children = array();
  715. // Parent exists so we let's proceed
  716. foreach ($pks as $pk) {
  717. // Check that the row actually exists
  718. if (!$table->load($pk)) {
  719. if ($error = $table->getError()) {
  720. // Fatal error
  721. $this->setError($error);
  722. return false;
  723. } else {
  724. // Not fatal error
  725. $this->setError(JText::sprintf('Menus_Batch_Move_row_not_found', $pk));
  726. continue;
  727. }
  728. }
  729. // Set the new location in the tree for the node.
  730. $table->setLocation($parentId, 'last-child');
  731. // Check if we are moving to a different menu
  732. if ($menuType != $table->menutype) {
  733. // Add the child node ids to the children array.
  734. $db->setQuery(
  735. 'SELECT `id`' .
  736. ' FROM `#__menu`' .
  737. ' WHERE `lft` BETWEEN '.(int) $table->lft.' AND '.(int) $table->rgt
  738. );
  739. $children = array_merge($children, (array) $db->loadResultArray());
  740. }
  741. // Store the row.
  742. if (!$table->store()) {
  743. $this->setError($table->getError());
  744. return false;
  745. }
  746. // Rebuild the tree path.
  747. if (!$table->rebuildPath()) {
  748. $this->setError($table->getError());
  749. return false;
  750. }
  751. }
  752. // Process the child rows
  753. if (!empty($children)) {
  754. // Remove any duplicates and sanitize ids.
  755. $children = array_unique($children);
  756. JArrayHelper::toInteger($children);
  757. // Update the menutype field in all nodes where necessary.
  758. $db->setQuery(
  759. 'UPDATE `#__menu`' .
  760. ' SET `menutype` = '.$db->quote($menuType).
  761. ' WHERE `id` IN ('.implode(',', $children).')'
  762. );
  763. $db->query();
  764. // Check for a database error.
  765. if ($db->getErrorNum()) {
  766. $this->setError($db->getErrorMsg());
  767. return false;
  768. }
  769. }
  770. return true;
  771. }
  772. /**
  773. * Batch copy menu items to a new menu or parent.
  774. *
  775. * @param int The new menu or sub-item.
  776. * @param array An array of row IDs.
  777. *
  778. * @return booelan True if successful, false otherwise and internal error is set.
  779. */
  780. protected function _batchCopy($value, $pks)
  781. {
  782. // $value comes as {menutype}.{parent_id}
  783. $parts = explode('.', $value);
  784. $menuType = $parts[0];
  785. $parentId = (int) JArrayHelper::getValue($parts, 1, 0);
  786. $table = $this->getTable();
  787. $db = $this->getDbo();
  788. // Check that the parent exists
  789. if ($parentId) {
  790. if (!$table->load($parentId)) {
  791. if ($error = $table->getError()) {
  792. // Fatal error
  793. $this->setError($error);
  794. return false;
  795. } else {
  796. // Non-fatal error
  797. $this->setError(JText::_('Menus_Batch_Move_parent_not_found'));
  798. $parentId = 0;
  799. }
  800. }
  801. }
  802. // If the parent is 0, set it to the ID of the root item in the tree
  803. if (empty($parentId)) {
  804. if (!$parentId = $table->getRootId()) {
  805. $this->setError($db->getErrorMsg());
  806. return false;
  807. }
  808. }
  809. // We need to log the parent ID
  810. $parents = array();
  811. // Calculate the emergency stop count as a precaution against a runaway loop bug
  812. $db->setQuery(
  813. 'SELECT COUNT(id)' .
  814. ' FROM #__menu'
  815. );
  816. $count = $db->loadResult();
  817. if ($error = $db->getErrorMsg()) {
  818. $this->setError($error);
  819. return false;
  820. }
  821. // Parent exists so we let's proceed
  822. while (!empty($pks) && $count > 0) {
  823. // Pop the first id off the stack
  824. $pk = array_shift($pks);
  825. $table->reset();
  826. // Check that the row actually exists
  827. if (!$table->load($pk)) {
  828. if ($error = $table->getError()) {
  829. // Fatal error
  830. $this->setError($error);
  831. return false;
  832. } else {
  833. // Not fatal error
  834. $this->setError(JText::sprintf('Menus_Batch_Move_row_not_found', $pk));
  835. continue;
  836. }
  837. }
  838. // Copy is a bit tricky, because we also need to copy the children
  839. $db->setQuery(
  840. 'SELECT id' .
  841. ' FROM #__menu' .
  842. ' WHERE lft > '.(int) $table->lft.' AND rgt < '.(int) $table->rgt
  843. );
  844. $childIds = $db->loadResultArray();
  845. // Add child ID's to the array only if they aren't already there.
  846. foreach ($childIds as $childId) {
  847. if (!in_array($childId, $pks)) {
  848. array_push($pks, $childId);
  849. }
  850. }
  851. // Make a copy of the old ID and Parent ID
  852. $oldId = $table->id;
  853. $oldParentId = $table->parent_id;
  854. // Reset the id because we are making a copy.
  855. $table->id = 0;
  856. // If we a copying children, the Old ID will turn up in the parents list
  857. // otherwise it's a new top level item
  858. $table->parent_id = isset($parents[$oldParentId]) ? $parents[$oldParentId] : $parentId;
  859. $table->menutype = $menuType;
  860. // TODO: Deal with ordering?
  861. //$table->ordering = 1;
  862. $table->level = null;
  863. $table->lft = null;
  864. $table->rgt = null;
  865. // Store the row.
  866. if (!$table->store()) {
  867. $this->setError($table->getError());
  868. return false;
  869. }
  870. // Now we log the old 'parent' to the new 'parent'
  871. $parents[$oldId] = $table->id;
  872. $count--;
  873. }
  874. // Rebuild the hierarchy.
  875. if (!$table->rebuild()) {
  876. $this->setError($table->getError());
  877. return false;
  878. }
  879. // Rebuild the tree path.
  880. if (!$table->rebuildPath($table->id)) {
  881. $this->setError($table->getError());
  882. return false;
  883. }
  884. return true;
  885. }
  886. }