[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Administrator 5 * @subpackage com_menus 6 * 7 * @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org> 8 * @license GNU General Public License version 2 or later; see LICENSE.txt 9 */ 10 11 namespace Joomla\Component\Menus\Administrator\Model; 12 13 use Joomla\CMS\Application\ApplicationHelper; 14 use Joomla\CMS\Component\ComponentHelper; 15 use Joomla\CMS\Factory; 16 use Joomla\CMS\Filesystem\Path; 17 use Joomla\CMS\Form\Form; 18 use Joomla\CMS\Language\Associations; 19 use Joomla\CMS\Language\LanguageHelper; 20 use Joomla\CMS\Language\Text; 21 use Joomla\CMS\MVC\Model\AdminModel; 22 use Joomla\CMS\Plugin\PluginHelper; 23 use Joomla\CMS\Uri\Uri; 24 use Joomla\Component\Menus\Administrator\Helper\MenusHelper; 25 use Joomla\Database\ParameterType; 26 use Joomla\Registry\Registry; 27 use Joomla\String\StringHelper; 28 use Joomla\Utilities\ArrayHelper; 29 30 // phpcs:disable PSR1.Files.SideEffects 31 \defined('_JEXEC') or die; 32 // phpcs:enable PSR1.Files.SideEffects 33 34 /** 35 * Menu Item Model for Menus. 36 * 37 * @since 1.6 38 */ 39 class ItemModel extends AdminModel 40 { 41 /** 42 * The type alias for this content type. 43 * 44 * @var string 45 * @since 3.4 46 */ 47 public $typeAlias = 'com_menus.item'; 48 49 /** 50 * The context used for the associations table 51 * 52 * @var string 53 * @since 3.4.4 54 */ 55 protected $associationsContext = 'com_menus.item'; 56 57 /** 58 * @var string The prefix to use with controller messages. 59 * @since 1.6 60 */ 61 protected $text_prefix = 'COM_MENUS_ITEM'; 62 63 /** 64 * @var string The help screen key for the menu item. 65 * @since 1.6 66 */ 67 protected $helpKey = 'Menu_Item:_New_Item'; 68 69 /** 70 * @var string The help screen base URL for the menu item. 71 * @since 1.6 72 */ 73 protected $helpURL; 74 75 /** 76 * @var boolean True to use local lookup for the help screen. 77 * @since 1.6 78 */ 79 protected $helpLocal = false; 80 81 /** 82 * Batch copy/move command. If set to false, 83 * the batch copy/move command is not supported 84 * 85 * @var string 86 */ 87 protected $batch_copymove = 'menu_id'; 88 89 /** 90 * Allowed batch commands 91 * 92 * @var array 93 */ 94 protected $batch_commands = array( 95 'assetgroup_id' => 'batchAccess', 96 'language_id' => 'batchLanguage' 97 ); 98 99 /** 100 * Method to test whether a record can be deleted. 101 * 102 * @param object $record A record object. 103 * 104 * @return boolean True if allowed to delete the record. Defaults to the permission set in the component. 105 * 106 * @since 1.6 107 */ 108 protected function canDelete($record) 109 { 110 if (empty($record->id) || $record->published != -2) { 111 return false; 112 } 113 114 $menuTypeId = 0; 115 116 if (!empty($record->menutype)) { 117 $menuTypeId = $this->getMenuTypeId($record->menutype); 118 } 119 120 return Factory::getUser()->authorise('core.delete', 'com_menus.menu.' . (int) $menuTypeId); 121 } 122 123 /** 124 * Method to test whether the state of a record can be edited. 125 * 126 * @param object $record A record object. 127 * 128 * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component. 129 * 130 * @since 3.6 131 */ 132 protected function canEditState($record) 133 { 134 $menuTypeId = !empty($record->menutype) ? $this->getMenuTypeId($record->menutype) : 0; 135 $assetKey = $menuTypeId ? 'com_menus.menu.' . (int) $menuTypeId : 'com_menus'; 136 137 return Factory::getUser()->authorise('core.edit.state', $assetKey); 138 } 139 140 /** 141 * Batch copy menu items to a new menu or parent. 142 * 143 * @param integer $value The new menu or sub-item. 144 * @param array $pks An array of row IDs. 145 * @param array $contexts An array of item contexts. 146 * 147 * @return mixed An array of new IDs on success, boolean false on failure. 148 * 149 * @since 1.6 150 */ 151 protected function batchCopy($value, $pks, $contexts) 152 { 153 // $value comes as {menutype}.{parent_id} 154 $parts = explode('.', $value); 155 $menuType = $parts[0]; 156 $parentId = ArrayHelper::getValue($parts, 1, 0, 'int'); 157 158 $table = $this->getTable(); 159 $db = $this->getDatabase(); 160 $query = $db->getQuery(true); 161 $newIds = array(); 162 163 // Check that the parent exists 164 if ($parentId) { 165 if (!$table->load($parentId)) { 166 if ($error = $table->getError()) { 167 // Fatal error 168 $this->setError($error); 169 170 return false; 171 } else { 172 // Non-fatal error 173 $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND')); 174 $parentId = 0; 175 } 176 } 177 } 178 179 // If the parent is 0, set it to the ID of the root item in the tree 180 if (empty($parentId)) { 181 if (!$parentId = $table->getRootId()) { 182 $this->setError($table->getError()); 183 184 return false; 185 } 186 } 187 188 // Check that user has create permission for menus 189 $user = Factory::getUser(); 190 191 $menuTypeId = (int) $this->getMenuTypeId($menuType); 192 193 if (!$user->authorise('core.create', 'com_menus.menu.' . $menuTypeId)) { 194 $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE')); 195 196 return false; 197 } 198 199 // We need to log the parent ID 200 $parents = array(); 201 202 // Calculate the emergency stop count as a precaution against a runaway loop bug 203 $query->select('COUNT(' . $db->quoteName('id') . ')') 204 ->from($db->quoteName('#__menu')); 205 $db->setQuery($query); 206 207 try { 208 $count = $db->loadResult(); 209 } catch (\RuntimeException $e) { 210 $this->setError($e->getMessage()); 211 212 return false; 213 } 214 215 // Parent exists so let's proceed 216 while (!empty($pks) && $count > 0) { 217 // Pop the first id off the stack 218 $pk = array_shift($pks); 219 220 $table->reset(); 221 222 // Check that the row actually exists 223 if (!$table->load($pk)) { 224 if ($error = $table->getError()) { 225 // Fatal error 226 $this->setError($error); 227 228 return false; 229 } else { 230 // Not fatal error 231 $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk)); 232 continue; 233 } 234 } 235 236 // Copy is a bit tricky, because we also need to copy the children 237 $query = $db->getQuery(true) 238 ->select($db->quoteName('id')) 239 ->from($db->quoteName('#__menu')) 240 ->where( 241 [ 242 $db->quoteName('lft') . ' > :lft', 243 $db->quoteName('rgt') . ' < :rgt', 244 ] 245 ) 246 ->bind(':lft', $table->lft, ParameterType::INTEGER) 247 ->bind(':rgt', $table->rgt, ParameterType::INTEGER); 248 $db->setQuery($query); 249 $childIds = $db->loadColumn(); 250 251 // Add child IDs to the array only if they aren't already there. 252 foreach ($childIds as $childId) { 253 if (!in_array($childId, $pks)) { 254 $pks[] = $childId; 255 } 256 } 257 258 // Make a copy of the old ID and Parent ID 259 $oldId = $table->id; 260 $oldParentId = $table->parent_id; 261 262 // Reset the id because we are making a copy. 263 $table->id = 0; 264 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 270 // Set the new location in the tree for the node. 271 $table->setLocation($table->parent_id, 'last-child'); 272 273 // @todo: Deal with ordering? 274 // $table->ordering = 1; 275 $table->level = null; 276 $table->lft = null; 277 $table->rgt = null; 278 $table->home = 0; 279 280 // Alter the title & alias 281 list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title); 282 $table->title = $title; 283 $table->alias = $alias; 284 285 // Check the row. 286 if (!$table->check()) { 287 $this->setError($table->getError()); 288 289 return false; 290 } 291 292 // Store the row. 293 if (!$table->store()) { 294 $this->setError($table->getError()); 295 296 return false; 297 } 298 299 // Get the new item ID 300 $newId = $table->get('id'); 301 302 // Add the new ID to the array 303 $newIds[$pk] = $newId; 304 305 // Now we log the old 'parent' to the new 'parent' 306 $parents[$oldId] = $table->id; 307 $count--; 308 } 309 310 // Rebuild the hierarchy. 311 if (!$table->rebuild()) { 312 $this->setError($table->getError()); 313 314 return false; 315 } 316 317 // Rebuild the tree path. 318 if (!$table->rebuildPath($table->id)) { 319 $this->setError($table->getError()); 320 321 return false; 322 } 323 324 // Clean the cache 325 $this->cleanCache(); 326 327 return $newIds; 328 } 329 330 /** 331 * Batch move menu items to a new menu or parent. 332 * 333 * @param integer $value The new menu or sub-item. 334 * @param array $pks An array of row IDs. 335 * @param array $contexts An array of item contexts. 336 * 337 * @return boolean True on success. 338 * 339 * @since 1.6 340 */ 341 protected function batchMove($value, $pks, $contexts) 342 { 343 // $value comes as {menutype}.{parent_id} 344 $parts = explode('.', $value); 345 $menuType = $parts[0]; 346 $parentId = ArrayHelper::getValue($parts, 1, 0, 'int'); 347 348 $table = $this->getTable(); 349 $db = $this->getDatabase(); 350 351 // Check that the parent exists. 352 if ($parentId) { 353 if (!$table->load($parentId)) { 354 if ($error = $table->getError()) { 355 // Fatal error 356 $this->setError($error); 357 358 return false; 359 } else { 360 // Non-fatal error 361 $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND')); 362 $parentId = 0; 363 } 364 } 365 } 366 367 // Check that user has create and edit permission for menus 368 $user = Factory::getUser(); 369 370 $menuTypeId = (int) $this->getMenuTypeId($menuType); 371 372 if (!$user->authorise('core.create', 'com_menus.menu.' . $menuTypeId)) { 373 $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE')); 374 375 return false; 376 } 377 378 if (!$user->authorise('core.edit', 'com_menus.menu.' . $menuTypeId)) { 379 $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_EDIT')); 380 381 return false; 382 } 383 384 // We are going to store all the children and just moved the menutype 385 $children = array(); 386 387 // Parent exists so let's proceed 388 foreach ($pks as $pk) { 389 // Check that the row actually exists 390 if (!$table->load($pk)) { 391 if ($error = $table->getError()) { 392 // Fatal error 393 $this->setError($error); 394 395 return false; 396 } else { 397 // Not fatal error 398 $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk)); 399 continue; 400 } 401 } 402 403 // Set the new location in the tree for the node. 404 $table->setLocation($parentId, 'last-child'); 405 406 // Set the new Parent Id 407 $table->parent_id = $parentId; 408 409 // Check if we are moving to a different menu 410 if ($menuType != $table->menutype) { 411 // Add the child node ids to the children array. 412 $query = $db->getQuery(true) 413 ->select($db->quoteName('id')) 414 ->from($db->quoteName('#__menu')) 415 ->where($db->quoteName('lft') . ' BETWEEN :lft AND :rgt') 416 ->bind(':lft', $table->lft, ParameterType::INTEGER) 417 ->bind(':rgt', $table->rgt, ParameterType::INTEGER); 418 $db->setQuery($query); 419 $children = array_merge($children, (array) $db->loadColumn()); 420 } 421 422 // Check the row. 423 if (!$table->check()) { 424 $this->setError($table->getError()); 425 426 return false; 427 } 428 429 // Store the row. 430 if (!$table->store()) { 431 $this->setError($table->getError()); 432 433 return false; 434 } 435 436 // Rebuild the tree path. 437 if (!$table->rebuildPath()) { 438 $this->setError($table->getError()); 439 440 return false; 441 } 442 } 443 444 // Process the child rows 445 if (!empty($children)) { 446 // Remove any duplicates and sanitize ids. 447 $children = array_unique($children); 448 $children = ArrayHelper::toInteger($children); 449 450 // Update the menutype field in all nodes where necessary. 451 $query = $db->getQuery(true) 452 ->update($db->quoteName('#__menu')) 453 ->set($db->quoteName('menutype') . ' = :menuType') 454 ->whereIn($db->quoteName('id'), $children) 455 ->bind(':menuType', $menuType); 456 457 try { 458 $db->setQuery($query); 459 $db->execute(); 460 } catch (\RuntimeException $e) { 461 $this->setError($e->getMessage()); 462 463 return false; 464 } 465 } 466 467 // Clean the cache 468 $this->cleanCache(); 469 470 return true; 471 } 472 473 /** 474 * Method to check if you can save a record. 475 * 476 * @param array $data An array of input data. 477 * @param string $key The name of the key for the primary key. 478 * 479 * @return boolean 480 * 481 * @since 1.6 482 */ 483 protected function canSave($data = array(), $key = 'id') 484 { 485 return Factory::getUser()->authorise('core.edit', $this->option); 486 } 487 488 /** 489 * Method to get the row form. 490 * 491 * @param array $data Data for the form. 492 * @param boolean $loadData True if the form is to load its own data (default case), false if not. 493 * 494 * @return mixed A Form object on success, false on failure 495 * 496 * @since 1.6 497 */ 498 public function getForm($data = array(), $loadData = true) 499 { 500 // The folder and element vars are passed when saving the form. 501 if (empty($data)) { 502 $item = $this->getItem(); 503 504 // The type should already be set. 505 $this->setState('item.link', $item->link); 506 } else { 507 $this->setState('item.link', ArrayHelper::getValue($data, 'link')); 508 $this->setState('item.type', ArrayHelper::getValue($data, 'type')); 509 } 510 511 $clientId = $this->getState('item.client_id'); 512 513 // Get the form. 514 if ($clientId == 1) { 515 $form = $this->loadForm('com_menus.item.admin', 'itemadmin', array('control' => 'jform', 'load_data' => $loadData), true); 516 } else { 517 $form = $this->loadForm('com_menus.item', 'item', array('control' => 'jform', 'load_data' => $loadData), true); 518 } 519 520 if (empty($form)) { 521 return false; 522 } 523 524 if ($loadData) { 525 $data = $this->loadFormData(); 526 } 527 528 // Modify the form based on access controls. 529 if (!$this->canEditState((object) $data)) { 530 // Disable fields for display. 531 $form->setFieldAttribute('menuordering', 'disabled', 'true'); 532 $form->setFieldAttribute('published', 'disabled', 'true'); 533 534 // Disable fields while saving. 535 // The controller has already verified this is an article you can edit. 536 $form->setFieldAttribute('menuordering', 'filter', 'unset'); 537 $form->setFieldAttribute('published', 'filter', 'unset'); 538 } 539 540 // Filter available menus 541 $action = $this->getState('item.id') > 0 ? 'edit' : 'create'; 542 543 $form->setFieldAttribute('menutype', 'accesstype', $action); 544 $form->setFieldAttribute('type', 'clientid', $clientId); 545 546 return $form; 547 } 548 549 /** 550 * Method to get the data that should be injected in the form. 551 * 552 * @return mixed The data for the form. 553 * 554 * @since 1.6 555 */ 556 protected function loadFormData() 557 { 558 // Check the session for previously entered form data, providing it has an ID and it is the same. 559 $itemData = (array) $this->getItem(); 560 561 // When a new item is requested, unset the access as it will be set later from the filter 562 if (empty($itemData['id'])) { 563 unset($itemData['access']); 564 } 565 566 $sessionData = (array) Factory::getApplication()->getUserState('com_menus.edit.item.data', array()); 567 568 // Only merge if there is a session and itemId or itemid is null. 569 if ( 570 isset($sessionData['id']) && isset($itemData['id']) && $sessionData['id'] === $itemData['id'] 571 || is_null($itemData['id']) 572 ) { 573 $data = array_merge($itemData, $sessionData); 574 } else { 575 $data = $itemData; 576 } 577 578 // For a new menu item, pre-select some filters (Status, Language, Access) in edit form if those have been selected in Menu Manager 579 if (empty($data['id'])) { 580 // Get selected fields 581 $filters = Factory::getApplication()->getUserState('com_menus.items.filter'); 582 $data['parent_id'] = $data['parent_id'] ?? ($filters['parent_id'] ?? null); 583 $data['published'] = $data['published'] ?? ($filters['published'] ?? null); 584 $data['language'] = $data['language'] ?? ($filters['language'] ?? null); 585 $data['access'] = $data['access'] ?? ($filters['access'] ?? Factory::getApplication()->get('access')); 586 } 587 588 if (isset($data['menutype']) && !$this->getState('item.menutypeid')) { 589 $menuTypeId = (int) $this->getMenuTypeId($data['menutype']); 590 591 $this->setState('item.menutypeid', $menuTypeId); 592 } 593 594 $data = (object) $data; 595 596 $this->preprocessData('com_menus.item', $data); 597 598 return $data; 599 } 600 601 /** 602 * Get the necessary data to load an item help screen. 603 * 604 * @return object An object with key, url, and local properties for loading the item help screen. 605 * 606 * @since 1.6 607 */ 608 public function getHelp() 609 { 610 return (object) array('key' => $this->helpKey, 'url' => $this->helpURL, 'local' => $this->helpLocal); 611 } 612 613 /** 614 * Method to get a menu item. 615 * 616 * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used. 617 * 618 * @return mixed Menu item data object on success, false on failure. 619 * 620 * @since 1.6 621 */ 622 public function getItem($pk = null) 623 { 624 $pk = (!empty($pk)) ? $pk : (int) $this->getState('item.id'); 625 626 // Get a level row instance. 627 $table = $this->getTable(); 628 629 // Attempt to load the row. 630 $table->load($pk); 631 632 // Check for a table object error. 633 if ($error = $table->getError()) { 634 $this->setError($error); 635 636 return false; 637 } 638 639 // Prime required properties. 640 641 if ($type = $this->getState('item.type')) { 642 $table->type = $type; 643 } 644 645 if (empty($table->id)) { 646 $table->parent_id = $this->getState('item.parent_id'); 647 $table->menutype = $this->getState('item.menutype'); 648 $table->client_id = $this->getState('item.client_id'); 649 $table->params = '{}'; 650 } 651 652 // If the link has been set in the state, possibly changing link type. 653 if ($link = $this->getState('item.link')) { 654 // Check if we are changing away from the actual link type. 655 if (MenusHelper::getLinkKey($table->link) !== MenusHelper::getLinkKey($link) && (int) $table->id === (int) $this->getState('item.id')) { 656 $table->link = $link; 657 } 658 } 659 660 switch ($table->type) { 661 case 'alias': 662 case 'url': 663 $table->component_id = 0; 664 $args = array(); 665 666 if ($table->link) { 667 $q = parse_url($table->link, PHP_URL_QUERY); 668 669 if ($q) { 670 parse_str($q, $args); 671 } 672 } 673 674 break; 675 676 case 'separator': 677 case 'heading': 678 case 'container': 679 $table->link = ''; 680 $table->component_id = 0; 681 break; 682 683 case 'component': 684 default: 685 // Enforce a valid type. 686 $table->type = 'component'; 687 688 // Ensure the integrity of the component_id field is maintained, particularly when changing the menu item type. 689 $args = []; 690 691 if ($table->link) { 692 $q = parse_url($table->link, PHP_URL_QUERY); 693 694 if ($q) { 695 parse_str($q, $args); 696 } 697 } 698 699 if (isset($args['option'])) { 700 // Load the language file for the component. 701 $lang = Factory::getLanguage(); 702 $lang->load($args['option'], JPATH_ADMINISTRATOR) 703 || $lang->load($args['option'], JPATH_ADMINISTRATOR . '/components/' . $args['option']); 704 705 // Determine the component id. 706 $component = ComponentHelper::getComponent($args['option']); 707 708 if (isset($component->id)) { 709 $table->component_id = $component->id; 710 } 711 } 712 break; 713 } 714 715 // We have a valid type, inject it into the state for forms to use. 716 $this->setState('item.type', $table->type); 717 718 // Convert to the \Joomla\CMS\Object\CMSObject before adding the params. 719 $properties = $table->getProperties(1); 720 $result = ArrayHelper::toObject($properties); 721 722 // Convert the params field to an array. 723 $registry = new Registry($table->params); 724 $result->params = $registry->toArray(); 725 726 // Merge the request arguments in to the params for a component. 727 if ($table->type == 'component') { 728 // Note that all request arguments become reserved parameter names. 729 $result->request = $args; 730 $result->params = array_merge($result->params, $args); 731 732 // Special case for the Login menu item. 733 // Display the login or logout redirect URL fields if not empty 734 if ($table->link == 'index.php?option=com_users&view=login') { 735 if (!empty($result->params['login_redirect_url'])) { 736 $result->params['loginredirectchoice'] = '0'; 737 } 738 739 if (!empty($result->params['logout_redirect_url'])) { 740 $result->params['logoutredirectchoice'] = '0'; 741 } 742 } 743 } 744 745 if ($table->type == 'alias') { 746 // Note that all request arguments become reserved parameter names. 747 $result->params = array_merge($result->params, $args); 748 } 749 750 if ($table->type == 'url') { 751 // Note that all request arguments become reserved parameter names. 752 $result->params = array_merge($result->params, $args); 753 } 754 755 // Load associated menu items, only supported for frontend for now 756 if ($this->getState('item.client_id') == 0 && Associations::isEnabled()) { 757 if ($pk != null) { 758 $result->associations = MenusHelper::getAssociations($pk); 759 } else { 760 $result->associations = array(); 761 } 762 } 763 764 $result->menuordering = $pk; 765 766 return $result; 767 } 768 769 /** 770 * Get the list of modules not in trash. 771 * 772 * @return mixed An array of module records (id, title, position), or false on error. 773 * 774 * @since 1.6 775 */ 776 public function getModules() 777 { 778 $clientId = (int) $this->getState('item.client_id'); 779 $id = (int) $this->getState('item.id'); 780 781 // Currently any setting that affects target page for a backend menu is not supported, hence load no modules. 782 if ($clientId == 1) { 783 return false; 784 } 785 786 $db = $this->getDatabase(); 787 $query = $db->getQuery(true); 788 789 /** 790 * Join on the module-to-menu mapping table. 791 * We are only interested if the module is displayed on ALL or THIS menu item (or the inverse ID number). 792 * sqlsrv changes for modulelink to menu manager 793 */ 794 $query->select( 795 [ 796 $db->quoteName('a.id'), 797 $db->quoteName('a.title'), 798 $db->quoteName('a.position'), 799 $db->quoteName('a.published'), 800 $db->quoteName('map.menuid'), 801 ] 802 ) 803 ->from($db->quoteName('#__modules', 'a')) 804 ->join( 805 'LEFT', 806 $db->quoteName('#__modules_menu', 'map'), 807 $db->quoteName('map.moduleid') . ' = ' . $db->quoteName('a.id') 808 . ' AND ' . $db->quoteName('map.menuid') . ' IN (' . implode(',', $query->bindArray([0, $id, -$id])) . ')' 809 ); 810 811 $subQuery = $db->getQuery(true) 812 ->select('COUNT(*)') 813 ->from($db->quoteName('#__modules_menu')) 814 ->where( 815 [ 816 $db->quoteName('moduleid') . ' = ' . $db->quoteName('a.id'), 817 $db->quoteName('menuid') . ' < 0', 818 ] 819 ); 820 821 $query->select('(' . $subQuery . ') AS ' . $db->quoteName('except')); 822 823 // Join on the asset groups table. 824 $query->select($db->quoteName('ag.title', 'access_title')) 825 ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access')) 826 ->where( 827 [ 828 $db->quoteName('a.published') . ' >= 0', 829 $db->quoteName('a.client_id') . ' = :clientId', 830 ] 831 ) 832 ->bind(':clientId', $clientId, ParameterType::INTEGER) 833 ->order( 834 [ 835 $db->quoteName('a.position'), 836 $db->quoteName('a.ordering'), 837 ] 838 ); 839 840 $db->setQuery($query); 841 842 try { 843 $result = $db->loadObjectList(); 844 } catch (\RuntimeException $e) { 845 $this->setError($e->getMessage()); 846 847 return false; 848 } 849 850 return $result; 851 } 852 853 /** 854 * Get the list of all view levels 855 * 856 * @return \stdClass[]|boolean An array of all view levels (id, title). 857 * 858 * @since 3.4 859 */ 860 public function getViewLevels() 861 { 862 $db = $this->getDatabase(); 863 $query = $db->getQuery(true); 864 865 // Get all the available view levels 866 $query->select($db->quoteName('id')) 867 ->select($db->quoteName('title')) 868 ->from($db->quoteName('#__viewlevels')) 869 ->order($db->quoteName('id')); 870 871 $db->setQuery($query); 872 873 try { 874 $result = $db->loadObjectList(); 875 } catch (\RuntimeException $e) { 876 $this->setError($e->getMessage()); 877 878 return false; 879 } 880 881 return $result; 882 } 883 884 /** 885 * Returns a Table object, always creating it 886 * 887 * @param string $type The table type to instantiate. 888 * @param string $prefix A prefix for the table class name. Optional. 889 * @param array $config Configuration array for model. Optional. 890 * 891 * @return \Joomla\Cms\Table\Table|\Joomla\Cms\Table\Nested A database object. 892 * 893 * @since 1.6 894 */ 895 public function getTable($type = 'Menu', $prefix = 'Administrator', $config = array()) 896 { 897 return parent::getTable($type, $prefix, $config); 898 } 899 900 /** 901 * A protected method to get the where clause for the reorder. 902 * This ensures that the row will be moved relative to a row with the same menutype. 903 * 904 * @param \Joomla\CMS\Table\Menu $table 905 * 906 * @return array An array of conditions to add to add to ordering queries. 907 * 908 * @since 1.6 909 */ 910 protected function getReorderConditions($table) 911 { 912 $db = $this->getDatabase(); 913 914 return [ 915 $db->quoteName('menutype') . ' = ' . $db->quote($table->menutype), 916 ]; 917 } 918 919 /** 920 * Auto-populate the model state. 921 * 922 * Note. Calling getState in this method will result in recursion. 923 * 924 * @return void 925 * 926 * @since 1.6 927 */ 928 protected function populateState() 929 { 930 $app = Factory::getApplication(); 931 932 // Load the User state. 933 $pk = $app->input->getInt('id'); 934 $this->setState('item.id', $pk); 935 936 if (!$app->isClient('api')) { 937 $parentId = $app->getUserState('com_menus.edit.item.parent_id'); 938 $menuType = $app->getUserStateFromRequest('com_menus.items.menutype', 'menutype', '', 'string'); 939 } else { 940 $parentId = null; 941 $menuType = $app->input->get('com_menus.items.menutype'); 942 } 943 944 if (!$parentId) { 945 $parentId = $app->input->getInt('parent_id'); 946 } 947 948 $this->setState('item.parent_id', $parentId); 949 950 // If we have a menutype we take client_id from there, unless forced otherwise 951 if ($menuType) { 952 $menuTypeObj = $this->getMenuType($menuType); 953 954 // An invalid menutype will be handled as clientId = 0 and menuType = '' 955 $menuType = (string) $menuTypeObj->menutype; 956 $menuTypeId = (int) $menuTypeObj->client_id; 957 $clientId = (int) $menuTypeObj->client_id; 958 } else { 959 $menuTypeId = 0; 960 $clientId = $app->isClient('api') ? $app->input->get('client_id') : 961 $app->getUserState('com_menus.items.client_id', 0); 962 } 963 964 // Forced client id will override/clear menuType if conflicted 965 $forcedClientId = $app->input->get('client_id', null, 'string'); 966 967 if (!$app->isClient('api')) { 968 // Set the menu type and client id on the list view state, so we return to this menu after saving. 969 $app->setUserState('com_menus.items.menutype', $menuType); 970 $app->setUserState('com_menus.items.client_id', $clientId); 971 } 972 973 // Current item if not new, we don't allow changing client id at all 974 if ($pk) { 975 $table = $this->getTable(); 976 $table->load($pk); 977 $forcedClientId = $table->get('client_id', $forcedClientId); 978 } 979 980 if (isset($forcedClientId) && $forcedClientId != $clientId) { 981 $clientId = $forcedClientId; 982 $menuType = ''; 983 $menuTypeId = 0; 984 } 985 986 $this->setState('item.menutype', $menuType); 987 $this->setState('item.client_id', $clientId); 988 $this->setState('item.menutypeid', $menuTypeId); 989 990 if (!($type = $app->getUserState('com_menus.edit.item.type'))) { 991 $type = $app->input->get('type'); 992 993 /** 994 * Note: a new menu item will have no field type. 995 * The field is required so the user has to change it. 996 */ 997 } 998 999 $this->setState('item.type', $type); 1000 1001 $link = $app->isClient('api') ? $app->input->get('link') : 1002 $app->getUserState('com_menus.edit.item.link'); 1003 1004 if ($link) { 1005 $this->setState('item.link', $link); 1006 } 1007 1008 // Load the parameters. 1009 $params = ComponentHelper::getParams('com_menus'); 1010 $this->setState('params', $params); 1011 } 1012 1013 /** 1014 * Loads the menutype object by a given menutype string 1015 * 1016 * @param string $menutype The given menutype 1017 * 1018 * @return \stdClass 1019 * 1020 * @since 3.7.0 1021 */ 1022 protected function getMenuType($menutype) 1023 { 1024 $table = $this->getTable('MenuType'); 1025 1026 $table->load(array('menutype' => $menutype)); 1027 1028 return (object) $table->getProperties(); 1029 } 1030 1031 /** 1032 * Loads the menutype ID by a given menutype string 1033 * 1034 * @param string $menutype The given menutype 1035 * 1036 * @return integer 1037 * 1038 * @since 3.6 1039 */ 1040 protected function getMenuTypeId($menutype) 1041 { 1042 $menu = $this->getMenuType($menutype); 1043 1044 return (int) $menu->id; 1045 } 1046 1047 /** 1048 * Method to preprocess the form. 1049 * 1050 * @param Form $form A Form object. 1051 * @param mixed $data The data expected for the form. 1052 * @param string $group The name of the plugin group to import. 1053 * 1054 * @return void 1055 * 1056 * @since 1.6 1057 * @throws \Exception if there is an error in the form event. 1058 */ 1059 protected function preprocessForm(Form $form, $data, $group = 'content') 1060 { 1061 $link = $this->getState('item.link'); 1062 $type = $this->getState('item.type'); 1063 $clientId = $this->getState('item.client_id'); 1064 $formFile = false; 1065 1066 // Load the specific type file 1067 $typeFile = $clientId == 1 ? 'itemadmin_' . $type : 'item_' . $type; 1068 $clientInfo = ApplicationHelper::getClientInfo($clientId); 1069 1070 // Initialise form with component view params if available. 1071 if ($type == 'component') { 1072 $link = $link ? htmlspecialchars_decode($link) : ''; 1073 1074 // Parse the link arguments. 1075 $args = []; 1076 1077 if ($link) { 1078 parse_str(parse_url(htmlspecialchars_decode($link), PHP_URL_QUERY), $args); 1079 } 1080 1081 // Confirm that the option is defined. 1082 $option = ''; 1083 $base = ''; 1084 1085 if (isset($args['option'])) { 1086 // The option determines the base path to work with. 1087 $option = $args['option']; 1088 $base = $clientInfo->path . '/components/' . $option; 1089 } 1090 1091 if (isset($args['view'])) { 1092 $view = $args['view']; 1093 1094 // Determine the layout to search for. 1095 if (isset($args['layout'])) { 1096 $layout = $args['layout']; 1097 } else { 1098 $layout = 'default'; 1099 } 1100 1101 // Check for the layout XML file. Use standard xml file if it exists. 1102 $tplFolders = array( 1103 $base . '/tmpl/' . $view, 1104 $base . '/views/' . $view . '/tmpl', 1105 $base . '/view/' . $view . '/tmpl', 1106 ); 1107 $path = Path::find($tplFolders, $layout . '.xml'); 1108 1109 if (is_file($path)) { 1110 $formFile = $path; 1111 } 1112 1113 // If custom layout, get the xml file from the template folder 1114 // template folder is first part of file name -- template:folder 1115 if (!$formFile && (strpos($layout, ':') > 0)) { 1116 list($altTmpl, $altLayout) = explode(':', $layout); 1117 1118 $templatePath = Path::clean($clientInfo->path . '/templates/' . $altTmpl . '/html/' . $option . '/' . $view . '/' . $altLayout . '.xml'); 1119 1120 if (is_file($templatePath)) { 1121 $formFile = $templatePath; 1122 } 1123 } 1124 } 1125 1126 // Now check for a view manifest file 1127 if (!$formFile) { 1128 if (isset($view)) { 1129 $metadataFolders = array( 1130 $base . '/view/' . $view, 1131 $base . '/views/' . $view 1132 ); 1133 $metaPath = Path::find($metadataFolders, 'metadata.xml'); 1134 1135 if (is_file($path = Path::clean($metaPath))) { 1136 $formFile = $path; 1137 } 1138 } elseif ($base) { 1139 // Now check for a component manifest file 1140 $path = Path::clean($base . '/metadata.xml'); 1141 1142 if (is_file($path)) { 1143 $formFile = $path; 1144 } 1145 } 1146 } 1147 } 1148 1149 if ($formFile) { 1150 // If an XML file was found in the component, load it first. 1151 // We need to qualify the full path to avoid collisions with component file names. 1152 1153 if ($form->loadFile($formFile, true, '/metadata') == false) { 1154 throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); 1155 } 1156 1157 // Attempt to load the xml file. 1158 if (!$xml = simplexml_load_file($formFile)) { 1159 throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); 1160 } 1161 1162 // Get the help data from the XML file if present. 1163 $help = $xml->xpath('/metadata/layout/help'); 1164 } else { 1165 // We don't have a component. Load the form XML to get the help path 1166 $xmlFile = Path::find(JPATH_ADMINISTRATOR . '/components/com_menus/forms', $typeFile . '.xml'); 1167 1168 if ($xmlFile) { 1169 if (!$xml = simplexml_load_file($xmlFile)) { 1170 throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); 1171 } 1172 1173 // Get the help data from the XML file if present. 1174 $help = $xml->xpath('/form/help'); 1175 } 1176 } 1177 1178 if (!empty($help)) { 1179 $helpKey = trim((string) $help[0]['key']); 1180 $helpURL = trim((string) $help[0]['url']); 1181 $helpLoc = trim((string) $help[0]['local']); 1182 1183 $this->helpKey = $helpKey ?: $this->helpKey; 1184 $this->helpURL = $helpURL ?: $this->helpURL; 1185 $this->helpLocal = (($helpLoc == 'true') || ($helpLoc == '1') || ($helpLoc == 'local')); 1186 } 1187 1188 if (!$form->loadFile($typeFile, true, false)) { 1189 throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); 1190 } 1191 1192 // Association menu items, we currently do not support this for admin menu… may be later 1193 if ($clientId == 0 && Associations::isEnabled()) { 1194 $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc'); 1195 1196 if (count($languages) > 1) { 1197 $addform = new \SimpleXMLElement('<form />'); 1198 $fields = $addform->addChild('fields'); 1199 $fields->addAttribute('name', 'associations'); 1200 $fieldset = $fields->addChild('fieldset'); 1201 $fieldset->addAttribute('name', 'item_associations'); 1202 $fieldset->addAttribute('addfieldprefix', 'Joomla\Component\Menus\Administrator\Field'); 1203 1204 foreach ($languages as $language) { 1205 $field = $fieldset->addChild('field'); 1206 $field->addAttribute('name', $language->lang_code); 1207 $field->addAttribute('type', 'modal_menu'); 1208 $field->addAttribute('language', $language->lang_code); 1209 $field->addAttribute('label', $language->title); 1210 $field->addAttribute('translate_label', 'false'); 1211 $field->addAttribute('select', 'true'); 1212 $field->addAttribute('new', 'true'); 1213 $field->addAttribute('edit', 'true'); 1214 $field->addAttribute('clear', 'true'); 1215 $field->addAttribute('propagate', 'true'); 1216 $option = $field->addChild('option', 'COM_MENUS_ITEM_FIELD_ASSOCIATION_NO_VALUE'); 1217 $option->addAttribute('value', ''); 1218 } 1219 1220 $form->load($addform, false); 1221 } 1222 } 1223 1224 // Trigger the default form events. 1225 parent::preprocessForm($form, $data, $group); 1226 } 1227 1228 /** 1229 * Method rebuild the entire nested set tree. 1230 * 1231 * @return boolean Boolean true on success, boolean false 1232 * 1233 * @since 1.6 1234 */ 1235 public function rebuild() 1236 { 1237 // Initialise variables. 1238 $db = $this->getDatabase(); 1239 $query = $db->getQuery(true); 1240 $table = $this->getTable(); 1241 1242 try { 1243 $rebuildResult = $table->rebuild(); 1244 } catch (\Exception $e) { 1245 $this->setError($e->getMessage()); 1246 1247 return false; 1248 } 1249 1250 if (!$rebuildResult) { 1251 $this->setError($table->getError()); 1252 1253 return false; 1254 } 1255 1256 $query->select( 1257 [ 1258 $db->quoteName('id'), 1259 $db->quoteName('params'), 1260 ] 1261 ) 1262 ->from($db->quoteName('#__menu')) 1263 ->where( 1264 [ 1265 $db->quoteName('params') . ' NOT LIKE ' . $db->quote('{%'), 1266 $db->quoteName('params') . ' <> ' . $db->quote(''), 1267 ] 1268 ); 1269 $db->setQuery($query); 1270 1271 try { 1272 $items = $db->loadObjectList(); 1273 } catch (\RuntimeException $e) { 1274 Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); 1275 1276 return false; 1277 } 1278 1279 $query = $db->getQuery(true) 1280 ->update($db->quoteName('#__menu')) 1281 ->set($db->quoteName('params') . ' = :params') 1282 ->where($db->quoteName('id') . ' = :id') 1283 ->bind(':params', $params) 1284 ->bind(':id', $id, ParameterType::INTEGER); 1285 $db->setQuery($query); 1286 1287 foreach ($items as &$item) { 1288 // Update query parameters. 1289 $id = $item->id; 1290 $params = new Registry($item->params); 1291 1292 try { 1293 $db->execute(); 1294 } catch (\RuntimeException $e) { 1295 Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); 1296 1297 return false; 1298 } 1299 } 1300 1301 // Clean the cache 1302 $this->cleanCache(); 1303 1304 return true; 1305 } 1306 1307 /** 1308 * Method to save the form data. 1309 * 1310 * @param array $data The form data. 1311 * 1312 * @return boolean True on success. 1313 * 1314 * @since 1.6 1315 */ 1316 public function save($data) 1317 { 1318 $pk = isset($data['id']) ? $data['id'] : (int) $this->getState('item.id'); 1319 $isNew = true; 1320 $db = $this->getDatabase(); 1321 $query = $db->getQuery(true); 1322 $table = $this->getTable(); 1323 $context = $this->option . '.' . $this->name; 1324 1325 // Include the plugins for the on save events. 1326 PluginHelper::importPlugin($this->events_map['save']); 1327 1328 // Load the row if saving an existing item. 1329 if ($pk > 0) { 1330 $table->load($pk); 1331 $isNew = false; 1332 } 1333 1334 if (!$isNew) { 1335 if ($table->parent_id == $data['parent_id']) { 1336 // If first is chosen make the item the first child of the selected parent. 1337 if ($data['menuordering'] == -1) { 1338 $table->setLocation($data['parent_id'], 'first-child'); 1339 } elseif ($data['menuordering'] == -2) { 1340 // If last is chosen make it the last child of the selected parent. 1341 $table->setLocation($data['parent_id'], 'last-child'); 1342 } elseif ($data['menuordering'] && $table->id != $data['menuordering'] || empty($data['id'])) { 1343 // Don't try to put an item after itself. All other ones put after the selected item. 1344 // $data['id'] is empty means it's a save as copy 1345 $table->setLocation($data['menuordering'], 'after'); 1346 } elseif ($data['menuordering'] && $table->id == $data['menuordering']) { 1347 // \Just leave it where it is if no change is made. 1348 unset($data['menuordering']); 1349 } 1350 } else { 1351 // Set the new parent id if parent id not matched and put in last position 1352 $table->setLocation($data['parent_id'], 'last-child'); 1353 } 1354 1355 // Check if we are moving to a different menu 1356 if ($data['menutype'] != $table->menutype) { 1357 // Add the child node ids to the children array. 1358 $query->clear() 1359 ->select($db->quoteName('id')) 1360 ->from($db->quoteName('#__menu')) 1361 ->where($db->quoteName('lft') . ' BETWEEN ' . (int) $table->lft . ' AND ' . (int) $table->rgt); 1362 $db->setQuery($query); 1363 $children = (array) $db->loadColumn(); 1364 } 1365 } else { 1366 // We have a new item, so it is not a change. 1367 $menuType = $this->getMenuType($data['menutype']); 1368 1369 $data['client_id'] = $menuType->client_id; 1370 1371 $table->setLocation($data['parent_id'], 'last-child'); 1372 } 1373 1374 // Bind the data. 1375 if (!$table->bind($data)) { 1376 $this->setError($table->getError()); 1377 1378 return false; 1379 } 1380 1381 // Alter the title & alias for save2copy when required. Also, unset the home record. 1382 if (Factory::getApplication()->input->get('task') === 'save2copy' && $data['id'] === 0) { 1383 $origTable = $this->getTable(); 1384 $origTable->load($this->getState('item.id')); 1385 1386 if ($table->title === $origTable->title) { 1387 list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title); 1388 $table->title = $title; 1389 $table->alias = $alias; 1390 } 1391 1392 if ($table->alias === $origTable->alias) { 1393 $table->alias = ''; 1394 } 1395 1396 $table->published = 0; 1397 $table->home = 0; 1398 } 1399 1400 // Check the data. 1401 if (!$table->check()) { 1402 $this->setError($table->getError()); 1403 1404 return false; 1405 } 1406 1407 // Trigger the before save event. 1408 $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, $isNew, $data)); 1409 1410 // Store the data. 1411 if (in_array(false, $result, true) || !$table->store()) { 1412 $this->setError($table->getError()); 1413 1414 return false; 1415 } 1416 1417 // Trigger the after save event. 1418 Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, $isNew)); 1419 1420 // Rebuild the tree path. 1421 if (!$table->rebuildPath($table->id)) { 1422 $this->setError($table->getError()); 1423 1424 return false; 1425 } 1426 1427 // Process the child rows 1428 if (!empty($children)) { 1429 // Remove any duplicates and sanitize ids. 1430 $children = array_unique($children); 1431 $children = ArrayHelper::toInteger($children); 1432 1433 // Update the menutype field in all nodes where necessary. 1434 $query = $db->getQuery(true) 1435 ->update($db->quoteName('#__menu')) 1436 ->set($db->quoteName('menutype') . ' = :menutype') 1437 ->whereIn($db->quoteName('id'), $children) 1438 ->bind(':menutype', $data['menutype']); 1439 1440 try { 1441 $db->setQuery($query); 1442 $db->execute(); 1443 } catch (\RuntimeException $e) { 1444 $this->setError($e->getMessage()); 1445 1446 return false; 1447 } 1448 } 1449 1450 $this->setState('item.id', $table->id); 1451 $this->setState('item.menutype', $table->menutype); 1452 1453 // Load associated menu items, for now not supported for admin menu… may be later 1454 if ($table->get('client_id') == 0 && Associations::isEnabled()) { 1455 // Adding self to the association 1456 $associations = isset($data['associations']) ? $data['associations'] : array(); 1457 1458 // Unset any invalid associations 1459 $associations = ArrayHelper::toInteger($associations); 1460 1461 foreach ($associations as $tag => $id) { 1462 if (!$id) { 1463 unset($associations[$tag]); 1464 } 1465 } 1466 1467 // Detecting all item menus 1468 $all_language = $table->language == '*'; 1469 1470 if ($all_language && !empty($associations)) { 1471 Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice'); 1472 } 1473 1474 // Get associationskey for edited item 1475 $db = $this->getDatabase(); 1476 $query = $db->getQuery(true) 1477 ->select($db->quoteName('key')) 1478 ->from($db->quoteName('#__associations')) 1479 ->where( 1480 [ 1481 $db->quoteName('context') . ' = :context', 1482 $db->quoteName('id') . ' = :id', 1483 ] 1484 ) 1485 ->bind(':context', $this->associationsContext) 1486 ->bind(':id', $table->id, ParameterType::INTEGER); 1487 $db->setQuery($query); 1488 $oldKey = $db->loadResult(); 1489 1490 if ($associations || $oldKey !== null) { 1491 // Deleting old associations for the associated items 1492 $where = []; 1493 $query = $db->getQuery(true) 1494 ->delete($db->quoteName('#__associations')) 1495 ->where($db->quoteName('context') . ' = :context') 1496 ->bind(':context', $this->associationsContext); 1497 1498 if ($associations) { 1499 $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')'; 1500 } 1501 1502 if ($oldKey !== null) { 1503 $where[] = $db->quoteName('key') . ' = :oldKey'; 1504 $query->bind(':oldKey', $oldKey); 1505 } 1506 1507 $query->extendWhere('AND', $where, 'OR'); 1508 1509 try { 1510 $db->setQuery($query); 1511 $db->execute(); 1512 } catch (\RuntimeException $e) { 1513 $this->setError($e->getMessage()); 1514 1515 return false; 1516 } 1517 } 1518 1519 // Adding self to the association 1520 if (!$all_language) { 1521 $associations[$table->language] = (int) $table->id; 1522 } 1523 1524 if (count($associations) > 1) { 1525 // Adding new association for these items 1526 $key = md5(json_encode($associations)); 1527 $query = $db->getQuery(true) 1528 ->insert($db->quoteName('#__associations')) 1529 ->columns( 1530 [ 1531 $db->quoteName('id'), 1532 $db->quoteName('context'), 1533 $db->quoteName('key'), 1534 ] 1535 ); 1536 1537 foreach ($associations as $id) { 1538 $query->values( 1539 implode( 1540 ',', 1541 $query->bindArray( 1542 [$id, $this->associationsContext, $key], 1543 [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING] 1544 ) 1545 ) 1546 ); 1547 } 1548 1549 try { 1550 $db->setQuery($query); 1551 $db->execute(); 1552 } catch (\RuntimeException $e) { 1553 $this->setError($e->getMessage()); 1554 1555 return false; 1556 } 1557 } 1558 } 1559 1560 // Clean the cache 1561 $this->cleanCache(); 1562 1563 if (isset($data['link'])) { 1564 $base = Uri::base(); 1565 $juri = Uri::getInstance($base . $data['link']); 1566 $option = $juri->getVar('option'); 1567 1568 // Clean the cache 1569 parent::cleanCache($option); 1570 } 1571 1572 if (Factory::getApplication()->input->get('task') === 'editAssociations') { 1573 return $this->redirectToAssociations($data); 1574 } 1575 1576 return true; 1577 } 1578 1579 /** 1580 * Method to save the reordered nested set tree. 1581 * First we save the new order values in the lft values of the changed ids. 1582 * Then we invoke the table rebuild to implement the new ordering. 1583 * 1584 * @param array $idArray Rows identifiers to be reordered 1585 * @param array $lftArray lft values of rows to be reordered 1586 * 1587 * @return boolean false on failure or error, true otherwise. 1588 * 1589 * @since 1.6 1590 */ 1591 public function saveorder($idArray = null, $lftArray = null) 1592 { 1593 // Get an instance of the table object. 1594 $table = $this->getTable(); 1595 1596 if (!$table->saveorder($idArray, $lftArray)) { 1597 $this->setError($table->getError()); 1598 1599 return false; 1600 } 1601 1602 // Clean the cache 1603 $this->cleanCache(); 1604 1605 return true; 1606 } 1607 1608 /** 1609 * Method to change the home state of one or more items. 1610 * 1611 * @param array $pks A list of the primary keys to change. 1612 * @param integer $value The value of the home state. 1613 * 1614 * @return boolean True on success. 1615 * 1616 * @since 1.6 1617 */ 1618 public function setHome(&$pks, $value = 1) 1619 { 1620 $table = $this->getTable(); 1621 $pks = (array) $pks; 1622 1623 $languages = array(); 1624 $onehome = false; 1625 1626 // Remember that we can set a home page for different languages, 1627 // so we need to loop through the primary key array. 1628 foreach ($pks as $i => $pk) { 1629 if ($table->load($pk)) { 1630 if (!array_key_exists($table->language, $languages)) { 1631 $languages[$table->language] = true; 1632 1633 if ($table->home == $value) { 1634 unset($pks[$i]); 1635 Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALREADY_HOME'), 'notice'); 1636 } elseif ($table->menutype == 'main') { 1637 // Prune items that you can't change. 1638 unset($pks[$i]); 1639 Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_MENUTYPE_HOME'), 'error'); 1640 } else { 1641 $table->home = $value; 1642 1643 if ($table->language == '*') { 1644 $table->published = 1; 1645 } 1646 1647 if (!$this->canSave($table)) { 1648 // Prune items that you can't change. 1649 unset($pks[$i]); 1650 Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error'); 1651 } elseif (!$table->check()) { 1652 // Prune the items that failed pre-save checks. 1653 unset($pks[$i]); 1654 Factory::getApplication()->enqueueMessage($table->getError(), 'error'); 1655 } elseif (!$table->store()) { 1656 // Prune the items that could not be stored. 1657 unset($pks[$i]); 1658 Factory::getApplication()->enqueueMessage($table->getError(), 'error'); 1659 } 1660 } 1661 } else { 1662 unset($pks[$i]); 1663 1664 if (!$onehome) { 1665 $onehome = true; 1666 Factory::getApplication()->enqueueMessage(Text::sprintf('COM_MENUS_ERROR_ONE_HOME'), 'notice'); 1667 } 1668 } 1669 } 1670 } 1671 1672 // Clean the cache 1673 $this->cleanCache(); 1674 1675 return true; 1676 } 1677 1678 /** 1679 * Method to change the published state of one or more records. 1680 * 1681 * @param array $pks A list of the primary keys to change. 1682 * @param integer $value The value of the published state. 1683 * 1684 * @return boolean True on success. 1685 * 1686 * @since 1.6 1687 */ 1688 public function publish(&$pks, $value = 1) 1689 { 1690 $table = $this->getTable(); 1691 $pks = (array) $pks; 1692 1693 // Default menu item existence checks. 1694 if ($value != 1) { 1695 foreach ($pks as $i => $pk) { 1696 if ($table->load($pk) && $table->home && $table->language == '*') { 1697 // Prune items that you can't change. 1698 Factory::getApplication()->enqueueMessage(Text::_('JLIB_DATABASE_ERROR_MENU_UNPUBLISH_DEFAULT_HOME'), 'error'); 1699 unset($pks[$i]); 1700 break; 1701 } 1702 } 1703 } 1704 1705 // Clean the cache 1706 $this->cleanCache(); 1707 1708 // Ensure that previous checks doesn't empty the array 1709 if (empty($pks)) { 1710 return true; 1711 } 1712 1713 return parent::publish($pks, $value); 1714 } 1715 1716 /** 1717 * Method to change the title & alias. 1718 * 1719 * @param integer $parentId The id of the parent. 1720 * @param string $alias The alias. 1721 * @param string $title The title. 1722 * 1723 * @return array Contains the modified title and alias. 1724 * 1725 * @since 1.6 1726 */ 1727 protected function generateNewTitle($parentId, $alias, $title) 1728 { 1729 // Alter the title & alias 1730 $table = $this->getTable(); 1731 1732 while ($table->load(array('alias' => $alias, 'parent_id' => $parentId))) { 1733 if ($title == $table->title) { 1734 $title = StringHelper::increment($title); 1735 } 1736 1737 $alias = StringHelper::increment($alias, 'dash'); 1738 } 1739 1740 return array($title, $alias); 1741 } 1742 1743 /** 1744 * Custom clean the cache 1745 * 1746 * @param string $group Cache group name. 1747 * @param integer $clientId @deprecated 5.0 No Longer Used. 1748 * 1749 * @return void 1750 * 1751 * @since 1.6 1752 */ 1753 protected function cleanCache($group = null, $clientId = 0) 1754 { 1755 parent::cleanCache('com_menus'); 1756 parent::cleanCache('com_modules'); 1757 parent::cleanCache('mod_menu'); 1758 } 1759 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |