[ 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\Access\Rules; 14 use Joomla\CMS\Association\AssociationServiceInterface; 15 use Joomla\CMS\Categories\CategoryServiceInterface; 16 use Joomla\CMS\Component\ComponentHelper; 17 use Joomla\CMS\Factory; 18 use Joomla\CMS\Filesystem\Path; 19 use Joomla\CMS\Form\Form; 20 use Joomla\CMS\Helper\TagsHelper; 21 use Joomla\CMS\Language\Associations; 22 use Joomla\CMS\Language\LanguageHelper; 23 use Joomla\CMS\Language\Text; 24 use Joomla\CMS\MVC\Factory\MVCFactoryInterface; 25 use Joomla\CMS\MVC\Model\AdminModel; 26 use Joomla\CMS\Plugin\PluginHelper; 27 use Joomla\CMS\Table\Category; 28 use Joomla\CMS\UCM\UCMType; 29 use Joomla\CMS\Versioning\VersionableModelTrait; 30 use Joomla\Component\Categories\Administrator\Helper\CategoriesHelper; 31 use Joomla\Database\ParameterType; 32 use Joomla\Registry\Registry; 33 use Joomla\String\StringHelper; 34 use Joomla\Utilities\ArrayHelper; 35 36 // phpcs:disable PSR1.Files.SideEffects 37 \defined('_JEXEC') or die; 38 // phpcs:enable PSR1.Files.SideEffects 39 40 /** 41 * Categories Component Category Model 42 * 43 * @since 1.6 44 */ 45 class CategoryModel extends AdminModel 46 { 47 use VersionableModelTrait; 48 49 /** 50 * The prefix to use with controller messages. 51 * 52 * @var string 53 * @since 1.6 54 */ 55 protected $text_prefix = 'COM_CATEGORIES'; 56 57 /** 58 * The type alias for this content type. Used for content version history. 59 * 60 * @var string 61 * @since 3.2 62 */ 63 public $typeAlias = null; 64 65 /** 66 * The context used for the associations table 67 * 68 * @var string 69 * @since 3.4.4 70 */ 71 protected $associationsContext = 'com_categories.item'; 72 73 /** 74 * Does an association exist? Caches the result of getAssoc(). 75 * 76 * @var boolean|null 77 * @since 3.10.4 78 */ 79 private $hasAssociation; 80 81 /** 82 * Override parent constructor. 83 * 84 * @param array $config An optional associative array of configuration settings. 85 * @param MVCFactoryInterface|null $factory The factory. 86 * 87 * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel 88 * @since 3.2 89 */ 90 public function __construct($config = array(), MVCFactoryInterface $factory = null) 91 { 92 $extension = Factory::getApplication()->input->get('extension', 'com_content'); 93 $this->typeAlias = $extension . '.category'; 94 95 // Add a new batch command 96 $this->batch_commands['flip_ordering'] = 'batchFlipordering'; 97 98 parent::__construct($config, $factory); 99 } 100 101 /** 102 * Method to test whether a record can be deleted. 103 * 104 * @param object $record A record object. 105 * 106 * @return boolean True if allowed to delete the record. Defaults to the permission set in the component. 107 * 108 * @since 1.6 109 */ 110 protected function canDelete($record) 111 { 112 if (empty($record->id) || $record->published != -2) { 113 return false; 114 } 115 116 return Factory::getUser()->authorise('core.delete', $record->extension . '.category.' . (int) $record->id); 117 } 118 119 /** 120 * Method to test whether a record can have its state changed. 121 * 122 * @param object $record A record object. 123 * 124 * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component. 125 * 126 * @since 1.6 127 */ 128 protected function canEditState($record) 129 { 130 $user = Factory::getUser(); 131 132 // Check for existing category. 133 if (!empty($record->id)) { 134 return $user->authorise('core.edit.state', $record->extension . '.category.' . (int) $record->id); 135 } 136 137 // New category, so check against the parent. 138 if (!empty($record->parent_id)) { 139 return $user->authorise('core.edit.state', $record->extension . '.category.' . (int) $record->parent_id); 140 } 141 142 // Default to component settings if neither category nor parent known. 143 return $user->authorise('core.edit.state', $record->extension); 144 } 145 146 /** 147 * Method to get a table object, load it if necessary. 148 * 149 * @param string $type The table name. Optional. 150 * @param string $prefix The class prefix. Optional. 151 * @param array $config Configuration array for model. Optional. 152 * 153 * @return \Joomla\CMS\Table\Table A Table object 154 * 155 * @since 1.6 156 */ 157 public function getTable($type = 'Category', $prefix = 'Administrator', $config = array()) 158 { 159 return parent::getTable($type, $prefix, $config); 160 } 161 162 /** 163 * Auto-populate the model state. 164 * 165 * Note. Calling getState in this method will result in recursion. 166 * 167 * @return void 168 * 169 * @since 1.6 170 */ 171 protected function populateState() 172 { 173 $app = Factory::getApplication(); 174 175 $parentId = $app->input->getInt('parent_id'); 176 $this->setState('category.parent_id', $parentId); 177 178 // Load the User state. 179 $pk = $app->input->getInt('id'); 180 $this->setState($this->getName() . '.id', $pk); 181 182 $extension = $app->input->get('extension', 'com_content'); 183 $this->setState('category.extension', $extension); 184 $parts = explode('.', $extension); 185 186 // Extract the component name 187 $this->setState('category.component', $parts[0]); 188 189 // Extract the optional section name 190 $this->setState('category.section', (\count($parts) > 1) ? $parts[1] : null); 191 192 // Load the parameters. 193 $params = ComponentHelper::getParams('com_categories'); 194 $this->setState('params', $params); 195 } 196 197 /** 198 * Method to get a category. 199 * 200 * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used. 201 * 202 * @return mixed Category data object on success, false on failure. 203 * 204 * @since 1.6 205 */ 206 public function getItem($pk = null) 207 { 208 if ($result = parent::getItem($pk)) { 209 // Prime required properties. 210 if (empty($result->id)) { 211 $result->parent_id = $this->getState('category.parent_id'); 212 $result->extension = $this->getState('category.extension'); 213 } 214 215 // Convert the metadata field to an array. 216 $registry = new Registry($result->metadata); 217 $result->metadata = $registry->toArray(); 218 219 if (!empty($result->id)) { 220 $result->tags = new TagsHelper(); 221 $result->tags->getTagIds($result->id, $result->extension . '.category'); 222 } 223 } 224 225 $assoc = $this->getAssoc(); 226 227 if ($assoc) { 228 if ($result->id != null) { 229 $result->associations = ArrayHelper::toInteger(CategoriesHelper::getAssociations($result->id, $result->extension)); 230 } else { 231 $result->associations = array(); 232 } 233 } 234 235 return $result; 236 } 237 238 /** 239 * Method to get the row form. 240 * 241 * @param array $data Data for the form. 242 * @param boolean $loadData True if the form is to load its own data (default case), false if not. 243 * 244 * @return Form|boolean A JForm object on success, false on failure 245 * 246 * @since 1.6 247 */ 248 public function getForm($data = array(), $loadData = true) 249 { 250 $extension = $this->getState('category.extension'); 251 $jinput = Factory::getApplication()->input; 252 253 // A workaround to get the extension into the model for save requests. 254 if (empty($extension) && isset($data['extension'])) { 255 $extension = $data['extension']; 256 $parts = explode('.', $extension); 257 258 $this->setState('category.extension', $extension); 259 $this->setState('category.component', $parts[0]); 260 $this->setState('category.section', @$parts[1]); 261 } 262 263 // Get the form. 264 $form = $this->loadForm('com_categories.category' . $extension, 'category', array('control' => 'jform', 'load_data' => $loadData)); 265 266 if (empty($form)) { 267 return false; 268 } 269 270 // Modify the form based on Edit State access controls. 271 if (empty($data['extension'])) { 272 $data['extension'] = $extension; 273 } 274 275 $categoryId = $jinput->get('id'); 276 $parts = explode('.', $extension); 277 $assetKey = $categoryId ? $extension . '.category.' . $categoryId : $parts[0]; 278 279 if (!Factory::getUser()->authorise('core.edit.state', $assetKey)) { 280 // Disable fields for display. 281 $form->setFieldAttribute('ordering', 'disabled', 'true'); 282 $form->setFieldAttribute('published', 'disabled', 'true'); 283 284 // Disable fields while saving. 285 // The controller has already verified this is a record you can edit. 286 $form->setFieldAttribute('ordering', 'filter', 'unset'); 287 $form->setFieldAttribute('published', 'filter', 'unset'); 288 } 289 290 // Don't allow to change the created_user_id user if not allowed to access com_users. 291 if (!Factory::getUser()->authorise('core.manage', 'com_users')) { 292 $form->setFieldAttribute('created_user_id', 'filter', 'unset'); 293 } 294 295 return $form; 296 } 297 298 /** 299 * A protected method to get the where clause for the reorder 300 * This ensures that the row will be moved relative to a row with the same extension 301 * 302 * @param Category $table Current table instance 303 * 304 * @return array An array of conditions to add to ordering queries. 305 * 306 * @since 1.6 307 */ 308 protected function getReorderConditions($table) 309 { 310 $db = $this->getDatabase(); 311 312 return [ 313 $db->quoteName('extension') . ' = ' . $db->quote($table->extension), 314 ]; 315 } 316 317 /** 318 * Method to get the data that should be injected in the form. 319 * 320 * @return mixed The data for the form. 321 * 322 * @since 1.6 323 */ 324 protected function loadFormData() 325 { 326 // Check the session for previously entered form data. 327 $app = Factory::getApplication(); 328 $data = $app->getUserState('com_categories.edit.' . $this->getName() . '.data', array()); 329 330 if (empty($data)) { 331 $data = $this->getItem(); 332 333 // Pre-select some filters (Status, Language, Access) in edit form if those have been selected in Category Manager 334 if (!$data->id) { 335 // Check for which extension the Category Manager is used and get selected fields 336 $extension = substr($app->getUserState('com_categories.categories.filter.extension', ''), 4); 337 $filters = (array) $app->getUserState('com_categories.categories.' . $extension . '.filter'); 338 339 $data->set( 340 'published', 341 $app->input->getInt( 342 'published', 343 ((isset($filters['published']) && $filters['published'] !== '') ? $filters['published'] : null) 344 ) 345 ); 346 $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null))); 347 $data->set( 348 'access', 349 $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access'))) 350 ); 351 } 352 } 353 354 $this->preprocessData('com_categories.category', $data); 355 356 return $data; 357 } 358 359 /** 360 * Method to validate the form data. 361 * 362 * @param Form $form The form to validate against. 363 * @param array $data The data to validate. 364 * @param string $group The name of the field group to validate. 365 * 366 * @return array|boolean Array of filtered data if valid, false otherwise. 367 * 368 * @see JFormRule 369 * @see JFilterInput 370 * @since 3.9.23 371 */ 372 public function validate($form, $data, $group = null) 373 { 374 if (!Factory::getUser()->authorise('core.admin', $data['extension'])) { 375 if (isset($data['rules'])) { 376 unset($data['rules']); 377 } 378 } 379 380 return parent::validate($form, $data, $group); 381 } 382 383 /** 384 * Method to preprocess the form. 385 * 386 * @param Form $form A Form object. 387 * @param mixed $data The data expected for the form. 388 * @param string $group The name of the plugin group to import. 389 * 390 * @return mixed 391 * 392 * @since 1.6 393 * 394 * @throws \Exception if there is an error in the form event. 395 * 396 * @see \Joomla\CMS\Form\FormField 397 */ 398 protected function preprocessForm(Form $form, $data, $group = 'content') 399 { 400 $lang = Factory::getLanguage(); 401 $component = $this->getState('category.component'); 402 $section = $this->getState('category.section'); 403 $extension = Factory::getApplication()->input->get('extension', null); 404 405 // Get the component form if it exists 406 $name = 'category' . ($section ? ('.' . $section) : ''); 407 408 // Looking first in the component forms folder 409 $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/forms/$name.xml"); 410 411 // Looking in the component models/forms folder (J! 3) 412 if (!file_exists($path)) { 413 $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/models/forms/$name.xml"); 414 } 415 416 // Old way: looking in the component folder 417 if (!file_exists($path)) { 418 $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/$name.xml"); 419 } 420 421 if (file_exists($path)) { 422 $lang->load($component, JPATH_BASE); 423 $lang->load($component, JPATH_BASE . '/components/' . $component); 424 425 if (!$form->loadFile($path, false)) { 426 throw new \Exception(Text::_('JERROR_LOADFILE_FAILED')); 427 } 428 } 429 430 $componentInterface = Factory::getApplication()->bootComponent($component); 431 432 if ($componentInterface instanceof CategoryServiceInterface) { 433 $componentInterface->prepareForm($form, $data); 434 } else { 435 // Try to find the component helper. 436 $eName = str_replace('com_', '', $component); 437 $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/helpers/category.php"); 438 439 if (file_exists($path)) { 440 $cName = ucfirst($eName) . ucfirst($section) . 'HelperCategory'; 441 442 \JLoader::register($cName, $path); 443 444 if (class_exists($cName) && \is_callable(array($cName, 'onPrepareForm'))) { 445 $lang->load($component, JPATH_BASE, null, false, false) 446 || $lang->load($component, JPATH_BASE . '/components/' . $component, null, false, false) 447 || $lang->load($component, JPATH_BASE, $lang->getDefault(), false, false) 448 || $lang->load($component, JPATH_BASE . '/components/' . $component, $lang->getDefault(), false, false); 449 \call_user_func_array(array($cName, 'onPrepareForm'), array(&$form)); 450 451 // Check for an error. 452 if ($form instanceof \Exception) { 453 $this->setError($form->getMessage()); 454 455 return false; 456 } 457 } 458 } 459 } 460 461 // Set the access control rules field component value. 462 $form->setFieldAttribute('rules', 'component', $component); 463 $form->setFieldAttribute('rules', 'section', $name); 464 465 // Association category items 466 if ($this->getAssoc()) { 467 $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc'); 468 469 if (\count($languages) > 1) { 470 $addform = new \SimpleXMLElement('<form />'); 471 $fields = $addform->addChild('fields'); 472 $fields->addAttribute('name', 'associations'); 473 $fieldset = $fields->addChild('fieldset'); 474 $fieldset->addAttribute('name', 'item_associations'); 475 476 foreach ($languages as $language) { 477 $field = $fieldset->addChild('field'); 478 $field->addAttribute('name', $language->lang_code); 479 $field->addAttribute('type', 'modal_category'); 480 $field->addAttribute('language', $language->lang_code); 481 $field->addAttribute('label', $language->title); 482 $field->addAttribute('translate_label', 'false'); 483 $field->addAttribute('extension', $extension); 484 $field->addAttribute('select', 'true'); 485 $field->addAttribute('new', 'true'); 486 $field->addAttribute('edit', 'true'); 487 $field->addAttribute('clear', 'true'); 488 $field->addAttribute('propagate', 'true'); 489 } 490 491 $form->load($addform, false); 492 } 493 } 494 495 // Trigger the default form events. 496 parent::preprocessForm($form, $data, $group); 497 } 498 499 /** 500 * Method to save the form data. 501 * 502 * @param array $data The form data. 503 * 504 * @return boolean True on success. 505 * 506 * @since 1.6 507 */ 508 public function save($data) 509 { 510 $table = $this->getTable(); 511 $input = Factory::getApplication()->input; 512 $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState($this->getName() . '.id'); 513 $isNew = true; 514 $context = $this->option . '.' . $this->name; 515 516 if (!empty($data['tags']) && $data['tags'][0] != '') { 517 $table->newTags = $data['tags']; 518 } 519 520 // Include the plugins for the save events. 521 PluginHelper::importPlugin($this->events_map['save']); 522 523 // Load the row if saving an existing category. 524 if ($pk > 0) { 525 $table->load($pk); 526 $isNew = false; 527 } 528 529 // Set the new parent id if parent id not matched OR while New/Save as Copy . 530 if ($table->parent_id != $data['parent_id'] || $data['id'] == 0) { 531 $table->setLocation($data['parent_id'], 'last-child'); 532 } 533 534 // Alter the title for save as copy 535 if ($input->get('task') == 'save2copy') { 536 $origTable = clone $this->getTable(); 537 $origTable->load($input->getInt('id')); 538 539 if ($data['title'] == $origTable->title) { 540 [$title, $alias] = $this->generateNewTitle($data['parent_id'], $data['alias'], $data['title']); 541 $data['title'] = $title; 542 $data['alias'] = $alias; 543 } else { 544 if ($data['alias'] == $origTable->alias) { 545 $data['alias'] = ''; 546 } 547 } 548 549 $data['published'] = 0; 550 } 551 552 // Bind the data. 553 if (!$table->bind($data)) { 554 $this->setError($table->getError()); 555 556 return false; 557 } 558 559 // Bind the rules. 560 if (isset($data['rules'])) { 561 $rules = new Rules($data['rules']); 562 $table->setRules($rules); 563 } 564 565 // Check the data. 566 if (!$table->check()) { 567 $this->setError($table->getError()); 568 569 return false; 570 } 571 572 // Trigger the before save event. 573 $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, $isNew, $data)); 574 575 if (\in_array(false, $result, true)) { 576 $this->setError($table->getError()); 577 578 return false; 579 } 580 581 // Store the data. 582 if (!$table->store()) { 583 $this->setError($table->getError()); 584 585 return false; 586 } 587 588 $assoc = $this->getAssoc(); 589 590 if ($assoc) { 591 // Adding self to the association 592 $associations = $data['associations'] ?? array(); 593 594 // Unset any invalid associations 595 $associations = ArrayHelper::toInteger($associations); 596 597 foreach ($associations as $tag => $id) { 598 if (!$id) { 599 unset($associations[$tag]); 600 } 601 } 602 603 // Detecting all item menus 604 $allLanguage = $table->language == '*'; 605 606 if ($allLanguage && !empty($associations)) { 607 Factory::getApplication()->enqueueMessage(Text::_('COM_CATEGORIES_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice'); 608 } 609 610 // Get associationskey for edited item 611 $db = $this->getDatabase(); 612 $id = (int) $table->id; 613 $query = $db->getQuery(true) 614 ->select($db->quoteName('key')) 615 ->from($db->quoteName('#__associations')) 616 ->where($db->quoteName('context') . ' = :associationscontext') 617 ->where($db->quoteName('id') . ' = :id') 618 ->bind(':associationscontext', $this->associationsContext) 619 ->bind(':id', $id, ParameterType::INTEGER); 620 $db->setQuery($query); 621 $oldKey = $db->loadResult(); 622 623 if ($associations || $oldKey !== null) { 624 $where = []; 625 626 // Deleting old associations for the associated items 627 $query = $db->getQuery(true) 628 ->delete($db->quoteName('#__associations')) 629 ->where($db->quoteName('context') . ' = :associationscontext') 630 ->bind(':associationscontext', $this->associationsContext); 631 632 if ($associations) { 633 $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')'; 634 } 635 636 if ($oldKey !== null) { 637 $where[] = $db->quoteName('key') . ' = :oldKey'; 638 $query->bind(':oldKey', $oldKey); 639 } 640 641 $query->extendWhere('AND', $where, 'OR'); 642 } 643 644 $db->setQuery($query); 645 646 try { 647 $db->execute(); 648 } catch (\RuntimeException $e) { 649 $this->setError($e->getMessage()); 650 651 return false; 652 } 653 654 // Adding self to the association 655 if (!$allLanguage) { 656 $associations[$table->language] = (int) $table->id; 657 } 658 659 if (\count($associations) > 1) { 660 // Adding new association for these items 661 $key = md5(json_encode($associations)); 662 $query->clear() 663 ->insert($db->quoteName('#__associations')) 664 ->columns( 665 [ 666 $db->quoteName('id'), 667 $db->quoteName('context'), 668 $db->quoteName('key'), 669 ] 670 ); 671 672 foreach ($associations as $id) { 673 $id = (int) $id; 674 675 $query->values( 676 implode( 677 ',', 678 $query->bindArray( 679 [$id, $this->associationsContext, $key], 680 [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING] 681 ) 682 ) 683 ); 684 } 685 686 $db->setQuery($query); 687 688 try { 689 $db->execute(); 690 } catch (\RuntimeException $e) { 691 $this->setError($e->getMessage()); 692 693 return false; 694 } 695 } 696 } 697 698 // Trigger the after save event. 699 Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, $isNew, $data)); 700 701 // Rebuild the path for the category: 702 if (!$table->rebuildPath($table->id)) { 703 $this->setError($table->getError()); 704 705 return false; 706 } 707 708 // Rebuild the paths of the category's children: 709 if (!$table->rebuild($table->id, $table->lft, $table->level, $table->path)) { 710 $this->setError($table->getError()); 711 712 return false; 713 } 714 715 $this->setState($this->getName() . '.id', $table->id); 716 717 if (Factory::getApplication()->input->get('task') == 'editAssociations') { 718 return $this->redirectToAssociations($data); 719 } 720 721 // Clear the cache 722 $this->cleanCache(); 723 724 return true; 725 } 726 727 /** 728 * Method to change the published state of one or more records. 729 * 730 * @param array $pks A list of the primary keys to change. 731 * @param integer $value The value of the published state. 732 * 733 * @return boolean True on success. 734 * 735 * @since 2.5 736 */ 737 public function publish(&$pks, $value = 1) 738 { 739 if (parent::publish($pks, $value)) { 740 $extension = Factory::getApplication()->input->get('extension'); 741 742 // Include the content plugins for the change of category state event. 743 PluginHelper::importPlugin('content'); 744 745 // Trigger the onCategoryChangeState event. 746 Factory::getApplication()->triggerEvent('onCategoryChangeState', array($extension, $pks, $value)); 747 748 return true; 749 } 750 } 751 752 /** 753 * Method rebuild the entire nested set tree. 754 * 755 * @return boolean False on failure or error, true otherwise. 756 * 757 * @since 1.6 758 */ 759 public function rebuild() 760 { 761 // Get an instance of the table object. 762 $table = $this->getTable(); 763 764 if (!$table->rebuild()) { 765 $this->setError($table->getError()); 766 767 return false; 768 } 769 770 // Clear the cache 771 $this->cleanCache(); 772 773 return true; 774 } 775 776 /** 777 * Method to save the reordered nested set tree. 778 * First we save the new order values in the lft values of the changed ids. 779 * Then we invoke the table rebuild to implement the new ordering. 780 * 781 * @param array $idArray An array of primary key ids. 782 * @param integer $lftArray The lft value 783 * 784 * @return boolean False on failure or error, True otherwise 785 * 786 * @since 1.6 787 */ 788 public function saveorder($idArray = null, $lftArray = null) 789 { 790 // Get an instance of the table object. 791 $table = $this->getTable(); 792 793 if (!$table->saveorder($idArray, $lftArray)) { 794 $this->setError($table->getError()); 795 796 return false; 797 } 798 799 // Clear the cache 800 $this->cleanCache(); 801 802 return true; 803 } 804 805 /** 806 * Batch flip category ordering. 807 * 808 * @param integer $value The new category. 809 * @param array $pks An array of row IDs. 810 * @param array $contexts An array of item contexts. 811 * 812 * @return mixed An array of new IDs on success, boolean false on failure. 813 * 814 * @since 3.6.3 815 */ 816 protected function batchFlipordering($value, $pks, $contexts) 817 { 818 $successful = array(); 819 820 $db = $this->getDatabase(); 821 $query = $db->getQuery(true); 822 823 /** 824 * For each category get the max ordering value 825 * Re-order with max - ordering 826 */ 827 foreach ($pks as $id) { 828 $query->select('MAX(' . $db->quoteName('ordering') . ')') 829 ->from($db->quoteName('#__content')) 830 ->where($db->quoteName('catid') . ' = :catid') 831 ->bind(':catid', $id, ParameterType::INTEGER); 832 833 $db->setQuery($query); 834 835 $max = (int) $db->loadResult(); 836 $max++; 837 838 $query->clear(); 839 840 $query->update($db->quoteName('#__content')) 841 ->set($db->quoteName('ordering') . ' = :max - ' . $db->quoteName('ordering')) 842 ->where($db->quoteName('catid') . ' = :catid') 843 ->bind(':max', $max, ParameterType::INTEGER) 844 ->bind(':catid', $id, ParameterType::INTEGER); 845 846 $db->setQuery($query); 847 848 if ($db->execute()) { 849 $successful[] = $id; 850 } 851 } 852 853 return empty($successful) ? false : $successful; 854 } 855 856 /** 857 * Batch copy categories to a new category. 858 * 859 * @param integer $value The new category. 860 * @param array $pks An array of row IDs. 861 * @param array $contexts An array of item contexts. 862 * 863 * @return mixed An array of new IDs on success, boolean false on failure. 864 * 865 * @since 1.6 866 */ 867 protected function batchCopy($value, $pks, $contexts) 868 { 869 $type = new UCMType(); 870 $this->type = $type->getTypeByAlias($this->typeAlias); 871 872 // $value comes as {parent_id}.{extension} 873 $parts = explode('.', $value); 874 $parentId = (int) ArrayHelper::getValue($parts, 0, 1); 875 876 $db = $this->getDatabase(); 877 $extension = Factory::getApplication()->input->get('extension', '', 'word'); 878 $newIds = array(); 879 880 // Check that the parent exists 881 if ($parentId) { 882 if (!$this->table->load($parentId)) { 883 if ($error = $this->table->getError()) { 884 // Fatal error 885 $this->setError($error); 886 887 return false; 888 } else { 889 // Non-fatal error 890 $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND')); 891 $parentId = 0; 892 } 893 } 894 895 // Check that user has create permission for parent category 896 if ($parentId == $this->table->getRootId()) { 897 $canCreate = $this->user->authorise('core.create', $extension); 898 } else { 899 $canCreate = $this->user->authorise('core.create', $extension . '.category.' . $parentId); 900 } 901 902 if (!$canCreate) { 903 // Error since user cannot create in parent category 904 $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE')); 905 906 return false; 907 } 908 } 909 910 // If the parent is 0, set it to the ID of the root item in the tree 911 if (empty($parentId)) { 912 if (!$parentId = $this->table->getRootId()) { 913 $this->setError($this->table->getError()); 914 915 return false; 916 } elseif (!$this->user->authorise('core.create', $extension)) { 917 // Make sure we can create in root 918 $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE')); 919 920 return false; 921 } 922 } 923 924 // We need to log the parent ID 925 $parents = array(); 926 927 // Calculate the emergency stop count as a precaution against a runaway loop bug 928 $query = $db->getQuery(true) 929 ->select('COUNT(' . $db->quoteName('id') . ')') 930 ->from($db->quoteName('#__categories')); 931 $db->setQuery($query); 932 933 try { 934 $count = $db->loadResult(); 935 } catch (\RuntimeException $e) { 936 $this->setError($e->getMessage()); 937 938 return false; 939 } 940 941 // Parent exists so let's proceed 942 while (!empty($pks) && $count > 0) { 943 // Pop the first id off the stack 944 $pk = array_shift($pks); 945 946 $this->table->reset(); 947 948 // Check that the row actually exists 949 if (!$this->table->load($pk)) { 950 if ($error = $this->table->getError()) { 951 // Fatal error 952 $this->setError($error); 953 954 return false; 955 } else { 956 // Not fatal error 957 $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk)); 958 continue; 959 } 960 } 961 962 // Copy is a bit tricky, because we also need to copy the children 963 $lft = (int) $this->table->lft; 964 $rgt = (int) $this->table->rgt; 965 $query->clear() 966 ->select($db->quoteName('id')) 967 ->from($db->quoteName('#__categories')) 968 ->where($db->quoteName('lft') . ' > :lft') 969 ->where($db->quoteName('rgt') . ' < :rgt') 970 ->bind(':lft', $lft, ParameterType::INTEGER) 971 ->bind(':rgt', $rgt, ParameterType::INTEGER); 972 $db->setQuery($query); 973 $childIds = $db->loadColumn(); 974 975 // Add child ID's to the array only if they aren't already there. 976 foreach ($childIds as $childId) { 977 if (!\in_array($childId, $pks)) { 978 $pks[] = $childId; 979 } 980 } 981 982 // Make a copy of the old ID, Parent ID and Asset ID 983 $oldId = $this->table->id; 984 $oldParentId = $this->table->parent_id; 985 $oldAssetId = $this->table->asset_id; 986 987 // Reset the id because we are making a copy. 988 $this->table->id = 0; 989 990 // If we a copying children, the Old ID will turn up in the parents list 991 // otherwise it's a new top level item 992 $this->table->parent_id = $parents[$oldParentId] ?? $parentId; 993 994 // Set the new location in the tree for the node. 995 $this->table->setLocation($this->table->parent_id, 'last-child'); 996 997 // @TODO: Deal with ordering? 998 // $this->table->ordering = 1; 999 $this->table->level = null; 1000 $this->table->asset_id = null; 1001 $this->table->lft = null; 1002 $this->table->rgt = null; 1003 1004 // Alter the title & alias 1005 [$title, $alias] = $this->generateNewTitle($this->table->parent_id, $this->table->alias, $this->table->title); 1006 $this->table->title = $title; 1007 $this->table->alias = $alias; 1008 1009 // Unpublish because we are making a copy 1010 $this->table->published = 0; 1011 1012 // Store the row. 1013 if (!$this->table->store()) { 1014 $this->setError($this->table->getError()); 1015 1016 return false; 1017 } 1018 1019 // Get the new item ID 1020 $newId = $this->table->get('id'); 1021 1022 // Add the new ID to the array 1023 $newIds[$pk] = $newId; 1024 1025 // Copy rules 1026 $query->clear() 1027 ->update($db->quoteName('#__assets', 't')) 1028 ->join( 1029 'INNER', 1030 $db->quoteName('#__assets', 's'), 1031 $db->quoteName('s.id') . ' = :oldid' 1032 ) 1033 ->bind(':oldid', $oldAssetId, ParameterType::INTEGER) 1034 ->set($db->quoteName('t.rules') . ' = ' . $db->quoteName('s.rules')) 1035 ->where($db->quoteName('t.id') . ' = :assetid') 1036 ->bind(':assetid', $this->table->asset_id, ParameterType::INTEGER); 1037 $db->setQuery($query)->execute(); 1038 1039 // Now we log the old 'parent' to the new 'parent' 1040 $parents[$oldId] = $this->table->id; 1041 $count--; 1042 } 1043 1044 // Rebuild the hierarchy. 1045 if (!$this->table->rebuild()) { 1046 $this->setError($this->table->getError()); 1047 1048 return false; 1049 } 1050 1051 // Rebuild the tree path. 1052 if (!$this->table->rebuildPath($this->table->id)) { 1053 $this->setError($this->table->getError()); 1054 1055 return false; 1056 } 1057 1058 return $newIds; 1059 } 1060 1061 /** 1062 * Batch move categories to a new category. 1063 * 1064 * @param integer $value The new category ID. 1065 * @param array $pks An array of row IDs. 1066 * @param array $contexts An array of item contexts. 1067 * 1068 * @return boolean True on success. 1069 * 1070 * @since 1.6 1071 */ 1072 protected function batchMove($value, $pks, $contexts) 1073 { 1074 $parentId = (int) $value; 1075 $type = new UCMType(); 1076 $this->type = $type->getTypeByAlias($this->typeAlias); 1077 1078 $db = $this->getDatabase(); 1079 $query = $db->getQuery(true); 1080 $extension = Factory::getApplication()->input->get('extension', '', 'word'); 1081 1082 // Check that the parent exists. 1083 if ($parentId) { 1084 if (!$this->table->load($parentId)) { 1085 if ($error = $this->table->getError()) { 1086 // Fatal error. 1087 $this->setError($error); 1088 1089 return false; 1090 } else { 1091 // Non-fatal error. 1092 $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND')); 1093 $parentId = 0; 1094 } 1095 } 1096 1097 // Check that user has create permission for parent category. 1098 if ($parentId == $this->table->getRootId()) { 1099 $canCreate = $this->user->authorise('core.create', $extension); 1100 } else { 1101 $canCreate = $this->user->authorise('core.create', $extension . '.category.' . $parentId); 1102 } 1103 1104 if (!$canCreate) { 1105 // Error since user cannot create in parent category 1106 $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE')); 1107 1108 return false; 1109 } 1110 1111 // Check that user has edit permission for every category being moved 1112 // Note that the entire batch operation fails if any category lacks edit permission 1113 foreach ($pks as $pk) { 1114 if (!$this->user->authorise('core.edit', $extension . '.category.' . $pk)) { 1115 // Error since user cannot edit this category 1116 $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_EDIT')); 1117 1118 return false; 1119 } 1120 } 1121 } 1122 1123 // We are going to store all the children and just move the category 1124 $children = array(); 1125 1126 // Parent exists so let's proceed 1127 foreach ($pks as $pk) { 1128 // Check that the row actually exists 1129 if (!$this->table->load($pk)) { 1130 if ($error = $this->table->getError()) { 1131 // Fatal error 1132 $this->setError($error); 1133 1134 return false; 1135 } else { 1136 // Not fatal error 1137 $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk)); 1138 continue; 1139 } 1140 } 1141 1142 // Set the new location in the tree for the node. 1143 $this->table->setLocation($parentId, 'last-child'); 1144 1145 // Check if we are moving to a different parent 1146 if ($parentId != $this->table->parent_id) { 1147 $lft = (int) $this->table->lft; 1148 $rgt = (int) $this->table->rgt; 1149 1150 // Add the child node ids to the children array. 1151 $query->clear() 1152 ->select($db->quoteName('id')) 1153 ->from($db->quoteName('#__categories')) 1154 ->where($db->quoteName('lft') . ' BETWEEN :lft AND :rgt') 1155 ->bind(':lft', $lft, ParameterType::INTEGER) 1156 ->bind(':rgt', $rgt, ParameterType::INTEGER); 1157 $db->setQuery($query); 1158 1159 try { 1160 $children = array_merge($children, (array) $db->loadColumn()); 1161 } catch (\RuntimeException $e) { 1162 $this->setError($e->getMessage()); 1163 1164 return false; 1165 } 1166 } 1167 1168 // Store the row. 1169 if (!$this->table->store()) { 1170 $this->setError($this->table->getError()); 1171 1172 return false; 1173 } 1174 1175 // Rebuild the tree path. 1176 if (!$this->table->rebuildPath()) { 1177 $this->setError($this->table->getError()); 1178 1179 return false; 1180 } 1181 } 1182 1183 // Process the child rows 1184 if (!empty($children)) { 1185 // Remove any duplicates and sanitize ids. 1186 $children = array_unique($children); 1187 $children = ArrayHelper::toInteger($children); 1188 } 1189 1190 return true; 1191 } 1192 1193 /** 1194 * Custom clean the cache of com_content and content modules 1195 * 1196 * @param string $group Cache group name. 1197 * @param integer $clientId @deprecated 5.0 No longer used. 1198 * 1199 * @return void 1200 * 1201 * @since 1.6 1202 */ 1203 protected function cleanCache($group = null, $clientId = 0) 1204 { 1205 $extension = Factory::getApplication()->input->get('extension'); 1206 1207 switch ($extension) { 1208 case 'com_content': 1209 parent::cleanCache('com_content'); 1210 parent::cleanCache('mod_articles_archive'); 1211 parent::cleanCache('mod_articles_categories'); 1212 parent::cleanCache('mod_articles_category'); 1213 parent::cleanCache('mod_articles_latest'); 1214 parent::cleanCache('mod_articles_news'); 1215 parent::cleanCache('mod_articles_popular'); 1216 break; 1217 default: 1218 parent::cleanCache($extension); 1219 break; 1220 } 1221 } 1222 1223 /** 1224 * Method to change the title & alias. 1225 * 1226 * @param integer $parentId The id of the parent. 1227 * @param string $alias The alias. 1228 * @param string $title The title. 1229 * 1230 * @return array Contains the modified title and alias. 1231 * 1232 * @since 1.7 1233 */ 1234 protected function generateNewTitle($parentId, $alias, $title) 1235 { 1236 // Alter the title & alias 1237 $table = $this->getTable(); 1238 1239 while ($table->load(array('alias' => $alias, 'parent_id' => $parentId))) { 1240 $title = StringHelper::increment($title); 1241 $alias = StringHelper::increment($alias, 'dash'); 1242 } 1243 1244 return array($title, $alias); 1245 } 1246 1247 /** 1248 * Method to determine if a category association is available. 1249 * 1250 * @return boolean True if a category association is available; false otherwise. 1251 */ 1252 public function getAssoc() 1253 { 1254 if (!\is_null($this->hasAssociation)) { 1255 return $this->hasAssociation; 1256 } 1257 1258 $extension = $this->getState('category.extension', ''); 1259 1260 $this->hasAssociation = Associations::isEnabled(); 1261 $extension = explode('.', $extension); 1262 $component = array_shift($extension); 1263 $cname = str_replace('com_', '', $component); 1264 1265 if (!$this->hasAssociation || !$component || !$cname) { 1266 $this->hasAssociation = false; 1267 1268 return $this->hasAssociation; 1269 } 1270 1271 $componentObject = $this->bootComponent($component); 1272 1273 if ($componentObject instanceof AssociationServiceInterface && $componentObject instanceof CategoryServiceInterface) { 1274 $this->hasAssociation = true; 1275 1276 return $this->hasAssociation; 1277 } 1278 1279 $hname = $cname . 'HelperAssociation'; 1280 \JLoader::register($hname, JPATH_SITE . '/components/' . $component . '/helpers/association.php'); 1281 1282 $this->hasAssociation = class_exists($hname) && !empty($hname::$category_association); 1283 1284 return $this->hasAssociation; 1285 } 1286 }
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 |