PageRenderTime 80ms CodeModel.GetById 37ms RepoModel.GetById 0ms app.codeStats 0ms

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

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