[ 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_finder 6 * 7 * @copyright (C) 2011 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\Finder\Administrator\Indexer; 12 13 use Exception; 14 use Joomla\CMS\Factory; 15 use Joomla\CMS\Plugin\CMSPlugin; 16 use Joomla\CMS\Table\Table; 17 use Joomla\Database\DatabaseInterface; 18 use Joomla\Database\QueryInterface; 19 use Joomla\Utilities\ArrayHelper; 20 21 // phpcs:disable PSR1.Files.SideEffects 22 \defined('_JEXEC') or die; 23 // phpcs:enable PSR1.Files.SideEffects 24 25 /** 26 * Prototype adapter class for the Finder indexer package. 27 * 28 * @since 2.5 29 */ 30 abstract class Adapter extends CMSPlugin 31 { 32 /** 33 * The context is somewhat arbitrary but it must be unique or there will be 34 * conflicts when managing plugin/indexer state. A good best practice is to 35 * use the plugin name suffix as the context. For example, if the plugin is 36 * named 'plgFinderContent', the context could be 'Content'. 37 * 38 * @var string 39 * @since 2.5 40 */ 41 protected $context; 42 43 /** 44 * The extension name. 45 * 46 * @var string 47 * @since 2.5 48 */ 49 protected $extension; 50 51 /** 52 * The sublayout to use when rendering the results. 53 * 54 * @var string 55 * @since 2.5 56 */ 57 protected $layout; 58 59 /** 60 * The mime type of the content the adapter indexes. 61 * 62 * @var string 63 * @since 2.5 64 */ 65 protected $mime; 66 67 /** 68 * The access level of an item before save. 69 * 70 * @var integer 71 * @since 2.5 72 */ 73 protected $old_access; 74 75 /** 76 * The access level of a category before save. 77 * 78 * @var integer 79 * @since 2.5 80 */ 81 protected $old_cataccess; 82 83 /** 84 * The type of content the adapter indexes. 85 * 86 * @var string 87 * @since 2.5 88 */ 89 protected $type_title; 90 91 /** 92 * The type id of the content. 93 * 94 * @var integer 95 * @since 2.5 96 */ 97 protected $type_id; 98 99 /** 100 * The database object. 101 * 102 * @var DatabaseInterface 103 * @since 2.5 104 */ 105 protected $db; 106 107 /** 108 * The table name. 109 * 110 * @var string 111 * @since 2.5 112 */ 113 protected $table; 114 115 /** 116 * The indexer object. 117 * 118 * @var Indexer 119 * @since 3.0 120 */ 121 protected $indexer; 122 123 /** 124 * The field the published state is stored in. 125 * 126 * @var string 127 * @since 2.5 128 */ 129 protected $state_field = 'state'; 130 131 /** 132 * Method to instantiate the indexer adapter. 133 * 134 * @param object $subject The object to observe. 135 * @param array $config An array that holds the plugin configuration. 136 * 137 * @since 2.5 138 */ 139 public function __construct(&$subject, $config) 140 { 141 // Call the parent constructor. 142 parent::__construct($subject, $config); 143 144 // Get the type id. 145 $this->type_id = $this->getTypeId(); 146 147 // Add the content type if it doesn't exist and is set. 148 if (empty($this->type_id) && !empty($this->type_title)) { 149 $this->type_id = Helper::addContentType($this->type_title, $this->mime); 150 } 151 152 // Check for a layout override. 153 if ($this->params->get('layout')) { 154 $this->layout = $this->params->get('layout'); 155 } 156 157 // Get the indexer object 158 $this->indexer = new Indexer($this->db); 159 } 160 161 /** 162 * Method to get the adapter state and push it into the indexer. 163 * 164 * @return void 165 * 166 * @since 2.5 167 * @throws Exception on error. 168 */ 169 public function onStartIndex() 170 { 171 // Get the indexer state. 172 $iState = Indexer::getState(); 173 174 // Get the number of content items. 175 $total = (int) $this->getContentCount(); 176 177 // Add the content count to the total number of items. 178 $iState->totalItems += $total; 179 180 // Populate the indexer state information for the adapter. 181 $iState->pluginState[$this->context]['total'] = $total; 182 $iState->pluginState[$this->context]['offset'] = 0; 183 184 // Set the indexer state. 185 Indexer::setState($iState); 186 } 187 188 /** 189 * Method to prepare for the indexer to be run. This method will often 190 * be used to include dependencies and things of that nature. 191 * 192 * @return boolean True on success. 193 * 194 * @since 2.5 195 * @throws Exception on error. 196 */ 197 public function onBeforeIndex() 198 { 199 // Get the indexer and adapter state. 200 $iState = Indexer::getState(); 201 $aState = $iState->pluginState[$this->context]; 202 203 // Check the progress of the indexer and the adapter. 204 if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total']) { 205 return true; 206 } 207 208 // Run the setup method. 209 return $this->setup(); 210 } 211 212 /** 213 * Method to index a batch of content items. This method can be called by 214 * the indexer many times throughout the indexing process depending on how 215 * much content is available for indexing. It is important to track the 216 * progress correctly so we can display it to the user. 217 * 218 * @return boolean True on success. 219 * 220 * @since 2.5 221 * @throws Exception on error. 222 */ 223 public function onBuildIndex() 224 { 225 // Get the indexer and adapter state. 226 $iState = Indexer::getState(); 227 $aState = $iState->pluginState[$this->context]; 228 229 // Check the progress of the indexer and the adapter. 230 if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total']) { 231 return true; 232 } 233 234 // Get the batch offset and size. 235 $offset = (int) $aState['offset']; 236 $limit = (int) ($iState->batchSize - $iState->batchOffset); 237 238 // Get the content items to index. 239 $items = $this->getItems($offset, $limit); 240 241 // Iterate through the items and index them. 242 for ($i = 0, $n = count($items); $i < $n; $i++) { 243 // Index the item. 244 $this->index($items[$i]); 245 246 // Adjust the offsets. 247 $offset++; 248 $iState->batchOffset++; 249 $iState->totalItems--; 250 } 251 252 // Update the indexer state. 253 $aState['offset'] = $offset; 254 $iState->pluginState[$this->context] = $aState; 255 Indexer::setState($iState); 256 257 return true; 258 } 259 260 /** 261 * Method to remove outdated index entries 262 * 263 * @return integer 264 * 265 * @since 4.2.0 266 */ 267 public function onFinderGarbageCollection() 268 { 269 $db = $this->db; 270 $type_id = $this->getTypeId(); 271 272 $query = $db->getQuery(true); 273 $subquery = $db->getQuery(true); 274 $subquery->select('CONCAT(' . $db->quote($this->getUrl('', $this->extension, $this->layout)) . ', id)') 275 ->from($db->quoteName($this->table)); 276 $query->select($db->quoteName('l.link_id')) 277 ->from($db->quoteName('#__finder_links', 'l')) 278 ->where($db->quoteName('l.type_id') . ' = ' . $type_id) 279 ->where($db->quoteName('l.url') . ' LIKE ' . $db->quote($this->getUrl('%', $this->extension, $this->layout))) 280 ->where($db->quoteName('l.url') . ' NOT IN (' . $subquery . ')'); 281 $db->setQuery($query); 282 $items = $db->loadColumn(); 283 284 foreach ($items as $item) { 285 $this->indexer->remove($item); 286 } 287 288 return count($items); 289 } 290 291 /** 292 * Method to change the value of a content item's property in the links 293 * table. This is used to synchronize published and access states that 294 * are changed when not editing an item directly. 295 * 296 * @param string $id The ID of the item to change. 297 * @param string $property The property that is being changed. 298 * @param integer $value The new value of that property. 299 * 300 * @return boolean True on success. 301 * 302 * @since 2.5 303 * @throws Exception on database error. 304 */ 305 protected function change($id, $property, $value) 306 { 307 // Check for a property we know how to handle. 308 if ($property !== 'state' && $property !== 'access') { 309 return true; 310 } 311 312 // Get the URL for the content id. 313 $item = $this->db->quote($this->getUrl($id, $this->extension, $this->layout)); 314 315 // Update the content items. 316 $query = $this->db->getQuery(true) 317 ->update($this->db->quoteName('#__finder_links')) 318 ->set($this->db->quoteName($property) . ' = ' . (int) $value) 319 ->where($this->db->quoteName('url') . ' = ' . $item); 320 $this->db->setQuery($query); 321 $this->db->execute(); 322 323 return true; 324 } 325 326 /** 327 * Method to index an item. 328 * 329 * @param Result $item The item to index as a Result object. 330 * 331 * @return boolean True on success. 332 * 333 * @since 2.5 334 * @throws Exception on database error. 335 */ 336 abstract protected function index(Result $item); 337 338 /** 339 * Method to reindex an item. 340 * 341 * @param integer $id The ID of the item to reindex. 342 * 343 * @return void 344 * 345 * @since 2.5 346 * @throws Exception on database error. 347 */ 348 protected function reindex($id) 349 { 350 // Run the setup method. 351 $this->setup(); 352 353 // Remove the old item. 354 $this->remove($id, false); 355 356 // Get the item. 357 $item = $this->getItem($id); 358 359 // Index the item. 360 $this->index($item); 361 362 Taxonomy::removeOrphanNodes(); 363 } 364 365 /** 366 * Method to remove an item from the index. 367 * 368 * @param string $id The ID of the item to remove. 369 * @param bool $removeTaxonomies Remove empty taxonomies 370 * 371 * @return boolean True on success. 372 * 373 * @since 2.5 374 * @throws Exception on database error. 375 */ 376 protected function remove($id, $removeTaxonomies = true) 377 { 378 // Get the item's URL 379 $url = $this->db->quote($this->getUrl($id, $this->extension, $this->layout)); 380 381 // Get the link ids for the content items. 382 $query = $this->db->getQuery(true) 383 ->select($this->db->quoteName('link_id')) 384 ->from($this->db->quoteName('#__finder_links')) 385 ->where($this->db->quoteName('url') . ' = ' . $url); 386 $this->db->setQuery($query); 387 $items = $this->db->loadColumn(); 388 389 // Check the items. 390 if (empty($items)) { 391 Factory::getApplication()->triggerEvent('onFinderIndexAfterDelete', array($id)); 392 393 return true; 394 } 395 396 // Remove the items. 397 foreach ($items as $item) { 398 $this->indexer->remove($item, $removeTaxonomies); 399 } 400 401 return true; 402 } 403 404 /** 405 * Method to setup the adapter before indexing. 406 * 407 * @return boolean True on success, false on failure. 408 * 409 * @since 2.5 410 * @throws Exception on database error. 411 */ 412 abstract protected function setup(); 413 414 /** 415 * Method to update index data on category access level changes 416 * 417 * @param Table $row A Table object 418 * 419 * @return void 420 * 421 * @since 2.5 422 */ 423 protected function categoryAccessChange($row) 424 { 425 $query = clone $this->getStateQuery(); 426 $query->where('c.id = ' . (int) $row->id); 427 428 // Get the access level. 429 $this->db->setQuery($query); 430 $items = $this->db->loadObjectList(); 431 432 // Adjust the access level for each item within the category. 433 foreach ($items as $item) { 434 // Set the access level. 435 $temp = max($item->access, $row->access); 436 437 // Update the item. 438 $this->change((int) $item->id, 'access', $temp); 439 } 440 } 441 442 /** 443 * Method to update index data on category access level changes 444 * 445 * @param array $pks A list of primary key ids of the content that has changed state. 446 * @param integer $value The value of the state that the content has been changed to. 447 * 448 * @return void 449 * 450 * @since 2.5 451 */ 452 protected function categoryStateChange($pks, $value) 453 { 454 /* 455 * The item's published state is tied to the category 456 * published state so we need to look up all published states 457 * before we change anything. 458 */ 459 foreach ($pks as $pk) { 460 $query = clone $this->getStateQuery(); 461 $query->where('c.id = ' . (int) $pk); 462 463 // Get the published states. 464 $this->db->setQuery($query); 465 $items = $this->db->loadObjectList(); 466 467 // Adjust the state for each item within the category. 468 foreach ($items as $item) { 469 // Translate the state. 470 $temp = $this->translateState($item->state, $value); 471 472 // Update the item. 473 $this->change($item->id, 'state', $temp); 474 } 475 } 476 } 477 478 /** 479 * Method to check the existing access level for categories 480 * 481 * @param Table $row A Table object 482 * 483 * @return void 484 * 485 * @since 2.5 486 */ 487 protected function checkCategoryAccess($row) 488 { 489 $query = $this->db->getQuery(true) 490 ->select($this->db->quoteName('access')) 491 ->from($this->db->quoteName('#__categories')) 492 ->where($this->db->quoteName('id') . ' = ' . (int) $row->id); 493 $this->db->setQuery($query); 494 495 // Store the access level to determine if it changes 496 $this->old_cataccess = $this->db->loadResult(); 497 } 498 499 /** 500 * Method to check the existing access level for items 501 * 502 * @param Table $row A Table object 503 * 504 * @return void 505 * 506 * @since 2.5 507 */ 508 protected function checkItemAccess($row) 509 { 510 $query = $this->db->getQuery(true) 511 ->select($this->db->quoteName('access')) 512 ->from($this->db->quoteName($this->table)) 513 ->where($this->db->quoteName('id') . ' = ' . (int) $row->id); 514 $this->db->setQuery($query); 515 516 // Store the access level to determine if it changes 517 $this->old_access = $this->db->loadResult(); 518 } 519 520 /** 521 * Method to get the number of content items available to index. 522 * 523 * @return integer The number of content items available to index. 524 * 525 * @since 2.5 526 * @throws Exception on database error. 527 */ 528 protected function getContentCount() 529 { 530 $return = 0; 531 532 // Get the list query. 533 $query = $this->getListQuery(); 534 535 // Check if the query is valid. 536 if (empty($query)) { 537 return $return; 538 } 539 540 // Tweak the SQL query to make the total lookup faster. 541 if ($query instanceof QueryInterface) { 542 $query = clone $query; 543 $query->clear('select') 544 ->select('COUNT(*)') 545 ->clear('order'); 546 } 547 548 // Get the total number of content items to index. 549 $this->db->setQuery($query); 550 551 return (int) $this->db->loadResult(); 552 } 553 554 /** 555 * Method to get a content item to index. 556 * 557 * @param integer $id The id of the content item. 558 * 559 * @return Result A Result object. 560 * 561 * @since 2.5 562 * @throws Exception on database error. 563 */ 564 protected function getItem($id) 565 { 566 // Get the list query and add the extra WHERE clause. 567 $query = $this->getListQuery(); 568 $query->where('a.id = ' . (int) $id); 569 570 // Get the item to index. 571 $this->db->setQuery($query); 572 $item = $this->db->loadAssoc(); 573 574 // Convert the item to a result object. 575 $item = ArrayHelper::toObject((array) $item, Result::class); 576 577 // Set the item type. 578 $item->type_id = $this->type_id; 579 580 // Set the item layout. 581 $item->layout = $this->layout; 582 583 return $item; 584 } 585 586 /** 587 * Method to get a list of content items to index. 588 * 589 * @param integer $offset The list offset. 590 * @param integer $limit The list limit. 591 * @param QueryInterface $query A QueryInterface object. [optional] 592 * 593 * @return Result[] An array of Result objects. 594 * 595 * @since 2.5 596 * @throws Exception on database error. 597 */ 598 protected function getItems($offset, $limit, $query = null) 599 { 600 // Get the content items to index. 601 $this->db->setQuery($this->getListQuery($query)->setLimit($limit, $offset)); 602 $items = $this->db->loadAssocList(); 603 604 foreach ($items as &$item) { 605 $item = ArrayHelper::toObject($item, Result::class); 606 607 // Set the item type. 608 $item->type_id = $this->type_id; 609 610 // Set the mime type. 611 $item->mime = $this->mime; 612 613 // Set the item layout. 614 $item->layout = $this->layout; 615 } 616 617 return $items; 618 } 619 620 /** 621 * Method to get the SQL query used to retrieve the list of content items. 622 * 623 * @param mixed $query A QueryInterface object. [optional] 624 * 625 * @return QueryInterface A database object. 626 * 627 * @since 2.5 628 */ 629 protected function getListQuery($query = null) 630 { 631 // Check if we can use the supplied SQL query. 632 return $query instanceof QueryInterface ? $query : $this->db->getQuery(true); 633 } 634 635 /** 636 * Method to get the plugin type 637 * 638 * @param integer $id The plugin ID 639 * 640 * @return string The plugin type 641 * 642 * @since 2.5 643 */ 644 protected function getPluginType($id) 645 { 646 // Prepare the query 647 $query = $this->db->getQuery(true) 648 ->select($this->db->quoteName('element')) 649 ->from($this->db->quoteName('#__extensions')) 650 ->where($this->db->quoteName('extension_id') . ' = ' . (int) $id); 651 $this->db->setQuery($query); 652 653 return $this->db->loadResult(); 654 } 655 656 /** 657 * Method to get a SQL query to load the published and access states for 658 * an article and category. 659 * 660 * @return QueryInterface A database object. 661 * 662 * @since 2.5 663 */ 664 protected function getStateQuery() 665 { 666 $query = $this->db->getQuery(true); 667 668 // Item ID 669 $query->select('a.id'); 670 671 // Item and category published state 672 $query->select('a.' . $this->state_field . ' AS state, c.published AS cat_state'); 673 674 // Item and category access levels 675 $query->select('a.access, c.access AS cat_access') 676 ->from($this->table . ' AS a') 677 ->join('LEFT', '#__categories AS c ON c.id = a.catid'); 678 679 return $query; 680 } 681 682 /** 683 * Method to get the query clause for getting items to update by time. 684 * 685 * @param string $time The modified timestamp. 686 * 687 * @return QueryInterface A database object. 688 * 689 * @since 2.5 690 */ 691 protected function getUpdateQueryByTime($time) 692 { 693 // Build an SQL query based on the modified time. 694 $query = $this->db->getQuery(true) 695 ->where('a.modified >= ' . $this->db->quote($time)); 696 697 return $query; 698 } 699 700 /** 701 * Method to get the query clause for getting items to update by id. 702 * 703 * @param array $ids The ids to load. 704 * 705 * @return QueryInterface A database object. 706 * 707 * @since 2.5 708 */ 709 protected function getUpdateQueryByIds($ids) 710 { 711 // Build an SQL query based on the item ids. 712 $query = $this->db->getQuery(true) 713 ->where('a.id IN(' . implode(',', $ids) . ')'); 714 715 return $query; 716 } 717 718 /** 719 * Method to get the type id for the adapter content. 720 * 721 * @return integer The numeric type id for the content. 722 * 723 * @since 2.5 724 * @throws Exception on database error. 725 */ 726 protected function getTypeId() 727 { 728 // Get the type id from the database. 729 $query = $this->db->getQuery(true) 730 ->select($this->db->quoteName('id')) 731 ->from($this->db->quoteName('#__finder_types')) 732 ->where($this->db->quoteName('title') . ' = ' . $this->db->quote($this->type_title)); 733 $this->db->setQuery($query); 734 735 return (int) $this->db->loadResult(); 736 } 737 738 /** 739 * Method to get the URL for the item. The URL is how we look up the link 740 * in the Finder index. 741 * 742 * @param integer $id The id of the item. 743 * @param string $extension The extension the category is in. 744 * @param string $view The view for the URL. 745 * 746 * @return string The URL of the item. 747 * 748 * @since 2.5 749 */ 750 protected function getUrl($id, $extension, $view) 751 { 752 return 'index.php?option=' . $extension . '&view=' . $view . '&id=' . $id; 753 } 754 755 /** 756 * Method to get the page title of any menu item that is linked to the 757 * content item, if it exists and is set. 758 * 759 * @param string $url The URL of the item. 760 * 761 * @return mixed The title on success, null if not found. 762 * 763 * @since 2.5 764 * @throws Exception on database error. 765 */ 766 protected function getItemMenuTitle($url) 767 { 768 $return = null; 769 770 // Set variables 771 $user = Factory::getUser(); 772 $groups = implode(',', $user->getAuthorisedViewLevels()); 773 774 // Build a query to get the menu params. 775 $query = $this->db->getQuery(true) 776 ->select($this->db->quoteName('params')) 777 ->from($this->db->quoteName('#__menu')) 778 ->where($this->db->quoteName('link') . ' = ' . $this->db->quote($url)) 779 ->where($this->db->quoteName('published') . ' = 1') 780 ->where($this->db->quoteName('access') . ' IN (' . $groups . ')'); 781 782 // Get the menu params from the database. 783 $this->db->setQuery($query); 784 $params = $this->db->loadResult(); 785 786 // Check the results. 787 if (empty($params)) { 788 return $return; 789 } 790 791 // Instantiate the params. 792 $params = json_decode($params); 793 794 // Get the page title if it is set. 795 if (isset($params->page_title) && $params->page_title) { 796 $return = $params->page_title; 797 } 798 799 return $return; 800 } 801 802 /** 803 * Method to update index data on access level changes 804 * 805 * @param Table $row A Table object 806 * 807 * @return void 808 * 809 * @since 2.5 810 */ 811 protected function itemAccessChange($row) 812 { 813 $query = clone $this->getStateQuery(); 814 $query->where('a.id = ' . (int) $row->id); 815 816 // Get the access level. 817 $this->db->setQuery($query); 818 $item = $this->db->loadObject(); 819 820 // Set the access level. 821 $temp = max($row->access, $item->cat_access); 822 823 // Update the item. 824 $this->change((int) $row->id, 'access', $temp); 825 } 826 827 /** 828 * Method to update index data on published state changes 829 * 830 * @param array $pks A list of primary key ids of the content that has changed state. 831 * @param integer $value The value of the state that the content has been changed to. 832 * 833 * @return void 834 * 835 * @since 2.5 836 */ 837 protected function itemStateChange($pks, $value) 838 { 839 /* 840 * The item's published state is tied to the category 841 * published state so we need to look up all published states 842 * before we change anything. 843 */ 844 foreach ($pks as $pk) { 845 $query = clone $this->getStateQuery(); 846 $query->where('a.id = ' . (int) $pk); 847 848 // Get the published states. 849 $this->db->setQuery($query); 850 $item = $this->db->loadObject(); 851 852 // Translate the state. 853 $temp = $this->translateState($value, $item->cat_state); 854 855 // Update the item. 856 $this->change($pk, 'state', $temp); 857 } 858 } 859 860 /** 861 * Method to update index data when a plugin is disabled 862 * 863 * @param array $pks A list of primary key ids of the content that has changed state. 864 * 865 * @return void 866 * 867 * @since 2.5 868 */ 869 protected function pluginDisable($pks) 870 { 871 // Since multiple plugins may be disabled at a time, we need to check first 872 // that we're handling the appropriate one for the context 873 foreach ($pks as $pk) { 874 if ($this->getPluginType($pk) == strtolower($this->context)) { 875 // Get all of the items to unindex them 876 $query = clone $this->getStateQuery(); 877 $this->db->setQuery($query); 878 $items = $this->db->loadColumn(); 879 880 // Remove each item 881 foreach ($items as $item) { 882 $this->remove($item); 883 } 884 } 885 } 886 } 887 888 /** 889 * Method to translate the native content states into states that the 890 * indexer can use. 891 * 892 * @param integer $item The item state. 893 * @param integer $category The category state. [optional] 894 * 895 * @return integer The translated indexer state. 896 * 897 * @since 2.5 898 */ 899 protected function translateState($item, $category = null) 900 { 901 // If category is present, factor in its states as well 902 if ($category !== null && $category == 0) { 903 $item = 0; 904 } 905 906 // Translate the state 907 switch ($item) { 908 // Published and archived items only should return a published state 909 case 1: 910 case 2: 911 return 1; 912 913 // All other states should return an unpublished state 914 default: 915 return 0; 916 } 917 } 918 }
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 |