MDL-34258: Plagiarism API now returns strings so mod_assign needs these updates
[moodle.git] / tag / coursetagslib.php
blob5b5557aa5e48acd4dc6c1fd91eb8889288b30c9d
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.
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/>.
18 /**
19 * coursetagslib.php
21 * @package core_tag
22 * @copyright 2007 j.beedell@open.ac.uk
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 require_once $CFG->dirroot.'/tag/lib.php';
28 /**
29 * Returns an ordered array of tags associated with visible courses
30 * (boosted replacement of get_all_tags() allowing association with user and tagtype).
32 * @package core_tag
33 * @category tag
34 * @param int $courseid A course id. Passing 0 will return all distinct tags for all visible courses
35 * @param int $userid (optional) the user id, a default of 0 will return all users tags for the course
36 * @param string $tagtype (optional) The type of tag, empty string returns all types. Currently (Moodle 2.2) there are two
37 * types of tags which are used within Moodle, they are 'official' and 'default'.
38 * @param int $numtags (optional) number of tags to display, default of 80 is set in the block, 0 returns all
39 * @param string $sort (optional) selected sorting, default is alpha sort (name) also timemodified or popularity
40 * @return array
42 function coursetag_get_tags($courseid, $userid=0, $tagtype='', $numtags=0, $sort='name') {
44 global $CFG, $DB;
46 // get visible course ids
47 $courselist = array();
48 if ($courseid === 0) {
49 if ($courses = $DB->get_records_select('course', 'visible=1 AND category>0', null, '', 'id')) {
50 foreach ($courses as $key => $value) {
51 $courselist[] = $key;
56 // get tags from the db ordered by highest count first
57 $params = array();
58 $sql = "SELECT id as tkey, name, id, tagtype, rawname, f.timemodified, flag, count
59 FROM {tag} t,
60 (SELECT tagid, MAX(timemodified) as timemodified, COUNT(id) as count
61 FROM {tag_instance}
62 WHERE itemtype = 'course' ";
64 if ($courseid > 0) {
65 $sql .= " AND itemid = :courseid ";
66 $params['courseid'] = $courseid;
67 } else {
68 if (!empty($courselist)) {
69 list($usql, $uparams) = $DB->get_in_or_equal($courselist, SQL_PARAMS_NAMED);
70 $sql .= "AND itemid $usql ";
71 $params = $params + $uparams;
75 if ($userid > 0) {
76 $sql .= " AND tiuserid = :userid ";
77 $params['userid'] = $userid;
80 $sql .= " GROUP BY tagid) f
81 WHERE t.id = f.tagid ";
82 if ($tagtype != '') {
83 $sql .= "AND tagtype = :tagtype ";
84 $params['tagtype'] = $tagtype;
86 $sql .= "ORDER BY count DESC, name ASC";
88 // limit the number of tags for output
89 if ($numtags == 0) {
90 $tags = $DB->get_records_sql($sql, $params);
91 } else {
92 $tags = $DB->get_records_sql($sql, $params, 0, $numtags);
95 // prepare the return
96 $return = array();
97 if ($tags) {
98 // sort the tag display order
99 if ($sort != 'popularity') {
100 $CFG->tagsort = $sort;
101 usort($tags, "coursetag_sort");
103 // avoid print_tag_cloud()'s ksort upsetting ordering by setting the key here
104 foreach ($tags as $value) {
105 $return[] = $value;
109 return $return;
114 * Returns an ordered array of tags
115 * (replaces popular_tags_count() allowing sorting).
117 * @package core_tag
118 * @category tag
119 * @param string $sort (optional) selected sorting, default is alpha sort (name) also timemodified or popularity
120 * @param int $numtags (optional) number of tags to display, default of 20 is set in the block, 0 returns all
121 * @return array
123 function coursetag_get_all_tags($sort='name', $numtags=0) {
125 global $CFG, $DB;
127 // note that this selects all tags except for courses that are not visible
128 $sql = "SELECT id, name, tagtype, rawname, f.timemodified, flag, count
129 FROM {tag} t,
130 (SELECT tagid, MAX(timemodified) as timemodified, COUNT(id) as count
131 FROM {tag_instance} WHERE tagid NOT IN
132 (SELECT tagid FROM {tag_instance} ti, {course} c
133 WHERE c.visible = 0
134 AND ti.itemtype = 'course'
135 AND ti.itemid = c.id)
136 GROUP BY tagid) f
137 WHERE t.id = f.tagid
138 ORDER BY count DESC, name ASC";
139 if ($numtags == 0) {
140 $tags = $DB->get_records_sql($sql);
141 } else {
142 $tags = $DB->get_records_sql($sql, null, 0, $numtags);
145 $return = array();
146 if ($tags) {
147 if ($sort != 'popularity') {
148 $CFG->tagsort = $sort;
149 usort($tags, "coursetag_sort");
151 foreach ($tags as $value) {
152 $return[] = $value;
156 return $return;
160 * Sorting callback function for coursetag_get_tags() and coursetag_get_all_tags() only
162 * This function does a comparision on a field withing two variables, $a and $b. The field used is specified by
163 * $CFG->tagsort or we just use the 'name' field if $CFG->tagsort is empty. The comparison works as follows:
164 * If $a->$tagsort is greater than $b->$tagsort, 1 is returned.
165 * If $a->$tagsort is equal to $b->$tagsort, 0 is returned.
166 * If $a->$tagsort is less than $b->$tagsort, -1 is returned.
168 * Also if $a->$tagsort is not numeric or a string, 0 is returned.
170 * @package core_tag
171 * @access private
172 * @param int|string|mixed $a Variable to compare against $b
173 * @param int|string|mixed $b Variable to compare against $a
174 * @return int The result of the comparison/validation 1, 0 or -1
176 function coursetag_sort($a, $b) {
177 // originally from block_blog_tags
178 global $CFG;
180 // set up the variable $tagsort as either 'name' or 'timemodified' only, 'popularity' does not need sorting
181 if (empty($CFG->tagsort)) {
182 $tagsort = 'name';
183 } else {
184 $tagsort = $CFG->tagsort;
187 if (is_numeric($a->$tagsort)) {
188 return ($a->$tagsort == $b->$tagsort) ? 0 : ($a->$tagsort > $b->$tagsort) ? 1 : -1;
189 } elseif (is_string($a->$tagsort)) {
190 return strcmp($a->$tagsort, $b->$tagsort);
191 } else {
192 return 0;
197 * Prints a tag cloud
199 * @package core_tag
200 * @category tag
201 * @param array $tagcloud array of tag objects (fields: id, name, rawname, count and flag)
202 * @param mixed $return if true return html string
203 * @param int $max_size maximum text size, in percentage
204 * @param int $min_size minimum text size, in percentage
206 function coursetag_print_cloud($tagcloud, $return=false, $max_size=180, $min_size=80) {
208 global $CFG;
210 if (empty($tagcloud)) {
211 return;
214 ksort($tagcloud);
216 $count = array();
217 foreach ($tagcloud as $key => $value) {
218 if(!empty($value->count)) {
219 $count[$key] = log10($value->count);
220 } else {
221 $count[$key] = 0;
225 $max = max($count);
226 $min = min($count);
228 $spread = $max - $min;
229 if (0 == $spread) { // we don't want to divide by zero
230 $spread = 1;
233 $step = ($max_size - $min_size)/($spread);
235 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
236 $can_manage_tags = has_capability('moodle/tag:manage', $systemcontext);
238 //prints the tag cloud
239 $output = '<ul class="tag-cloud inline-list">';
240 foreach ($tagcloud as $key => $tag) {
242 $size = $min_size + ((log10($tag->count) - $min) * $step);
243 $size = ceil($size);
245 $style = 'style="font-size: '.$size.'%"';
247 if ($tag->count > 1) {
248 $title = 'title="'.s(get_string('thingstaggedwith','tag', $tag)).'"';
249 } else {
250 $title = 'title="'.s(get_string('thingtaggedwith','tag', $tag)).'"';
253 $href = 'href="'.$CFG->wwwroot.'/tag/index.php?id='.$tag->id.'"';
255 //highlight tags that have been flagged as inappropriate for those who can manage them
256 $tagname = tag_display_name($tag);
257 if ($tag->flag > 0 && $can_manage_tags) {
258 $tagname = '<span class="flagged-tag">' . tag_display_name($tag) . '</span>';
261 $tag_link = '<li><a '.$href.' '.$title.' '. $style .'>'.$tagname.'</a></li> ';
263 $output .= $tag_link;
266 $output .= '</ul>'."\n";
268 if ($return) {
269 return $output;
270 } else {
271 echo $output;
276 * Returns javascript for use in tags block and supporting pages
278 * @package core_tag
279 * @category tag
280 * @param string $coursetagdivs comma separated divs ids
281 * @return null
283 function coursetag_get_jscript($coursetagdivs = '') {
284 global $CFG, $DB, $PAGE;
286 $PAGE->requires->js('/tag/tag.js');
287 $PAGE->requires->strings_for_js(array('jserror1', 'jserror2'), 'block_tags');
289 if ($coursetagdivs) {
290 $PAGE->requires->js_function_call('set_course_tag_divs', $coursetagdivs);
293 if ($coursetags = $DB->get_records('tag', null, 'name ASC', 'name, id')) {
294 foreach ($coursetags as $key => $value) {
295 $PAGE->requires->js_function_call('set_course_tag', array($key));
299 $PAGE->requires->js('/blocks/tags/coursetags.js');
301 return '';
305 * Returns javascript to create the links in the tag block footer.
307 * @package core_tag
308 * @category tag
309 * @param string $elementid the element to attach the footer to
310 * @param array $coursetagslinks links arrays each consisting of 'title', 'onclick' and 'text' elements
311 * @return string always returns a blank string
313 function coursetag_get_jscript_links($elementid, $coursetagslinks) {
314 global $PAGE;
316 if (!empty($coursetagslinks)) {
317 foreach ($coursetagslinks as $a) {
318 $PAGE->requires->js_function_call('add_tag_footer_link', array($elementid, $a['title'], $a['onclick'], $a['text']), true);
322 return '';
326 * Returns all tags created by a user for a course
328 * @package core_tag
329 * @category tag
330 * @param int $courseid tags are returned for the course that has this courseid
331 * @param int $userid return tags which were created by this user
333 function coursetag_get_records($courseid, $userid) {
334 global $CFG, $DB;
336 $sql = "SELECT t.id, name, rawname
337 FROM {tag} t, {tag_instance} ti
338 WHERE t.id = ti.tagid
339 AND ti.tiuserid = :userid
340 AND ti.itemid = :courseid
341 ORDER BY name ASC";
343 return $DB->get_records_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid));
347 * Stores a tag for a course for a user
349 * @package core_tag
350 * @category tag
351 * @param array $tags simple array of keywords to be stored
352 * @param int $courseid the id of the course we wish to store a tag for
353 * @param int $userid the id of the user we wish to store a tag for
354 * @param string $tagtype official or default only
355 * @param string $myurl (optional) for logging creation of course tags
357 function coursetag_store_keywords($tags, $courseid, $userid=0, $tagtype='official', $myurl='') {
359 global $CFG;
361 if (is_array($tags) and !empty($tags)) {
362 foreach($tags as $tag) {
363 $tag = trim($tag);
364 if (strlen($tag) > 0) {
365 //tag_set_add('course', $courseid, $tag, $userid); //deletes official tags
367 //add tag if does not exist
368 if (!$tagid = tag_get_id($tag)) {
369 $tag_id_array = tag_add(array($tag), $tagtype);
370 $tagid = $tag_id_array[textlib::strtolower($tag)];
372 //ordering
373 $ordering = 0;
374 if ($current_ids = tag_get_tags_ids('course', $courseid)) {
375 end($current_ids);
376 $ordering = key($current_ids) + 1;
378 //set type
379 tag_type_set($tagid, $tagtype);
381 //tag_instance entry
382 tag_assign('course', $courseid, $tagid, $ordering, $userid);
384 //logging - note only for user added tags
385 if ($tagtype == 'default' and $myurl != '') {
386 $url = $myurl.'?query='.urlencode($tag);
387 add_to_log($courseid, 'coursetags', 'add', $url, 'Course tagged');
396 * Deletes a personal tag for a user for a course.
398 * @package core_tag
399 * @category tag
400 * @param int $tagid the tag we wish to delete
401 * @param int $userid the user that the tag is associated with
402 * @param int $courseid the course that the tag is associated with
404 function coursetag_delete_keyword($tagid, $userid, $courseid) {
406 global $CFG, $DB;
408 $sql = "SELECT COUNT(*)
409 FROM {tag_instance}
410 WHERE tagid = $tagid
411 AND tiuserid = $userid
412 AND itemtype = 'course'
413 AND itemid = $courseid";
414 if ($DB->count_records_sql($sql) == 1) {
415 $sql = "tagid = $tagid
416 AND tiuserid = $userid
417 AND itemtype = 'course'
418 AND itemid = $courseid";
419 $DB->delete_records_select('tag_instance', $sql);
420 // if there are no other instances of the tag then consider deleting the tag as well
421 if (!$DB->record_exists('tag_instance', array('tagid' => $tagid))) {
422 // if the tag is a personal tag then delete it - don't do official tags
423 if ($DB->record_exists('tag', array('id' => $tagid, 'tagtype' => 'default'))) {
424 $DB->delete_records('tag', array('id' => $tagid, 'tagtype' => 'default'));
427 } else {
428 print_error("errordeleting", 'tag', '', $tagid);
434 * Get courses tagged with a tag
436 * @global moodle_database $DB
437 * @package core_tag
438 * @category tag
439 * @param int $tagid
440 * @return array of course objects
442 function coursetag_get_tagged_courses($tagid) {
443 global $DB;
445 $courses = array();
447 $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
449 $sql = "SELECT c.*, $ctxselect
450 FROM {course} c
451 JOIN {tag_instance} t ON t.itemid = c.id
452 JOIN {context} ctx ON ctx.instanceid = c.id
453 WHERE t.tagid = :tagid AND
454 t.itemtype = 'course' AND
455 ctx.contextlevel = :contextlevel
456 ORDER BY c.sortorder ASC";
457 $params = array('tagid' => $tagid, 'contextlevel' => CONTEXT_COURSE);
458 $rs = $DB->get_recordset_sql($sql, $params);
459 foreach ($rs as $course) {
460 context_helper::preload_from_record($course);
461 if ($course->visible == 1 || has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) {
462 $courses[$course->id] = $course;
465 return $courses;
469 * Course tagging function used only during the deletion of a course (called by lib/moodlelib.php) to clean up associated tags
471 * @package core_tag
472 * @param int $courseid the course we wish to delete tag instances from
473 * @param bool $showfeedback if we should output a notification of the delete to the end user
475 function coursetag_delete_course_tags($courseid, $showfeedback=false) {
476 global $DB, $OUTPUT;
478 if ($tags = $DB->get_records_select('tag_instance', "itemtype='course' AND itemid=:courseid", array('courseid'=>$courseid))) {
479 foreach ($tags as $tag) {
480 //delete the course tag instance record
481 $DB->delete_records('tag_instance', array('tagid'=>$tag->tagid, 'itemtype'=>'course', 'itemid'=> $courseid));
482 // delete tag if there are no other tag_instance entries now
483 if (!($DB->record_exists('tag_instance', array('tagid'=>$tag->tagid)))) {
484 $DB->delete_records('tag', array('id'=>$tag->tagid));
485 // Delete files
486 $fs = get_file_storage();
487 $fs->delete_area_files(get_system_context()->id, 'tag', 'description', $tag->tagid);
492 if ($showfeedback) {
493 echo $OUTPUT->notification(get_string('deletedcoursetags', 'tag'), 'notifysuccess');
498 * Function called by cron to create/update users rss feeds
500 * @return true
502 * Function removed.
503 * rsslib.php needs updating to accept Dublin Core tags (dc/cc) input before this can work.
506 function coursetag_rss_feeds() {
508 global $CFG, $DB;
509 require_once($CFG->dirroot.'/lib/dmllib.php');
510 require_once($CFG->dirroot.'/lib/rsslib.php');
512 $status = true;
513 mtrace(' Preparing to update all user unit tags RSS feeds');
514 if (empty($CFG->enablerssfeeds)) {
515 mtrace(' RSS DISABLED (admin variables - enablerssfeeds)');
516 } else {
518 // Load all the categories for use later on
519 $categories = $DB->get_records('course_categories');
521 // get list of users who have tagged a unit
522 $sql = "
523 SELECT DISTINCT u.id as userid, u.username, u.firstname, u.lastname, u.email
524 FROM {user} u, {course} c, {tag_instance} cti, {tag} t
525 WHERE c.id = cti.itemid
526 AND u.id = cti.tiuserid
527 AND t.id = cti.tagid
528 AND t.tagtype = 'personal'
529 AND u.confirmed = 1
530 AND u.deleted = 0
531 ORDER BY userid";
532 if ($users = $DB->get_records_sql($sql)) {
534 $items = array(); //contains rss data items for each user
535 foreach ($users as $user) {
537 // loop through each user, getting the data (tags for courses)
538 $sql = "
539 SELECT cti.id, c.id as courseid, c.fullname, c.shortname, c.category, t.rawname, cti.timemodified
540 FROM {course} c, {tag_instance} cti, {tag} t
541 WHERE c.id = cti.itemid
542 AND cti.tiuserid = :userid{$user->userid}
543 AND cti.tagid = t.id
544 AND t.tagtype = 'personal'
545 ORDER BY courseid";
546 if ($usertags = $DB->get_records_sql($sql, array('userid' => $user->userid))) {
547 $latest_date = 0; //latest date any tag was created by a user
548 $c = 0; //course identifier
550 foreach ($usertags as $usertag) {
551 if ($usertag->courseid != $c) {
552 $c = $usertag->courseid;
553 $items[$c] = new stdClass();
554 $items[$c]->title = $usertag->fullname . '(' . $usertag->shortname . ')';
555 $items[$c]->link = $CFG->wwwroot . '/course/view.php?name=' . $usertag->shortname;
556 $items[$c]->description = ''; //needs to be blank
557 $items[$c]->category = $categories[$usertag->category]->name;
558 $items[$c]->subject[] = $usertag->rawname;
559 $items[$c]->pubdate = $usertag->timemodified;
560 $items[$c]->tag = true;
561 } else {
562 $items[$c]->subject[] .= $usertag->rawname;
564 // Check and set the latest modified date.
565 $latest_date = $usertag->timemodified > $latest_date ? $usertag->timemodified : $latest_date;
568 // Setup some vars for use while creating the file
569 $path = $CFG->dataroot.'/1/usertagsrss/'.$user->userid;
570 $file_name = 'user_unit_tags_rss.xml';
571 $title = get_string('rsstitle', 'tag', ucwords(strtolower($user->firstname.' '.$user->lastname)));
572 $desc = get_string('rssdesc', 'tag');
573 // check that the path exists
574 if (!file_exists($path)) {
575 mtrace(' Creating folder '.$path);
576 check_dir_exists($path, TRUE, TRUE);
579 // create or update the feed for the user
580 // this functionality can be copied into seperate lib as in next two lines
581 //require_once($CFG->dirroot.'/local/ocilib.php');
582 //oci_create_rss_feed( $path, $file_name, $latest_date, $items, $title, $desc, $dc=true, $cc=false);
584 // Set path to RSS file
585 $full_path = "$save_path/$file_name";
587 mtrace(" Preparing to update RSS feed for $file_name");
589 // First let's make sure there is work to do by checking the time the file was last modified,
590 // if a course was update after the file was mofified
591 if (file_exists($full_path)) {
592 if ($lastmodified = filemtime($full_path)) {
593 mtrace(" XML File $file_name Created on ".date( "D, j M Y G:i:s T", $lastmodified ));
594 mtrace(' Lastest course modification on '.date( "D, j M Y G:i:s T", $latest_date ));
595 if ($latest_date > $lastmodified) {
596 mtrace(" XML File $file_name needs updating");
597 $changes = true;
598 } else {
599 mtrace(" XML File $file_name doesn't need updating");
600 $changes = false;
603 } else {
604 mtrace(" XML File $file_name needs updating");
605 $changes = true;
608 if ($changes) {
609 // Now we know something has changed, write the new file
611 if (!empty($items)) {
612 // First set rss feeds common headers
613 $header = rss_standard_header(strip_tags(format_string($title,true)),
614 $CFG->wwwroot,
615 $desc,
616 true, true);
617 // Now all the rss items
618 if (!empty($header)) {
619 $articles = rss_add_items($items,$dc,$cc);
621 // Now all rss feeds common footers
622 if (!empty($header) && !empty($articles)) {
623 $footer = rss_standard_footer();
625 // Now, if everything is ok, concatenate it
626 if (!empty($header) && !empty($articles) && !empty($footer)) {
627 $result = $header.$articles.$footer;
628 } else {
629 $result = false;
631 } else {
632 $result = false;
635 // Save the XML contents to file
636 if (!empty($result)) {
637 $rss_file = fopen($full_path, "w");
638 if ($rss_file) {
639 $status = fwrite ($rss_file, $result);
640 fclose($rss_file);
641 } else {
642 $status = false;
646 // Output result
647 if (empty($result)) {
648 // There was nothing to put into the XML file. Delete it!
649 if( is_file($full_path) ) {
650 mtrace(" There were no items for XML File $file_name. Deleting XML File");
651 unlink($full_path);
652 mtrace(" $full_path -> (deleted)");
653 } else {
654 mtrace(" There were no items for the XML File $file_name and no file to delete. Ignore.");
656 } else {
657 if (!empty($status)) {
658 mtrace(" $full_path -> OK");
659 } else {
660 mtrace(" $full_path -> FAILED");
664 //end of oci_create_rss_feed()
670 return $status;
675 * Get official keywords for the <meta name="keywords"> in header.html
676 * use: echo '<meta name="keywords" content="'.coursetag_get_official_keywords($COURSE->id).'"/>';
678 * @param int $courseid
679 * @return string
681 * Function removed but fully working
682 * This function is potentially useful to anyone wanting to improve search results for course pages.
683 * The idea is to add official tags (not personal tags to prevent their deletion) to all
684 * courses (facility not added yet) which will be automatically added to the page header to boost
685 * search engine specificity/ratings.
688 function coursetag_get_official_keywords($courseid, $asarray=false) {
689 global $CFG;
690 $returnstr = '';
691 $sql = "SELECT t.id, name, rawname
692 FROM {tag} t, {tag_instance} ti
693 WHERE ti.itemid = :courseid
694 AND ti.itemtype = 'course'
695 AND t.tagtype = 'official'
696 AND ti.tagid = t.id
697 ORDER BY name ASC";
698 if ($tags = $DB->get_records_sql($sql, array('courseid' => $courseid))) {
699 if ($asarray) {
700 return $tags;
702 foreach ($tags as $tag) {
703 if( empty($CFG->keeptagnamecase) ) {
704 $name = textlib::strtotitle($tag->name);
705 } else {
706 $name = $tag->rawname;
708 $returnstr .= $name.', ';
710 $returnstr = rtrim($returnstr, ', ');
712 return $returnstr;