[ Index ] |
PHP Cross Reference of Unnamed Project |
[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 * Class core_tag_area for managing tag areas 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 areas 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_area { 35 36 /** 37 * Returns the list of areas indexed by itemtype and component 38 * 39 * @param int $tagcollid return only areas in this tag collection 40 * @param bool $enabledonly return only enabled tag areas 41 * @return array itemtype=>component=>tagarea object 42 */ 43 public static function get_areas($tagcollid = null, $enabledonly = false) { 44 global $DB; 45 $cache = cache::make('core', 'tags'); 46 if (($itemtypes = $cache->get('tag_area')) === false) { 47 $colls = core_tag_collection::get_collections(); 48 $defaultcoll = reset($colls); 49 $itemtypes = array(); 50 $areas = $DB->get_records('tag_area', array(), 'component,itemtype'); 51 foreach ($areas as $area) { 52 if ($colls[$area->tagcollid]->component) { 53 $area->locked = true; 54 } 55 $itemtypes[$area->itemtype][$area->component] = $area; 56 } 57 $cache->set('tag_area', $itemtypes); 58 } 59 if ($tagcollid || $enabledonly) { 60 $rv = array(); 61 foreach ($itemtypes as $itemtype => $it) { 62 foreach ($it as $component => $v) { 63 if (($v->tagcollid == $tagcollid || !$tagcollid) && (!$enabledonly || $v->enabled)) { 64 $rv[$itemtype][$component] = $v; 65 } 66 } 67 } 68 return $rv; 69 } 70 return $itemtypes; 71 } 72 73 /** 74 * Retrieves info about one tag area 75 * 76 * @param int $tagareaid 77 * @return stdClass 78 */ 79 public static function get_by_id($tagareaid) { 80 $tagareas = self::get_areas(); 81 foreach ($tagareas as $itemtype => $it) { 82 foreach ($it as $component => $v) { 83 if ($v->id == $tagareaid) { 84 return $v; 85 } 86 } 87 } 88 return null; 89 } 90 91 /** 92 * Returns the display name for this area 93 * 94 * @param string $component 95 * @param string $itemtype 96 * @return lang_string 97 */ 98 public static function display_name($component, $itemtype) { 99 $identifier = 'tagarea_' . clean_param($itemtype, PARAM_STRINGID); 100 if ($component === 'core') { 101 $component = 'tag'; 102 } 103 return new lang_string($identifier, $component); 104 } 105 106 /** 107 * Returns whether the tag area is enabled 108 * 109 * @param string $component component responsible for tagging 110 * @param string $itemtype what is being tagged, for example, 'post', 'course', 'user', etc. 111 * @return bool|null 112 */ 113 public static function is_enabled($component, $itemtype) { 114 global $CFG; 115 if (empty($CFG->usetags)) { 116 return false; 117 } 118 $itemtypes = self::get_areas(); 119 if (isset($itemtypes[$itemtype][$component])) { 120 return $itemtypes[$itemtype][$component]->enabled ? true : false; 121 } 122 return null; 123 } 124 125 /** 126 * Returns the id of the tag collection that should be used for storing tags of this itemtype 127 * 128 * @param string $component component responsible for tagging 129 * @param string $itemtype what is being tagged, for example, 'post', 'course', 'user', etc. 130 * @return int 131 */ 132 public static function get_collection($component, $itemtype) { 133 $itemtypes = self::get_areas(); 134 if (array_key_exists($itemtype, $itemtypes)) { 135 if (!array_key_exists($component, $itemtypes[$itemtype])) { 136 $component = key($itemtypes[$itemtype]); 137 } 138 return $itemtypes[$itemtype][$component]->tagcollid; 139 } 140 return core_tag_collection::get_default(); 141 } 142 143 /** 144 * Returns wether this tag area should display or not standard tags when user edits it. 145 * 146 * @param string $component component responsible for tagging 147 * @param string $itemtype what is being tagged, for example, 'post', 'course', 'user', etc. 148 * @return int 149 */ 150 public static function get_showstandard($component, $itemtype) { 151 $itemtypes = self::get_areas(); 152 if (array_key_exists($itemtype, $itemtypes)) { 153 if (!array_key_exists($component, $itemtypes[$itemtype])) { 154 $component = key($itemtypes[$itemtype]); 155 } 156 return $itemtypes[$itemtype][$component]->showstandard; 157 } 158 return core_tag_tag::BOTH_STANDARD_AND_NOT; 159 } 160 161 /** 162 * Returns all tag areas and collections that are currently cached in DB for this component 163 * 164 * @param string $componentname 165 * @return array first element is the list of areas and the second list of collections 166 */ 167 protected static function get_definitions_for_component($componentname) { 168 global $DB; 169 list($a, $b) = core_component::normalize_component($componentname); 170 $component = $b ? ($a . '_' . $b) : $a; 171 $sql = 'component = :component'; 172 $params = array('component' => $component); 173 if ($component === 'core') { 174 $sql .= ' OR component LIKE :coreprefix'; 175 $params['coreprefix'] = 'core_%'; 176 } 177 $fields = $DB->sql_concat_join("':'", array('itemtype', 'component')); 178 $existingareas = $DB->get_records_sql( 179 "SELECT $fields AS returnkey, a.* FROM {tag_area} a WHERE $sql", $params); 180 $fields = $DB->sql_concat_join("':'", array('name', 'component')); 181 $existingcolls = $DB->get_records_sql( 182 "SELECT $fields AS returnkey, t.* FROM {tag_coll} t WHERE $sql", $params); 183 return array($existingareas, $existingcolls); 184 185 } 186 187 /** 188 * Completely delete a tag area and all instances inside it 189 * 190 * @param stdClass $record 191 */ 192 protected static function delete($record) { 193 global $DB; 194 195 core_tag_tag::delete_instances($record->component, $record->itemtype); 196 197 $DB->delete_records('tag_area', 198 array('itemtype' => $record->itemtype, 199 'component' => $record->component)); 200 201 // Reset cache. 202 cache::make('core', 'tags')->delete('tag_area'); 203 } 204 205 /** 206 * Create a new tag area 207 * 208 * @param stdClass $record 209 */ 210 protected static function create($record) { 211 global $DB; 212 if (empty($record->tagcollid)) { 213 $record->tagcollid = core_tag_collection::get_default(); 214 } 215 $DB->insert_record('tag_area', array('component' => $record->component, 216 'itemtype' => $record->itemtype, 217 'tagcollid' => $record->tagcollid, 218 'callback' => $record->callback, 219 'callbackfile' => $record->callbackfile, 220 'showstandard' => isset($record->showstandard) ? $record->showstandard : core_tag_tag::BOTH_STANDARD_AND_NOT)); 221 222 // Reset cache. 223 cache::make('core', 'tags')->delete('tag_area'); 224 } 225 226 /** 227 * Update the tag area 228 * 229 * @param stdClass $existing current record from DB table tag_area 230 * @param array|stdClass $data fields that need updating 231 */ 232 public static function update($existing, $data) { 233 global $DB; 234 $data = array_intersect_key((array)$data, 235 array('enabled' => 1, 'tagcollid' => 1, 236 'callback' => 1, 'callbackfile' => 1, 'showstandard' => 1)); 237 foreach ($data as $key => $value) { 238 if ($existing->$key == $value) { 239 unset($data[$key]); 240 } 241 } 242 if (!$data) { 243 return; 244 } 245 246 if (!empty($data['tagcollid'])) { 247 self::move_tags($existing->component, $existing->itemtype, $data['tagcollid']); 248 } 249 250 $data['id'] = $existing->id; 251 $DB->update_record('tag_area', $data); 252 253 // Reset cache. 254 cache::make('core', 'tags')->delete('tag_area'); 255 } 256 257 /** 258 * Update the database to contain a list of tagged areas for a component. 259 * The list of tagged areas is read from [plugindir]/db/tag.php 260 * 261 * @param string $componentname - The frankenstyle component name. 262 */ 263 public static function reset_definitions_for_component($componentname) { 264 global $DB; 265 $dir = core_component::get_component_directory($componentname); 266 $file = $dir . '/db/tag.php'; 267 $tagareas = null; 268 if (file_exists($file)) { 269 require_once($file); 270 } 271 272 list($a, $b) = core_component::normalize_component($componentname); 273 $component = $b ? ($a . '_' . $b) : $a; 274 275 list($existingareas, $existingcolls) = self::get_definitions_for_component($componentname); 276 277 $itemtypes = array(); 278 $collections = array(); 279 $needcleanup = false; 280 if ($tagareas) { 281 foreach ($tagareas as $tagarea) { 282 $record = (object)$tagarea; 283 if ($component !== 'core' || empty($record->component)) { 284 if (isset($record->component) && $record->component !== $component) { 285 debugging("Item type {$record->itemtype} has illegal component {$record->component}", DEBUG_DEVELOPER); 286 } 287 $record->component = $component; 288 } 289 unset($record->tagcollid); 290 if (!empty($record->collection)) { 291 // Create collection if it does not exist, or update 'searchable' and/or 'customurl' if needed. 292 $key = $record->collection . ':' . $record->component; 293 $collectiondata = array_intersect_key((array)$record, 294 array('component' => 1, 'searchable' => 1, 'customurl' => 1)); 295 $collectiondata['name'] = $record->collection; 296 if (!array_key_exists($key, $existingcolls)) { 297 $existingcolls[$key] = core_tag_collection::create($collectiondata); 298 } else { 299 core_tag_collection::update($existingcolls[$key], $collectiondata); 300 } 301 $record->tagcollid = $existingcolls[$key]->id; 302 $collections[$key] = $existingcolls[$key]; 303 unset($record->collection); 304 } 305 unset($record->searchable); 306 unset($record->customurl); 307 if (!isset($record->callback)) { 308 $record->callback = null; 309 } 310 if (!isset($record->callbackfile)) { 311 $record->callbackfile = null; 312 } 313 $itemtypes[$record->itemtype . ':' . $record->component] = $record; 314 } 315 } 316 $todeletearea = array_diff_key($existingareas, $itemtypes); 317 $todeletecoll = array_diff_key($existingcolls, $collections); 318 319 // Delete tag areas that are no longer needed. 320 foreach ($todeletearea as $key => $record) { 321 self::delete($record); 322 } 323 324 // Update tag areas if changed. 325 $toupdatearea = array_intersect_key($existingareas, $itemtypes); 326 foreach ($toupdatearea as $key => $tagarea) { 327 if (!isset($itemtypes[$key]->tagcollid)) { 328 foreach ($todeletecoll as $tagcoll) { 329 if ($tagcoll->id == $tagarea->tagcollid) { 330 $itemtypes[$key]->tagcollid = core_tag_collection::get_default(); 331 } 332 } 333 } 334 unset($itemtypes[$key]->showstandard); // Do not override value that was already changed by admin with the default. 335 self::update($tagarea, $itemtypes[$key]); 336 } 337 338 // Create new tag areas. 339 $toaddarea = array_diff_key($itemtypes, $existingareas); 340 foreach ($toaddarea as $record) { 341 self::create($record); 342 } 343 344 // Delete tag collections that are no longer needed. 345 foreach ($todeletecoll as $key => $tagcoll) { 346 core_tag_collection::delete($tagcoll); 347 } 348 } 349 350 /** 351 * Deletes all tag areas, collections and instances associated with the plugin. 352 * 353 * @param string $pluginname 354 */ 355 public static function uninstall($pluginname) { 356 global $DB; 357 358 list($a, $b) = core_component::normalize_component($pluginname); 359 if (empty($b) || $a === 'core') { 360 throw new coding_exception('Core component can not be uninstalled'); 361 } 362 $component = $a . '_' . $b; 363 364 core_tag_tag::delete_instances($component); 365 366 $DB->delete_records('tag_area', array('component' => $component)); 367 $DB->delete_records('tag_coll', array('component' => $component)); 368 cache::make('core', 'tags')->delete_many(array('tag_area', 'tag_coll')); 369 } 370 371 /** 372 * Moves existing tags associated with an item type to another tag collection 373 * 374 * @param string $component 375 * @param string $itemtype 376 * @param int $tagcollid 377 */ 378 public static function move_tags($component, $itemtype, $tagcollid) { 379 global $DB; 380 $params = array('itemtype1' => $itemtype, 'component1' => $component, 381 'itemtype2' => $itemtype, 'component2' => $component, 382 'tagcollid1' => $tagcollid, 'tagcollid2' => $tagcollid); 383 384 // Find all collections that need to be cleaned later. 385 $sql = "SELECT DISTINCT t.tagcollid " . 386 "FROM {tag_instance} ti " . 387 "JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1 " . 388 "WHERE ti.itemtype = :itemtype2 AND ti.component = :component2 "; 389 $cleanupcollections = $DB->get_fieldset_sql($sql, $params); 390 391 // Find all tags that are related to the tags being moved and make sure they are present in the target tagcoll. 392 // This query is a little complicated because Oracle does not allow to run SELECT DISTINCT on CLOB fields. 393 $sql = "SELECT name, rawname, description, descriptionformat, userid, isstandard, flag, timemodified ". 394 "FROM {tag} WHERE id IN ". 395 "(SELECT r.id ". 396 "FROM {tag_instance} ti ". // Instances that need moving. 397 "JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1 ". // Tags that need moving. 398 "JOIN {tag_instance} tr ON tr.itemtype = 'tag' and tr.component = 'core' AND tr.itemid = t.id ". 399 "JOIN {tag} r ON r.id = tr.tagid ". // Tags related to the tags that need moving. 400 "LEFT JOIN {tag} re ON re.name = r.name AND re.tagcollid = :tagcollid2 ". // Existing tags in the target tagcoll with the same name as related tags. 401 "WHERE ti.itemtype = :itemtype2 AND ti.component = :component2 ". 402 " AND re.id IS NULL)"; // We need related tags that ARE NOT present in the target tagcoll. 403 $result = $DB->get_records_sql($sql, $params); 404 foreach ($result as $tag) { 405 $tag->tagcollid = $tagcollid; 406 $tag->id = $DB->insert_record('tag', $tag); 407 \core\event\tag_created::create_from_tag($tag); 408 } 409 410 // Find all tags that need moving and have related tags, remember their related tags. 411 $sql = "SELECT t.name AS tagname, r.rawname AS relatedtag ". 412 "FROM {tag_instance} ti ". // Instances that need moving. 413 "JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1 ". // Tags that need moving. 414 "JOIN {tag_instance} tr ON t.id = tr.tagid AND tr.itemtype = 'tag' and tr.component = 'core' ". 415 "JOIN {tag} r ON r.id = tr.itemid ". // Tags related to the tags that need moving. 416 "WHERE ti.itemtype = :itemtype2 AND ti.component = :component2 ". 417 "ORDER BY t.id, tr.ordering "; 418 $relatedtags = array(); 419 $result = $DB->get_recordset_sql($sql, $params); 420 foreach ($result as $record) { 421 $relatedtags[$record->tagname][] = $record->relatedtag; 422 } 423 $result->close(); 424 425 // Find all tags that are used for this itemtype/component and are not present in the target tag collection. 426 // This query is a little complicated because Oracle does not allow to run SELECT DISTINCT on CLOB fields. 427 $sql = "SELECT id, name, rawname, description, descriptionformat, userid, isstandard, flag, timemodified 428 FROM {tag} WHERE id IN 429 (SELECT t.id 430 FROM {tag_instance} ti 431 JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1 432 LEFT JOIN {tag} tt ON tt.name = t.name AND tt.tagcollid = :tagcollid2 433 WHERE ti.itemtype = :itemtype2 AND ti.component = :component2 434 AND tt.id IS NULL)"; 435 $movedtags = array(); // Keep track of moved tags so we don't hit DB index violation. 436 $result = $DB->get_records_sql($sql, $params); 437 foreach ($result as $tag) { 438 $originaltagid = $tag->id; 439 if (array_key_exists($tag->name, $movedtags)) { 440 // Case of corrupted data when the same tag was in several collections. 441 $tag->id = $movedtags[$tag->name]; 442 } else { 443 // Copy the tag into the new collection. 444 unset($tag->id); 445 $tag->tagcollid = $tagcollid; 446 $tag->id = $DB->insert_record('tag', $tag); 447 \core\event\tag_created::create_from_tag($tag); 448 $movedtags[$tag->name] = $tag->id; 449 } 450 $DB->execute("UPDATE {tag_instance} SET tagid = ? WHERE tagid = ? AND itemtype = ? AND component = ?", 451 array($tag->id, $originaltagid, $itemtype, $component)); 452 } 453 454 // Find all tags that are used for this itemtype/component and are already present in the target tag collection. 455 $sql = "SELECT DISTINCT t.id, tt.id AS targettagid 456 FROM {tag_instance} ti 457 JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1 458 JOIN {tag} tt ON tt.name = t.name AND tt.tagcollid = :tagcollid2 459 WHERE ti.itemtype = :itemtype2 AND ti.component = :component2"; 460 $result = $DB->get_records_sql($sql, $params); 461 foreach ($result as $tag) { 462 $DB->execute("UPDATE {tag_instance} SET tagid = ? WHERE tagid = ? AND itemtype = ? AND component = ?", 463 array($tag->targettagid, $tag->id, $itemtype, $component)); 464 } 465 466 // Add related tags to the moved tags. 467 if ($relatedtags) { 468 $tags = core_tag_tag::get_by_name_bulk($tagcollid, array_keys($relatedtags)); 469 foreach ($tags as $tag) { 470 $tag->add_related_tags($relatedtags[$tag->name]); 471 } 472 } 473 474 if ($cleanupcollections) { 475 core_tag_collection::cleanup_unused_tags($cleanupcollections); 476 } 477 478 // Reset caches. 479 cache::make('core', 'tags')->delete('tag_area'); 480 } 481 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |