[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/administrator/components/com_menus/src/Model/ -> ItemModel.php (source)

   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  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer