First checkin of tag system from GSOC student Luiz Cruz
[moodle.git] / tag / lib.php
CommitLineData
c933a060 1<?php // $Id$
2
3require_once('../config.php');
4
5/**
6 * Creates tags
7
8 * Ex: tag_create('A VeRY cOoL Tag, Another NICE tag')
9 * will create the following normalized {@link tag_normalize()} entries in tags table:
10 * 'a very cool tag'
11 * 'another nice tag'
12 *
13 * @param string $tag_names_csv CSV tag names (can be unnormalized) to be created.
14 * @param string $tag_type type of tag to be created ("default" is the default value).
15 * @return an array of tags ids, indexed by their normalized names
16 */
17function tag_create($tag_names_csv, $tag_type="default") {
18 global $USER;
19
20 $normalized_tag_names_csv = tag_normalize($tag_names_csv) ;
21
22 $tags = explode(",", $normalized_tag_names_csv );
23
24 $tag_object = new StdClass;
25 $tag_object->tagtype = $tag_type;
26 $tag_object->userid = $USER->id;
27
28 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
29 $can_create_tags = has_capability('moodle/tag:create',$systemcontext);
30
31 foreach ($tags as $tag_name) {
32
33 $tag_object->name = $tag_name;
34 $tag_object->timemodified = time();
35
36// if ( !record_exists('tag', 'name', $tag_name) && !empty($tag_name) && !is_numeric($tag_name) ) {
37 if ( $can_create_tags && !empty($tag_name) && !is_numeric($tag_name) ) {
38 insert_record('tag', $tag_object);
39 }
40 }
41
42 return tags_id($normalized_tag_names_csv);
43
44}
45
46/**
47 * Deletes tags
48 *
49 * Ex 1: tag_delete('a very cool tag, another nice tag')
50 * Will delete the tags with names 'a very cool tag' and 'another nice tag' from the 'tags' table, if they exist!
51 *
52 * Ex 2: tag_delete('computers, 123, 143, algorithms')
53 * Will delete tags with names 'computers' and 'algorithms' and tags with ids 123 and 143.
54 *
55 *
56 * @param string $tag_names_or_ids_csv **normalized** tag names or ids of the tags to be deleted.
57 */
58
59function tag_delete($tag_names_or_ids_csv) {
60
61 //covert all ids to names
62 $tag_names_csv = tag_name_from_string($tag_names_or_ids_csv);
63
64 //put apostrophes in names
65 $tag_names_csv_with_apos = "'" . str_replace(',', "','", $tag_names_csv) . "'";
66
67 delete_records_select('tag',"name IN ($tag_names_csv_with_apos)");
68
69}
70
71/**
72 * Get all tags from the records
73 *
74 * @param string $tag_types_csv (optional, default value is "default". If '*' is passed, tags of any type will be returned).
75 * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter).
76 * @param string $fields a comma separated list of fields to return
77 * (optional, by default 'id, tagtype, name'). The first field will be used as key for the
78 * array so must be a unique field such as 'id'.
79 */
80function get_all_tags($tag_types_csv="default", $sort='name ASC', $fields='id, tagtype, name') {
81
82 if ($tag_types_csv == '*'){
83 return get_records('tag', '', '', $sort, $fields);
84 }
85
86 $tag_types_csv_with_apos = "'" . str_replace(',', "','", $tag_types_csv ) . "'";
87
88 return get_records_list('tag', 'tagtype', $tag_types_csv_with_apos, $sort, $fields);
89}
90
91/**
92 * Determines if a tag exists
93 *
94 * @param string $tag_name_or_id **normalized** tag name, or an id.
95 * @return true if exists or false otherwise
96 *
97 */
98function tag_exists($tag_name_or_id) {
99
100 if (is_numeric($tag_name_or_id)) {
101 return record_exists('tag', 'id', $tag_name_or_id);
102 }
103 elseif (is_string($tag_name_or_id)) {
104 return record_exists('tag', 'name', $tag_name_or_id);
105 }
106}
107
108/**
109 * Function that returns the id of a tag
110 *
111 * @param String $tag_name **normalized** name of the tag
112 * @return int id of the matching tag
113 */
114function tag_id($tag_name) {
115 $tag = get_record('tag', 'name', trim($tag_name), '', '', '', '', 'id');
116 return $tag->id;
117}
118
119/**
120 * Function that returns the ids of tags
121 *
122 * Ex: tags_id('computers, algorithms')
123 *
124 * @param String $tag_names_csv comma separated **normalized** tag names.
125 * @return Array array with the tags ids, indexed by their **normalized** names
126 */
127function tags_id($tag_names_csv) {
128
129 $normalized_tag_names_csv = tag_normalize($tag_names_csv);
130 $tag_names_csv_with_apos = "'" . str_replace(',', "','", $normalized_tag_names_csv ) . "'";
131
132 $tag_objects = get_records_list('tag','name', $tag_names_csv_with_apos, "" , "name, id" );
133
134 $tags_ids = array();
135 foreach ($tag_objects as $tag) {
136 $tags_ids[$tag->name] = $tag->id;
137 }
138
139 return $tags_ids;
140}
141
142/**
143 * Function that returns the name of a tag
144 *
145 * @param int $tag_id id of the tag
146 * @return String name of the tag with the id passed
147 */
148function tag_name($tag_id) {
149 $tag = get_record('tag', 'id', $tag_id, '', '', '', '', 'name');
150 return $tag->name;
151}
152
153/**
154 * Function that retrieves the names of tags given their ids
155 *
156 * @param String $tag_ids_csv comma separated tag ids
157 * @return Array an array with the tags names, indexed by their ids
158 */
159
160function tags_name($tag_ids_csv) {
161
162 $tag_ids_csv_with_apos = "'" . str_replace(',', "','", $tag_ids_csv ) . "'";
163
164 $tag_objects = get_records_list('tag','id', $tag_ids_csv_with_apos, "" , "name, id" );
165
166 $tags_names = array();
167 foreach ($tag_objects as $tag) {
168 $tags_names[$tag->id] = $tag->name;
169 }
170
171 return $tags_names;
172}
173
174/**
175 * Function that retrieves a tag object by its id
176 *
177 * @param String $tag_id
178 * @return mixed a fieldset object containing the first matching record, or false if none found
179 */
180function tag_by_id($tag_id) {
181
182 return get_record('tag','id',$tag_id);
183}
184
185/**
186 * Function that retrieves a tag object by its name
187 *
188 * @param String $tag_name
189 * @return mixed a fieldset object containing the first matching record, or false if none found
190 */
191function tag_by_name($tag_name) {
192 $tag = get_record('tag','name',$tag_name);
193 return $tag;
194}
195
196/**
197 * Returns comma separated ids of tags given a string with comma separated names or ids of tags
198 *
199 * Ex: tag_id_from_string('moodle, 12, education, 33, 11')
200 * might return the string '10,12,22,33,11'
201 *
202 * This is a helper function used by functions of this API to process function arguments ($tag_name_or_id)
203 *
204 * @param string $tag_names_or_ids_csv comma separated **normalized** names or ids of tags
205 * @return int comma separated ids of the tags
206 */
207function tag_id_from_string($tag_names_or_ids_csv) {
208
209 $tag_names_or_ids = explode(',', $tag_names_or_ids_csv);
210
211 $tag_ids = array();
212 foreach ($tag_names_or_ids as $name_or_id) {
213
214 if (is_numeric($name_or_id)){
215 $tag_ids[] = $name_or_id;
216 }
217 elseif (is_string($name_or_id)) {
218 $tag_ids[] = tag_id( $name_or_id );
219 }
220
221 }
222
223 $tag_ids_csv = implode(',',$tag_ids);
224 $tag_ids_csv = str_replace(' ', '', $tag_ids_csv);
225
226 return rtrim($tag_ids_csv, ',');
227}
228
229/**
230 * Returns comma separated **normalized** names of tags given a string with comma separated names or ids of tags
231 *
232 * Ex: tag_name_from_string('mOOdle, 12, eduCAtIOn, 33, 11')
233 * might return the string 'moodle,computers,education,algorithms,software'
234 *
235 * This is a helper function used by functions of this API to process function arguments ($tag_name_or_id)
236 *
237 * @param string $tag_names_or_ids_csv comma separated **normalized** names or ids of tags
238 * @return int comma separated ids of the tags
239 */
240function tag_name_from_string($tag_names_or_ids_csv) {
241
242 $tag_names_or_ids = explode(',', $tag_names_or_ids_csv);
243
244 $tag_names = array();
245 foreach ($tag_names_or_ids as $name_or_id) {
246
247 if (is_numeric($name_or_id)){
248 $tag_names[] = tag_name($name_or_id);
249 }
250 elseif (is_string($name_or_id)) {
251 $tag_names[] = tag_normalize($name_or_id);
252 }
253
254 }
255
256 $tag_names_csv = implode(',',$tag_names);
257
258 return rtrim($tag_names_csv, ',');
259
260}
261
262/**
263 * Associates a tag with an item
264 *
265 * Ex 1: tag_an_item('user', '1', 'hisTOrY, RELIGIONS, roman' )
266 * This will tag an user whose id is 1 with "history", "religions", "roman"
267 * If the tag names passed do not exist, they will get created.
268 *
269 * Ex 2: tag_an_item('user', '1', 'hisTory, 12, 11, roman')
270 * This will tag an user whose id is 1 with 'history', 'roman' and with tags of ids 12 and 11
271 *
272 * @param string $item_type name of the table where the item is stored. Ex: 'user'
273 * @param string $item_id id of the item to be tagged
274 * @param string $tag_names_or_ids_csv comma separated tag names (can be unormalized) or ids of existing tags
275 * @param string $tag_type type of the tags that are beeing added (optional, default value is "default")
276 */
277
278function tag_an_item($item_type, $item_id, $tag_names_or_ids_csv, $tag_type="default") {
279
280 $norm_tag_names_or_ids_csv = tag_normalize($tag_names_or_ids_csv);
281
282 //convert any tag ids passed to their corresponding tag names
283 $tag_names_csv = tag_name_from_string($norm_tag_names_or_ids_csv);
284
285 //create the tags
286 $tags_created_ids = tag_create($tag_names_csv,$tag_type);
287
288 $tag_instance = new StdClass;
289 $tag_instance->itemtype = $item_type;
290 $tag_instance->itemid = $item_id;
291
292 //create tag instances
293 foreach ($tags_created_ids as $tag_id) {
294 $tag_instance->tagid = $tag_id;
295 insert_record('tag_instance',$tag_instance);
296 }
297
298
299 // update_tag_correlations($item_type, $item_id);
300
301}
302
303
304/**
305 * Updates the tags associated with an item
306 *
307 * Ex 1:
308 * Suppose user 1 is tagged only with "algorithms", "computers" and "software"
309 * By calling update_item_tags('user', 1, 'algorithms, software, mathematics')
310 * User 1 will now be tagged only with "algorithms", "software" and "mathematics"
311 *
312 * Ex 2:
313 * update_item_tags('user', '1', 'algorithms, 12, 13')
314 * User 1 will now be tagged only with "algorithms", and with tags of ids 12 and 13
315 *
316 *
317 * @param string $item_type name of the table where the item is stored. Ex: 'user'
318 * @param string $item_id id of the item to be tagged
319 * @param string $tag_names_or_ids_csv comma separated tag names (can be unormalized) or ids of existing tags
320 * @param string $tag_type type of the tags that are beeing added (optional, default value is "default")
321 */
322
323function update_item_tags($item_type, $item_id, $tag_names_or_ids_csv, $tag_type="default") {
324
325 //if $tag_names_csv is an empty string, remove all tag associations of the item
326 if( empty($tag_names_or_ids_csv) ){
327 untag_an_item($item_type, $item_id);
328 return;
329 }
330
331 //normalize tags names
332 $norm_tag_names_or_ids_csv = tag_normalize($tag_names_or_ids_csv);
333
334 //convert any tag ids passed to their corresponding tag names
335 $tag_names_csv = tag_name_from_string($norm_tag_names_or_ids_csv);
336
337 //associate the tags passed with the item
338 tag_an_item($item_type, $item_id, $tag_names_csv, $tag_type );
339
340 //get the ids of the tags passed
341 $existing_and_new_tags_ids = tags_id( $tag_names_csv );
342
343 // delete any tag instance with $item_type and $item_id
344 // that are not in $tag_names_csv
345 $tags_id_csv = "'" . implode("','", $existing_and_new_tags_ids) . "'" ;
346
347 $select = "
348 itemid = '{$item_id}'
349 AND
350 itemtype = '{$item_type}'
351 AND
352 tagid NOT IN ({$tags_id_csv})
353 ";
354
355 delete_records_select('tag_instance', $select);
356
357}
358
359/**
360 * Removes the association of an item with a tag
361 *
362 * Ex: untag_an_item('user', '1', 'history, 11, roman' )
363 * The user with id 1 will no longer be tagged with 'history', 'roman' and the tag of id 11
364 * Calling untag_an_item('user','1') will remove all tags associated with user 1.
365 *
366 * @param string $item_type name of the table where the item is stored. Ex: 'user'
367 * @param string $item_id id of the item to be untagged
368 * @param string $tag_names_or_ids_csv comma separated tag **normalized** names or ids of existing tags (optional,
369 * if none is given, all tags of the item will be removed)
370 */
371
372function untag_an_item($item_type, $item_id, $tag_names_or_ids_csv='') {
373
374 if ($tag_names_or_ids_csv == ""){
375
376 delete_records('tag_instance','itemtype', $item_type, 'itemid', $item_id);
377
378 }
379 else {
380
381 $tag_ids_csv = tag_id_from_string($norm_tag_names_or_ids_csv);
382
383 $tag_ids_csv_with_apos = "'" . str_replace(',', "','", $tag_ids_csv ) . "'";
384
385 delete_records_select('tag_instance',
386 "tagid IN ($tags_id_csv_with_apos) AND itemtype='$item_type' AND itemid='$item_id'");
387 }
388
389 //update_tag_correlations($item_type, $item_id);
390
391}
392
393/**
394 * Function that gets the tags that are associated with an item
395 *
396 * Ex: get_item_tags('user', '1')
397 *
398 * @param string $item_type name of the table where the item is stored. Ex: 'user'
399 * @param string $item_id id of the item beeing queried
400 * @param string $fields tag fields to be selected (optional, default is 'id, name, tagtype')
401 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
402 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
403 * @return mixed an array of objects, or false if no records were found or an error occured.
404 */
405
406function get_item_tags($item_type, $item_id, $fields='id, name, tagtype, flag', $limitfrom='', $limitnum='') {
407
408 global $CFG;
409
410 $fields = 'tg.' . $fields;
411 $fields = str_replace(',', ',tg.', $fields);
412
413 $query = "
414 SELECT
415 {$fields}
416 FROM
417 {$CFG->prefix}tag_instance ti
418 INNER JOIN
419 {$CFG->prefix}tag tg
420 ON
421 tg.id = ti.tagid
422 WHERE
423 ti.itemtype = '{$item_type}' AND
424 ti.itemid = '{$item_id}'";
425
426 return get_records_sql($query, $limitfrom, $limitnum);
427
428}
429
430
431
432/**
433 * Function that returns the items of a certain type associated with a certain tag
434 *
435 * Ex 1: get_items_tagged_with('user', 'banana')
436 * Ex 2: get_items_tagged_with('user', '11')
437 *
438 * @param string $item_type name of the table where the item is stored. Ex: 'user'
439 * @param string $tag_name_or_id is a single **normalized** tag name or the id of a tag
440 * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter).
441 * (to avoid field name ambiguity in the query, use the identifier "it" Ex: 'it.name ASC' )
442 * @param string $fields a comma separated list of fields to return
443 * (optional, by default all fields are returned). The first field will be used as key for the
444 * array so must be a unique field such as 'id'.
445 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
446 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
447 * @return mixed an array of objects indexed by their ids, or false if no records were found or an error occured.
448 */
449
450function get_items_tagged_with($item_type, $tag_name_or_id, $sort='', $fields='*', $limitfrom='', $limitnum='') {
451
452 global $CFG;
453
454 $tag_id = tag_id_from_string($tag_name_or_id);
455
456 $fields = 'it.' . $fields;
457 $fields = str_replace(',', ',it.', $fields);
458
459 if ($sort) {
460 $sort = ' ORDER BY '. $sort;
461 }
462
463 $query = "
464 SELECT
465 {$fields}
466 FROM
467 {$CFG->prefix}{$item_type} it
468 INNER JOIN
469 {$CFG->prefix}tag_instance tt
470 ON
471 it.id = tt.itemid
472 WHERE
473 tt.itemtype = '{$item_type}' AND
474 tt.tagid = '{$tag_id}'
475 {$sort}
476 ";
477
478
479 return get_records_sql($query, $limitfrom, $limitnum);
480
481}
482
483/**
484 * Returns the number of items tagged with a tag
485 *
486 * @param string $tag_name_or_id is a single **normalized** tag name or the id of a tag
487 * @param string $item_type name of the table where the item is stored. Ex: 'user' (optional, if none is set any
488 * type will be counted)
489 * @return int the count. If an error occurrs, 0 is returned.
490 */
491function count_items_tagged_with($tag_name_or_id, $item_type='') {
492
493 global $CFG;
494
495 $tag_id = tag_id_from_string($tag_name_or_id);
496
497 if (empty($item_type)){
498 $query = "
499 SELECT
500 COUNT(*) AS count
501 FROM
502 {$CFG->prefix}tag_instance tt
503 WHERE
504 tagid = {$tag_id}";
505 }
506 else
507 {
508 $query = "
509 SELECT
510 COUNT(*) AS count
511 FROM
512 {$CFG->prefix}{$item_type} it
513 INNER JOIN
514 {$CFG->prefix}tag_instance tt
515 ON
516 it.id = tt.itemid
517 WHERE
518 tt.itemtype = '{$item_type}' AND
519 tt.tagid = '{$tag_id}' ";
520 }
521
522
523 return count_records_sql($query);
524
525}
526
527
528/**
529 * Determines if an item is tagged with a certain tag
530 *
531 * @param string $item_type name of the table where the item is stored. Ex: 'user'
532 * @param string $item_id id of the item beeing queried
533 * @param string $tag_name_or_id is a single **normalized** tag name or the id of a tag
534 * @return bool true if a matching record exists, else false.
535 */
536function is_item_tagged_with($item_type,$item_id, $tag_name_or_id) {
537
538 $tag_id = tag_id_from_string($tag_name_or_id);
539
540 return record_exists('tag_instance','itemtype',$item_type,'itemid',$item_id, 'tagid', $tag_id);
541}
542
543/**
544 * Search for tags with names that match some text
545 *
546 * @param string $text string that the tag names will be matched against
547 * @param boolean $ordered If true, tags are ordered by their popularity. If false, no ordering.
548 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
549 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
550 * @return mixed an array of objects, or false if no records were found or an error occured.
551 */
552
553function search_tags($text, $ordered=true, $limitfrom='' , $limitnum='' ) {
554
555 global $CFG;
556
557 $text = tag_normalize($text);
558
559 if ($ordered) {
560 $query = "
561 SELECT
562 tg.id, tg.name, COUNT(ti.id) AS count
563 FROM
564 {$CFG->prefix}tag tg
565 LEFT JOIN
566 {$CFG->prefix}tag_instance ti
567 ON
568 tg.id = ti.tagid
569 WHERE
570 tg.name
571 LIKE
572 '%{$text}%'
573 GROUP BY
574 tg.id
575 ORDER BY
576 count
577 DESC";
578 } else {
579 $query = "
580 SELECT
581 tg.id, tg.name
582 FROM
583 {$CFG->prefix}tag tg
584 WHERE
585 tg.name
586 LIKE
587 '%{$text}%'
588 ";
589 }
590
591
592 return get_records_sql($query, $limitfrom , $limitnum);
593
594}
595
596/**
597 * Function that returns tags that start with some text
598 *
599 * @param string $text string that the tag names will be matched against
600 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
601 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
602 * @return mixed an array of objects, or false if no records were found or an error occured.
603 */
604function similar_tags($text, $limitfrom='' , $limitnum='' ) {
605
606 global $CFG;
607
608 $text = tag_normalize($text);
609
610 $query = "
611 SELECT
612 *
613 FROM
614 {$CFG->prefix}tag t
615 WHERE
616 t.name
617 LIKE
618 '{$text}%'
619 ";
620
621 return get_records_sql($query, $limitfrom , $limitnum);
622}
623
624/**
625 * Returns tags related to a tag
626 *
627 * Related tags of a tag come from two sources:
628 * - manually added related tags, which are tag_instance entries for that tag
629 * - correlated tags, which are a calculated
630 *
631 * @param string $tag_name_or_id is a single **normalized** tag name or the id of a tag
632 * @param int $limitnum return a subset comprising this many records (optional, default is 10)
633 * @return mixed an array of tag objects
634 */
635
636function related_tags($tag_name_or_id, $limitnum=10) {
637
638 $tag_id = tag_id_from_string($tag_name_or_id);
639
640 //gets the manually added related tags
641 $manual_related_tags = get_item_tags('tag',$tag_id);
642 if ($manual_related_tags == false) $manual_related_tags = array();
643
644 //gets the correlated tags
645 $automatic_related_tags = correlated_tags($tag_id);
646 if ($automatic_related_tags == false) $automatic_related_tags = array();
647
648 $related_tags = array_merge($manual_related_tags,$automatic_related_tags);
649
650 return array_slice( object_array_unique($related_tags) , 0 , $limitnum );
651
652
653}
654
655/**
656 * Returns the correlated tags of a tag
657 * The correlated tags are retrieved from the tag_correlation table, which is a caching table.
658 *
659 * @param string $tag_name_or_id is a single **normalized** tag name or the id of a tag
660 * @return mixed an array of tag objects
661 */
662function correlated_tags($tag_name_or_id) {
663
664 $tag_id = tag_id_from_string($tag_name_or_id);
665
666 if (!$tag_correlation = get_record('tag_correlation','tagid',$tag_id)) {
667 return array();
668 }
669
670 $tags_id_csv_with_apos = stripcslashes($tag_correlation->correlatedtags);
671
672 return get_records_select('tag', "id IN ({$tags_id_csv_with_apos})", '', 'id, name, tagtype');
673}
674
675/**
676 * Recalculates tag correlations of all the tags associated with an item
677 * This function could be called whenever the tags associations with an item changes
678 * ( for example when tag_an_item() or untag_an_item() is called )
679 *
680 * @param string $item_type name of the table where the item is stored. Ex: 'user'
681 * @param string $item_id id of the item
682 */
683function update_tag_correlations($item_type, $item_id) {
684
685 $item_tags = get_item_tags($item_type, $item_id);
686
687 foreach ($item_tags as $tag) {
688 cache_correlated_tags($tag->id);
689 }
690}
691
692/**
693 * Calculates and stores the correlated tags of a tag.
694 * The correlations are stored in the 'tag_correlation' table.
695 *
696 * Two tags are correlated if they appear together a lot.
697 * Ex.: Users tagged with "computers" will probably also be tagged with "algorithms".
698 *
699 * The rationale for the 'tag_correlation' table is performance.
700 * It works as a cache for a potentially heavy load query done at the 'tag_instance' table.
701 * So, the 'tag_correlation' table stores redundant information derived from the 'tag_instance' table.
702 *
703 * @param string $tag_name_or_id is a single **normalized** tag name or the id of a tag
704 * @param number $min_correlation cutoff percentage (optional, default is 0.25)
705 * @param int $limitnum return a subset comprising this many records (optional, default is 10)
706 */
707function cache_correlated_tags($tag_name_or_id, $min_correlation=0.25, $limitnum=10) {
708
709 global $CFG;
710
711 $tag_id = tag_id_from_string($tag_name_or_id);
712
713 // query that counts how many times any tag appears together in items
714 // with the tag passed as argument ($tag_id)
715 $query =
716 " SELECT
717 tb.tagid , COUNT(*) nr
718 FROM
719 {$CFG->prefix}tag_instance ta
720 INNER JOIN
721 {$CFG->prefix}tag_instance tb
722 ON
723 ta.itemid = tb.itemid
724 WHERE
725 ta.tagid = {$tag_id}
726 GROUP BY
727 tb.tagid
728 ORDER BY
729 nr DESC";
730
731 $tag_correlations = get_records_sql($query, 0, $limitnum);
732
733 $tags_id_csv_with_apos = "'";
734 $cutoff = $tag_correlations[$tag_id]->nr * $min_correlation;
735
736 foreach($tag_correlations as $correlation) {
737 if($correlation->nr >= $cutoff && $correlation->tagid != $tag_id ){
738 $tags_id_csv_with_apos .= $correlation->tagid."','";
739 }
740 }
741 $tags_id_csv_with_apos = substr($tags_id_csv_with_apos,0,-2);
742
743
744 //saves correlation info in the caching table
745
746 $tag_correlation_obj = get_record('tag_correlation','tagid',$tag_id);
747
748 if ($tag_correlation_obj) {
749 $tag_correlation_obj->correlatedtags = addslashes($tags_id_csv_with_apos);
750 update_record('tag_correlation',$tag_correlation_obj);
751 }
752 else {
753 $tag_correlation_obj = new StdClass;
754 $tag_correlation_obj->tagid = $tag_id;
755 $tag_correlation_obj->correlatedtags = addslashes($tags_id_csv_with_apos);
756 insert_record('tag_correlation',$tag_correlation_obj);
757 }
758
759
760}
761
762/**
763 * This function cleans up the 'tag_instance' table
764 * It removes orphans in 'tag_instances' table
765 *
766 */
767function tag_instance_table_cleanup() {
768
769 global $CFG;
770
771 //get the itemtypes present in the 'tag_instance' table
772 $query = "
773 SELECT
774 DISTINCT(itemtype)
775 FROM
776 {$CFG->prefix}tag_instance
777 ";
778
779 $items_types = get_records_sql($query);
780
781 // for each itemtype, remove tag_instances that are orphans
782 // That is: For a given tag_instance, if in the itemtype table there's no entry with id equal to itemid,
783 // then this tag_instance is an orphan and it will be removed.
784 foreach ($items_types as $type) {
785
786 $query = "
787 {$CFG->prefix}tag_instance.id
788 IN
789 ( SELECT sq1.id
790 FROM
791 (SELECT sq2.*
792 FROM {$CFG->prefix}tag_instance sq2
793 LEFT JOIN {$CFG->prefix}{$type->itemtype} item
794 ON sq2.itemid = item.id
795 WHERE item.id IS NULL
796 AND sq2.itemtype = '{$type->itemtype}')
797 sq1
798 ) ";
799
800 delete_records_select('tag_instance', $query);
801 }
802
803 // remove tag_instances that are orphans because tagid does not correspond to an
804 // existing tag
805 $query = "
806 {$CFG->prefix}tag_instance.id
807 IN
808 (SELECT sq1.id
809 FROM
810 (SELECT sq2.*
811 FROM {$CFG->prefix}tag_instance sq2
812 LEFT JOIN {$CFG->prefix}tag tg
813 ON sq2.tagid = tg.id
814 WHERE tg.id IS NULL )
815 sq1
816 )
817 ";
818
819 delete_records_select('tag_instance', $query);
820}
821
822
823/**
824 * Function that normalizes a tag name
825 *
826 * Ex: tag_normalize('bANAana') -> returns 'banana'
827 * tag_normalize('lots of spaces') -> returns 'lots of spaces'
828 * tag_normalize('%!%!% non alpha numeric %!%!%') -> returns 'non alpha numeric'
829 * tag_normalize('tag one, TAG TWO, TAG three, and anotheR tag')
830 * -> returns 'tag one,tag two,tag three,and another tag'
831 *
832 * @param string $tag_names_csv unnormalized CSV tag names
833 * @return string **normalized** CSV tag names
834 */
835
836function tag_normalize($tag_names_csv) {
837
838 $tags = explode(',', $tag_names_csv);
839
840 if (sizeof($tags) > 1) {
841
842 foreach ($tags as $key => $tag) {
843 $tags[$key] = tag_normalize($tag);
844 }
845
846 return implode(',' , $tags);
847
848 }
849
850 // only one tag was passed
851 else {
852 // value is converted to lowercase and all non-alphanumeric characters are removed
853 //$value = preg_replace('|[^\w ]|i', '', strtolower(trim($tag_names_csv)));
854 $value = preg_replace('|[\!\@\#\$\%\^\&\*\(\)\-\+\=\~\`\.\[\]\{\}\:\;\\\/\<\>\|]|i', '', moodle_strtolower(trim($tag_names_csv)));
855
856 //removes excess white spaces
857 $value = preg_replace('/\s\s+/', ' ', $value);
858
859 return $value;
860 }
861
862}
863
864function is_tag_clean($tag_names_csv) {
865
866}
867
868function tag_flag_inappropriate($tag_names_or_ids_csv){
869
870 $tag_ids_csv = tag_id_from_string($tag_names_or_ids_csv);
871
872 $tag_ids = explode(',', $tag_ids_csv);
873
874 foreach ($tag_ids as $id){
875 $tag = get_record('tag','id',$id, '', '', '', '', 'id,flag');
876
877 $tag->flag++;
878 $tag->timemodified = time();
879
880 update_record('tag', $tag);
881 }
882}
883
884function tag_flag_reset($tag_names_or_ids_csv){
885
886 global $CFG;
887
888 $tag_ids_csv = tag_id_from_string($tag_names_or_ids_csv);
889
890 $tag_ids_csv_with_apos = "'" . str_replace(',', "','", $tag_ids_csv) . "'";
891
892 $timemodified = time();
893
894 $query = "
895 UPDATE
896 {$CFG->prefix}tag tg
897 SET
898 tg.flag = 0,
899 tg.timemodified = {$timemodified}
900 WHERE
901 tg.id
902 IN
903 ({$tag_ids_csv_with_apos})
904 ";
905
906 execute_sql($query, false);
907}
908
909/**
910 * Function that returns comma separated HTML links to the tag pages of the tags passed
911 *
912 * @param array $tag_objects an array of tag objects
913 * @return string CSV, HTML links to tag pages
914 */
915
916function tag_links_csv($tag_objects) {
917
918 global $CFG;
919 $tag_links = '';
920
921 if (empty($tag_objects)) {
922 return '';
923 }
924
925 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
926 $can_manage_tags = has_capability('moodle/tag:manage', $systemcontext);
927
928 foreach ($tag_objects as $tag){
929 //highlight tags that have been flagged as inappropriate for those who can manage them
930 $tagname = $tag->name;
931 if ($tag->flag > 0 && $can_manage_tags) {
932 $tagname = '<span class="flagged-tag">' . $tagname . '</span>';
933 }
934 $tag_links .= ' <a href="'.$CFG->wwwroot.'/tag/index.php?id='.$tag->id.'">'.$tagname.'</a>,';
935 }
936
937 return rtrim($tag_links, ',');
938}
939
940/**
941 * Function that returns comma separated names of the tags passed
942 * Example of string that might be returned: 'history, wars, greek history'
943 *
944 * @param array $tag_objects
945 * @return string CSV tag names
946 */
947
948function tag_names_csv($tag_objects) {
949
950 if (empty($tag_objects)) {
951 return '';
952 }
953
954 $tags = array();
955
956 foreach ($tag_objects as $tag){
957 $tags[] = $tag->name;
958 }
959
960 return implode(',', $tags);
961}
962
963
964/**
965 * Returns a number of random tags, ordered by their popularity
966 *
967 * @param int $nr_of_tags number of random tags to be returned
968 * @param unknown_type $tag_type
969 * @return mixed an array of tag objects with the following fields: id, name and count
970 */
971function rand_tags_count($nr_of_tags=20, $tag_type = 'default') {
972
973 global $CFG;
974
975 if (!$tags = get_all_tags($tag_type)) {
976 return array();
977 }
978
979 if(sizeof($tags) < $nr_of_tags) {
980 $nr_of_tags = sizeof($tags);
981 }
982
983 $rndtags = array_rand($tags, $nr_of_tags);
984
985 $tags_id_csv_with_apos = "'";
986 foreach($rndtags as $tagid) {
987 $tags_id_csv_with_apos .= $tags[$tagid]->id . "','";
988 }
989 $tags_id_csv_with_apos = substr($tags_id_csv_with_apos,0,-2);
990
991
992 $query = "
993 SELECT
994 tg.id, tg.name, COUNT(ti.id) AS count, tg.flag
995 FROM
996 {$CFG->prefix}tag_instance ti
997 INNER JOIN
998 {$CFG->prefix}tag tg
999 ON
1000 tg.id = ti.tagid
1001 WHERE
1002 ti.tagid
1003 IN
1004 ({$tags_id_csv_with_apos})
1005 GROUP BY
1006 tagid
1007 ORDER BY
1008 count
1009 ASC";
1010
1011 return get_records_sql($query);
1012
1013
1014}
1015
1016/*-------------------- Printing functions -------------------- */
1017
1018/**
1019 * Prints a box that contains the management links of a tag
1020 *
1021 * @param $tag_object
1022 */
1023
1024function print_tag_management_box($tag_object) {
1025
1026 global $USER, $CFG;
1027
1028 $tagname = /*ucwords*/($tag_object->name);
1029
1030 print_box_start('box','tag-management-box');
1031
1032 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1033
1034 $addtaglink = '';
1035 if ( has_capability('moodle/tag:manage',$systemcontext) ) {
1036 $manage_link = "<a href=\"{$CFG->wwwroot}/tag/manage.php\">" . get_string('managetags', 'tag') . "</a>" ;
1037 echo $manage_link .' | ';
1038 }
1039
1040 // if the user is not tagged with the $tag_object tag, a link "add blahblah to my interests" will appear
1041 if( !is_item_tagged_with('user', $USER->id, $tag_object->id )) {
1042 $addtaglink = '<a href="' . $CFG->wwwroot . '/user/tag.php?action=addinterest&amp;id='. $tag_object->id .'">';
1043 $addtaglink .= get_string('addtagtomyinterests','tag',$tagname). '</a>';
1044 echo $addtaglink .' | ';
1045 }
1046
1047 // only people with moodle/tag:edittags capability may edit the tag description
1048 if ( has_capability('moodle/tag:edit',$systemcontext) && is_item_tagged_with('user', $USER->id, $tag_object->id ) ) {
1049 echo ' <a href="'. $CFG->wwwroot . '/tag/edit.php?id='.$tag_object->id .'">'.get_string('edittag', 'tag').'</a> | ';
1050 }
1051
1052 // flag as inappropriate link
1053 $flagtaglink = '<a href="' . $CFG->wwwroot . '/user/tag.php?action=flaginappropriate&amp;id='. $tag_object->id .'">';
1054 $flagtaglink .= get_string('flagasinappropriate','tag',$tagname). '</a>';
1055 echo $flagtaglink;
1056
1057 print_box_end();
1058
1059}
1060
1061/**
1062 * Prints a box with the description of a tag and its related tags
1063 *
1064 * @param unknown_type $tag_object
1065 */
1066
1067function print_tag_description_box($tag_object) {
1068
1069 global $USER, $CFG;
1070
1071 $tagname = /*ucwords*/($tag_object->name);
1072 $related_tags = related_tags($tag_object->id); //get_item_tags('tags',$tag_object->id);
1073
1074
1075 print_box_start('generalbox', 'tag-description');
1076
1077 if (!empty($tag_object->description)) {
1078 echo format_text($tag_object->description, $tag_object->descriptionformat );
1079 }
1080 else {
1081 echo format_text(get_string('thistaghasnodesc','tag'));
1082 }
1083
1084
1085 if ($related_tags) {
1086 echo '<br/><b>'.get_string('relatedtags','tag').': </b>' . tag_links_csv($related_tags);
1087 }
1088
1089 print_box_end();
1090}
1091
1092/**
1093 * Prints a table of the users tagged with the tag passed as argument
1094 *
1095 * @param $tag_object
1096 * @param int $users_per_row number of users per row to display
1097 * @param int $limitfrom prints users starting at this point (optional, required if $limitnum is set).
1098 * @param int $limitnum prints this many users (optional, required if $limitfrom is set).
1099 */
1100
1101function print_tagged_users_table($tag_object, $users_per_row='5', $limitfrom='' , $limitnum='') {
1102
1103 //List of users with this tag
1104 $userlist = array_values( get_items_tagged_with(
1105 'user',
1106 $tag_object->id,
1107 'lastaccess DESC' ,
1108 'id, firstname, lastname, picture',
1109 $limitfrom,
1110 $limitnum) );
1111
1112 //user table box
1113 print_box_start('generalbox', 'tag-user-table');
1114
1115 print_user_table($userlist, $users_per_row);
1116
1117 print_box_end();
1118 //end table box
1119
1120
1121
1122}
1123
1124/**
1125 * Prints a table of users
1126 *
1127 * @param array $userlist an array of user objects
1128 * @param int $users_per_row number of users per row to display
1129 */
1130function print_user_table($userlist, $users_per_row='5') {
1131
1132 $nrofrows = ceil( count($userlist) / $users_per_row );
1133 for($row = 0; $row < $nrofrows; $row++){
1134
1135 //table row box
1136 print_box_start('user-table-row', 'row_'.$row);
1137
1138 for($col = 0; ($col < $users_per_row) ; $col++ ) {
1139
1140 $index = $row * $users_per_row + $col;
1141
1142 if ($index < count($userlist) ) {
1143
1144 print_user_box( $userlist[$index] );
1145
1146 }
1147
1148 }
1149
1150 print_box_end();
1151 //end table row box
1152
1153 }
1154
1155}
1156
1157/**
1158 * Prints an individual user box
1159 *
1160 * @param $user user object (contains the following fields: id, firstname, lastname and picture)
1161 */
1162function print_user_box($user) {
1163
1164 global $CFG;
1165
1166 $usercontext = get_context_instance(CONTEXT_USER, $user->id);
1167
1168 $profilelink = '';
1169 if ( has_capability('moodle/user:viewdetails', $usercontext) ) {
1170 $profilelink = $CFG->wwwroot.'/user/view.php?id='.$user->id;
1171 }
1172
1173 print_box_start('user-box', $user->id);
1174
1175 if (!empty($profilelink)) echo '<a href="'.$profilelink.'">';
1176
1177 //print user image
1178 if ($user->picture) {
1179 echo '<img class="user-image" src="'. $CFG->wwwroot .'/user/pix.php/'. $user->id .'/f1.jpg"'.'/>';
1180 }
1181 else {
1182 echo '<img class="user-image" src="'. $CFG->wwwroot .'/pix/u/f1.png"'.'/>';
1183 }
1184
1185 echo '<br/>';
1186
1187 if (!empty($profilelink)) echo '</a>';
1188
1189 $fullname = fullname($user);
1190 //truncate name if it's too big
1191 if (strlen($fullname) > 26) $fullname = substr($fullname,0,26) . '...';
1192
1193 echo '<strong>' . $fullname . '</strong>';
1194
1195 print_box_end();
1196
1197}
1198
1199/**
1200 * Prints the tag search box
1201 *
1202 */
1203function print_tag_search_box($search='') {
1204
1205 global $CFG;
1206
1207 print_box_start('','tag-search-box');
1208
1209 echo '<form action="'.$CFG->wwwroot.'/tag/search.php" style="display:inline">';
1210 echo '<div>';
1211 echo '<input id="searchform_search" name="query" type="text" size="40" />';
1212 echo '<button id="searchform_button" type="submit">'. get_string('search', 'tag') .'</button><br />';
1213 echo '</div>';
1214 echo '</form>';
1215
1216 print_box_end();
1217}
1218
1219/**
1220 * Prints the tag search results
1221 *
1222 * @param string $query text that tag names will be matched against
1223 * @param int $page current page
1224 * @param int $perpage nr of users displayed per page
1225 */
1226function print_tag_search_results($query, $page, $perpage) {
1227
1228 global $CFG, $USER;
1229
1230 $count = sizeof( search_tags($query,false) );
1231 $tags = array_values(search_tags($query, true, $page * $perpage , $perpage));
1232
1233 $baseurl = $CFG->wwwroot.'/tag/search.php?query=' . $query;
1234
1235 // link "Add $query to my interests"
1236 $addtaglink = '';
1237 if( !is_item_tagged_with('user', $USER->id, $query )) {
1238 $addtaglink = '<a href="' . $CFG->wwwroot . '/user/tag.php?action=addinterest&name='. $query .'">';
1239 $addtaglink .= get_string('addtagtomyinterests','tag',$query). '</a>';
1240 }
1241
1242
1243 if($tags) { // there are results to display!!
1244
1245 print_heading(get_string('searchresultsfor', 'tag') . " \"{$query}\" : {$count}", '', 3);
1246
1247 //print a link "Add $query to my interests"
1248 if (!empty($addtaglink)) {
1249 print_box($addtaglink,'box','tag-management-box');
1250 }
1251
1252 $nr_of_lis_per_ul = 6;
1253 $nr_of_uls = ceil( sizeof($tags) / $nr_of_lis_per_ul);
1254
1255 echo '<ul id="tag-search-results">';
1256 for($i = 0; $i < $nr_of_uls; $i++) {
1257 echo '<li>';
1258 foreach (array_slice($tags, $i * $nr_of_lis_per_ul, $nr_of_lis_per_ul ) as $tag) {
1259 $tag_link = ' <a href="'.$CFG->wwwroot.'/tag/index.php?id='.$tag->id.'">'.$tag->name.'</a>';
1260 echo '&#8226;' . $tag_link . '<br/>';
1261 }
1262 echo '</li>';
1263 }
1264 echo '</ul>';
1265 echo '<div>&nbsp;</div>'; // <-- small layout hack in order to look good in Firefox
1266
1267 print_paging_bar($count, $page, $perpage, $baseurl.'&amp;', 'page');
1268 }
1269 else { //no results were found!!
1270
1271 print_heading(get_string('noresultsfor', 'tag') . " \"{$query}\" ", '', 3);
1272
1273 //print a link "Add $query to my interests"
1274 if (!empty($addtaglink)) {
1275 print_box($addtaglink,'box','tag-management-box');
1276 }
1277
1278 }
1279
1280
1281}
1282
1283/**
1284 * Prints a tag cloud
1285 *
1286 * @param int $nr_of_tags number of tags in the cloud
1287 * @param int $max_size maximum text size, in percentage
1288 * @param int $min_size minimum text size, in percentage
1289 */
1290function print_tag_cloud($tagcloud, $shuffle=true, $max_size=180, $min_size=80) {
1291
1292 global $CFG;
1293
1294 if (empty($tagcloud)) {
1295 return;
1296 }
1297
1298 if ( $shuffle ) {
1299 shuffle($tagcloud);
1300 }
1301
1302 $count = array();
1303 foreach ($tagcloud as $key => $value){
1304 if(!empty($value->count)) {
1305 $count[$key] = log10($value->count);
1306 }
1307 else{
1308 $count[$key] = 0;
1309 }
1310 }
1311
1312 $max = max($count);
1313 $min = min($count);
1314
1315 $spread = $max - $min;
1316 if (0 == $spread) { // we don't want to divide by zero
1317 $spread = 1;
1318 }
1319
1320 $step = ($max_size - $min_size)/($spread);
1321
1322 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1323 $can_manage_tags = has_capability('moodle/tag:manage', $systemcontext);
1324
1325 //prints the tag cloud
1326 echo '<ul id="tag-cloud-list">';
1327 foreach ($tagcloud as $key => $tag) {
1328
1329 $size = $min_size + ((log10($tag->count) - $min) * $step);
1330 $size = ceil($size);
1331
1332 $style = 'style="font-size: '.$size.'%"';
1333 $title = 'title="'.s(get_string('thingstaggedwith','tag', $tag)).'"';
1334 $href = 'href="'.$CFG->wwwroot.'/tag/index.php?id='.$tag->id.'"';
1335
1336 //highlight tags that have been flagged as inappropriate for those who can manage them
1337 $tagname = $tag->name;
1338 if ($tag->flag > 0 && $can_manage_tags) {
1339 $tagname = '<span class="flagged-tag">' . $tag->name . '</span>';
1340 }
1341
1342 $tag_link = '<li><a '.$href.' '.$title.' '. $style .'>'.$tagname.'</a></li> ';
1343
1344 echo $tag_link;
1345
1346 }
1347 echo '</ul>';
1348
1349}
1350
1351function print_tag_management_list($perpage='100') {
1352
1353 global $CFG, $USER;
1354 require_once($CFG->libdir.'/tablelib.php');
1355
1356 //setup table
1357 $tablecolumns = array('name', 'owner', 'count', 'flag', 'timemodified', '');
1358 $tableheaders = array( get_string('name' , 'tag'),
1359 get_string('owner','tag'),
1360 get_string('count','tag'),
1361 get_string('flag','tag'),
1362 get_string('timemodified','tag'),
1363 get_string('select', 'tag')
1364 );
1365
1366 $table = new flexible_table('tag-management-list-'.$USER->id);
1367
1368 $baseurl = $CFG->wwwroot.'/tag/manage.php';
1369
1370 $table->define_columns($tablecolumns);
1371 $table->define_headers($tableheaders);
1372 $table->define_baseurl($baseurl);
1373
1374 $table->sortable(true, 'flag', SORT_DESC);
1375
1376 $table->set_attribute('cellspacing', '0');
1377 $table->set_attribute('id', 'tag-management-list');
1378 $table->set_attribute('class', 'generaltable generalbox');
1379
1380 $table->set_control_variables(array(
1381 TABLE_VAR_SORT => 'ssort',
1382 TABLE_VAR_HIDE => 'shide',
1383 TABLE_VAR_SHOW => 'sshow',
1384 TABLE_VAR_IFIRST => 'sifirst',
1385 TABLE_VAR_ILAST => 'silast',
1386 TABLE_VAR_PAGE => 'spage'
1387 ));
1388
1389 $table->setup();
1390
1391 if ($table->get_sql_sort()) {
1392 $sort = ' ORDER BY '.$table->get_sql_sort();
1393 } else {
1394 $sort = '';
1395 }
1396
1397 if ($table->get_sql_where()) {
1398 $where = 'WHERE '.$table->get_sql_where();
1399 } else {
1400 $where = '';
1401 }
1402
1403 $query = "
1404 SELECT
1405 tg.id, tg.name, COUNT(ti.id) AS count, u.id AS owner, tg.flag, tg.timemodified
1406 FROM
1407 {$CFG->prefix}tag_instance ti
1408 RIGHT JOIN
1409 {$CFG->prefix}tag tg
1410 ON
1411 tg.id = ti.tagid
1412 LEFT JOIN
1413 {$CFG->prefix}user u
1414 ON
1415 tg.userid = u.id
1416 {$where}
1417 GROUP BY
1418 tg.id
1419 {$sort}
1420 ";
1421
1422 $totalcount = count_records('tag');
1423
1424 $table->initialbars($totalcount > $perpage);
1425 $table->pagesize($perpage, $totalcount);
1426
1427
1428 echo '<form id="tag-management-form" method="post" action="'.$CFG->wwwroot.'/tag/manage.php">';
1429
1430 //retrieve tags from DB
1431 if ($tagrecords = get_records_sql($query, $table->get_page_start(), $table->get_page_size())) {
1432
1433 $taglist = array_values($tagrecords);
1434
1435 //print_tag_cloud(array_values(get_records_sql($query)), false);
1436
1437 //populate table with data
1438 foreach ($taglist as $tag ){
1439
1440 $name = '<a href="'.$CFG->wwwroot.'/tag/index.php?id='.$tag->id.'">'.$tag->name.'</a>';
1441 $owner = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$tag->owner.'">' . $tag->owner . '</a>';
1442 $count = $tag->count;
1443 $flag = $tag->flag;
1444 $timemodified = format_time(time() - $tag->timemodified);
1445 $checkbox = '<input type="checkbox" name="tagschecked[]" value="'.$tag->id.'" />';
1446
1447 //if the tag if flagged, highlight it
1448 if ($tag->flag > 0) {
1449 $name = '<span class="flagged-tag">' . $name . '</span>';
1450 $owner = '<span class="flagged-tag">' . $owner . '</span>';
1451 $count = '<span class="flagged-tag">' . $count . '</span>';
1452 $flag = '<span class="flagged-tag">' . $flag . '</span>';
1453 $timemodified = '<span class="flagged-tag">' . $timemodified . '</span>';
1454 }
1455
1456 $data = array($name , $owner ,$count ,$flag, $timemodified, $checkbox);
1457
1458 $table->add_data($data);
1459 }
1460
1461
1462 echo '<input type="button" onclick="checkall()" value="'.get_string('selectall').'" /> ';
1463 echo '<input type="button" onclick="checknone()" value="'.get_string('deselectall').'" /> ';
1464 echo '<br/><br/>';
1465 echo '<select id="menuformaction" name="action">
1466 <option value="" selected="selected">With selected tags...</option>
1467 <option value="reset">'. get_string('resetflag', 'tag') .'</option>
1468 <option value="delete">'. get_string('delete', 'tag') .'</option>
1469 </select>';
1470
1471 echo '<button id="tag-management-submit" type="submit">'. get_string('ok') .'</button>';
1472
1473 }
1474
1475 $table->print_html();
1476
1477 echo '</form>';
1478}
1479
1480?>