[ 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_categories 6 * 7 * @copyright (C) 2008 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\Categories\Administrator\Model; 12 13 use Joomla\CMS\Association\AssociationServiceInterface; 14 use Joomla\CMS\Categories\CategoryServiceInterface; 15 use Joomla\CMS\Factory; 16 use Joomla\CMS\Language\Associations; 17 use Joomla\CMS\MVC\Factory\MVCFactoryInterface; 18 use Joomla\CMS\MVC\Model\ListModel; 19 use Joomla\Database\DatabaseQuery; 20 use Joomla\Database\ParameterType; 21 use Joomla\Utilities\ArrayHelper; 22 23 // phpcs:disable PSR1.Files.SideEffects 24 \defined('_JEXEC') or die; 25 // phpcs:enable PSR1.Files.SideEffects 26 27 /** 28 * Categories Component Categories Model 29 * 30 * @since 1.6 31 */ 32 class CategoriesModel extends ListModel 33 { 34 /** 35 * Does an association exist? Caches the result of getAssoc(). 36 * 37 * @var boolean|null 38 * @since 4.0.5 39 */ 40 private $hasAssociation; 41 42 /** 43 * Constructor. 44 * 45 * @param array $config An optional associative array of configuration settings. 46 * @param MVCFactoryInterface|null $factory The factory. 47 * 48 * @since 1.6 49 */ 50 public function __construct($config = array(), MVCFactoryInterface $factory = null) 51 { 52 if (empty($config['filter_fields'])) { 53 $config['filter_fields'] = array( 54 'id', 'a.id', 55 'title', 'a.title', 56 'alias', 'a.alias', 57 'published', 'a.published', 58 'access', 'a.access', 'access_level', 59 'language', 'a.language', 'language_title', 60 'checked_out', 'a.checked_out', 61 'checked_out_time', 'a.checked_out_time', 62 'created_time', 'a.created_time', 63 'created_user_id', 'a.created_user_id', 64 'lft', 'a.lft', 65 'rgt', 'a.rgt', 66 'level', 'a.level', 67 'path', 'a.path', 68 'tag', 69 ); 70 } 71 72 if (Associations::isEnabled()) { 73 $config['filter_fields'][] = 'association'; 74 } 75 76 parent::__construct($config, $factory); 77 } 78 79 /** 80 * Method to auto-populate the model state. 81 * 82 * Note. Calling getState in this method will result in recursion. 83 * 84 * @param string $ordering An optional ordering field. 85 * @param string $direction An optional direction (asc|desc). 86 * 87 * @return void 88 * 89 * @since 1.6 90 */ 91 protected function populateState($ordering = 'a.lft', $direction = 'asc') 92 { 93 $app = Factory::getApplication(); 94 95 $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd'); 96 97 // Adjust the context to support modal layouts. 98 if ($layout = $app->input->get('layout')) { 99 $this->context .= '.' . $layout; 100 } 101 102 // Adjust the context to support forced languages. 103 if ($forcedLanguage) { 104 $this->context .= '.' . $forcedLanguage; 105 } 106 107 $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', 'com_content', 'cmd'); 108 109 $this->setState('filter.extension', $extension); 110 $parts = explode('.', $extension); 111 112 // Extract the component name 113 $this->setState('filter.component', $parts[0]); 114 115 // Extract the optional section name 116 $this->setState('filter.section', (\count($parts) > 1) ? $parts[1] : null); 117 118 // List state information. 119 parent::populateState($ordering, $direction); 120 121 // Force a language. 122 if (!empty($forcedLanguage)) { 123 $this->setState('filter.language', $forcedLanguage); 124 } 125 } 126 127 /** 128 * Method to get a store id based on model configuration state. 129 * 130 * This is necessary because the model is used by the component and 131 * different modules that might need different sets of data or different 132 * ordering requirements. 133 * 134 * @param string $id A prefix for the store id. 135 * 136 * @return string A store id. 137 * 138 * @since 1.6 139 */ 140 protected function getStoreId($id = '') 141 { 142 // Compile the store id. 143 $id .= ':' . $this->getState('filter.extension'); 144 $id .= ':' . $this->getState('filter.search'); 145 $id .= ':' . $this->getState('filter.published'); 146 $id .= ':' . $this->getState('filter.access'); 147 $id .= ':' . $this->getState('filter.language'); 148 $id .= ':' . $this->getState('filter.level'); 149 $id .= ':' . serialize($this->getState('filter.tag')); 150 151 return parent::getStoreId($id); 152 } 153 154 /** 155 * Method to get a database query to list categories. 156 * 157 * @return \Joomla\Database\DatabaseQuery 158 * 159 * @since 1.6 160 */ 161 protected function getListQuery() 162 { 163 // Create a new query object. 164 $db = $this->getDatabase(); 165 $query = $db->getQuery(true); 166 $user = Factory::getUser(); 167 168 // Select the required fields from the table. 169 $query->select( 170 $this->getState( 171 'list.select', 172 'a.id, a.title, a.alias, a.note, a.published, a.access' . 173 ', a.checked_out, a.checked_out_time, a.created_user_id' . 174 ', a.path, a.parent_id, a.level, a.lft, a.rgt' . 175 ', a.language' 176 ) 177 ); 178 $query->from($db->quoteName('#__categories', 'a')); 179 180 // Join over the language 181 $query->select( 182 [ 183 $db->quoteName('l.title', 'language_title'), 184 $db->quoteName('l.image', 'language_image'), 185 ] 186 ) 187 ->join( 188 'LEFT', 189 $db->quoteName('#__languages', 'l'), 190 $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language') 191 ); 192 193 // Join over the users for the checked out user. 194 $query->select($db->quoteName('uc.name', 'editor')) 195 ->join( 196 'LEFT', 197 $db->quoteName('#__users', 'uc'), 198 $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out') 199 ); 200 201 // Join over the asset groups. 202 $query->select($db->quoteName('ag.title', 'access_level')) 203 ->join( 204 'LEFT', 205 $db->quoteName('#__viewlevels', 'ag'), 206 $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access') 207 ); 208 209 // Join over the users for the author. 210 $query->select($db->quoteName('ua.name', 'author_name')) 211 ->join( 212 'LEFT', 213 $db->quoteName('#__users', 'ua'), 214 $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_user_id') 215 ); 216 217 // Join over the associations. 218 $assoc = $this->getAssoc(); 219 220 if ($assoc) { 221 $query->select('COUNT(asso2.id)>1 as association') 222 ->join( 223 'LEFT', 224 $db->quoteName('#__associations', 'asso'), 225 $db->quoteName('asso.id') . ' = ' . $db->quoteName('a.id') 226 . ' AND ' . $db->quoteName('asso.context') . ' = ' . $db->quote('com_categories.item') 227 ) 228 ->join( 229 'LEFT', 230 $db->quoteName('#__associations', 'asso2'), 231 $db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key') 232 ) 233 ->group('a.id, l.title, uc.name, ag.title, ua.name'); 234 } 235 236 // Filter by extension 237 if ($extension = $this->getState('filter.extension')) { 238 $query->where($db->quoteName('a.extension') . ' = :extension') 239 ->bind(':extension', $extension); 240 } 241 242 // Filter on the level. 243 if ($level = (int) $this->getState('filter.level')) { 244 $query->where($db->quoteName('a.level') . ' <= :level') 245 ->bind(':level', $level, ParameterType::INTEGER); 246 } 247 248 // Filter by access level. 249 if ($access = (int) $this->getState('filter.access')) { 250 $query->where($db->quoteName('a.access') . ' = :access') 251 ->bind(':access', $access, ParameterType::INTEGER); 252 } 253 254 // Implement View Level Access 255 if (!$user->authorise('core.admin')) { 256 $groups = $user->getAuthorisedViewLevels(); 257 $query->whereIn($db->quoteName('a.access'), $groups); 258 } 259 260 // Filter by published state 261 $published = (string) $this->getState('filter.published'); 262 263 if (is_numeric($published)) { 264 $published = (int) $published; 265 $query->where($db->quoteName('a.published') . ' = :published') 266 ->bind(':published', $published, ParameterType::INTEGER); 267 } elseif ($published === '') { 268 $query->whereIn($db->quoteName('a.published'), [0, 1]); 269 } 270 271 // Filter by search in title 272 $search = $this->getState('filter.search'); 273 274 if (!empty($search)) { 275 if (stripos($search, 'id:') === 0) { 276 $search = (int) substr($search, 3); 277 $query->where($db->quoteName('a.id') . ' = :search') 278 ->bind(':search', $search, ParameterType::INTEGER); 279 } else { 280 $search = '%' . str_replace(' ', '%', trim($search)) . '%'; 281 $query->extendWhere( 282 'AND', 283 [ 284 $db->quoteName('a.title') . ' LIKE :title', 285 $db->quoteName('a.alias') . ' LIKE :alias', 286 $db->quoteName('a.note') . ' LIKE :note', 287 ], 288 'OR' 289 ) 290 ->bind(':title', $search) 291 ->bind(':alias', $search) 292 ->bind(':note', $search); 293 } 294 } 295 296 // Filter on the language. 297 if ($language = $this->getState('filter.language')) { 298 $query->where($db->quoteName('a.language') . ' = :language') 299 ->bind(':language', $language); 300 } 301 302 // Filter by a single or group of tags. 303 $tag = $this->getState('filter.tag'); 304 $typeAlias = $extension . '.category'; 305 306 // Run simplified query when filtering by one tag. 307 if (\is_array($tag) && \count($tag) === 1) { 308 $tag = $tag[0]; 309 } 310 311 if ($tag && \is_array($tag)) { 312 $tag = ArrayHelper::toInteger($tag); 313 314 $subQuery = $db->getQuery(true) 315 ->select('DISTINCT ' . $db->quoteName('content_item_id')) 316 ->from($db->quoteName('#__contentitem_tag_map')) 317 ->where( 318 [ 319 $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')', 320 $db->quoteName('type_alias') . ' = :typeAlias', 321 ] 322 ); 323 324 $query->join( 325 'INNER', 326 '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'), 327 $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id') 328 ) 329 ->bind(':typeAlias', $typeAlias); 330 } elseif ($tag = (int) $tag) { 331 $query->join( 332 'INNER', 333 $db->quoteName('#__contentitem_tag_map', 'tagmap'), 334 $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id') 335 ) 336 ->where( 337 [ 338 $db->quoteName('tagmap.tag_id') . ' = :tag', 339 $db->quoteName('tagmap.type_alias') . ' = :typeAlias', 340 ] 341 ) 342 ->bind(':tag', $tag, ParameterType::INTEGER) 343 ->bind(':typeAlias', $typeAlias); 344 } 345 346 // Add the list ordering clause 347 $listOrdering = $this->getState('list.ordering', 'a.lft'); 348 $listDirn = $db->escape($this->getState('list.direction', 'ASC')); 349 350 if ($listOrdering == 'a.access') { 351 $query->order('a.access ' . $listDirn . ', a.lft ' . $listDirn); 352 } else { 353 $query->order($db->escape($listOrdering) . ' ' . $listDirn); 354 } 355 356 // Group by on Categories for \JOIN with component tables to count items 357 $query->group('a.id, 358 a.title, 359 a.alias, 360 a.note, 361 a.published, 362 a.access, 363 a.checked_out, 364 a.checked_out_time, 365 a.created_user_id, 366 a.path, 367 a.parent_id, 368 a.level, 369 a.lft, 370 a.rgt, 371 a.language, 372 l.title, 373 l.image, 374 uc.name, 375 ag.title, 376 ua.name'); 377 378 return $query; 379 } 380 381 /** 382 * Method to determine if an association exists 383 * 384 * @return boolean True if the association exists 385 * 386 * @since 3.0 387 */ 388 public function getAssoc() 389 { 390 if (!\is_null($this->hasAssociation)) { 391 return $this->hasAssociation; 392 } 393 394 $extension = $this->getState('filter.extension'); 395 396 $this->hasAssociation = Associations::isEnabled(); 397 $extension = explode('.', $extension); 398 $component = array_shift($extension); 399 $cname = str_replace('com_', '', $component); 400 401 if (!$this->hasAssociation || !$component || !$cname) { 402 $this->hasAssociation = false; 403 404 return $this->hasAssociation; 405 } 406 407 $componentObject = $this->bootComponent($component); 408 409 if ($componentObject instanceof AssociationServiceInterface && $componentObject instanceof CategoryServiceInterface) { 410 $this->hasAssociation = true; 411 412 return $this->hasAssociation; 413 } 414 415 $hname = $cname . 'HelperAssociation'; 416 \JLoader::register($hname, JPATH_SITE . '/components/' . $component . '/helpers/association.php'); 417 418 $this->hasAssociation = class_exists($hname) && !empty($hname::$category_association); 419 420 return $this->hasAssociation; 421 } 422 423 /** 424 * Method to get an array of data items. 425 * 426 * @return mixed An array of data items on success, false on failure. 427 * 428 * @since 3.0.1 429 */ 430 public function getItems() 431 { 432 $items = parent::getItems(); 433 434 if ($items != false) { 435 $extension = $this->getState('filter.extension'); 436 437 $this->countItems($items, $extension); 438 } 439 440 return $items; 441 } 442 443 /** 444 * Method to load the countItems method from the extensions 445 * 446 * @param \stdClass[] $items The category items 447 * @param string $extension The category extension 448 * 449 * @return void 450 * 451 * @since 3.5 452 */ 453 public function countItems(&$items, $extension) 454 { 455 $parts = explode('.', $extension, 2); 456 $section = ''; 457 458 if (\count($parts) > 1) { 459 $section = $parts[1]; 460 } 461 462 $component = Factory::getApplication()->bootComponent($parts[0]); 463 464 if ($component instanceof CategoryServiceInterface) { 465 $component->countItems($items, $section); 466 } 467 } 468 469 /** 470 * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension. 471 * 472 * @return DatabaseQuery 473 * 474 * @since 4.0.0 475 */ 476 protected function getEmptyStateQuery() 477 { 478 $query = parent::getEmptyStateQuery(); 479 480 // Get the extension from the filter 481 $extension = $this->getState('filter.extension'); 482 483 $query->where($this->getDatabase()->quoteName('extension') . ' = :extension') 484 ->bind(':extension', $extension); 485 486 return $query; 487 } 488 }
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 |