1<?php 2/** 3 * Joomla! Content Management System 4 * 5 * @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE.txt 7 */ 8 9namespace Joomla\CMS\Helper; 10 11defined('JPATH_PLATFORM') or die; 12 13use Joomla\CMS\Component\ComponentHelper; 14use Joomla\CMS\Table\Table; 15use Joomla\CMS\Table\TableInterface; 16use Joomla\Utilities\ArrayHelper; 17 18/** 19 * Tags helper class, provides methods to perform various tasks relevant 20 * tagging of content. 21 * 22 * @since 3.1 23 */ 24class TagsHelper extends CMSHelper 25{ 26 /** 27 * Helper object for storing and deleting tag information. 28 * 29 * @var boolean 30 * @since 3.1 31 */ 32 protected $tagsChanged = false; 33 34 /** 35 * Whether up replace all tags or just add tags 36 * 37 * @var boolean 38 * @since 3.1 39 */ 40 protected $replaceTags = false; 41 42 /** 43 * Alias for querying mapping and content type table. 44 * 45 * @var string 46 * @since 3.1 47 */ 48 public $typeAlias = null; 49 50 /** 51 * Method to add tag rows to mapping table. 52 * 53 * @param integer $ucmId ID of the #__ucm_content item being tagged 54 * @param TableInterface $table Table object being tagged 55 * @param array $tags Array of tags to be applied. 56 * 57 * @return boolean true on success, otherwise false. 58 * 59 * @since 3.1 60 */ 61 public function addTagMapping($ucmId, TableInterface $table, $tags = array()) 62 { 63 $db = $table->getDbo(); 64 $key = $table->getKeyName(); 65 $item = $table->$key; 66 $typeId = $this->getTypeId($this->typeAlias); 67 68 // Insert the new tag maps 69 if (strpos('#', implode(',', $tags)) === false) 70 { 71 $tags = self::createTagsFromField($tags); 72 } 73 74 // Prevent saving duplicate tags 75 $tags = array_unique($tags); 76 77 $query = $db->getQuery(true); 78 $query->insert('#__contentitem_tag_map'); 79 $query->columns( 80 array( 81 $db->quoteName('type_alias'), 82 $db->quoteName('core_content_id'), 83 $db->quoteName('content_item_id'), 84 $db->quoteName('tag_id'), 85 $db->quoteName('tag_date'), 86 $db->quoteName('type_id'), 87 ) 88 ); 89 90 foreach ($tags as $tag) 91 { 92 $query->values( 93 $db->quote($this->typeAlias) 94 . ', ' . (int) $ucmId 95 . ', ' . (int) $item 96 . ', ' . $db->quote($tag) 97 . ', ' . $query->currentTimestamp() 98 . ', ' . (int) $typeId 99 ); 100 } 101 102 $db->setQuery($query); 103 104 return (boolean) $db->execute(); 105 } 106 107 /** 108 * Function that converts tags paths into paths of names 109 * 110 * @param array $tags Array of tags 111 * 112 * @return array 113 * 114 * @since 3.1 115 */ 116 public static function convertPathsToNames($tags) 117 { 118 // We will replace path aliases with tag names 119 if ($tags) 120 { 121 // Create an array with all the aliases of the results 122 $aliases = array(); 123 124 foreach ($tags as $tag) 125 { 126 if (!empty($tag->path)) 127 { 128 if ($pathParts = explode('/', $tag->path)) 129 { 130 $aliases = array_merge($aliases, $pathParts); 131 } 132 } 133 } 134 135 // Get the aliases titles in one single query and map the results 136 if ($aliases) 137 { 138 // Remove duplicates 139 $aliases = array_unique($aliases); 140 141 $db = \JFactory::getDbo(); 142 143 $query = $db->getQuery(true) 144 ->select('alias, title') 145 ->from('#__tags') 146 ->where('alias IN (' . implode(',', array_map(array($db, 'quote'), $aliases)) . ')'); 147 $db->setQuery($query); 148 149 try 150 { 151 $aliasesMapper = $db->loadAssocList('alias'); 152 } 153 catch (\RuntimeException $e) 154 { 155 return false; 156 } 157 158 // Rebuild the items path 159 if ($aliasesMapper) 160 { 161 foreach ($tags as $tag) 162 { 163 $namesPath = array(); 164 165 if (!empty($tag->path)) 166 { 167 if ($pathParts = explode('/', $tag->path)) 168 { 169 foreach ($pathParts as $alias) 170 { 171 if (isset($aliasesMapper[$alias])) 172 { 173 $namesPath[] = $aliasesMapper[$alias]['title']; 174 } 175 else 176 { 177 $namesPath[] = $alias; 178 } 179 } 180 181 $tag->text = implode('/', $namesPath); 182 } 183 } 184 } 185 } 186 } 187 } 188 189 return $tags; 190 } 191 192 /** 193 * Create any new tags by looking for #new# in the strings 194 * 195 * @param array $tags Tags text array from the field 196 * 197 * @return mixed If successful, metadata with new tag titles replaced by tag ids. Otherwise false. 198 * 199 * @since 3.1 200 */ 201 public function createTagsFromField($tags) 202 { 203 if (empty($tags) || $tags[0] == '') 204 { 205 return; 206 } 207 else 208 { 209 // We will use the tags table to store them 210 Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables'); 211 $tagTable = Table::getInstance('Tag', 'TagsTable'); 212 $newTags = array(); 213 $canCreate = \JFactory::getUser()->authorise('core.create', 'com_tags'); 214 215 foreach ($tags as $key => $tag) 216 { 217 // User is not allowed to create tags, so don't create. 218 if (!$canCreate && strpos($tag, '#new#') !== false) 219 { 220 continue; 221 } 222 223 // Remove the #new# prefix that identifies new tags 224 $tagText = str_replace('#new#', '', $tag); 225 226 if ($tagText === $tag) 227 { 228 $newTags[] = (int) $tag; 229 } 230 else 231 { 232 // Clear old data if exist 233 $tagTable->reset(); 234 235 // Try to load the selected tag 236 if ($tagTable->load(array('title' => $tagText))) 237 { 238 $newTags[] = (int) $tagTable->id; 239 } 240 else 241 { 242 // Prepare tag data 243 $tagTable->id = 0; 244 $tagTable->title = $tagText; 245 $tagTable->published = 1; 246 247 // $tagTable->language = property_exists ($item, 'language') ? $item->language : '*'; 248 $tagTable->language = '*'; 249 $tagTable->access = 1; 250 251 // Make this item a child of the root tag 252 $tagTable->setLocation($tagTable->getRootId(), 'last-child'); 253 254 // Try to store tag 255 if ($tagTable->check()) 256 { 257 // Assign the alias as path (autogenerated tags have always level 1) 258 $tagTable->path = $tagTable->alias; 259 260 if ($tagTable->store()) 261 { 262 $newTags[] = (int) $tagTable->id; 263 } 264 } 265 } 266 } 267 } 268 269 // At this point $tags is an array of all tag ids 270 $this->tags = $newTags; 271 $result = $newTags; 272 } 273 274 return $result; 275 } 276 277 /** 278 * Create any new tags by looking for #new# in the metadata 279 * 280 * @param string $metadata Metadata JSON string 281 * 282 * @return mixed If successful, metadata with new tag titles replaced by tag ids. Otherwise false. 283 * 284 * @since 3.1 285 * @deprecated 4.0 This method is no longer used in the CMS and will not be replaced. 286 */ 287 public function createTagsFromMetadata($metadata) 288 { 289 $metaObject = json_decode($metadata); 290 291 if (empty($metaObject->tags)) 292 { 293 return $metadata; 294 } 295 296 $tags = $metaObject->tags; 297 298 if (empty($tags) || !is_array($tags)) 299 { 300 $result = $metadata; 301 } 302 else 303 { 304 // We will use the tags table to store them 305 Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables'); 306 $tagTable = Table::getInstance('Tag', 'TagsTable'); 307 $newTags = array(); 308 309 foreach ($tags as $tag) 310 { 311 // Remove the #new# prefix that identifies new tags 312 $tagText = str_replace('#new#', '', $tag); 313 314 if ($tagText === $tag) 315 { 316 $newTags[] = (int) $tag; 317 } 318 else 319 { 320 // Clear old data if exist 321 $tagTable->reset(); 322 323 // Try to load the selected tag 324 if ($tagTable->load(array('title' => $tagText))) 325 { 326 $newTags[] = (int) $tagTable->id; 327 } 328 else 329 { 330 // Prepare tag data 331 $tagTable->id = 0; 332 $tagTable->title = $tagText; 333 $tagTable->published = 1; 334 335 // $tagTable->language = property_exists ($item, 'language') ? $item->language : '*'; 336 $tagTable->language = '*'; 337 $tagTable->access = 1; 338 339 // Make this item a child of the root tag 340 $tagTable->setLocation($tagTable->getRootId(), 'last-child'); 341 342 // Try to store tag 343 if ($tagTable->check()) 344 { 345 // Assign the alias as path (autogenerated tags have always level 1) 346 $tagTable->path = $tagTable->alias; 347 348 if ($tagTable->store()) 349 { 350 $newTags[] = (int) $tagTable->id; 351 } 352 } 353 } 354 } 355 } 356 357 // At this point $tags is an array of all tag ids 358 $metaObject->tags = $newTags; 359 $result = json_encode($metaObject); 360 } 361 362 return $result; 363 } 364 365 /** 366 * Method to delete the tag mappings and #__ucm_content record for for an item 367 * 368 * @param TableInterface $table Table object of content table where delete occurred 369 * @param integer|array $contentItemId ID of the content item. Or an array of key/value pairs with array key 370 * being a primary key name and value being the content item ID. Note 371 * multiple primary keys are not supported 372 * 373 * @return boolean true on success, false on failure 374 * 375 * @since 3.1 376 * @throws \InvalidArgumentException 377 */ 378 public function deleteTagData(TableInterface $table, $contentItemId) 379 { 380 $key = $table->getKeyName(); 381 382 if (!is_array($contentItemId)) 383 { 384 $contentItemId = array($key => $contentItemId); 385 } 386 387 // If we have multiple items for the content item primary key we currently don't support this so 388 // throw an InvalidArgumentException for now 389 if (count($contentItemId) != 1) 390 { 391 throw new \InvalidArgumentException('Multiple primary keys are not supported as a content item id'); 392 } 393 394 $result = $this->unTagItem($contentItemId[$key], $table); 395 396 /** @var \JTableCorecontent $ucmContentTable */ 397 $ucmContentTable = Table::getInstance('Corecontent'); 398 399 return $result && $ucmContentTable->deleteByContentId($contentItemId[$key], $this->typeAlias); 400 } 401 402 /** 403 * Method to get a list of tags for an item, optionally with the tag data. 404 * 405 * @param string $contentType Content type alias. Dot separated. 406 * @param integer $id Id of the item to retrieve tags for. 407 * @param boolean $getTagData If true, data from the tags table will be included, defaults to true. 408 * 409 * @return array Array of of tag objects 410 * 411 * @since 3.1 412 */ 413 public function getItemTags($contentType, $id, $getTagData = true) 414 { 415 // Initialize some variables. 416 $db = \JFactory::getDbo(); 417 $query = $db->getQuery(true) 418 ->select($db->quoteName('m.tag_id')) 419 ->from($db->quoteName('#__contentitem_tag_map') . ' AS m ') 420 ->where( 421 array( 422 $db->quoteName('m.type_alias') . ' = ' . $db->quote($contentType), 423 $db->quoteName('m.content_item_id') . ' = ' . (int) $id, 424 $db->quoteName('t.published') . ' = 1', 425 ) 426 ); 427 428 $user = \JFactory::getUser(); 429 $groups = implode(',', $user->getAuthorisedViewLevels()); 430 431 $query->where('t.access IN (' . $groups . ')'); 432 433 // Optionally filter on language 434 $language = ComponentHelper::getParams('com_tags')->get('tag_list_language_filter', 'all'); 435 436 if ($language !== 'all') 437 { 438 if ($language === 'current_language') 439 { 440 $language = $this->getCurrentLanguage(); 441 } 442 443 $query->where($db->quoteName('language') . ' IN (' . $db->quote($language) . ', ' . $db->quote('*') . ')'); 444 } 445 446 if ($getTagData) 447 { 448 $query->select($db->quoteName('t') . '.*'); 449 } 450 451 $query->join('INNER', $db->quoteName('#__tags') . ' AS t ' . ' ON ' . $db->quoteName('m.tag_id') . ' = ' . $db->quoteName('t.id')); 452 453 $db->setQuery($query); 454 $this->itemTags = $db->loadObjectList(); 455 456 return $this->itemTags; 457 } 458 459 /** 460 * Method to get a list of tags for a given item. 461 * Normally used for displaying a list of tags within a layout 462 * 463 * @param mixed $ids The id or array of ids (primary key) of the item to be tagged. 464 * @param string $prefix Dot separated string with the option and view to be used for a url. 465 * 466 * @return string Comma separated list of tag Ids. 467 * 468 * @since 3.1 469 */ 470 public function getTagIds($ids, $prefix) 471 { 472 if (empty($ids)) 473 { 474 return; 475 } 476 477 /** 478 * Ids possible formats: 479 * --------------------- 480 * $id = 1; 481 * $id = array(1,2); 482 * $id = array('1,3,4,19'); 483 * $id = '1,3'; 484 */ 485 $ids = (array) $ids; 486 $ids = implode(',', $ids); 487 $ids = explode(',', $ids); 488 $ids = ArrayHelper::toInteger($ids); 489 490 $db = \JFactory::getDbo(); 491 492 // Load the tags. 493 $query = $db->getQuery(true) 494 ->select($db->quoteName('t.id')) 495 ->from($db->quoteName('#__tags') . ' AS t ') 496 ->join( 497 'INNER', $db->quoteName('#__contentitem_tag_map') . ' AS m' 498 . ' ON ' . $db->quoteName('m.tag_id') . ' = ' . $db->quoteName('t.id') 499 . ' AND ' . $db->quoteName('m.type_alias') . ' = ' . $db->quote($prefix) 500 . ' AND ' . $db->quoteName('m.content_item_id') . ' IN ( ' . implode(',', $ids) . ')' 501 ); 502 503 $db->setQuery($query); 504 505 // Add the tags to the content data. 506 $tagsList = $db->loadColumn(); 507 $this->tags = implode(',', $tagsList); 508 509 return $this->tags; 510 } 511 512 /** 513 * Method to get a query to retrieve a detailed list of items for a tag. 514 * 515 * @param mixed $tagId Tag or array of tags to be matched 516 * @param mixed $typesr Null, type or array of type aliases for content types to be included in the results 517 * @param boolean $includeChildren True to include the results from child tags 518 * @param string $orderByOption Column to order the results by 519 * @param string $orderDir Direction to sort the results in 520 * @param boolean $anyOrAll True to include items matching at least one tag, false to include 521 * items all tags in the array. 522 * @param string $languageFilter Optional filter on language. Options are 'all', 'current' or any string. 523 * @param string $stateFilter Optional filtering on publication state, defaults to published or unpublished. 524 * 525 * @return \JDatabaseQuery Query to retrieve a list of tags 526 * 527 * @since 3.1 528 */ 529 public function getTagItemsQuery($tagId, $typesr = null, $includeChildren = false, $orderByOption = 'c.core_title', $orderDir = 'ASC', 530 $anyOrAll = true, $languageFilter = 'all', $stateFilter = '0,1') 531 { 532 // Create a new query object. 533 $db = \JFactory::getDbo(); 534 $query = $db->getQuery(true); 535 $user = \JFactory::getUser(); 536 $nullDate = $db->quote($db->getNullDate()); 537 $nowDate = $db->quote(\JFactory::getDate()->toSql()); 538 539 // Force ids to array and sanitize 540 $tagIds = (array) $tagId; 541 $tagIds = implode(',', $tagIds); 542 $tagIds = explode(',', $tagIds); 543 $tagIds = ArrayHelper::toInteger($tagIds); 544 545 $ntagsr = count($tagIds); 546 547 // If we want to include children we have to adjust the list of tags. 548 // We do not search child tags when the match all option is selected. 549 if ($includeChildren) 550 { 551 $tagTreeArray = array(); 552 553 foreach ($tagIds as $tag) 554 { 555 $this->getTagTreeArray($tag, $tagTreeArray); 556 } 557 558 $tagIds = array_unique(array_merge($tagIds, $tagTreeArray)); 559 } 560 561 // Sanitize filter states 562 $stateFilters = explode(',', $stateFilter); 563 $stateFilters = ArrayHelper::toInteger($stateFilters); 564 565 // M is the mapping table. C is the core_content table. Ct is the content_types table. 566 $query 567 ->select( 568 'm.type_alias' 569 . ', ' . 'm.content_item_id' 570 . ', ' . 'm.core_content_id' 571 . ', ' . 'count(m.tag_id) AS match_count' 572 . ', ' . 'MAX(m.tag_date) as tag_date' 573 . ', ' . 'MAX(c.core_title) AS core_title' 574 . ', ' . 'MAX(c.core_params) AS core_params' 575 ) 576 ->select('MAX(c.core_alias) AS core_alias, MAX(c.core_body) AS core_body, MAX(c.core_state) AS core_state, MAX(c.core_access) AS core_access') 577 ->select( 578 'MAX(c.core_metadata) AS core_metadata' 579 . ', ' . 'MAX(c.core_created_user_id) AS core_created_user_id' 580 . ', ' . 'MAX(c.core_created_by_alias) AS core_created_by_alias' 581 ) 582 ->select('MAX(c.core_created_time) as core_created_time, MAX(c.core_images) as core_images') 583 ->select('CASE WHEN c.core_modified_time = ' . $nullDate . ' THEN c.core_created_time ELSE c.core_modified_time END as core_modified_time') 584 ->select('MAX(c.core_language) AS core_language, MAX(c.core_catid) AS core_catid') 585 ->select('MAX(c.core_publish_up) AS core_publish_up, MAX(c.core_publish_down) as core_publish_down') 586 ->select('MAX(ct.type_title) AS content_type_title, MAX(ct.router) AS router') 587 588 ->from('#__contentitem_tag_map AS m') 589 ->join( 590 'INNER', 591 '#__ucm_content AS c ON m.type_alias = c.core_type_alias AND m.core_content_id = c.core_content_id AND c.core_state IN (' 592 . implode(',', $stateFilters) . ')' 593 . (in_array('0', $stateFilters) ? '' : ' AND (c.core_publish_up = ' . $nullDate 594 . ' OR c.core_publish_up <= ' . $nowDate . ') ' 595 . ' AND (c.core_publish_down = ' . $nullDate . ' OR c.core_publish_down >= ' . $nowDate . ')') 596 ) 597 ->join('INNER', '#__content_types AS ct ON ct.type_alias = m.type_alias') 598 599 // Join over categories for get only tags from published categories 600 ->join('LEFT', '#__categories AS tc ON tc.id = c.core_catid') 601 602 // Join over the users for the author and email 603 ->select("CASE WHEN c.core_created_by_alias > ' ' THEN c.core_created_by_alias ELSE ua.name END AS author") 604 ->select('ua.email AS author_email') 605 606 ->join('LEFT', '#__users AS ua ON ua.id = c.core_created_user_id') 607 608 ->where('m.tag_id IN (' . implode(',', $tagIds) . ')') 609 ->where('(c.core_catid = 0 OR tc.published = 1)'); 610 611 // Optionally filter on language 612 if (empty($language)) 613 { 614 $language = $languageFilter; 615 } 616 617 if ($language !== 'all') 618 { 619 if ($language === 'current_language') 620 { 621 $language = $this->getCurrentLanguage(); 622 } 623 624 $query->where($db->quoteName('c.core_language') . ' IN (' . $db->quote($language) . ', ' . $db->quote('*') . ')'); 625 } 626 627 // Get the type data, limited to types in the request if there are any specified. 628 $typesarray = self::getTypes('assocList', $typesr, false); 629 630 $typeAliases = array(); 631 632 foreach ($typesarray as $type) 633 { 634 $typeAliases[] = $db->quote($type['type_alias']); 635 } 636 637 $query->where('m.type_alias IN (' . implode(',', $typeAliases) . ')'); 638 639 $groups = '0,' . implode(',', array_unique($user->getAuthorisedViewLevels())); 640 $query->where('c.core_access IN (' . $groups . ')') 641 ->group('m.type_alias, m.content_item_id, m.core_content_id, core_modified_time, core_created_time, core_created_by_alias, author, author_email'); 642 643 // Use HAVING if matching all tags and we are matching more than one tag. 644 if ($ntagsr > 1 && $anyOrAll != 1 && $includeChildren != 1) 645 { 646 // The number of results should equal the number of tags requested. 647 $query->having("COUNT('m.tag_id') = " . (int) $ntagsr); 648 } 649 650 // Set up the order by using the option chosen 651 if ($orderByOption === 'match_count') 652 { 653 $orderBy = 'COUNT(m.tag_id)'; 654 } 655 else 656 { 657 $orderBy = 'MAX(' . $db->quoteName($orderByOption) . ')'; 658 } 659 660 $query->order($orderBy . ' ' . $orderDir); 661 662 return $query; 663 } 664 665 /** 666 * Function that converts tag ids to their tag names 667 * 668 * @param array $tagIds Array of integer tag ids. 669 * 670 * @return array An array of tag names. 671 * 672 * @since 3.1 673 */ 674 public function getTagNames($tagIds) 675 { 676 $tagNames = array(); 677 678 if (is_array($tagIds) && count($tagIds) > 0) 679 { 680 $tagIds = ArrayHelper::toInteger($tagIds); 681 682 $db = \JFactory::getDbo(); 683 $query = $db->getQuery(true) 684 ->select($db->quoteName('title')) 685 ->from($db->quoteName('#__tags')) 686 ->where($db->quoteName('id') . ' IN (' . implode(',', $tagIds) . ')'); 687 $query->order($db->quoteName('title')); 688 689 $db->setQuery($query); 690 $tagNames = $db->loadColumn(); 691 } 692 693 return $tagNames; 694 } 695 696 /** 697 * Method to get an array of tag ids for the current tag and its children 698 * 699 * @param integer $id An optional ID 700 * @param array &$tagTreeArray Array containing the tag tree 701 * 702 * @return mixed 703 * 704 * @since 3.1 705 */ 706 public function getTagTreeArray($id, &$tagTreeArray = array()) 707 { 708 // Get a level row instance. 709 Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables'); 710 $table = Table::getInstance('Tag', 'TagsTable'); 711 712 if ($table->isLeaf($id)) 713 { 714 $tagTreeArray[] = $id; 715 716 return $tagTreeArray; 717 } 718 719 $tagTree = $table->getTree($id); 720 721 // Attempt to load the tree 722 if ($tagTree) 723 { 724 foreach ($tagTree as $tag) 725 { 726 $tagTreeArray[] = $tag->id; 727 } 728 729 return $tagTreeArray; 730 } 731 } 732 733 /** 734 * Method to get the type id for a type alias. 735 * 736 * @param string $typeAlias A type alias. 737 * 738 * @return string Name of the table for a type 739 * 740 * @since 3.1 741 * @deprecated 4.0 Use \JUcmType::getTypeId() instead 742 */ 743 public function getTypeId($typeAlias) 744 { 745 $contentType = new \JUcmType; 746 747 return $contentType->getTypeId($typeAlias); 748 } 749 750 /** 751 * Method to get a list of types with associated data. 752 * 753 * @param string $arrayType Optionally specify that the returned list consist of objects, associative arrays, or arrays. 754 * Options are: rowList, assocList, and objectList 755 * @param array $selectTypes Optional array of type ids to limit the results to. Often from a request. 756 * @param boolean $useAlias If true, the alias is used to match, if false the type_id is used. 757 * 758 * @return array Array of of types 759 * 760 * @since 3.1 761 */ 762 public static function getTypes($arrayType = 'objectList', $selectTypes = null, $useAlias = true) 763 { 764 // Initialize some variables. 765 $db = \JFactory::getDbo(); 766 $query = $db->getQuery(true) 767 ->select('*'); 768 769 if (!empty($selectTypes)) 770 { 771 $selectTypes = (array) $selectTypes; 772 773 if ($useAlias) 774 { 775 $selectTypes = array_map(array($db, 'quote'), $selectTypes); 776 777 $query->where($db->quoteName('type_alias') . ' IN (' . implode(',', $selectTypes) . ')'); 778 } 779 else 780 { 781 $selectTypes = ArrayHelper::toInteger($selectTypes); 782 783 $query->where($db->quoteName('type_id') . ' IN (' . implode(',', $selectTypes) . ')'); 784 } 785 } 786 787 $query->from($db->quoteName('#__content_types')); 788 789 $db->setQuery($query); 790 791 switch ($arrayType) 792 { 793 case 'assocList': 794 $types = $db->loadAssocList(); 795 break; 796 797 case 'rowList': 798 $types = $db->loadRowList(); 799 break; 800 801 case 'objectList': 802 default: 803 $types = $db->loadObjectList(); 804 break; 805 } 806 807 return $types; 808 } 809 810 /** 811 * Function that handles saving tags used in a table class after a store() 812 * 813 * @param TableInterface $table Table being processed 814 * @param array $newTags Array of new tags 815 * @param boolean $replace Flag indicating if all existing tags should be replaced 816 * 817 * @return boolean 818 * 819 * @since 3.1 820 */ 821 public function postStoreProcess(TableInterface $table, $newTags = array(), $replace = true) 822 { 823 if (!empty($table->newTags) && empty($newTags)) 824 { 825 $newTags = $table->newTags; 826 } 827 828 // If existing row, check to see if tags have changed. 829 $newTable = clone $table; 830 $newTable->reset(); 831 832 $result = true; 833 834 // Process ucm_content and ucm_base if either tags have changed or we have some tags. 835 if ($this->tagsChanged || (!empty($newTags) && $newTags[0] != '')) 836 { 837 if (!$newTags && $replace == true) 838 { 839 // Delete all tags data 840 $key = $table->getKeyName(); 841 $result = $this->deleteTagData($table, $table->$key); 842 } 843 else 844 { 845 // Process the tags 846 $data = $this->getRowData($table); 847 $ucmContentTable = Table::getInstance('Corecontent'); 848 849 $ucm = new \JUcmContent($table, $this->typeAlias); 850 $ucmData = $data ? $ucm->mapData($data) : $ucm->ucmData; 851 852 $primaryId = $ucm->getPrimaryKey($ucmData['common']['core_type_id'], $ucmData['common']['core_content_item_id']); 853 $result = $ucmContentTable->load($primaryId); 854 $result = $result && $ucmContentTable->bind($ucmData['common']); 855 $result = $result && $ucmContentTable->check(); 856 $result = $result && $ucmContentTable->store(); 857 $ucmId = $ucmContentTable->core_content_id; 858 859 // Store the tag data if the article data was saved and run related methods. 860 $result = $result && $this->tagItem($ucmId, $table, $newTags, $replace); 861 } 862 } 863 864 return $result; 865 } 866 867 /** 868 * Function that preProcesses data from a table prior to a store() to ensure proper tag handling 869 * 870 * @param TableInterface $table Table being processed 871 * @param array $newTags Array of new tags 872 * 873 * @return null 874 * 875 * @since 3.1 876 */ 877 public function preStoreProcess(TableInterface $table, $newTags = array()) 878 { 879 if ($newTags != array()) 880 { 881 $this->newTags = $newTags; 882 } 883 884 // If existing row, check to see if tags have changed. 885 $oldTable = clone $table; 886 $oldTable->reset(); 887 $key = $oldTable->getKeyName(); 888 $typeAlias = $this->typeAlias; 889 890 if ($oldTable->$key && $oldTable->load()) 891 { 892 $this->oldTags = $this->getTagIds($oldTable->$key, $typeAlias); 893 } 894 895 // New items with no tags bypass this step. 896 if ((!empty($newTags) && is_string($newTags) || (isset($newTags[0]) && $newTags[0] != '')) || isset($this->oldTags)) 897 { 898 if (is_array($newTags)) 899 { 900 $newTags = implode(',', $newTags); 901 } 902 903 // We need to process tags if the tags have changed or if we have a new row 904 $this->tagsChanged = (empty($this->oldTags) && !empty($newTags)) ||(!empty($this->oldTags) && $this->oldTags != $newTags) || !$table->$key; 905 } 906 } 907 908 /** 909 * Function to search tags 910 * 911 * @param array $filters Filter to apply to the search 912 * 913 * @return array 914 * 915 * @since 3.1 916 */ 917 public static function searchTags($filters = array()) 918 { 919 $db = \JFactory::getDbo(); 920 $query = $db->getQuery(true) 921 ->select('a.id AS value') 922 ->select('a.path AS text') 923 ->select('a.path') 924 ->from('#__tags AS a') 925 ->join('LEFT', $db->quoteName('#__tags', 'b') . ' ON a.lft > b.lft AND a.rgt < b.rgt'); 926 927 // Filter language 928 if (!empty($filters['flanguage'])) 929 { 930 $query->where('a.language IN (' . $db->quote($filters['flanguage']) . ',' . $db->quote('*') . ') '); 931 } 932 933 // Do not return root 934 $query->where($db->quoteName('a.alias') . ' <> ' . $db->quote('root')); 935 936 // Search in title or path 937 if (!empty($filters['like'])) 938 { 939 $query->where( 940 '(' . $db->quoteName('a.title') . ' LIKE ' . $db->quote('%' . $filters['like'] . '%') 941 . ' OR ' . $db->quoteName('a.path') . ' LIKE ' . $db->quote('%' . $filters['like'] . '%') . ')' 942 ); 943 } 944 945 // Filter title 946 if (!empty($filters['title'])) 947 { 948 $query->where($db->quoteName('a.title') . ' = ' . $db->quote($filters['title'])); 949 } 950 951 // Filter on the published state 952 if (isset($filters['published']) && is_numeric($filters['published'])) 953 { 954 $query->where('a.published = ' . (int) $filters['published']); 955 } 956 957 // Filter on the access level 958 if (isset($filters['access']) && is_array($filters['access']) && count($filters['access'])) 959 { 960 $groups = ArrayHelper::toInteger($filters['access']); 961 $query->where('a.access IN (' . implode(",", $groups) . ')'); 962 } 963 964 // Filter by parent_id 965 if (isset($filters['parent_id']) && is_numeric($filters['parent_id'])) 966 { 967 Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables'); 968 $tagTable = Table::getInstance('Tag', 'TagsTable'); 969 970 if ($children = $tagTable->getTree($filters['parent_id'])) 971 { 972 foreach ($children as $child) 973 { 974 $childrenIds[] = $child->id; 975 } 976 977 $query->where('a.id IN (' . implode(',', $childrenIds) . ')'); 978 } 979 } 980 981 $query->group('a.id, a.title, a.level, a.lft, a.rgt, a.parent_id, a.published, a.path') 982 ->order('a.lft ASC'); 983 984 // Get the options. 985 $db->setQuery($query); 986 987 try 988 { 989 $results = $db->loadObjectList(); 990 } 991 catch (\RuntimeException $e) 992 { 993 return array(); 994 } 995 996 // We will replace path aliases with tag names 997 return self::convertPathsToNames($results); 998 } 999 1000 /** 1001 * Method to delete all instances of a tag from the mapping table. Generally used when a tag is deleted. 1002 * 1003 * @param integer $tagId The tag_id (primary key) for the deleted tag. 1004 * 1005 * @return void 1006 * 1007 * @since 3.1 1008 */ 1009 public function tagDeleteInstances($tagId) 1010 { 1011 // Delete the old tag maps. 1012 $db = \JFactory::getDbo(); 1013 $query = $db->getQuery(true) 1014 ->delete($db->quoteName('#__contentitem_tag_map')) 1015 ->where($db->quoteName('tag_id') . ' = ' . (int) $tagId); 1016 $db->setQuery($query); 1017 $db->execute(); 1018 } 1019 1020 /** 1021 * Method to add or update tags associated with an item. 1022 * 1023 * @param integer $ucmId Id of the #__ucm_content item being tagged 1024 * @param TableInterface $table Table object being tagged 1025 * @param array $tags Array of tags to be applied. 1026 * @param boolean $replace Flag indicating if all existing tags should be replaced 1027 * 1028 * @return boolean true on success, otherwise false. 1029 * 1030 * @since 3.1 1031 */ 1032 public function tagItem($ucmId, TableInterface $table, $tags = array(), $replace = true) 1033 { 1034 $key = $table->get('_tbl_key'); 1035 $oldTags = $this->getTagIds((int) $table->$key, $this->typeAlias); 1036 $oldTags = explode(',', $oldTags); 1037 $result = $this->unTagItem($ucmId, $table); 1038 1039 if ($replace) 1040 { 1041 $newTags = $tags; 1042 } 1043 else 1044 { 1045 if ($tags == array()) 1046 { 1047 $newTags = $table->newTags; 1048 } 1049 else 1050 { 1051 $newTags = $tags; 1052 } 1053 1054 if ($oldTags[0] != '') 1055 { 1056 $newTags = array_unique(array_merge($newTags, $oldTags)); 1057 } 1058 } 1059 1060 if (is_array($newTags) && count($newTags) > 0 && $newTags[0] != '') 1061 { 1062 $result = $result && $this->addTagMapping($ucmId, $table, $newTags); 1063 } 1064 1065 return $result; 1066 } 1067 1068 /** 1069 * Method to untag an item 1070 * 1071 * @param integer $contentId ID of the content item being untagged 1072 * @param TableInterface $table Table object being untagged 1073 * @param array $tags Array of tags to be untagged. Use an empty array to untag all existing tags. 1074 * 1075 * @return boolean true on success, otherwise false. 1076 * 1077 * @since 3.1 1078 */ 1079 public function unTagItem($contentId, TableInterface $table, $tags = array()) 1080 { 1081 $key = $table->getKeyName(); 1082 $id = $table->$key; 1083 $db = \JFactory::getDbo(); 1084 $query = $db->getQuery(true) 1085 ->delete('#__contentitem_tag_map') 1086 ->where($db->quoteName('type_alias') . ' = ' . $db->quote($this->typeAlias)) 1087 ->where($db->quoteName('content_item_id') . ' = ' . (int) $id); 1088 1089 if (is_array($tags) && count($tags) > 0) 1090 { 1091 $tags = ArrayHelper::toInteger($tags); 1092 1093 $query->where($db->quoteName('tag_id') . ' IN (' . implode(',', $tags) . ')'); 1094 } 1095 1096 $db->setQuery($query); 1097 1098 return (boolean) $db->execute(); 1099 } 1100} 1101