[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

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

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Administrator
   5   * @subpackage  com_menus
   6   *
   7   * @copyright   (C) 2009 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\Component\ComponentHelper;
  14  use Joomla\CMS\Factory;
  15  use Joomla\CMS\Form\Form;
  16  use Joomla\CMS\Language\Associations;
  17  use Joomla\CMS\Language\Text;
  18  use Joomla\CMS\Log\Log;
  19  use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
  20  use Joomla\CMS\MVC\Model\ListModel;
  21  use Joomla\Database\ParameterType;
  22  
  23  // phpcs:disable PSR1.Files.SideEffects
  24  \defined('_JEXEC') or die;
  25  // phpcs:enable PSR1.Files.SideEffects
  26  
  27  /**
  28   * Menu Item List Model for Menus.
  29   *
  30   * @since  1.6
  31   */
  32  class ItemsModel extends ListModel
  33  {
  34      /**
  35       * Constructor.
  36       *
  37       * @param   array                $config   An optional associative array of configuration settings.
  38       * @param   MVCFactoryInterface  $factory  The factory.
  39       *
  40       * @see     \Joomla\CMS\MVC\Model\BaseDatabaseModel
  41       * @since   3.2
  42       */
  43      public function __construct($config = array(), MVCFactoryInterface $factory = null)
  44      {
  45          if (empty($config['filter_fields'])) {
  46              $config['filter_fields'] = array(
  47                  'id', 'a.id',
  48                  'menutype', 'a.menutype', 'menutype_title',
  49                  'title', 'a.title',
  50                  'alias', 'a.alias',
  51                  'published', 'a.published',
  52                  'access', 'a.access', 'access_level',
  53                  'language', 'a.language',
  54                  'checked_out', 'a.checked_out',
  55                  'checked_out_time', 'a.checked_out_time',
  56                  'lft', 'a.lft',
  57                  'rgt', 'a.rgt',
  58                  'level', 'a.level',
  59                  'path', 'a.path',
  60                  'client_id', 'a.client_id',
  61                  'home', 'a.home',
  62                  'parent_id', 'a.parent_id',
  63                  'publish_up', 'a.publish_up',
  64                  'publish_down', 'a.publish_down',
  65                  'a.ordering'
  66              );
  67  
  68              if (Associations::isEnabled()) {
  69                  $config['filter_fields'][] = 'association';
  70              }
  71          }
  72  
  73          parent::__construct($config, $factory);
  74      }
  75  
  76      /**
  77       * Method to auto-populate the model state.
  78       *
  79       * Note. Calling getState in this method will result in recursion.
  80       *
  81       * @param   string  $ordering   An optional ordering field.
  82       * @param   string  $direction  An optional direction (asc|desc).
  83       *
  84       * @return  void
  85       *
  86       * @since   1.6
  87       */
  88      protected function populateState($ordering = 'a.lft', $direction = 'asc')
  89      {
  90          $app = Factory::getApplication();
  91  
  92          $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
  93  
  94          // Adjust the context to support modal layouts.
  95          if ($layout = $app->input->get('layout')) {
  96              $this->context .= '.' . $layout;
  97          }
  98  
  99          // Adjust the context to support forced languages.
 100          if ($forcedLanguage) {
 101              $this->context .= '.' . $forcedLanguage;
 102          }
 103  
 104          $search = $this->getUserStateFromRequest($this->context . '.search', 'filter_search');
 105          $this->setState('filter.search', $search);
 106  
 107          $published = $this->getUserStateFromRequest($this->context . '.published', 'filter_published', '');
 108          $this->setState('filter.published', $published);
 109  
 110          $access = $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access');
 111          $this->setState('filter.access', $access);
 112  
 113          $parentId = $this->getUserStateFromRequest($this->context . '.filter.parent_id', 'filter_parent_id');
 114          $this->setState('filter.parent_id', $parentId);
 115  
 116          $level = $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level');
 117          $this->setState('filter.level', $level);
 118  
 119          // Watch changes in client_id and menutype and keep sync whenever needed.
 120          $currentClientId = $app->getUserState($this->context . '.client_id', 0);
 121          $clientId        = $app->input->getInt('client_id', $currentClientId);
 122  
 123          // Load mod_menu.ini file when client is administrator
 124          if ($clientId == 1) {
 125              Factory::getLanguage()->load('mod_menu', JPATH_ADMINISTRATOR);
 126          }
 127  
 128          $currentMenuType = $app->getUserState($this->context . '.menutype', '');
 129          $menuType        = $app->input->getString('menutype', $currentMenuType);
 130  
 131          // If client_id changed clear menutype and reset pagination
 132          if ($clientId != $currentClientId) {
 133              $menuType = '';
 134  
 135              $app->input->set('limitstart', 0);
 136              $app->input->set('menutype', '');
 137          }
 138  
 139          // If menutype changed reset pagination.
 140          if ($menuType != $currentMenuType) {
 141              $app->input->set('limitstart', 0);
 142          }
 143  
 144          if (!$menuType) {
 145              $app->setUserState($this->context . '.menutype', '');
 146              $this->setState('menutypetitle', '');
 147              $this->setState('menutypeid', '');
 148          } elseif ($menuType == 'main') {
 149              // Special menu types, if selected explicitly, will be allowed as a filter
 150              // Adjust client_id to match the menutype. This is safe as client_id was not changed in this request.
 151              $app->input->set('client_id', 1);
 152  
 153              $app->setUserState($this->context . '.menutype', $menuType);
 154              $this->setState('menutypetitle', ucfirst($menuType));
 155              $this->setState('menutypeid', -1);
 156          } elseif ($cMenu = $this->getMenu($menuType, true)) {
 157              // Get the menutype object with appropriate checks.
 158              // Adjust client_id to match the menutype. This is safe as client_id was not changed in this request.
 159              $app->input->set('client_id', $cMenu->client_id);
 160  
 161              $app->setUserState($this->context . '.menutype', $menuType);
 162              $this->setState('menutypetitle', $cMenu->title);
 163              $this->setState('menutypeid', $cMenu->id);
 164          } else {
 165              // This menutype does not exist, leave client id unchanged but reset menutype and pagination
 166              $menuType = '';
 167  
 168              $app->input->set('limitstart', 0);
 169              $app->input->set('menutype', $menuType);
 170  
 171              $app->setUserState($this->context . '.menutype', $menuType);
 172              $this->setState('menutypetitle', '');
 173              $this->setState('menutypeid', '');
 174          }
 175  
 176          // Client id filter
 177          $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
 178          $this->setState('filter.client_id', $clientId);
 179  
 180          // Use a different filter file when client is administrator
 181          if ($clientId == 1) {
 182              $this->filterFormName = 'filter_itemsadmin';
 183          }
 184  
 185          $this->setState('filter.menutype', $menuType);
 186  
 187          $language = $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '');
 188          $this->setState('filter.language', $language);
 189  
 190          // Component parameters.
 191          $params = ComponentHelper::getParams('com_menus');
 192          $this->setState('params', $params);
 193  
 194          // List state information.
 195          parent::populateState($ordering, $direction);
 196  
 197          // Force a language.
 198          if (!empty($forcedLanguage)) {
 199              $this->setState('filter.language', $forcedLanguage);
 200          }
 201      }
 202  
 203      /**
 204       * Method to get a store id based on model configuration state.
 205       *
 206       * This is necessary because the model is used by the component and
 207       * different modules that might need different sets of data or different
 208       * ordering requirements.
 209       *
 210       * @param   string  $id  A prefix for the store id.
 211       *
 212       * @return  string  A store id.
 213       *
 214       * @since   1.6
 215       */
 216      protected function getStoreId($id = '')
 217      {
 218          // Compile the store id.
 219          $id .= ':' . $this->getState('filter.access');
 220          $id .= ':' . $this->getState('filter.published');
 221          $id .= ':' . $this->getState('filter.language');
 222          $id .= ':' . $this->getState('filter.search');
 223          $id .= ':' . $this->getState('filter.parent_id');
 224          $id .= ':' . $this->getState('filter.menutype');
 225          $id .= ':' . $this->getState('filter.client_id');
 226  
 227          return parent::getStoreId($id);
 228      }
 229  
 230      /**
 231       * Builds an SQL query to load the list data.
 232       *
 233       * @return  \Joomla\Database\DatabaseQuery    A query object.
 234       *
 235       * @since   1.6
 236       */
 237      protected function getListQuery()
 238      {
 239          // Create a new query object.
 240          $db       = $this->getDatabase();
 241          $query    = $db->getQuery(true);
 242          $user     = Factory::getUser();
 243          $clientId = (int) $this->getState('filter.client_id');
 244  
 245          // Select all fields from the table.
 246          $query->select(
 247              // We can't quote state values because they could contain expressions.
 248              $this->getState(
 249                  'list.select',
 250                  [
 251                      $db->quoteName('a.id'),
 252                      $db->quoteName('a.menutype'),
 253                      $db->quoteName('a.title'),
 254                      $db->quoteName('a.alias'),
 255                      $db->quoteName('a.note'),
 256                      $db->quoteName('a.path'),
 257                      $db->quoteName('a.link'),
 258                      $db->quoteName('a.type'),
 259                      $db->quoteName('a.parent_id'),
 260                      $db->quoteName('a.level'),
 261                      $db->quoteName('a.component_id'),
 262                      $db->quoteName('a.checked_out'),
 263                      $db->quoteName('a.checked_out_time'),
 264                      $db->quoteName('a.browserNav'),
 265                      $db->quoteName('a.access'),
 266                      $db->quoteName('a.img'),
 267                      $db->quoteName('a.template_style_id'),
 268                      $db->quoteName('a.params'),
 269                      $db->quoteName('a.lft'),
 270                      $db->quoteName('a.rgt'),
 271                      $db->quoteName('a.home'),
 272                      $db->quoteName('a.language'),
 273                      $db->quoteName('a.client_id'),
 274                      $db->quoteName('a.publish_up'),
 275                      $db->quoteName('a.publish_down'),
 276                  ]
 277              )
 278          )
 279              ->select(
 280                  [
 281                      $db->quoteName('l.title', 'language_title'),
 282                      $db->quoteName('l.image', 'language_image'),
 283                      $db->quoteName('l.sef', 'language_sef'),
 284                      $db->quoteName('u.name', 'editor'),
 285                      $db->quoteName('c.element', 'componentname'),
 286                      $db->quoteName('ag.title', 'access_level'),
 287                      $db->quoteName('mt.id', 'menutype_id'),
 288                      $db->quoteName('mt.title', 'menutype_title'),
 289                      $db->quoteName('e.enabled'),
 290                      $db->quoteName('e.name'),
 291                      'CASE WHEN ' . $db->quoteName('a.type') . ' = ' . $db->quote('component')
 292                      . ' THEN ' . $db->quoteName('a.published') . ' +2 * (' . $db->quoteName('e.enabled') . ' -1)'
 293                      . ' ELSE ' . $db->quoteName('a.published') . ' END AS ' . $db->quoteName('published'),
 294                  ]
 295              )
 296              ->from($db->quoteName('#__menu', 'a'));
 297  
 298          // Join over the language
 299          $query->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
 300  
 301          // Join over the users.
 302          $query->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('a.checked_out'));
 303  
 304          // Join over components
 305          $query->join('LEFT', $db->quoteName('#__extensions', 'c'), $db->quoteName('c.extension_id') . ' = ' . $db->quoteName('a.component_id'));
 306  
 307          // Join over the asset groups.
 308          $query->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'));
 309  
 310          // Join over the menu types.
 311          $query->join('LEFT', $db->quoteName('#__menu_types', 'mt'), $db->quoteName('mt.menutype') . ' = ' . $db->quoteName('a.menutype'));
 312  
 313          // Join over the extensions
 314          $query->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'));
 315  
 316          // Join over the associations.
 317          if (Associations::isEnabled()) {
 318              $subQuery = $db->getQuery(true)
 319                  ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
 320                  ->from($db->quoteName('#__associations', 'asso1'))
 321                  ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
 322                  ->where(
 323                      [
 324                          $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
 325                          $db->quoteName('asso1.context') . ' = ' . $db->quote('com_menus.item'),
 326                      ]
 327                  );
 328  
 329              $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
 330          }
 331  
 332          // Exclude the root category.
 333          $query->where(
 334              [
 335                  $db->quoteName('a.id') . ' > 1',
 336                  $db->quoteName('a.client_id') . ' = :clientId',
 337              ]
 338          )
 339              ->bind(':clientId', $clientId, ParameterType::INTEGER);
 340  
 341          // Filter on the published state.
 342          $published = $this->getState('filter.published');
 343  
 344          if (is_numeric($published)) {
 345              $published = (int) $published;
 346              $query->where($db->quoteName('a.published') . ' = :published')
 347                  ->bind(':published', $published, ParameterType::INTEGER);
 348          } elseif ($published === '') {
 349              $query->where($db->quoteName('a.published') . ' IN (0, 1)');
 350          }
 351  
 352          // Filter by search in title, alias or id
 353          if ($search = trim($this->getState('filter.search', ''))) {
 354              if (stripos($search, 'id:') === 0) {
 355                  $search = (int) substr($search, 3);
 356                  $query->where($db->quoteName('a.id') . ' = :search')
 357                      ->bind(':search', $search, ParameterType::INTEGER);
 358              } elseif (stripos($search, 'link:') === 0) {
 359                  if ($search = str_replace(' ', '%', trim(substr($search, 5)))) {
 360                      $query->where($db->quoteName('a.link') . ' LIKE :search')
 361                          ->bind(':search', $search);
 362                  }
 363              } else {
 364                  $search = '%' . str_replace(' ', '%', trim($search)) . '%';
 365                  $query->extendWhere(
 366                      'AND',
 367                      [
 368                          $db->quoteName('a.title') . ' LIKE :search1',
 369                          $db->quoteName('a.alias') . ' LIKE :search2',
 370                          $db->quoteName('a.note') . ' LIKE :search3',
 371                      ],
 372                      'OR'
 373                  )
 374                      ->bind([':search1', ':search2', ':search3'], $search);
 375              }
 376          }
 377  
 378          // Filter the items over the parent id if set.
 379          $parentId = (int) $this->getState('filter.parent_id');
 380          $level    = (int) $this->getState('filter.level');
 381  
 382          if ($parentId) {
 383              // Create a subquery for the sub-items list
 384              $subQuery = $db->getQuery(true)
 385                  ->select($db->quoteName('sub.id'))
 386                  ->from($db->quoteName('#__menu', 'sub'))
 387                  ->join(
 388                      'INNER',
 389                      $db->quoteName('#__menu', 'this'),
 390                      $db->quoteName('sub.lft') . ' > ' . $db->quoteName('this.lft')
 391                      . ' AND ' . $db->quoteName('sub.rgt') . ' < ' . $db->quoteName('this.rgt')
 392                  )
 393                  ->where($db->quoteName('this.id') . ' = :parentId1');
 394  
 395              if ($level) {
 396                  $subQuery->where($db->quoteName('sub.level') . ' <= ' . $db->quoteName('this.level') . ' + :level - 1');
 397                  $query->bind(':level', $level, ParameterType::INTEGER);
 398              }
 399  
 400              // Add the subquery to the main query
 401              $query->extendWhere(
 402                  'AND',
 403                  [
 404                      $db->quoteName('a.parent_id') . ' = :parentId2',
 405                      $db->quoteName('a.parent_id') . ' IN (' . (string) $subQuery . ')',
 406                  ],
 407                  'OR'
 408              )
 409                  ->bind([':parentId1', ':parentId2'], $parentId, ParameterType::INTEGER);
 410          } elseif ($level) {
 411              // Filter on the level.
 412              $query->where($db->quoteName('a.level') . ' <= :level')
 413                  ->bind(':level', $level, ParameterType::INTEGER);
 414          }
 415  
 416          // Filter the items over the menu id if set.
 417          $menuType = $this->getState('filter.menutype');
 418  
 419          // A value "" means all
 420          if ($menuType == '') {
 421              // Load all menu types we have manage access
 422              $query2 = $db->getQuery(true)
 423                  ->select(
 424                      [
 425                          $db->quoteName('id'),
 426                          $db->quoteName('menutype'),
 427                      ]
 428                  )
 429                  ->from($db->quoteName('#__menu_types'))
 430                  ->where($db->quoteName('client_id') . ' = :clientId')
 431                  ->bind(':clientId', $clientId, ParameterType::INTEGER)
 432                  ->order($db->quoteName('title'));
 433  
 434              // Show protected items on explicit filter only
 435              $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote('main'));
 436  
 437              $menuTypes = $db->setQuery($query2)->loadObjectList();
 438  
 439              if ($menuTypes) {
 440                  $types = array();
 441  
 442                  foreach ($menuTypes as $type) {
 443                      if ($user->authorise('core.manage', 'com_menus.menu.' . (int) $type->id)) {
 444                          $types[] = $type->menutype;
 445                      }
 446                  }
 447  
 448                  if ($types) {
 449                      $query->whereIn($db->quoteName('a.menutype'), $types);
 450                  } else {
 451                      $query->where(0);
 452                  }
 453              }
 454          } elseif (strlen($menuType)) {
 455              // Default behavior => load all items from a specific menu
 456              $query->where($db->quoteName('a.menutype') . ' = :menuType')
 457                  ->bind(':menuType', $menuType);
 458          } else {
 459              // Empty menu type => error
 460              $query->where('1 != 1');
 461          }
 462  
 463          // Filter on the access level.
 464          if ($access = (int) $this->getState('filter.access')) {
 465              $query->where($db->quoteName('a.access') . ' = :access')
 466                  ->bind(':access', $access, ParameterType::INTEGER);
 467          }
 468  
 469          // Implement View Level Access
 470          if (!$user->authorise('core.admin')) {
 471              if ($groups = $user->getAuthorisedViewLevels()) {
 472                  $query->whereIn($db->quoteName('a.access'), $groups);
 473              }
 474          }
 475  
 476          // Filter on the language.
 477          if ($language = $this->getState('filter.language')) {
 478              $query->where($db->quoteName('a.language') . ' = :language')
 479                  ->bind(':language', $language);
 480          }
 481  
 482          // Add the list ordering clause.
 483          $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
 484  
 485          return $query;
 486      }
 487  
 488      /**
 489       * Method to allow derived classes to preprocess the form.
 490       *
 491       * @param   Form    $form   A Form object.
 492       * @param   mixed   $data   The data expected for the form.
 493       * @param   string  $group  The name of the plugin group to import (defaults to "content").
 494       *
 495       * @return  void
 496       *
 497       * @since   3.2
 498       * @throws  \Exception if there is an error in the form event.
 499       */
 500      protected function preprocessForm(Form $form, $data, $group = 'content')
 501      {
 502          $name = $form->getName();
 503  
 504          if ($name == 'com_menus.items.filter') {
 505              $clientId = $this->getState('filter.client_id');
 506              $form->setFieldAttribute('menutype', 'clientid', $clientId);
 507          } elseif (false !== strpos($name, 'com_menus.items.modal.')) {
 508              $form->removeField('client_id');
 509  
 510              $clientId = $this->getState('filter.client_id');
 511              $form->setFieldAttribute('menutype', 'clientid', $clientId);
 512          }
 513      }
 514  
 515      /**
 516       * Get the client id for a menu
 517       *
 518       * @param   string   $menuType  The menutype identifier for the menu
 519       * @param   boolean  $check     Flag whether to perform check against ACL as well as existence
 520       *
 521       * @return  integer
 522       *
 523       * @since   3.7.0
 524       */
 525      protected function getMenu($menuType, $check = false)
 526      {
 527          $db    = $this->getDatabase();
 528          $query = $db->getQuery(true);
 529  
 530          $query->select($db->quoteName('a') . '.*')
 531              ->from($db->quoteName('#__menu_types', 'a'))
 532              ->where($db->quoteName('menutype') . ' = :menuType')
 533              ->bind(':menuType', $menuType);
 534  
 535          $cMenu = $db->setQuery($query)->loadObject();
 536  
 537          if ($check) {
 538              // Check if menu type exists.
 539              if (!$cMenu) {
 540                  Log::add(Text::_('COM_MENUS_ERROR_MENUTYPE_NOT_FOUND'), Log::ERROR, 'jerror');
 541  
 542                  return false;
 543              } elseif (!Factory::getUser()->authorise('core.manage', 'com_menus.menu.' . $cMenu->id)) {
 544                  // Check if menu type is valid against ACL.
 545                  Log::add(Text::_('JERROR_ALERTNOAUTHOR'), Log::ERROR, 'jerror');
 546  
 547                  return false;
 548              }
 549          }
 550  
 551          return $cMenu;
 552      }
 553  
 554      /**
 555       * Method to get an array of data items.
 556       *
 557       * @return  mixed  An array of data items on success, false on failure.
 558       *
 559       * @since   3.0.1
 560       */
 561      public function getItems()
 562      {
 563          $store = $this->getStoreId();
 564  
 565          if (!isset($this->cache[$store])) {
 566              $items  = parent::getItems();
 567              $lang   = Factory::getLanguage();
 568              $client = $this->state->get('filter.client_id');
 569  
 570              if ($items) {
 571                  foreach ($items as $item) {
 572                      if ($extension = $item->componentname) {
 573                          $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
 574                          || $lang->load("$extension.sys", JPATH_ADMINISTRATOR . '/components/' . $extension);
 575                      }
 576  
 577                      // Translate component name
 578                      if ($client === 1) {
 579                          $item->title = Text::_($item->title);
 580                      }
 581                  }
 582              }
 583  
 584              $this->cache[$store] = $items;
 585          }
 586  
 587          return $this->cache[$store];
 588      }
 589  }


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