[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Administrator 5 * @subpackage com_menus 6 * 7 * @copyright (C) 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |