Differences Between: [Versions 310 and 402] [Versions 310 and 403]
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 * Class to manage tag collections 19 * 20 * @package core_tag 21 * @copyright 2015 Marina Glancy 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 /** 28 * Class to manage tag collections 29 * 30 * @package core_tag 31 * @copyright 2015 Marina Glancy 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 class core_tag_collection { 35 36 /** @var string used for function cloud_sort() */ 37 public static $cloudsortfield = 'name'; 38 39 /** 40 * Returns the list of tag collections defined in the system. 41 * 42 * @param bool $onlysearchable only return collections that can be searched. 43 * @return array array of objects where each object has properties: id, name, isdefault, itemtypes, sortorder 44 */ 45 public static function get_collections($onlysearchable = false) { 46 global $DB; 47 $cache = cache::make('core', 'tags'); 48 if (($tagcolls = $cache->get('tag_coll')) === false) { 49 // Retrieve records from DB and create a default one if it is not present. 50 $tagcolls = $DB->get_records('tag_coll', null, 'isdefault DESC, sortorder, id'); 51 if (empty($tagcolls)) { 52 // When this method is called for the first time it automatically creates the default tag collection. 53 $DB->insert_record('tag_coll', array('isdefault' => 1, 'sortorder' => 0)); 54 $tagcolls = $DB->get_records('tag_coll'); 55 } else { 56 // Make sure sortorder is correct. 57 $idx = 0; 58 foreach ($tagcolls as $id => $tagcoll) { 59 if ($tagcoll->sortorder != $idx) { 60 $DB->update_record('tag_coll', array('sortorder' => $idx, 'id' => $id)); 61 $tagcolls[$id]->sortorder = $idx; 62 } 63 $idx++; 64 } 65 } 66 $cache->set('tag_coll', $tagcolls); 67 } 68 if ($onlysearchable) { 69 $rv = array(); 70 foreach ($tagcolls as $id => $tagcoll) { 71 if ($tagcoll->searchable) { 72 $rv[$id] = $tagcoll; 73 } 74 } 75 return $rv; 76 } 77 return $tagcolls; 78 } 79 80 /** 81 * Returns the tag collection object 82 * 83 * @param int $tagcollid 84 * @return stdClass 85 */ 86 public static function get_by_id($tagcollid) { 87 $tagcolls = self::get_collections(); 88 if (array_key_exists($tagcollid, $tagcolls)) { 89 return $tagcolls[$tagcollid]; 90 } 91 return null; 92 } 93 94 /** 95 * Returns the list of existing tag collections as id=>name 96 * 97 * @param bool $unlockedonly 98 * @param bool $onlysearchable 99 * @param string $selectalllabel 100 * @return array 101 */ 102 public static function get_collections_menu($unlockedonly = false, $onlysearchable = false, 103 $selectalllabel = null) { 104 $tagcolls = self::get_collections($onlysearchable); 105 $options = array(); 106 foreach ($tagcolls as $id => $tagcoll) { 107 if (!$unlockedonly || empty($tagcoll->component)) { 108 $options[$id] = self::display_name($tagcoll); 109 } 110 } 111 if (count($options) > 1 && $selectalllabel) { 112 $options = array(0 => $selectalllabel) + $options; 113 } 114 return $options; 115 } 116 117 /** 118 * Returns id of the default tag collection 119 * 120 * @return int 121 */ 122 public static function get_default() { 123 $collections = self::get_collections(); 124 $keys = array_keys($collections); 125 return $keys[0]; 126 } 127 128 /** 129 * Returns formatted name of the tag collection 130 * 131 * @param stdClass $record record from DB table tag_coll 132 * @return string 133 */ 134 public static function display_name($record) { 135 $syscontext = context_system::instance(); 136 if (!empty($record->component)) { 137 $identifier = 'tagcollection_' . 138 clean_param($record->name, PARAM_STRINGID); 139 $component = $record->component; 140 if ($component === 'core') { 141 $component = 'tag'; 142 } 143 return get_string($identifier, $component); 144 } 145 if (!empty($record->name)) { 146 return format_string($record->name, true, array('context' => $syscontext)); 147 } else if ($record->isdefault) { 148 return get_string('defautltagcoll', 'tag'); 149 } else { 150 return $record->id; 151 } 152 } 153 154 /** 155 * Returns all tag areas in the given tag collection 156 * 157 * @param int $tagcollid 158 * @return array 159 */ 160 public static function get_areas($tagcollid) { 161 $allitemtypes = core_tag_area::get_areas($tagcollid, true); 162 $itemtypes = array(); 163 foreach ($allitemtypes as $itemtype => $it) { 164 foreach ($it as $component => $v) { 165 $itemtypes[$v->id] = $v; 166 } 167 } 168 return $itemtypes; 169 } 170 171 /** 172 * Returns the list of names of areas (enabled only) that are in this collection. 173 * 174 * @param int $tagcollid 175 * @return array 176 */ 177 public static function get_areas_names($tagcollid, $enabledonly = true) { 178 $allitemtypes = core_tag_area::get_areas($tagcollid, $enabledonly); 179 $itemtypes = array(); 180 foreach ($allitemtypes as $itemtype => $it) { 181 foreach ($it as $component => $v) { 182 $itemtypes[$v->id] = core_tag_area::display_name($component, $itemtype); 183 } 184 } 185 return $itemtypes; 186 } 187 188 /** 189 * Creates a new tag collection 190 * 191 * @param stdClass $data data from form core_tag_collection_form 192 * @return int|false id of created tag collection or false if failed 193 */ 194 public static function create($data) { 195 global $DB; 196 $data = (object)$data; 197 $tagcolls = self::get_collections(); 198 $tagcoll = (object)array( 199 'name' => $data->name, 200 'isdefault' => 0, 201 'component' => !empty($data->component) ? $data->component : null, 202 'sortorder' => count($tagcolls), 203 'searchable' => isset($data->searchable) ? (int)(bool)$data->searchable : 1, 204 'customurl' => !empty($data->customurl) ? $data->customurl : null, 205 ); 206 $tagcoll->id = $DB->insert_record('tag_coll', $tagcoll); 207 208 // Reset cache. 209 cache::make('core', 'tags')->delete('tag_coll'); 210 211 \core\event\tag_collection_created::create_from_record($tagcoll)->trigger(); 212 return $tagcoll; 213 } 214 215 /** 216 * Updates the tag collection information 217 * 218 * @param stdClass $tagcoll existing record in DB table tag_coll 219 * @param stdClass $data data to update 220 * @return bool wether the record was updated 221 */ 222 public static function update($tagcoll, $data) { 223 global $DB; 224 $defaulttagcollid = self::get_default(); 225 $allowedfields = array('name', 'searchable', 'customurl'); 226 if ($tagcoll->id == $defaulttagcollid) { 227 $allowedfields = array('name'); 228 } 229 230 $updatedata = array(); 231 $data = (array)$data; 232 foreach ($allowedfields as $key) { 233 if (array_key_exists($key, $data) && $data[$key] !== $tagcoll->$key) { 234 $updatedata[$key] = $data[$key]; 235 } 236 } 237 238 if (!$updatedata) { 239 // Nothing to update. 240 return false; 241 } 242 243 if (isset($updatedata['searchable'])) { 244 $updatedata['searchable'] = (int)(bool)$updatedata['searchable']; 245 } 246 foreach ($updatedata as $key => $value) { 247 $tagcoll->$key = $value; 248 } 249 $updatedata['id'] = $tagcoll->id; 250 $DB->update_record('tag_coll', $updatedata); 251 252 // Reset cache. 253 cache::make('core', 'tags')->delete('tag_coll'); 254 255 \core\event\tag_collection_updated::create_from_record($tagcoll)->trigger(); 256 257 return true; 258 } 259 260 /** 261 * Deletes a custom tag collection 262 * 263 * @param stdClass $tagcoll existing record in DB table tag_coll 264 * @return bool wether the tag collection was deleted 265 */ 266 public static function delete($tagcoll) { 267 global $DB, $CFG; 268 269 $defaulttagcollid = self::get_default(); 270 if ($tagcoll->id == $defaulttagcollid) { 271 return false; 272 } 273 274 // Move all tags from this tag collection to the default one. 275 $allitemtypes = core_tag_area::get_areas($tagcoll->id); 276 foreach ($allitemtypes as $it) { 277 foreach ($it as $v) { 278 core_tag_area::update($v, array('tagcollid' => $defaulttagcollid)); 279 } 280 } 281 282 // Delete tags from this tag_coll. 283 core_tag_tag::delete_tags($DB->get_fieldset_select('tag', 'id', 'tagcollid = ?', array($tagcoll->id))); 284 285 // Delete the tag collection. 286 $DB->delete_records('tag_coll', array('id' => $tagcoll->id)); 287 288 // Reset cache. 289 cache::make('core', 'tags')->delete('tag_coll'); 290 291 \core\event\tag_collection_deleted::create_from_record($tagcoll)->trigger(); 292 293 return true; 294 } 295 296 /** 297 * Moves the tag collection in the list one position up or down 298 * 299 * @param stdClass $tagcoll existing record in DB table tag_coll 300 * @param int $direction move direction: +1 or -1 301 * @return bool 302 */ 303 public static function change_sortorder($tagcoll, $direction) { 304 global $DB; 305 if ($direction != -1 && $direction != 1) { 306 throw new coding_exception('Second argument in tag_coll_change_sortorder() can be only 1 or -1'); 307 } 308 $tagcolls = self::get_collections(); 309 $keys = array_keys($tagcolls); 310 $idx = array_search($tagcoll->id, $keys); 311 if ($idx === false || $idx == 0 || $idx + $direction < 1 || $idx + $direction >= count($tagcolls)) { 312 return false; 313 } 314 $otherid = $keys[$idx + $direction]; 315 $DB->update_record('tag_coll', array('id' => $tagcoll->id, 'sortorder' => $idx + $direction)); 316 $DB->update_record('tag_coll', array('id' => $otherid, 'sortorder' => $idx)); 317 // Reset cache. 318 cache::make('core', 'tags')->delete('tag_coll'); 319 return true; 320 } 321 322 /** 323 * Permanently deletes all non-standard tags that no longer have any instances pointing to them 324 * 325 * @param array $collections optional list of tag collections ids to cleanup 326 */ 327 public static function cleanup_unused_tags($collections = null) { 328 global $DB, $CFG; 329 330 $params = array(); 331 $sql = "SELECT tg.id FROM {tag} tg LEFT OUTER JOIN {tag_instance} ti ON ti.tagid = tg.id 332 WHERE ti.id IS NULL AND tg.isstandard = 0"; 333 if ($collections) { 334 list($sqlcoll, $params) = $DB->get_in_or_equal($collections, SQL_PARAMS_NAMED); 335 $sql .= " AND tg.tagcollid " . $sqlcoll; 336 } 337 if ($unusedtags = $DB->get_fieldset_sql($sql, $params)) { 338 core_tag_tag::delete_tags($unusedtags); 339 } 340 } 341 342 /** 343 * Returns the list of tags with number of items tagged 344 * 345 * @param int $tagcollid 346 * @param null|bool $isstandard return only standard tags 347 * @param int $limit maximum number of tags to retrieve, tags are sorted by the instance count 348 * descending here regardless of $sort parameter 349 * @param string $sort sort order for display, default 'name' - tags will be sorted after they are retrieved 350 * @param string $search search string 351 * @param int $fromctx context id where this tag cloud is displayed 352 * @param int $ctx only retrieve tag instances in this context 353 * @param int $rec retrieve tag instances in the $ctx context and it's children (default 1) 354 * @return \core_tag\output\tagcloud 355 */ 356 public static function get_tag_cloud($tagcollid, $isstandard = false, $limit = 150, $sort = 'name', 357 $search = '', $fromctx = 0, $ctx = 0, $rec = 1) { 358 global $DB; 359 360 $fromclause = 'FROM {tag_instance} ti JOIN {tag} tg ON tg.id = ti.tagid'; 361 $whereclause = 'WHERE ti.itemtype <> \'tag\''; 362 list($sql, $params) = $DB->get_in_or_equal($tagcollid ? array($tagcollid) : 363 array_keys(self::get_collections(true))); 364 $whereclause .= ' AND tg.tagcollid ' . $sql; 365 if ($isstandard) { 366 $whereclause .= ' AND tg.isstandard = 1'; 367 } 368 $context = $ctx ? context::instance_by_id($ctx) : context_system::instance(); 369 if ($rec && $context->contextlevel != CONTEXT_SYSTEM) { 370 $fromclause .= ' JOIN {context} ctx ON ctx.id = ti.contextid '; 371 $whereclause .= ' AND ctx.path LIKE ?'; 372 $params[] = $context->path . '%'; 373 } else if (!$rec) { 374 $whereclause .= ' AND ti.contextid = ?'; 375 $params[] = $context->id; 376 } 377 if (strval($search) !== '') { 378 $whereclause .= ' AND tg.name LIKE ?'; 379 $params[] = '%' . core_text::strtolower($search) . '%'; 380 } 381 $tagsincloud = $DB->get_records_sql( 382 "SELECT tg.id, tg.rawname, tg.name, tg.isstandard, COUNT(ti.id) AS count, tg.flag, tg.tagcollid 383 $fromclause 384 $whereclause 385 GROUP BY tg.id, tg.rawname, tg.name, tg.flag, tg.isstandard, tg.tagcollid 386 ORDER BY count DESC, tg.name ASC", 387 $params, 0, $limit); 388 389 $tagscount = count($tagsincloud); 390 if ($tagscount == $limit) { 391 $tagscount = $DB->get_field_sql("SELECT COUNT(DISTINCT tg.id) $fromclause $whereclause", $params); 392 } 393 394 self::$cloudsortfield = $sort; 395 usort($tagsincloud, "self::cloud_sort"); 396 397 return new core_tag\output\tagcloud($tagsincloud, $tagscount, $fromctx, $ctx, $rec); 398 } 399 400 /** 401 * This function is used to sort the tags in the cloud. 402 * 403 * @param string $a Tag name to compare against $b 404 * @param string $b Tag name to compare against $a 405 * @return int The result of the comparison/validation 1, 0 or -1 406 */ 407 public static function cloud_sort($a, $b) { 408 $tagsort = self::$cloudsortfield ?: 'name'; 409 410 if (is_numeric($a->$tagsort)) { 411 return (($a->$tagsort == $b->$tagsort) ? 0 : ($a->$tagsort > $b->$tagsort)) ? 1 : -1; 412 } else if (is_string($a->$tagsort)) { 413 return strcmp($a->$tagsort, $b->$tagsort); 414 } else { 415 return 0; 416 } 417 } 418 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body