[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 18 /** 19 * Moodle tag library 20 * 21 * Tag strings : you can use any character in tags, except the comma (which is the separator) and 22 * the '\' (backslash). Note that many spaces (or other blank characters) will get "compressed" 23 * into one. A tag string is always a rawurlencode'd string. This is the same behavior as 24 * http://del.icio.us. 25 * 26 * A "record" is a php array (note that an object will work too) that contains the following 27 * variables : 28 * - type: The database table containing the record that we are tagging (eg: for a blog, this is 29 * the table named 'post', and for a user it is the table name 'user') 30 * - id: The id of the record 31 * 32 * BASIC INSTRUCTIONS : 33 * - to "tag a blog post" (for example): 34 * tag_set('post', $blog_post->id, $array_of_tags, 'core', $thecontext); 35 * 36 * - to "remove all the tags on a blog post": 37 * tag_set('post', $blog_post->id, array(), 'core', $thecontext); 38 * 39 * Tag set will create tags that need to be created. 40 * 41 * @package core_tag 42 * @category tag 43 * @todo MDL-31090 turn this into a full-fledged categorization system. This could start by 44 * modifying (removing, probably) the 'tag type' to use another table describing the 45 * relationship between tags (parents, sibling, etc.), which could then be merged with 46 * the 'course categorization' system. 47 * @see http://www.php.net/manual/en/function.urlencode.php 48 * @copyright 2007 Luiz Cruz <[email protected]> 49 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 50 */ 51 52 /** 53 * Used to require that the return value from a function is an array. 54 * @see tag_set() 55 */ 56 define('TAG_RETURN_ARRAY', 0); 57 /** 58 * Used to require that the return value from a function is an object. 59 * @see tag_set() 60 */ 61 define('TAG_RETURN_OBJECT', 1); 62 /** 63 * Use to specify that HTML free text is expected to be returned from a function. 64 * @see tag_display_name() 65 */ 66 define('TAG_RETURN_TEXT', 2); 67 /** 68 * Use to specify that encoded HTML is expected to be returned from a function. 69 * @see tag_display_name() 70 */ 71 define('TAG_RETURN_HTML', 3); 72 73 /** 74 * Used to specify that we wish a lowercased string to be returned 75 * @see tag_normal() 76 */ 77 define('TAG_CASE_LOWER', 0); 78 /** 79 * Used to specify that we do not wish the case of the returned string to change 80 * @see tag_normal() 81 */ 82 define('TAG_CASE_ORIGINAL', 1); 83 84 /** 85 * Used to specify that we want all related tags returned, no matter how they are related. 86 * @see tag_get_related_tags() 87 */ 88 define('TAG_RELATED_ALL', 0); 89 /** 90 * Used to specify that we only want back tags that were manually related. 91 * @see tag_get_related_tags() 92 */ 93 define('TAG_RELATED_MANUAL', 1); 94 /** 95 * Used to specify that we only want back tags where the relationship was automatically correlated. 96 * @see tag_get_related_tags() 97 */ 98 define('TAG_RELATED_CORRELATED', 2); 99 100 /////////////////////////////////////////////////////// 101 /////////////////// PUBLIC TAG API //////////////////// 102 103 /// Functions for settings tags ////////////////////// 104 105 /** 106 * Set the tags assigned to a record. This overwrites the current tags. 107 * 108 * This function is meant to be fed the string coming up from the user interface, which contains all tags assigned to a record. 109 * 110 * @package core_tag 111 * @category tag 112 * @access public 113 * @param string $record_type the type of record to tag ('post' for blogs, 'user' for users, 'tag' for tags, etc.) 114 * @param int $record_id the id of the record to tag 115 * @param array $tags the array of tags to set on the record. If given an empty array, all tags will be removed. 116 * @param string|null $component the component that was tagged 117 * @param int|null $contextid the context id of where this tag was assigned 118 * @return bool|null 119 */ 120 function tag_set($record_type, $record_id, $tags, $component = null, $contextid = null) { 121 122 static $in_recursion_semaphore = false; // this is to prevent loops when tagging a tag 123 124 if ( $record_type == 'tag' && !$in_recursion_semaphore) { 125 $current_tagged_tag_name = tag_get_name($record_id); 126 } 127 128 $tags_ids = tag_get_id($tags, TAG_RETURN_ARRAY); // force an array, even if we only have one tag. 129 $cleaned_tags = tag_normalize($tags); 130 //echo 'tags-in-tag_set'; var_dump($tags); var_dump($tags_ids); var_dump($cleaned_tags); 131 132 $current_ids = tag_get_tags_ids($record_type, $record_id); 133 //var_dump($current_ids); 134 135 // for data coherence reasons, it's better to remove deleted tags 136 // before adding new data: ordering could be duplicated. 137 foreach($current_ids as $current_id) { 138 if (!in_array($current_id, $tags_ids)) { 139 tag_delete_instance($record_type, $record_id, $current_id); 140 if ( $record_type == 'tag' && !$in_recursion_semaphore) { 141 // if we are removing a tag-on-a-tag (manually related tag), 142 // we need to remove the opposite relationship as well. 143 tag_delete_instance('tag', $current_id, $record_id); 144 } 145 } 146 } 147 148 if (empty($tags)) { 149 return true; 150 } 151 152 foreach($tags as $ordering => $tag) { 153 $tag = trim($tag); 154 if (!$tag) { 155 continue; 156 } 157 158 $clean_tag = $cleaned_tags[$tag]; 159 $tag_current_id = $tags_ids[$clean_tag]; 160 161 if ( is_null($tag_current_id) ) { 162 // create new tags 163 //echo "call to add tag $tag\n"; 164 $new_tag = tag_add($tag); 165 $tag_current_id = $new_tag[$clean_tag]; 166 } 167 168 tag_assign($record_type, $record_id, $tag_current_id, $ordering, 0, $component, $contextid); 169 170 // if we are tagging a tag (adding a manually-assigned related tag), we 171 // need to create the opposite relationship as well. 172 if ( $record_type == 'tag' && !$in_recursion_semaphore) { 173 $in_recursion_semaphore = true; 174 tag_set_add('tag', $tag_current_id, $current_tagged_tag_name, $component, $contextid); 175 $in_recursion_semaphore = false; 176 } 177 } 178 } 179 180 /** 181 * Adds a tag to a record, without overwriting the current tags. 182 * 183 * @package core_tag 184 * @category tag 185 * @access public 186 * @param string $record_type the type of record to tag ('post' for blogs, 'user' for users, etc.) 187 * @param int $record_id the id of the record to tag 188 * @param string $tag the tag to add 189 * @param string|null $component the component that was tagged 190 * @param int|null $contextid the context id of where this tag was assigned 191 * @return bool|null 192 */ 193 function tag_set_add($record_type, $record_id, $tag, $component = null, $contextid = null) { 194 195 $new_tags = array(); 196 foreach( tag_get_tags($record_type, $record_id) as $current_tag ) { 197 $new_tags[] = $current_tag->rawname; 198 } 199 $new_tags[] = $tag; 200 201 return tag_set($record_type, $record_id, $new_tags, $component, $contextid); 202 } 203 204 /** 205 * Removes a tag from a record, without overwriting other current tags. 206 * 207 * @package core_tag 208 * @category tag 209 * @access public 210 * @param string $record_type the type of record to tag ('post' for blogs, 'user' for users, etc.) 211 * @param int $record_id the id of the record to tag 212 * @param string $tag the tag to delete 213 * @param string|null $component the component that was tagged 214 * @param int|null $contextid the context id of where this tag was assigned 215 * @return bool|null 216 */ 217 function tag_set_delete($record_type, $record_id, $tag, $component = null, $contextid = null) { 218 219 $new_tags = array(); 220 foreach( tag_get_tags($record_type, $record_id) as $current_tag ) { 221 if ($current_tag->name != $tag) { // Keep all tags but the one specified 222 $new_tags[] = $current_tag->name; 223 } 224 } 225 226 return tag_set($record_type, $record_id, $new_tags, $component, $contextid); 227 } 228 229 /** 230 * Set the type of a tag. At this time (version 2.2) the possible values are 'default' or 'official'. Official tags will be 231 * displayed separately "at tagging time" (while selecting the tags to apply to a record). 232 * 233 * @package core_tag 234 * @category tag 235 * @access public 236 * @param string $tagid tagid to modify 237 * @param string $type either 'default' or 'official' 238 * @return bool true on success, false otherwise 239 */ 240 function tag_type_set($tagid, $type) { 241 global $DB; 242 243 if ($tag = $DB->get_record('tag', array('id' => $tagid), 'id, userid, name, rawname')) { 244 $tag->tagtype = $type; 245 $tag->timemodified = time(); 246 $DB->update_record('tag', $tag); 247 248 $event = \core\event\tag_updated::create(array( 249 'objectid' => $tag->id, 250 'relateduserid' => $tag->userid, 251 'context' => context_system::instance(), 252 'other' => array( 253 'name' => $tag->name, 254 'rawname' => $tag->rawname 255 ) 256 )); 257 $event->trigger(); 258 259 return true; 260 } 261 return false; 262 } 263 264 /** 265 * Set the description of a tag 266 * 267 * @package core_tag 268 * @category tag 269 * @access public 270 * @param int $tagid the id of the tag 271 * @param string $description the tag's description string to be set 272 * @param int $descriptionformat the moodle text format of the description 273 * {@link http://docs.moodle.org/dev/Text_formats_2.0#Database_structure} 274 * @return bool true on success, false otherwise 275 */ 276 function tag_description_set($tagid, $description, $descriptionformat) { 277 global $DB; 278 279 if ($tag = $DB->get_record('tag', array('id' => $tagid), 'id, userid, name, rawname')) { 280 $tag->description = $description; 281 $tag->descriptionformat = $descriptionformat; 282 $tag->timemodified = time(); 283 $DB->update_record('tag', $tag); 284 285 $event = \core\event\tag_updated::create(array( 286 'objectid' => $tag->id, 287 'relateduserid' => $tag->userid, 288 'context' => context_system::instance(), 289 'other' => array( 290 'name' => $tag->name, 291 'rawname' => $tag->rawname 292 ) 293 )); 294 $event->trigger(); 295 296 return true; 297 } 298 299 return false; 300 } 301 302 303 304 305 306 307 /// Functions for getting information about tags ////// 308 309 /** 310 * Simple function to just return a single tag object when you know the name or something 311 * 312 * @package core_tag 313 * @category tag 314 * @access public 315 * @param string $field which field do we use to identify the tag: id, name or rawname 316 * @param string $value the required value of the aforementioned field 317 * @param string $returnfields which fields do we want returned. This is a comma seperated string containing any combination of 318 * 'id', 'name', 'rawname' or '*' to include all fields. 319 * @return mixed tag object 320 */ 321 function tag_get($field, $value, $returnfields='id, name, rawname') { 322 global $DB; 323 324 if ($field == 'name') { 325 $value = core_text::strtolower($value); // To cope with input that might just be wrong case 326 } 327 return $DB->get_record('tag', array($field=>$value), $returnfields); 328 } 329 330 331 /** 332 * Get the array of db record of tags associated to a record (instances). Use {@see tag_get_tags_csv()} if you wish to get the same 333 * data in a comma-separated string, for instances such as needing to simply display a list of tags to the end user. This should 334 * really be called tag_get_tag_instances(). 335 * 336 * @package core_tag 337 * @category tag 338 * @access public 339 * @param string $record_type the record type for which we want to get the tags 340 * @param int $record_id the record id for which we want to get the tags 341 * @param string $type the tag type (either 'default' or 'official'). By default, all tags are returned. 342 * @param int $userid (optional) only required for course tagging 343 * @return array the array of tags 344 */ 345 function tag_get_tags($record_type, $record_id, $type=null, $userid=0) { 346 global $CFG, $DB; 347 348 $params = array(); 349 350 if ($type) { 351 $sql_type = "AND tg.tagtype = :type"; 352 $params['type'] = $type; 353 } else { 354 $sql_type = ''; 355 } 356 357 $u = null; 358 if ($userid) { 359 $u = "AND ti.tiuserid = :userid "; 360 $params['userid'] = $userid; 361 } 362 363 $sql = "SELECT ti.id AS taginstanceid, tg.id, tg.tagtype, tg.name, tg.rawname, tg.flag, ti.ordering 364 FROM {tag_instance} ti 365 JOIN {tag} tg ON tg.id = ti.tagid 366 WHERE ti.itemtype = :recordtype AND ti.itemid = :recordid $u $sql_type 367 ORDER BY ti.ordering ASC"; 368 $params['recordtype'] = $record_type; 369 $params['recordid'] = $record_id; 370 371 // if the fields in this query are changed, you need to do the same changes in tag_get_correlated_tags 372 return $DB->get_records_sql($sql, $params); 373 // This version of the query, reversing the ON clause, "correctly" returns 374 // a row with NULL values for instances that are still in the DB even though 375 // the tag has been deleted. This shouldn't happen, but if it did, using 376 // this query could help "clean it up". This causes bugs at this time. 377 //$tags = $DB->get_records_sql("SELECT ti.tagid, tg.tagtype, tg.name, tg.rawname, tg.flag, ti.ordering ". 378 // "FROM {tag_instance} ti LEFT JOIN {tag} tg ON ti.tagid = tg.id ". 379 // "WHERE ti.itemtype = '{$record_type}' AND ti.itemid = '{$record_id}' {$type} ". 380 // "ORDER BY ti.ordering ASC"); 381 } 382 383 /** 384 * Get the array of tags display names, indexed by id. 385 * 386 * @package core_tag 387 * @category tag 388 * @access public 389 * @param string $record_type the record type for which we want to get the tags 390 * @param int $record_id the record id for which we want to get the tags 391 * @param string $type the tag type (either 'default' or 'official'). By default, all tags are returned. 392 * @return array the array of tags (with the value returned by tag_display_name), indexed by id 393 */ 394 function tag_get_tags_array($record_type, $record_id, $type=null) { 395 $tags = array(); 396 foreach(tag_get_tags($record_type, $record_id, $type) as $tag) { 397 $tags[$tag->id] = tag_display_name($tag); 398 } 399 return $tags; 400 } 401 402 /** 403 * Get a comma-separated string of tags associated to a record. Use {@see tag_get_tags()} to get the same information in an array. 404 * 405 * @package core_tag 406 * @category tag 407 * @access public 408 * @param string $record_type the record type for which we want to get the tags 409 * @param int $record_id the record id for which we want to get the tags 410 * @param int $html either TAG_RETURN_HTML or TAG_RETURN_TEXT, depending on the type of output desired 411 * @param string $type either 'official' or 'default', if null, all tags are returned 412 * @return string the comma-separated list of tags. 413 */ 414 function tag_get_tags_csv($record_type, $record_id, $html=TAG_RETURN_HTML, $type=null) { 415 global $CFG; 416 417 $tags_names = array(); 418 foreach(tag_get_tags($record_type, $record_id, $type) as $tag) { 419 if ($html == TAG_RETURN_TEXT) { 420 $tags_names[] = tag_display_name($tag, TAG_RETURN_TEXT); 421 } else { // TAG_RETURN_HTML 422 $tags_names[] = '<a href="'. $CFG->wwwroot .'/tag/index.php?tag='. rawurlencode($tag->name) .'">'. tag_display_name($tag) .'</a>'; 423 } 424 } 425 return implode(', ', $tags_names); 426 } 427 428 /** 429 * Get an array of tag ids associated to a record. 430 * 431 * @package core_tag 432 * @category tag 433 * @access public 434 * @todo MDL-31150 Update ordering property 435 * @param string $record_type the record type for which we want to get the tags 436 * @param int $record_id the record id for which we want to get the tags 437 * @return array tag ids, indexed and sorted by 'ordering' 438 */ 439 function tag_get_tags_ids($record_type, $record_id) { 440 $tag_ids = array(); 441 foreach (tag_get_tags($record_type, $record_id) as $tag) { 442 if ( array_key_exists($tag->ordering, $tag_ids) ) { 443 // until we can add a unique constraint, in table tag_instance, 444 // on (itemtype, itemid, ordering), this is needed to prevent a bug 445 // TODO MDL-31150 modify database in 2.0 446 $tag->ordering++; 447 } 448 $tag_ids[$tag->ordering] = $tag->id; 449 } 450 ksort($tag_ids); 451 return $tag_ids; 452 } 453 454 /** 455 * Returns the database ID of a set of tags. 456 * 457 * @package core_tag 458 * @category tag 459 * @access public 460 * @todo MDL-31152 Test the commented MDL-31152 todo in this function to see if it helps performance 461 * without breaking anything. 462 * @param mixed $tags one tag, or array of tags, to look for. 463 * @param bool $return_value specify the type of the returned value. Either TAG_RETURN_OBJECT, or TAG_RETURN_ARRAY (default). 464 * If TAG_RETURN_ARRAY is specified, an array will be returned even if only one tag was passed in $tags. 465 * @return mixed tag-indexed array of ids (or objects, if second parameter is TAG_RETURN_OBJECT), or only an int, if only one tag 466 * is given *and* the second parameter is null. No value for a key means the tag wasn't found. 467 */ 468 function tag_get_id($tags, $return_value=null) { 469 global $CFG, $DB; 470 471 static $tag_id_cache = array(); 472 473 $return_an_int = false; 474 if (!is_array($tags)) { 475 if(is_null($return_value) || $return_value == TAG_RETURN_OBJECT) { 476 $return_an_int = true; 477 } 478 $tags = array($tags); 479 } 480 481 $result = array(); 482 483 //TODO MDL-31152 test this and see if it helps performance without breaking anything 484 //foreach($tags as $key => $tag) { 485 // $clean_tag = core_text::strtolower($tag); 486 // if ( array_key_exists($clean_tag), $tag_id_cache) ) { 487 // $result[$clean_tag] = $tag_id_cache[$clean_tag]; 488 // $tags[$key] = ''; // prevent further processing for this one. 489 // } 490 //} 491 492 $tags = array_values(tag_normalize($tags)); 493 foreach($tags as $key => $tag) { 494 $tags[$key] = core_text::strtolower($tag); 495 $result[core_text::strtolower($tag)] = null; // key must exists : no value for a key means the tag wasn't found. 496 } 497 498 if (empty($tags)) { 499 return array(); 500 } 501 502 list($tag_string, $params) = $DB->get_in_or_equal($tags); 503 504 $rs = $DB->get_recordset_sql("SELECT * FROM {tag} WHERE name $tag_string ORDER BY name", $params); 505 foreach ($rs as $record) { 506 if ($return_value == TAG_RETURN_OBJECT) { 507 $result[$record->name] = $record; 508 } else { // TAG_RETURN_ARRAY 509 $result[$record->name] = $record->id; 510 } 511 } 512 $rs->close(); 513 514 if ($return_an_int) { 515 return array_pop($result); 516 } 517 518 return $result; 519 } 520 521 522 /** 523 * Returns tags related to a tag 524 * 525 * Related tags of a tag come from two sources: 526 * - manually added related tags, which are tag_instance entries for that tag 527 * - correlated tags, which are calculated 528 * 529 * @package core_tag 530 * @category tag 531 * @access public 532 * @param string $tagid is a single **normalized** tag name or the id of a tag 533 * @param int $type the function will return either manually (TAG_RELATED_MANUAL) related tags or correlated 534 * (TAG_RELATED_CORRELATED) tags. Default is TAG_RELATED_ALL, which returns everything. 535 * @param int $limitnum (optional) return a subset comprising this many records, the default is 10 536 * @return array an array of tag objects 537 */ 538 function tag_get_related_tags($tagid, $type=TAG_RELATED_ALL, $limitnum=10) { 539 540 $related_tags = array(); 541 542 if ( $type == TAG_RELATED_ALL || $type == TAG_RELATED_MANUAL) { 543 //gets the manually added related tags 544 $related_tags = tag_get_tags('tag', $tagid); 545 } 546 547 if ( $type == TAG_RELATED_ALL || $type == TAG_RELATED_CORRELATED ) { 548 //gets the correlated tags 549 $automatic_related_tags = tag_get_correlated($tagid, $limitnum); 550 if (is_array($automatic_related_tags)) { 551 $related_tags = array_merge($related_tags, $automatic_related_tags); 552 } 553 } 554 555 return array_slice(object_array_unique($related_tags), 0 , $limitnum); 556 } 557 558 /** 559 * Get a comma-separated list of tags related to another tag. 560 * 561 * @package core_tag 562 * @category tag 563 * @access public 564 * @param array $related_tags the array returned by tag_get_related_tags 565 * @param int $html either TAG_RETURN_HTML (default) or TAG_RETURN_TEXT : return html links, or just text. 566 * @return string comma-separated list 567 */ 568 function tag_get_related_tags_csv($related_tags, $html=TAG_RETURN_HTML) { 569 global $CFG; 570 571 $tags_names = array(); 572 foreach($related_tags as $tag) { 573 if ( $html == TAG_RETURN_TEXT) { 574 $tags_names[] = tag_display_name($tag, TAG_RETURN_TEXT); 575 } else { 576 // TAG_RETURN_HTML 577 $tags_names[] = '<a href="'. $CFG->wwwroot .'/tag/index.php?tag='. rawurlencode($tag->name) .'">'. tag_display_name($tag) .'</a>'; 578 } 579 } 580 581 return implode(', ', $tags_names); 582 } 583 584 /** 585 * Change the "value" of a tag, and update the associated 'name'. 586 * 587 * @package core_tag 588 * @category tag 589 * @access public 590 * @param int $tagid the id of the tag to modify 591 * @param string $newrawname the new rawname 592 * @return bool true on success, false otherwise 593 */ 594 function tag_rename($tagid, $newrawname) { 595 global $COURSE, $DB; 596 597 $norm = tag_normalize($newrawname, TAG_CASE_ORIGINAL); 598 if (! $newrawname_clean = array_shift($norm) ) { 599 return false; 600 } 601 602 if (! $newname_clean = core_text::strtolower($newrawname_clean)) { 603 return false; 604 } 605 606 // Prevent the rename if a tag with that name already exists 607 if ($existing = tag_get('name', $newname_clean, 'id, name, rawname')) { 608 if ($existing->id != $tagid) { // Another tag already exists with this name 609 return false; 610 } 611 } 612 613 if ($tag = tag_get('id', $tagid, 'id, userid, name, rawname')) { 614 // Store the name before we change it. 615 $oldname = $tag->name; 616 617 $tag->rawname = $newrawname_clean; 618 $tag->name = $newname_clean; 619 $tag->timemodified = time(); 620 $DB->update_record('tag', $tag); 621 622 $event = \core\event\tag_updated::create(array( 623 'objectid' => $tag->id, 624 'relateduserid' => $tag->userid, 625 'context' => context_system::instance(), 626 'other' => array( 627 'name' => $newname_clean, 628 'rawname' => $newrawname_clean 629 ) 630 )); 631 $event->set_legacy_logdata(array($COURSE->id, 'tag', 'update', 'index.php?id='. $tag->id, $oldname . '->'. $tag->name)); 632 $event->trigger(); 633 634 return true; 635 } 636 return false; 637 } 638 639 640 /** 641 * Delete one or more tag, and all their instances if there are any left. 642 * 643 * @package core_tag 644 * @category tag 645 * @access public 646 * @param mixed $tagids one tagid (int), or one array of tagids to delete 647 * @return bool true on success, false otherwise 648 */ 649 function tag_delete($tagids) { 650 global $DB; 651 652 if (!is_array($tagids)) { 653 $tagids = array($tagids); 654 } 655 656 // Use the tagids to create a select statement to be used later. 657 list($tagsql, $tagparams) = $DB->get_in_or_equal($tagids); 658 659 // Store the tags and tag instances we are going to delete. 660 $tags = $DB->get_records_select('tag', 'id ' . $tagsql, $tagparams); 661 $taginstances = $DB->get_records_select('tag_instance', 'tagid ' . $tagsql, $tagparams); 662 663 // Delete all the tag instances. 664 $select = 'WHERE tagid ' . $tagsql; 665 $sql = "DELETE FROM {tag_instance} $select"; 666 $DB->execute($sql, $tagparams); 667 668 // Delete all the tag correlations. 669 $sql = "DELETE FROM {tag_correlation} $select"; 670 $DB->execute($sql, $tagparams); 671 672 // Delete all the tags. 673 $select = 'WHERE id ' . $tagsql; 674 $sql = "DELETE FROM {tag} $select"; 675 $DB->execute($sql, $tagparams); 676 677 // Fire an event that these items were untagged. 678 if ($taginstances) { 679 // Save the system context in case the 'contextid' column in the 'tag_instance' table is null. 680 $syscontextid = context_system::instance()->id; 681 // Loop through the tag instances and fire a 'tag_removed'' event. 682 foreach ($taginstances as $taginstance) { 683 // We can not fire an event with 'null' as the contextid. 684 if (is_null($taginstance->contextid)) { 685 $taginstance->contextid = $syscontextid; 686 } 687 688 // Trigger tag removed event. 689 $event = \core\event\tag_removed::create(array( 690 'objectid' => $taginstance->id, 691 'contextid' => $taginstance->contextid, 692 'other' => array( 693 'tagid' => $taginstance->tagid, 694 'tagname' => $tags[$taginstance->tagid]->name, 695 'tagrawname' => $tags[$taginstance->tagid]->rawname, 696 'itemid' => $taginstance->itemid, 697 'itemtype' => $taginstance->itemtype 698 ) 699 )); 700 $event->add_record_snapshot('tag_instance', $taginstance); 701 $event->trigger(); 702 } 703 } 704 705 // Fire an event that these tags were deleted. 706 if ($tags) { 707 $context = context_system::instance(); 708 foreach ($tags as $tag) { 709 // Delete all files associated with this tag 710 $fs = get_file_storage(); 711 $files = $fs->get_area_files($context->id, 'tag', 'description', $tag->id); 712 foreach ($files as $file) { 713 $file->delete(); 714 } 715 716 // Trigger an event for deleting this tag. 717 $event = \core\event\tag_deleted::create(array( 718 'objectid' => $tag->id, 719 'relateduserid' => $tag->userid, 720 'context' => $context, 721 'other' => array( 722 'name' => $tag->name, 723 'rawname' => $tag->rawname 724 ) 725 )); 726 $event->add_record_snapshot('tag', $tag); 727 $event->trigger(); 728 } 729 } 730 731 return true; 732 } 733 734 /** 735 * Deletes all the tag instances given a component and an optional contextid. 736 * 737 * @param string $component 738 * @param int $contextid if null, then we delete all tag instances for the $component 739 */ 740 function tag_delete_instances($component, $contextid = null) { 741 global $DB; 742 743 $sql = "SELECT ti.*, t.name, t.rawname 744 FROM {tag_instance} ti 745 JOIN {tag} t 746 ON ti.tagid = t.id "; 747 if (is_null($contextid)) { 748 $params = array('component' => $component); 749 $sql .= "WHERE ti.component = :component"; 750 } else { 751 $params = array('component' => $component, 'contextid' => $contextid); 752 $sql .= "WHERE ti.component = :component 753 AND ti.contextid = :contextid"; 754 } 755 if ($taginstances = $DB->get_records_sql($sql, $params)) { 756 // Now remove all the tag instances. 757 $DB->delete_records('tag_instance',$params); 758 // Save the system context in case the 'contextid' column in the 'tag_instance' table is null. 759 $syscontextid = context_system::instance()->id; 760 // Loop through the tag instances and fire an 'tag_removed' event. 761 foreach ($taginstances as $taginstance) { 762 // We can not fire an event with 'null' as the contextid. 763 if (is_null($taginstance->contextid)) { 764 $taginstance->contextid = $syscontextid; 765 } 766 767 // Trigger tag removed event. 768 $event = \core\event\tag_removed::create(array( 769 'objectid' => $taginstance->id, 770 'contextid' => $taginstance->contextid, 771 'other' => array( 772 'tagid' => $taginstance->tagid, 773 'tagname' => $taginstance->name, 774 'tagrawname' => $taginstance->rawname, 775 'itemid' => $taginstance->itemid, 776 'itemtype' => $taginstance->itemtype 777 ) 778 )); 779 $event->add_record_snapshot('tag_instance', $taginstance); 780 $event->trigger(); 781 } 782 } 783 } 784 785 /** 786 * Delete one instance of a tag. If the last instance was deleted, it will also delete the tag, unless its type is 'official'. 787 * 788 * @package core_tag 789 * @category tag 790 * @access public 791 * @param string $record_type the type of the record for which to remove the instance 792 * @param int $record_id the id of the record for which to remove the instance 793 * @param int $tagid the tagid that needs to be removed 794 * @param int $userid (optional) the userid 795 * @return bool true on success, false otherwise 796 */ 797 function tag_delete_instance($record_type, $record_id, $tagid, $userid = null) { 798 global $DB; 799 800 if (is_null($userid)) { 801 $taginstance = $DB->get_record('tag_instance', array('tagid' => $tagid, 'itemtype' => $record_type, 'itemid' => $record_id)); 802 } else { 803 $taginstance = $DB->get_record('tag_instance', array('tagid' => $tagid, 'itemtype' => $record_type, 'itemid' => $record_id, 804 'tiuserid' => $userid)); 805 } 806 if ($taginstance) { 807 // Get the tag. 808 $tag = $DB->get_record('tag', array('id' => $tagid)); 809 810 $DB->delete_records('tag_instance', array('id' => $taginstance->id)); 811 812 // We can not fire an event with 'null' as the contextid. 813 if (is_null($taginstance->contextid)) { 814 $taginstance->contextid = context_system::instance()->id; 815 } 816 817 // Trigger tag removed event. 818 $event = \core\event\tag_removed::create(array( 819 'objectid' => $taginstance->id, 820 'contextid' => $taginstance->contextid, 821 'other' => array( 822 'tagid' => $tag->id, 823 'tagname' => $tag->name, 824 'tagrawname' => $tag->rawname, 825 'itemid' => $taginstance->itemid, 826 'itemtype' => $taginstance->itemtype 827 ) 828 )); 829 $event->add_record_snapshot('tag_instance', $taginstance); 830 $event->trigger(); 831 832 // If there are no other instances of the tag then consider deleting the tag as well. 833 if (!$DB->record_exists('tag_instance', array('tagid' => $tagid))) { 834 // If the tag is a personal tag then delete it - don't delete official tags. 835 if ($tag->tagtype == 'default') { 836 tag_delete($tagid); 837 } 838 } 839 } else { 840 return false; 841 } 842 843 return true; 844 } 845 846 847 /** 848 * Function that returns the name that should be displayed for a specific tag 849 * 850 * @package core_tag 851 * @category tag 852 * @access public 853 * @param object $tagobject a line out of tag table, as returned by the adobd functions 854 * @param int $html TAG_RETURN_HTML (default) will return htmlspecialchars encoded string, TAG_RETURN_TEXT will not encode. 855 * @return string 856 */ 857 function tag_display_name($tagobject, $html=TAG_RETURN_HTML) { 858 global $CFG; 859 860 if (!isset($tagobject->name)) { 861 return ''; 862 } 863 864 if (empty($CFG->keeptagnamecase)) { 865 //this is the normalized tag name 866 $tagname = core_text::strtotitle($tagobject->name); 867 } else { 868 //original casing of the tag name 869 $tagname = $tagobject->rawname; 870 } 871 872 // clean up a bit just in case the rules change again 873 $tagname = clean_param($tagname, PARAM_TAG); 874 875 if ($html == TAG_RETURN_TEXT) { 876 return $tagname; 877 } else { // TAG_RETURN_HTML 878 return htmlspecialchars($tagname); 879 } 880 } 881 882 /** 883 * Find all records tagged with a tag of a given type ('post', 'user', etc.) 884 * 885 * @package core_tag 886 * @category tag 887 * @access public 888 * @param string $tag tag to look for 889 * @param string $type type to restrict search to. If null, every matching record will be returned 890 * @param int $limitfrom (optional, required if $limitnum is set) return a subset of records, starting at this point. 891 * @param int $limitnum (optional, required if $limitfrom is set) return a subset comprising this many records. 892 * @return array of matching objects, indexed by record id, from the table containing the type requested 893 */ 894 function tag_find_records($tag, $type, $limitfrom='', $limitnum='') { 895 global $CFG, $DB; 896 897 if (!$tag || !$type) { 898 return array(); 899 } 900 901 $tagid = tag_get_id($tag); 902 903 $query = "SELECT it.* 904 FROM {".$type."} it INNER JOIN {tag_instance} tt ON it.id = tt.itemid 905 WHERE tt.itemtype = ? AND tt.tagid = ?"; 906 $params = array($type, $tagid); 907 908 return $DB->get_records_sql($query, $params, $limitfrom, $limitnum); 909 } 910 911 912 913 914 /////////////////////////////////////////////////////// 915 /////////////////// PRIVATE TAG API /////////////////// 916 917 /** 918 * Adds one or more tag in the database. This function should not be called directly : you should 919 * use tag_set. 920 * 921 * @package core_tag 922 * @access private 923 * @param mixed $tags one tag, or an array of tags, to be created 924 * @param string $type type of tag to be created ("default" is the default value and "official" is the only other supported 925 * value at this time). An official tag is kept even if there are no records tagged with it. 926 * @return array $tags ids indexed by their lowercase normalized names. Any boolean false in the array indicates an error while 927 * adding the tag. 928 */ 929 function tag_add($tags, $type="default") { 930 global $USER, $DB; 931 932 if (!is_array($tags)) { 933 $tags = array($tags); 934 } 935 936 $tag_object = new StdClass; 937 $tag_object->tagtype = $type; 938 $tag_object->userid = $USER->id; 939 $tag_object->timemodified = time(); 940 941 $clean_tags = tag_normalize($tags, TAG_CASE_ORIGINAL); 942 943 $tags_ids = array(); 944 foreach($clean_tags as $tag) { 945 $tag = trim($tag); 946 if (!$tag) { 947 $tags_ids[$tag] = false; 948 } else { 949 // note that the difference between rawname and name is only 950 // capitalization : the rawname is NOT the same at the rawtag. 951 $tag_object->rawname = $tag; 952 $tag_name_lc = core_text::strtolower($tag); 953 $tag_object->name = $tag_name_lc; 954 //var_dump($tag_object); 955 $tags_ids[$tag_name_lc] = $DB->insert_record('tag', $tag_object); 956 957 $event = \core\event\tag_created::create(array( 958 'objectid' => $tags_ids[$tag_name_lc], 959 'relateduserid' => $tag_object->userid, 960 'context' => context_system::instance(), 961 'other' => array( 962 'name' => $tag_object->name, 963 'rawname' => $tag_object->rawname 964 ) 965 )); 966 $event->trigger(); 967 } 968 } 969 970 return $tags_ids; 971 } 972 973 /** 974 * Assigns a tag to a record; if the record already exists, the time and ordering will be updated. 975 * 976 * @package core_tag 977 * @access private 978 * @param string $record_type the type of the record that will be tagged 979 * @param int $record_id the id of the record that will be tagged 980 * @param string $tagid the tag id to set on the record. 981 * @param int $ordering the order of the instance for this record 982 * @param int $userid (optional) only required for course tagging 983 * @param string|null $component the component that was tagged 984 * @param int|null $contextid the context id of where this tag was assigned 985 * @return bool true on success, false otherwise 986 */ 987 function tag_assign($record_type, $record_id, $tagid, $ordering, $userid = 0, $component = null, $contextid = null) { 988 global $DB; 989 990 if ($component === null || $contextid === null) { 991 debugging('You should specify the component and contextid of the item being tagged in your call to tag_assign.', 992 DEBUG_DEVELOPER); 993 } 994 995 // Get the tag. 996 $tag = $DB->get_record('tag', array('id' => $tagid), 'name, rawname', MUST_EXIST); 997 998 if ( $tag_instance_object = $DB->get_record('tag_instance', array('tagid'=>$tagid, 'itemtype'=>$record_type, 'itemid'=>$record_id, 'tiuserid'=>$userid), 'id')) { 999 $tag_instance_object->ordering = $ordering; 1000 $tag_instance_object->timemodified = time(); 1001 1002 $DB->update_record('tag_instance', $tag_instance_object); 1003 } else { 1004 $tag_instance_object = new StdClass; 1005 $tag_instance_object->tagid = $tagid; 1006 $tag_instance_object->component = $component; 1007 $tag_instance_object->itemid = $record_id; 1008 $tag_instance_object->itemtype = $record_type; 1009 $tag_instance_object->contextid = $contextid; 1010 $tag_instance_object->ordering = $ordering; 1011 $tag_instance_object->timecreated = time(); 1012 $tag_instance_object->timemodified = $tag_instance_object->timecreated; 1013 $tag_instance_object->tiuserid = $userid; 1014 1015 $tag_instance_object->id = $DB->insert_record('tag_instance', $tag_instance_object); 1016 } 1017 1018 // We can not fire an event with 'null' as the contextid. 1019 if (is_null($contextid)) { 1020 $contextid = context_system::instance()->id; 1021 } 1022 1023 // Trigger tag added event. 1024 $event = \core\event\tag_added::create(array( 1025 'objectid' => $tag_instance_object->id, 1026 'contextid' => $contextid, 1027 'other' => array( 1028 'tagid' => $tagid, 1029 'tagname' => $tag->name, 1030 'tagrawname' => $tag->rawname, 1031 'itemid' => $record_id, 1032 'itemtype' => $record_type 1033 ) 1034 )); 1035 $event->trigger(); 1036 1037 return true; 1038 } 1039 1040 /** 1041 * Function that returns tags that start with some text, for use by the autocomplete feature 1042 * 1043 * @package core_tag 1044 * @access private 1045 * @param string $text string that the tag names will be matched against 1046 * @return mixed an array of objects, or false if no records were found or an error occured. 1047 */ 1048 function tag_autocomplete($text) { 1049 global $DB; 1050 return $DB->get_records_sql("SELECT tg.id, tg.name, tg.rawname 1051 FROM {tag} tg 1052 WHERE tg.name LIKE ?", array(core_text::strtolower($text)."%")); 1053 } 1054 1055 /** 1056 * Clean up the tag tables, making sure all tagged object still exists. 1057 * 1058 * This should normally not be necessary, but in case related tags are not deleted when the tagged record is removed, this should be 1059 * done once in a while, perhaps on an occasional cron run. On a site with lots of tags, this could become an expensive function to 1060 * call: don't run at peak time. 1061 * 1062 * @package core_tag 1063 * @access private 1064 * @todo MDL-31212 Update tag cleanup sql so that it supports multiple types of tags 1065 */ 1066 function tag_cleanup() { 1067 global $DB; 1068 1069 $instances = $DB->get_recordset('tag_instance'); 1070 1071 // cleanup tag instances 1072 foreach ($instances as $instance) { 1073 $delete = false; 1074 1075 if (!$DB->record_exists('tag', array('id'=>$instance->tagid))) { 1076 // if the tag has been removed, instance should be deleted. 1077 $delete = true; 1078 } else { 1079 switch ($instance->itemtype) { 1080 case 'user': // users are marked as deleted, but not actually deleted 1081 if ($DB->record_exists('user', array('id'=>$instance->itemid, 'deleted'=>1))) { 1082 $delete = true; 1083 } 1084 break; 1085 default: // anything else, if the instance is not there, delete. 1086 if (!$DB->record_exists($instance->itemtype, array('id'=>$instance->itemid))) { 1087 $delete = true; 1088 } 1089 break; 1090 } 1091 } 1092 if ($delete) { 1093 tag_delete_instance($instance->itemtype, $instance->itemid, $instance->tagid); 1094 //debugging('deleting tag_instance #'. $instance->id .', linked to tag id #'. $instance->tagid, DEBUG_DEVELOPER); 1095 } 1096 } 1097 $instances->close(); 1098 1099 // TODO MDL-31212 this will only clean tags of type 'default'. This is good as 1100 // it won't delete 'official' tags, but the day we get more than two 1101 // types, we need to fix this. 1102 $unused_tags = $DB->get_recordset_sql("SELECT tg.id 1103 FROM {tag} tg 1104 WHERE tg.tagtype = 'default' 1105 AND NOT EXISTS ( 1106 SELECT 'x' 1107 FROM {tag_instance} ti 1108 WHERE ti.tagid = tg.id 1109 )"); 1110 1111 // cleanup tags 1112 foreach ($unused_tags as $unused_tag) { 1113 tag_delete($unused_tag->id); 1114 //debugging('deleting unused tag #'. $unused_tag->id, DEBUG_DEVELOPER); 1115 } 1116 $unused_tags->close(); 1117 } 1118 1119 /** 1120 * Calculates and stores the correlated tags of all tags. The correlations are stored in the 'tag_correlation' table. 1121 * 1122 * Two tags are correlated if they appear together a lot. Ex.: Users tagged with "computers" will probably also be tagged with "algorithms". 1123 * 1124 * The rationale for the 'tag_correlation' table is performance. It works as a cache for a potentially heavy load query done at the 1125 * 'tag_instance' table. So, the 'tag_correlation' table stores redundant information derived from the 'tag_instance' table. 1126 * 1127 * @package core_tag 1128 * @access private 1129 * @param int $mincorrelation Only tags with more than $mincorrelation correlations will be identified. 1130 */ 1131 function tag_compute_correlations($mincorrelation = 2) { 1132 global $DB; 1133 1134 // This mighty one line query fetches a row from the database for every 1135 // individual tag correlation. We then need to process the rows collecting 1136 // the correlations for each tag id. 1137 // The fields used by this query are as follows: 1138 // tagid : This is the tag id, there should be at least $mincorrelation 1139 // rows for each tag id. 1140 // correlation : This is the tag id that correlates to the above tagid field. 1141 // correlationid : This is the id of the row in the tag_correlation table that 1142 // relates to the tagid field and will be NULL if there are no 1143 // existing correlations 1144 $sql = 'SELECT pairs.tagid, pairs.correlation, pairs.ocurrences, co.id AS correlationid 1145 FROM ( 1146 SELECT ta.tagid, tb.tagid AS correlation, COUNT(*) AS ocurrences 1147 FROM {tag_instance} ta 1148 JOIN {tag_instance} tb ON (ta.itemtype = tb.itemtype AND ta.itemid = tb.itemid AND ta.tagid <> tb.tagid) 1149 GROUP BY ta.tagid, tb.tagid 1150 HAVING COUNT(*) > :mincorrelation 1151 ) pairs 1152 LEFT JOIN {tag_correlation} co ON co.tagid = pairs.tagid 1153 ORDER BY pairs.tagid ASC, pairs.ocurrences DESC, pairs.correlation ASC'; 1154 $rs = $DB->get_recordset_sql($sql, array('mincorrelation' => $mincorrelation)); 1155 1156 // Set up an empty tag correlation object 1157 $tagcorrelation = new stdClass; 1158 $tagcorrelation->id = null; 1159 $tagcorrelation->tagid = null; 1160 $tagcorrelation->correlatedtags = array(); 1161 1162 // We store each correlation id in this array so we can remove any correlations 1163 // that no longer exist. 1164 $correlations = array(); 1165 1166 // Iterate each row of the result set and build them into tag correlations. 1167 // We add all of a tag's correlations to $tagcorrelation->correlatedtags[] 1168 // then save the $tagcorrelation object 1169 foreach ($rs as $row) { 1170 if ($row->tagid != $tagcorrelation->tagid) { 1171 // The tag id has changed so we have all of the correlations for this tag 1172 $tagcorrelationid = tag_process_computed_correlation($tagcorrelation); 1173 if ($tagcorrelationid) { 1174 $correlations[] = $tagcorrelationid; 1175 } 1176 // Now we reset the tag correlation object so we can reuse it and set it 1177 // up for the current record. 1178 $tagcorrelation = new stdClass; 1179 $tagcorrelation->id = $row->correlationid; 1180 $tagcorrelation->tagid = $row->tagid; 1181 $tagcorrelation->correlatedtags = array(); 1182 } 1183 //Save the correlation on the tag correlation object 1184 $tagcorrelation->correlatedtags[] = $row->correlation; 1185 } 1186 // Update the current correlation after the last record. 1187 $tagcorrelationid = tag_process_computed_correlation($tagcorrelation); 1188 if ($tagcorrelationid) { 1189 $correlations[] = $tagcorrelationid; 1190 } 1191 1192 1193 // Close the recordset 1194 $rs->close(); 1195 1196 // Remove any correlations that weren't just identified 1197 if (empty($correlations)) { 1198 //there are no tag correlations 1199 $DB->delete_records('tag_correlation'); 1200 } else { 1201 list($sql, $params) = $DB->get_in_or_equal($correlations, SQL_PARAMS_NAMED, 'param0000', false); 1202 $DB->delete_records_select('tag_correlation', 'id '.$sql, $params); 1203 } 1204 } 1205 1206 /** 1207 * This function processes a tag correlation and makes changes in the database as required. 1208 * 1209 * The tag correlation object needs have both a tagid property and a correlatedtags property that is an array. 1210 * 1211 * @package core_tag 1212 * @access private 1213 * @param stdClass $tagcorrelation 1214 * @return int/bool The id of the tag correlation that was just processed or false. 1215 */ 1216 function tag_process_computed_correlation(stdClass $tagcorrelation) { 1217 global $DB; 1218 1219 // You must provide a tagid and correlatedtags must be set and be an array 1220 if (empty($tagcorrelation->tagid) || !isset($tagcorrelation->correlatedtags) || !is_array($tagcorrelation->correlatedtags)) { 1221 return false; 1222 } 1223 1224 $tagcorrelation->correlatedtags = join(',', $tagcorrelation->correlatedtags); 1225 if (!empty($tagcorrelation->id)) { 1226 // The tag correlation already exists so update it 1227 $DB->update_record('tag_correlation', $tagcorrelation); 1228 } else { 1229 // This is a new correlation to insert 1230 $tagcorrelation->id = $DB->insert_record('tag_correlation', $tagcorrelation); 1231 } 1232 return $tagcorrelation->id; 1233 } 1234 1235 /** 1236 * Tasks that should be performed at cron time 1237 * 1238 * @package core_tag 1239 * @access private 1240 */ 1241 function tag_cron() { 1242 tag_compute_correlations(); 1243 tag_cleanup(); 1244 } 1245 1246 /** 1247 * Search for tags with names that match some text 1248 * 1249 * @package core_tag 1250 * @access private 1251 * @param string $text escaped string that the tag names will be matched against 1252 * @param bool $ordered If true, tags are ordered by their popularity. If false, no ordering. 1253 * @param int/string $limitfrom (optional, required if $limitnum is set) return a subset of records, starting at this point. 1254 * @param int/string $limitnum (optional, required if $limitfrom is set) return a subset comprising this many records. 1255 * @return array/boolean an array of objects, or false if no records were found or an error occured. 1256 */ 1257 function tag_find_tags($text, $ordered=true, $limitfrom='', $limitnum='') { 1258 global $DB; 1259 1260 $norm = tag_normalize($text, TAG_CASE_LOWER); 1261 $text = array_shift($norm); 1262 1263 if ($ordered) { 1264 $query = "SELECT tg.id, tg.name, tg.rawname, COUNT(ti.id) AS count 1265 FROM {tag} tg LEFT JOIN {tag_instance} ti ON tg.id = ti.tagid 1266 WHERE tg.name LIKE ? 1267 GROUP BY tg.id, tg.name, tg.rawname 1268 ORDER BY count DESC"; 1269 } else { 1270 $query = "SELECT tg.id, tg.name, tg.rawname 1271 FROM {tag} tg 1272 WHERE tg.name LIKE ?"; 1273 } 1274 $params = array("%{$text}%"); 1275 return $DB->get_records_sql($query, $params, $limitfrom , $limitnum); 1276 } 1277 1278 /** 1279 * Get the name of a tag 1280 * 1281 * @package core_tag 1282 * @access private 1283 * @param mixed $tagids the id of the tag, or an array of ids 1284 * @return mixed string name of one tag, or id-indexed array of strings 1285 */ 1286 function tag_get_name($tagids) { 1287 global $DB; 1288 1289 if (!is_array($tagids)) { 1290 if ($tag = $DB->get_record('tag', array('id'=>$tagids))) { 1291 return $tag->name; 1292 } 1293 return false; 1294 } 1295 1296 $tag_names = array(); 1297 foreach($DB->get_records_list('tag', 'id', $tagids) as $tag) { 1298 $tag_names[$tag->id] = $tag->name; 1299 } 1300 1301 return $tag_names; 1302 } 1303 1304 /** 1305 * Returns the correlated tags of a tag, retrieved from the tag_correlation table. Make sure cron runs, otherwise the table will be 1306 * empty and this function won't return anything. 1307 * 1308 * @package core_tag 1309 * @access private 1310 * @param int $tag_id is a single tag id 1311 * @param int $limitnum this parameter does not appear to have any function??? 1312 * @return array an array of tag objects or an empty if no correlated tags are found 1313 */ 1314 function tag_get_correlated($tag_id, $limitnum=null) { 1315 global $DB; 1316 1317 $tag_correlation = $DB->get_record('tag_correlation', array('tagid'=>$tag_id)); 1318 1319 if (!$tag_correlation || empty($tag_correlation->correlatedtags)) { 1320 return array(); 1321 } 1322 1323 // this is (and has to) return the same fields as the query in tag_get_tags 1324 $sql = "SELECT DISTINCT tg.id, tg.tagtype, tg.name, tg.rawname, tg.flag, ti.ordering 1325 FROM {tag} tg 1326 INNER JOIN {tag_instance} ti ON tg.id = ti.tagid 1327 WHERE tg.id IN ({$tag_correlation->correlatedtags})"; 1328 $result = $DB->get_records_sql($sql); 1329 if (!$result) { 1330 return array(); 1331 } 1332 1333 return $result; 1334 } 1335 1336 /** 1337 * Function that normalizes a list of tag names. 1338 * 1339 * @package core_tag 1340 * @access private 1341 * @param array/string $rawtags array of tags, or a single tag. 1342 * @param int $case case to use for returned value (default: lower case). Either TAG_CASE_LOWER (default) or TAG_CASE_ORIGINAL 1343 * @return array lowercased normalized tags, indexed by the normalized tag, in the same order as the original array. 1344 * (Eg: 'Banana' => 'banana'). 1345 */ 1346 function tag_normalize($rawtags, $case = TAG_CASE_LOWER) { 1347 1348 // cache normalized tags, to prevent costly repeated calls to clean_param 1349 static $cleaned_tags_lc = array(); // lower case - use for comparison 1350 static $cleaned_tags_mc = array(); // mixed case - use for saving to database 1351 1352 if ( !is_array($rawtags) ) { 1353 $rawtags = array($rawtags); 1354 } 1355 1356 $result = array(); 1357 foreach($rawtags as $rawtag) { 1358 $rawtag = trim($rawtag); 1359 if (!$rawtag) { 1360 continue; 1361 } 1362 if ( !array_key_exists($rawtag, $cleaned_tags_lc) ) { 1363 $cleaned_tags_lc[$rawtag] = core_text::strtolower( clean_param($rawtag, PARAM_TAG) ); 1364 $cleaned_tags_mc[$rawtag] = clean_param($rawtag, PARAM_TAG); 1365 } 1366 if ( $case == TAG_CASE_LOWER ) { 1367 $result[$rawtag] = $cleaned_tags_lc[$rawtag]; 1368 } else { // TAG_CASE_ORIGINAL 1369 $result[$rawtag] = $cleaned_tags_mc[$rawtag]; 1370 } 1371 } 1372 1373 return $result; 1374 } 1375 1376 /** 1377 * Count how many records are tagged with a specific tag. 1378 * 1379 * @package core_tag 1380 * @access private 1381 * @param string $record_type record to look for ('post', 'user', etc.) 1382 * @param int $tagid is a single tag id 1383 * @return int number of mathing tags. 1384 */ 1385 function tag_record_count($record_type, $tagid) { 1386 global $DB; 1387 return $DB->count_records('tag_instance', array('itemtype'=>$record_type, 'tagid'=>$tagid)); 1388 } 1389 1390 /** 1391 * Determine if a record is tagged with a specific tag 1392 * 1393 * @package core_tag 1394 * @access private 1395 * @param string $record_type the record type to look for 1396 * @param int $record_id the record id to look for 1397 * @param string $tag a tag name 1398 * @return bool/int true if it is tagged, 0 (false) otherwise 1399 */ 1400 function tag_record_tagged_with($record_type, $record_id, $tag) { 1401 global $DB; 1402 if ($tagid = tag_get_id($tag)) { 1403 return $DB->count_records('tag_instance', array('itemtype'=>$record_type, 'itemid'=>$record_id, 'tagid'=>$tagid)); 1404 } else { 1405 return 0; // tag doesn't exist 1406 } 1407 } 1408 1409 /** 1410 * Flag a tag as inappropriate. 1411 * 1412 * @param int|array $tagids a single tagid, or an array of tagids 1413 */ 1414 function tag_set_flag($tagids) { 1415 global $DB; 1416 1417 $tagids = (array) $tagids; 1418 1419 // Use the tagids to create a select statement to be used later. 1420 list($tagsql, $tagparams) = $DB->get_in_or_equal($tagids, SQL_PARAMS_NAMED); 1421 1422 // Update all the tags to flagged. 1423 $sql = "UPDATE {tag} 1424 SET flag = flag + 1, timemodified = :time 1425 WHERE id $tagsql"; 1426 1427 // Update all the tags. 1428 $DB->execute($sql, array_merge(array('time' => time()), $tagparams)); 1429 1430 // Get all the tags. 1431 if ($tags = $DB->get_records_select('tag', 'id '. $tagsql, $tagparams, 'id ASC')) { 1432 // Loop through and fire an event for each tag that it was flagged. 1433 foreach ($tags as $tag) { 1434 $event = \core\event\tag_flagged::create(array( 1435 'objectid' => $tag->id, 1436 'relateduserid' => $tag->userid, 1437 'context' => context_system::instance(), 1438 'other' => array( 1439 'name' => $tag->name, 1440 'rawname' => $tag->rawname 1441 ) 1442 1443 )); 1444 $event->add_record_snapshot('tag', $tag); 1445 $event->trigger(); 1446 } 1447 } 1448 } 1449 1450 /** 1451 * Remove the inappropriate flag on a tag. 1452 * 1453 * @param int|array $tagids a single tagid, or an array of tagids 1454 */ 1455 function tag_unset_flag($tagids) { 1456 global $DB; 1457 1458 $tagids = (array) $tagids; 1459 1460 // Use the tagids to create a select statement to be used later. 1461 list($tagsql, $tagparams) = $DB->get_in_or_equal($tagids, SQL_PARAMS_NAMED); 1462 1463 // Update all the tags to unflagged. 1464 $sql = "UPDATE {tag} 1465 SET flag = 0, timemodified = :time 1466 WHERE id $tagsql"; 1467 1468 // Update all the tags. 1469 $DB->execute($sql, array_merge(array('time' => time()), $tagparams)); 1470 1471 // Get all the tags. 1472 if ($tags = $DB->get_records_select('tag', 'id '. $tagsql, $tagparams, 'id ASC')) { 1473 // Loop through and fire an event for each tag that it was unflagged. 1474 foreach ($tags as $tag) { 1475 $event = \core\event\tag_unflagged::create(array( 1476 'objectid' => $tag->id, 1477 'relateduserid' => $tag->userid, 1478 'context' => context_system::instance(), 1479 'other' => array( 1480 'name' => $tag->name, 1481 'rawname' => $tag->rawname 1482 ) 1483 )); 1484 $event->add_record_snapshot('tag', $tag); 1485 $event->trigger(); 1486 } 1487 } 1488 } 1489 1490 /** 1491 * Return a list of page types 1492 * 1493 * @package core_tag 1494 * @access private 1495 * @param string $pagetype current page type 1496 * @param stdClass $parentcontext Block's parent context 1497 * @param stdClass $currentcontext Current context of block 1498 */ 1499 function tag_page_type_list($pagetype, $parentcontext, $currentcontext) { 1500 return array( 1501 'tag-*'=>get_string('page-tag-x', 'tag'), 1502 'tag-index'=>get_string('page-tag-index', 'tag'), 1503 'tag-search'=>get_string('page-tag-search', 'tag'), 1504 'tag-manage'=>get_string('page-tag-manage', 'tag') 1505 ); 1506 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |