First checkin of tag system from GSOC student Luiz Cruz
[moodle.git] / tag / lib.php
1 <?php // $Id$
3 require_once('../config.php');
5 /**
6  * Creates tags
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  */
17 function tag_create($tag_names_csv, $tag_type="default") {
18     global $USER;
20     $normalized_tag_names_csv = tag_normalize($tag_names_csv) ;
22     $tags = explode(",", $normalized_tag_names_csv );
24     $tag_object = new StdClass;
25     $tag_object->tagtype = $tag_type;
26     $tag_object->userid = $USER->id;
28     $systemcontext   = get_context_instance(CONTEXT_SYSTEM);
29     $can_create_tags = has_capability('moodle/tag:create',$systemcontext);
30     
31     foreach ($tags as $tag_name) {
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     }
42     return tags_id($normalized_tag_names_csv);
44 }
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  */
59 function tag_delete($tag_names_or_ids_csv) {
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) . "'";
67     delete_records_select('tag',"name IN ($tag_names_csv_with_apos)");
69 }
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  */
80 function 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 }
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  */
98 function 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     }
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  */
114 function tag_id($tag_name) {
115     $tag = get_record('tag', 'name', trim($tag_name), '', '', '', '', 'id');
116     return $tag->id;
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  */
127 function 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 ) . "'";
132     $tag_objects = get_records_list('tag','name', $tag_names_csv_with_apos, "" , "name, id" );
134     $tags_ids = array();
135     foreach ($tag_objects as $tag) {
136         $tags_ids[$tag->name] = $tag->id;
137     }
139     return $tags_ids;
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  */
148 function tag_name($tag_id) {
149     $tag = get_record('tag', 'id', $tag_id, '', '', '', '', 'name');
150     return $tag->name;
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  */
160 function 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" );
166     $tags_names = array();
167     foreach ($tag_objects as $tag) {
168         $tags_names[$tag->id] = $tag->name;
169     }
171     return $tags_names;
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  */
180 function tag_by_id($tag_id) {
181     
182     return get_record('tag','id',$tag_id);
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  */
191 function tag_by_name($tag_name) {
192     $tag = get_record('tag','name',$tag_name);
193     return $tag;
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  */
207 function tag_id_from_string($tag_names_or_ids_csv) {
209     $tag_names_or_ids = explode(',', $tag_names_or_ids_csv);
211     $tag_ids = array();
212     foreach ($tag_names_or_ids as $name_or_id) {
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         }
221     }
223     $tag_ids_csv = implode(',',$tag_ids);
224     $tag_ids_csv = str_replace(' ', '', $tag_ids_csv);
225     
226     return rtrim($tag_ids_csv, ',');
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  */
240 function tag_name_from_string($tag_names_or_ids_csv) {
241     
242     $tag_names_or_ids = explode(',', $tag_names_or_ids_csv);
244     $tag_names = array();
245     foreach ($tag_names_or_ids as $name_or_id) {
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         }
254     }
256     $tag_names_csv = implode(',',$tag_names);
257     
258     return rtrim($tag_names_csv, ',');
259     
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  */
278 function tag_an_item($item_type, $item_id, $tag_names_or_ids_csv, $tag_type="default") {
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);
288     $tag_instance = new StdClass;
289     $tag_instance->itemtype = $item_type;
290     $tag_instance->itemid = $item_id;
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    
299    // update_tag_correlations($item_type, $item_id);
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  */
323 function 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);
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 );
340     //get the ids of the tags passed
341     $existing_and_new_tags_ids = tags_id( $tag_names_csv );
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) . "'" ;
347     $select = "
348         itemid = '{$item_id}'
349     AND
350         itemtype = '{$item_type}'
351     AND
352         tagid NOT IN ({$tags_id_csv})
353     ";
355     delete_records_select('tag_instance', $select);
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  */
372 function untag_an_item($item_type, $item_id, $tag_names_or_ids_csv='') {
373     
374     if ($tag_names_or_ids_csv == ""){
376         delete_records('tag_instance','itemtype', $item_type, 'itemid', $item_id);
378     }
379     else {
380     
381         $tag_ids_csv = tag_id_from_string($norm_tag_names_or_ids_csv);
383         $tag_ids_csv_with_apos = "'" . str_replace(',', "','", $tag_ids_csv ) . "'";
385         delete_records_select('tag_instance',
386         "tagid IN ($tags_id_csv_with_apos) AND itemtype='$item_type' AND itemid='$item_id'");
387     }
389     //update_tag_correlations($item_type, $item_id);
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  */
406 function 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}'";
426     return get_records_sql($query, $limitfrom, $limitnum);
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  */
450 function 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);
456     $fields = 'it.' . $fields;
457     $fields = str_replace(',', ',it.', $fields);
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         ";
479     return get_records_sql($query, $limitfrom, $limitnum);
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  */
491 function count_items_tagged_with($tag_name_or_id, $item_type='') {
492     
493     global $CFG;
495     $tag_id = tag_id_from_string($tag_name_or_id);
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     }
523     return count_records_sql($query);
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  */
536 function is_item_tagged_with($item_type,$item_id, $tag_name_or_id) {
538     $tag_id = tag_id_from_string($tag_name_or_id);
540     return record_exists('tag_instance','itemtype',$item_type,'itemid',$item_id, 'tagid', $tag_id);
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  */
553 function search_tags($text, $ordered=true, $limitfrom='' , $limitnum='' ) {
555     global $CFG;
557     $text = tag_normalize($text);
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         
592     return get_records_sql($query, $limitfrom , $limitnum);
593         
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  */
604 function similar_tags($text, $limitfrom='' , $limitnum='' ) {
605     
606     global $CFG;
608     $text = tag_normalize($text);
610     $query = "
611         SELECT 
612             *
613         FROM
614             {$CFG->prefix}tag t
615         WHERE
616             t.name
617         LIKE
618             '{$text}%'
619         ";
621     return get_records_sql($query, $limitfrom , $limitnum);
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  */
636 function related_tags($tag_name_or_id, $limitnum=10) {
638     $tag_id = tag_id_from_string($tag_name_or_id);
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();
644     //gets the correlated tags
645     $automatic_related_tags = correlated_tags($tag_id);
646     if ($automatic_related_tags == false) $automatic_related_tags = array();
648     $related_tags = array_merge($manual_related_tags,$automatic_related_tags);
650     return array_slice( object_array_unique($related_tags) , 0 , $limitnum  ); 
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  */
662 function correlated_tags($tag_name_or_id) {
663     
664     $tag_id = tag_id_from_string($tag_name_or_id);
666     if (!$tag_correlation = get_record('tag_correlation','tagid',$tag_id)) {
667         return array();
668     }
670     $tags_id_csv_with_apos = stripcslashes($tag_correlation->correlatedtags);
672     return get_records_select('tag', "id IN ({$tags_id_csv_with_apos})", '', 'id, name, tagtype');
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  */
683 function update_tag_correlations($item_type, $item_id) {
685     $item_tags = get_item_tags($item_type, $item_id);
687     foreach ($item_tags as $tag) {
688         cache_correlated_tags($tag->id);
689     }
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  */
707 function cache_correlated_tags($tag_name_or_id, $min_correlation=0.25, $limitnum=10) {
708     
709     global $CFG;
711     $tag_id = tag_id_from_string($tag_name_or_id);
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";
731     $tag_correlations = get_records_sql($query, 0, $limitnum);
733     $tags_id_csv_with_apos = "'";
734     $cutoff = $tag_correlations[$tag_id]->nr * $min_correlation;
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);
744     //saves correlation info in the caching table
746     $tag_correlation_obj = get_record('tag_correlation','tagid',$tag_id);
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     }
762 /**
763  * This function cleans up the 'tag_instance' table 
764  * It removes orphans in 'tag_instances' table
765  *
766  */
767 function tag_instance_table_cleanup() {
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);
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  */
836 function tag_normalize($tag_names_csv) {
838     $tags = explode(',', $tag_names_csv);
840     if (sizeof($tags) > 1) {
842         foreach ($tags as $key => $tag) {
843             $tags[$key] = tag_normalize($tag);
844         }
846         return implode(',' , $tags);
848     }
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)));
856         //removes excess white spaces
857         $value = preg_replace('/\s\s+/', ' ', $value);
858         
859         return $value;
860     }
864 function is_tag_clean($tag_names_csv) {
868 function 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     }
884 function 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);
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  */
916 function tag_links_csv($tag_objects) {
917     
918     global $CFG;
919     $tag_links = '';
921     if (empty($tag_objects)) {
922         return '';
923     }
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     }
937     return rtrim($tag_links, ',');
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  */
948 function tag_names_csv($tag_objects) {
950     if (empty($tag_objects)) {
951         return '';
952     }
953     
954     $tags = array();
956     foreach ($tag_objects as $tag){
957         $tags[] = $tag->name;
958     }
960     return implode(',', $tags);
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  */
971 function 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);    
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     
1016 /*-------------------- Printing functions -------------------- */
1018 /**
1019  * Prints a box that contains the management links of a tag
1020  *
1021  * @param $tag_object
1022  */
1024 function print_tag_management_box($tag_object) {
1026     global $USER, $CFG;
1028     $tagname  = /*ucwords*/($tag_object->name);
1030     print_box_start('box','tag-management-box');
1032     $systemcontext   = get_context_instance(CONTEXT_SYSTEM);
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     }
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     }
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     }
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;
1057     print_box_end();
1061 /**
1062  * Prints a box with the description of a tag and its related tags
1063  *
1064  * @param unknown_type $tag_object
1065  */
1067 function print_tag_description_box($tag_object) {
1068     
1069     global $USER, $CFG;
1071     $tagname  = /*ucwords*/($tag_object->name);
1072     $related_tags =  related_tags($tag_object->id); //get_item_tags('tags',$tag_object->id);
1075     print_box_start('generalbox', 'tag-description');
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     }
1085     if ($related_tags) {
1086         echo '<br/><b>'.get_string('relatedtags','tag').': </b>' . tag_links_csv($related_tags);
1087     }
1089     print_box_end();
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  */
1101 function print_tagged_users_table($tag_object, $users_per_row='5', $limitfrom='' , $limitnum='') {
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) );
1112     //user table box
1113     print_box_start('generalbox', 'tag-user-table');
1115         print_user_table($userlist, $users_per_row);
1117     print_box_end();
1118     //end table box
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  */
1130 function print_user_table($userlist, $users_per_row='5') {
1131     
1132     $nrofrows = ceil( count($userlist) / $users_per_row );
1133     for($row = 0; $row < $nrofrows; $row++){
1135         //table row box
1136         print_box_start('user-table-row', 'row_'.$row);
1138         for($col = 0; ($col < $users_per_row) ; $col++ ) {
1140             $index = $row * $users_per_row + $col;
1142             if ($index < count($userlist) ) {
1144                 print_user_box( $userlist[$index] );
1146             }
1148         }
1150         print_box_end();
1151         //end table row box
1153     }
1154     
1157 /**
1158  * Prints an individual user box
1159  *
1160  * @param $user user object (contains the following fields: id, firstname, lastname and picture)
1161  */
1162 function print_user_box($user) {
1163     
1164     global $CFG;
1166     $usercontext = get_context_instance(CONTEXT_USER, $user->id);
1168     $profilelink = '';
1169     if ( has_capability('moodle/user:viewdetails', $usercontext) ) {
1170         $profilelink = $CFG->wwwroot.'/user/view.php?id='.$user->id;
1171     }
1173     print_box_start('user-box', $user->id);
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>';
1195     print_box_end();
1199 /**
1200  * Prints the tag search box
1201  *
1202  */
1203 function 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();
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  */
1226 function print_tag_search_results($query,  $page, $perpage) {
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;
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     }
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         }
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);
1273         //print a link "Add $query to my interests"
1274         if (!empty($addtaglink)) {
1275             print_box($addtaglink,'box','tag-management-box');
1276         }
1277                 
1278     }
1280   
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  */
1290 function print_tag_cloud($tagcloud, $shuffle=true, $max_size=180, $min_size=80) {
1291     
1292     global $CFG;
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);
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);
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.'"';
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     
1351 function print_tag_management_list($perpage='100') {
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                             );    
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);
1376     $table->set_attribute('cellspacing', '0');
1377     $table->set_attribute('id', 'tag-management-list');
1378     $table->set_attribute('class', 'generaltable generalbox');
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     }
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');
1424     $table->initialbars($totalcount > $perpage);
1425     $table->pagesize($perpage, $totalcount);
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     
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     }
1475     $table->print_html();
1477     echo '</form>';
1480 ?>