Index: common/common.php
===================================================================
--- common/common.php	(revision 1068)
+++ common/common.php	(working copy)
@@ -162,7 +162,7 @@
 }
 
 // Check valid score names.
-// Does not check existence. 
+// Does not check existence.
 function is_score_name($score_name) {
     return preg_match('/^'.IA_RE_SCORE_NAME.'$/xi', $score_name) &&
         strlen($score_name) < 64;
@@ -206,6 +206,28 @@
            strlen($tag_name) < 64;
 }
 
+// Check tag type
+function is_tag_type($tag_type) {
+    return in_array($tag_type, array(
+        'author', 'contest', 'year', 'round', 'age_group',
+        'method', 'algorithm', 'tag'
+    ));
+}
+
+// Check tag
+function is_tag($tag) {
+    if (!is_array($tag)) {
+        return false;
+    }
+    if (!array_key_exists("name", $tag)) {
+        return false;
+    }
+    if (!array_key_exists("type", $tag)) {
+        return false;
+    }
+    return is_tag_name($tag["name"]) && is_tag_type($tag["type"]);
+}
+
 // Taggable objects
 function is_taggable($obj) {
     return $obj == 'user' || $obj == 'task' || $obj == 'round' || $obj == 'textblock';
Index: common/db/blog.php
===================================================================
--- common/db/blog.php	(revision 1068)
+++ common/db/blog.php	(working copy)
@@ -4,7 +4,7 @@
 
 function blog_get_range($tag_name, $start, $range) {
     if (is_tag_name($tag_name)) {
-        $tag_id = tag_get_id($tag_name);
+        $tag_id = tag_get_id(array("name" => $tag_name, "type" => "tag"));
         if (is_null($tag_id)) {
             $tag_id = -1;
         }
@@ -12,11 +12,11 @@
     } else {
         $where = "TRUE";
     }
-    $query = sprintf("SELECT * FROM ia_textblock 
-                      WHERE %s AND name LIKE 'blog/%%' 
+    $query = sprintf("SELECT * FROM ia_textblock
+                      WHERE %s AND name LIKE 'blog/%%'
                       AND security <> 'private'
                       ORDER BY ia_textblock.creation_timestamp DESC
-                      LIMIT %s, %s", 
+                      LIMIT %s, %s",
                      $where, db_quote((int)$start), db_quote((int)$range));
     return db_fetch_all($query);
 }
@@ -29,7 +29,7 @@
 }
 
 function blog_get_comment_count($topic_id) {
-    $query = sprintf("SELECT `numReplies` FROM ia_smf_topics 
+    $query = sprintf("SELECT `numReplies` FROM ia_smf_topics
                       WHERE ID_TOPIC = %d", db_escape($topic_id));
     $result = db_fetch($query);
     return $result['numReplies'];
@@ -37,7 +37,7 @@
 
 function blog_count($tag_name) {
     if (is_tag_name($tag_name)) {
-        $tag_id = tag_get_id($tag_name);
+        $tag_id = tag_get_id(array("name" => $tag_name, "type" => "tag"));
         if (is_null($tag_id)) {
             $tag_id = -1;
         }
@@ -53,9 +53,9 @@
 }
 
 function blog_get_tags() {
-    $query = "SELECT name, 
+    $query = "SELECT name,
                      (SELECT COUNT(*) FROM ia_textblock_tags WHERE tag_id = id AND textblock_id LIKE 'blog/%%') AS cnt
-              FROM ia_tags WHERE id IN 
+              FROM ia_tags WHERE id IN
               (SELECT DISTINCT tag_id FROM ia_textblock_tags WHERE textblock_id LIKE 'blog/%%')
               ORDER BY name";
     return db_fetch_all($query);
Index: common/db/tags.php
===================================================================
--- common/db/tags.php	(revision 1068)
+++ common/db/tags.php	(working copy)
@@ -3,42 +3,80 @@
 require_once(IA_ROOT_DIR."common/db/db.php");
 require_once(IA_ROOT_DIR."common/common.php");
 
-// get list of all tag names
-function tag_get_all_names() {
-    $query = sprintf("SELECT name FROM ia_tags");
+// Get list of all tag names, filtered by type
+function tag_get_all($types = null) {
+    $query = "SELECT name, type FROM ia_tags";
+    if (!is_null($types)) {
+        log_assert(is_array($types), "types should be an array");
+        foreach ($types as $type) {
+            log_assert(is_tag_type($type));
+        }
+
+        $query .= sprintf(" WHERE type IN (%s)",
+            implode(',', array_map('db_quote', $types))
+        );
+    }
     return db_fetch_all($query);
 }
 
-function tag_get_names($obj, $obj_id) {
+// Get list of all tags for a certain object, filtered by type
+function tag_get($obj, $obj_id, $type = null) {
     log_assert(is_taggable($obj));
-    $query = sprintf("SELECT %s_id, tag_id, tags.name AS tag_name
-                      FROM ia_%s_tags AS obj_tags
-                      LEFT JOIN ia_tags AS tags ON obj_tags.tag_id = tags.id 
-                      WHERE %s_id = %s",
-                      db_escape($obj), db_escape($obj), db_escape($obj),  db_quote($obj_id));
-    return db_fetch_all($query);        
+    log_assert(is_null($type) || is_tag_type($type));
+    if (is_null($type)) {
+        $where_type = "";
+    } else {
+        $where_type = sprintf(" AND tags.type = %s", db_quote($type));
+    }
+    $query = sprintf(
+        "SELECT %s_id, tag_id, tags.name AS tag_name, tags.type AS tag_type
+        FROM ia_%s_tags AS obj_tags
+        LEFT JOIN ia_tags AS tags ON obj_tags.tag_id = tags.id
+        WHERE %s_id = %s%s",
+        db_escape($obj), db_escape($obj), db_escape($obj),
+        db_quote($obj_id), $where_type
+    );
+    return db_fetch_all($query);
 }
 
-// get tag id for a certain tag name
-function tag_get_id($tag_name) {
-    log_assert(is_tag_name($tag_name));
-    $query = sprintf("SELECT id FROM ia_tags WHERE name = %s",
-                     db_quote($tag_name));
+// Get tag id for a certain tag
+function tag_get_id($tag) {
+    log_assert(is_tag($tag));
+    $query = sprintf(
+        "SELECT id FROM ia_tags WHERE name = %s AND type = %s",
+        db_quote($tag["name"]), db_quote($tag["type"])
+    );
     $result = db_fetch($query);
     return $result['id'];
 }
 
-// get list of tag ids for a list of tag names
-function tag_get_ids($tag_names) {
-    foreach ($tag_names as &$name) {
-        log_assert(is_tag_name($name));
-        $name = db_quote($name);
+// Get list of tag ids for a list of tags
+function tag_get_ids($tags) {
+    $tag_wheres = array();
+    foreach ($tags as $tag) {
+        log_assert(is_tag($tag));
+        $tag_wheres[] = sprintf("(name = %s AND type = %s)",
+            db_quote($tag["name"]), db_quote($tag["type"]));
     }
-    $query = sprintf("SELECT id FROM ia_tags WHERE name IN (%s)", implode(", ", $tag_names));
+    $query = sprintf("SELECT id FROM ia_tags WHERE %s",
+        implode(" OR ", $tag_wheres));
     return db_fetch_all($query);
 }
 
-// build ugly where clause to be used in subqueries
+// Assign numeric id to a given tag name
+function tag_assign_id($tag) {
+    log_assert(is_tag($tag));
+    $id = tag_get_id($tag);
+    if (is_null($id)) {
+        $query = sprintf("INSERT INTO ia_tags (name, type) VALUES (%s, %s)",
+            db_quote($tag['name']), db_quote($tag['type']));
+        db_query($query);
+        return db_insert_id();
+    }
+    return $id;
+}
+
+// Build ugly where clause to be used in subqueries
 function tag_build_where($obj, $tag_ids, $parent_table = null) {
     log_assert(is_taggable($obj));
     log_assert(is_array($tag_ids));
@@ -51,55 +89,66 @@
         $field = 'id';
     }
     $where = sprintf("(SELECT COUNT(*) FROM ia_%s_tags WHERE %s_id = %s.%s".
-                     " AND tag_id IN (%s)) = %d", 
-                     db_escape($obj), db_escape($obj), 
-                     db_escape($parent_table), db_escape($field), 
+                     " AND tag_id IN (%s)) = %d",
+                     db_escape($obj), db_escape($obj),
+                     db_escape($parent_table), db_escape($field),
                      implode(", ", $tag_ids), count($tag_ids));
     return $where;
 }
 
-// assign numeric id to a given tag name 
-function tag_assign_id($tag_name) {
-    log_assert(is_tag_name($tag_name));
-    $id = tag_get_id($tag_name);
-    if (is_null($id)) {
-        $query = sprintf("INSERT INTO ia_tags (name) VALUES (%s)", db_quote($tag_name));
-        db_query($query);
-        return db_insert_id();
-    }
-    return $id;
-}
-
-// get all objects containting all tags from a list of tag ids
+// Get all objects containting all tags from a list of tag ids
 function tag_get_objects($obj, $tag_ids, $content = true) {
     log_assert(is_taggable($obj));
     log_assert(is_array($tag_ids));
     if ($content) {
         $fields = "*";
-    }
-    else 
-    if ($obj == 'textblock') {
+    } elseif ($obj == 'textblock') {
         $fields = "name";
-    }
-    else {
+    } else {
         $fields = "id";
     }
-    $query = sprintf("SELECT %s FROM ia_%s WHERE %s", $fields, db_escape($obj), 
+    $query = sprintf("SELECT %s FROM ia_%s WHERE %s", $fields, db_escape($obj),
                      tag_build_where($obj, $tag_ids));
     return db_fetch_all($query);
 }
 
-// count the number of objects containing all tags from a list of tag ids
+// Count the number of objects containing all tags from a list of tag ids
 function tag_count_objects($obj, $tag_ids) {
     log_assert(is_taggable($obj));
     log_assert(is_array($tag_ids));
-    $query = sprintf("SELECT COUNT(*) as `cnt` FROM ia_%s WHERE %s", db_escape($obj), 
+    $query = sprintf("SELECT COUNT(*) as `cnt` FROM ia_%s WHERE %s", db_escape($obj),
                      tag_build_where($obj, $tag_ids));
     $result = db_fetch($query);
     return $result['cnt'];
 }
 
-// check if a certain object has a certain tag 
+// Clear all tags, filtered by type
+function tag_clear($obj, $obj_id, $type = null) {
+    log_assert(is_taggable($obj));
+    log_assert(is_null($type) || is_tag_type($type));
+    $where = sprintf("%s_id = %s", db_escape($obj), db_quote($obj_id));
+    $query = sprintf("DELETE FROM ia_%s_tags WHERE %s AND tag_id IN (%s)",
+        db_escape($obj), $where, "%s"
+    );
+    if (is_null($type)) {
+        $join = "";
+    } else {
+        $join = "LEFT JOIN ia_tags AS tags ON obj_tags.tag_id = tags.id";
+        $where .= sprintf(" AND tags.type = %s", db_quote($type));
+    }
+    $subquery = sprintf("SELECT tag_id FROM ia_%s_tags AS obj_tags %s WHERE %s",
+        db_escape($obj), $join, $where);
+    $tag_ids = array();
+    foreach (db_fetch_all($subquery) as $tag) {
+        $tag_ids[] = $tag["tag_id"];
+    }
+    if ($tag_ids) {
+        $query = sprintf($query, implode(",", $tag_ids));
+        db_query($query);
+    }
+}
+
+// Check if a certain object has a certain tag
 function tag_exists($obj, $obj_id, $tag_id) {
     log_assert(is_taggable($obj));
     log_assert(is_tag_id($tag_id));
@@ -109,19 +158,11 @@
     $result = db_fetch($query);
     if ($result['cnt'] == 0) {
         return false;
-    } 
+    }
     return true;
 }
 
-// clear all tags 
-function tag_clear($obj, $obj_id) {
-    log_assert(is_taggable($obj));
-    $query = sprintf("DELETE FROM ia_%s_tags WHERE %s_id = %s", db_escape($obj),
-                     db_escape($obj), db_quote($obj_id));
-    db_query($query);
-}
-
-// remove a tag
+// Remove a tag
 function tag_remove($obj, $obj_id, $tag_id) {
     log_assert(is_taggable($obj));
     log_assert(is_tag_id($tag_id));
@@ -130,7 +171,7 @@
     db_query($query);
 }
 
-// add a tag
+// Add a tag
 function tag_add($obj, $obj_id, $tag_id) {
     log_assert(is_taggable($obj));
     log_assert(is_tag_id($tag_id));
Index: common/tags.php
===================================================================
--- common/tags.php	(revision 1068)
+++ common/tags.php	(working copy)
@@ -29,8 +29,8 @@
     }
 }
 
-function tag_build_list($obj, $obj_id) {
-    $tag_list = tag_get_names($obj, $obj_id);
+function tag_build_list($obj, $obj_id, $type) {
+    $tag_list = tag_get($obj, $obj_id, $type);
     $tag_names = array();
     foreach ($tag_list as $tag) {
         $tag_names[] = $tag['tag_name'];
@@ -38,11 +38,11 @@
     return implode(", ", $tag_names);
 }
 
-function tag_update($obj, $obj_id, $tag_data) {
-    tag_clear($obj, $obj_id);
+function tag_update($obj, $obj_id, $type, $tag_data) {
+    tag_clear($obj, $obj_id, $type);
     $tag_data = tag_split($tag_data);
     foreach ($tag_data as $tag_name) {
-        $tag_id = tag_assign_id($tag_name);
+        $tag_id = tag_assign_id(array("name" => $tag_name, "type" => $type));
         tag_add($obj, $obj_id, $tag_id);
     }
 }
Index: common/textblock.php
===================================================================
--- common/textblock.php	(revision 1068)
+++ common/textblock.php	(working copy)
@@ -48,7 +48,8 @@
         // May be there is a better way to find out if $tb is tagged
         // 'stiri'. On the other hand, this operation is cached and we
         // shouldn't worry too much about performance.
-        if ($whole_news && tag_exists('textblock', $tb['name'], tag_get_id('stiri'))) {
+        if ($whole_news && tag_exists('textblock', $tb['name'],
+            tag_get_id(array("name" => 'stiri', "type" => "tag")))) {
             // Don't compute snippet for news -- they should be rendered as
             // they are in the snippet.
             $cache_res .= $html_text;
Index: scripts/migrate-task-tags
===================================================================
--- scripts/migrate-task-tags	(revision 0)
+++ scripts/migrate-task-tags	(revision 0)
@@ -0,0 +1,13 @@
+#! /usr/bin/env php
+<?php
+
+require_once(dirname($argv[0]) . "/utilities.php");
+
+db_connect();
+db_query("
+    ALTER TABLE `ia_tags`
+    ADD COLUMN `type` ENUM('author','contest','year','round','age_group','method','algorithm','tag') NOT NULL
+");
+db_query('UPDATE `ia_tags` SET `type` = "tag"');
+
+?>

Property changes on: scripts/migrate-task-tags
___________________________________________________________________
Added: svn:executable
   + *

Index: www/controllers/account.php
===================================================================
--- www/controllers/account.php	(revision 1068)
+++ www/controllers/account.php	(working copy)
@@ -75,7 +75,7 @@
         $errors = validate_profile_data($data, $user);
 
         // validate tag data
-        $data['tags'] = request('tags', tag_build_list("user", $user['id']));
+        $data['tags'] = request('tags', tag_build_list("user", $user['id'], "tag"));
         tag_validate($data, $errors);
 
         // validate avatar
@@ -161,9 +161,9 @@
 
             // update tags info
             if (identity_can('user-tag', $new_user)) {
-                tag_update("user", $new_user['id'], $data['tags']);
+                tag_update("user", $new_user['id'], "tag", $data['tags']);
             }
-            $data['tags'] = tag_build_list("user", $new_user['id']);
+            $data['tags'] = tag_build_list("user", $new_user['id'], "tag");
 
             // done. redirect to same page so user has a strong confirmation
             // of data being saved
@@ -174,7 +174,7 @@
     } else {
         // form is displayed for the first time. Fill in default values
         $data = $user;
-        $data['tags'] = tag_build_list("user", $user['id']);
+        $data['tags'] = tag_build_list("user", $user['id'], "tag");
 
         // unset some fields we do not $data to carry
         unset($data['id']);
Index: www/controllers/blog.php
===================================================================
--- www/controllers/blog.php	(revision 1068)
+++ www/controllers/blog.php	(working copy)
@@ -57,7 +57,7 @@
         $subpage['rating_cache'] = $first_textblock['rating_cache'];
         $subpage['forum_topic'] = blog_get_forum_topic($subpage['name']);
         $subpage['comment_count'] = blog_get_comment_count($subpage['forum_topic']);
-        $subpage['tags'] = tag_get_names("textblock", $subpage['name']);
+        $subpage['tags'] = tag_get("textblock", $subpage['name']);
     }
 
     execute_view_die('views/blog_index.php', $view);
@@ -101,7 +101,7 @@
     $view['page_name'] = $page['name'];
     $view['textblock'] = $page;
     $view['forum_topic'] = $page['forum_topic'];
-    $view['tags'] = tag_get_names("textblock", $page['name']);
+    $view['tags'] = tag_get("textblock", $page['name']);
     $view['first_textblock'] = textblock_get_revision($page_name, 1, true);
 
     // This emits a warning on the stripped database used by devs because
Index: www/controllers/round.php
===================================================================
--- www/controllers/round.php	(revision 1068)
+++ www/controllers/round.php	(working copy)
@@ -99,7 +99,7 @@
     }
 
     // Tag data
-    $values['tags'] = request('tags', tag_build_list("round", $round_id));
+    $values['tags'] = request('tags', tag_build_list("round", $round_id, "tag"));
 
     // Validate the monkey.
 
@@ -168,7 +168,7 @@
         round_update_task_list($round_id, $round_tasks, $new_round_tasks);
 
         if (identity_can('round-tag', $new_round)) {
-            tag_update("round", $new_round['id'], $values['tags']);
+            tag_update("round", $new_round['id'], "tag", $values['tags']);
         }
 
         flash("Runda a fost modificata cu succes.");
Index: www/controllers/task.php
===================================================================
--- www/controllers/task.php	(revision 1068)
+++ www/controllers/task.php	(working copy)
@@ -64,7 +64,7 @@
     }
 
     // Tag data
-    $values['tags'] = request('tags', tag_build_list("task", $task_id));
+    $values['tags'] = request('tags', tag_build_list("task", $task_id, "tag"));
 
     // Task owner
     if (identity_can('task-edit-owner', $task)) {
@@ -137,7 +137,7 @@
             task_update($new_task);
 
             if (identity_can('task-tag', $new_task)) {
-                tag_update("task", $new_task['id'], $values['tags']);
+                tag_update("task", $new_task['id'], "tag", $values['tags']);
             }
 
             flash("Task-ul a fost modificat cu succes.");
Index: www/controllers/textblock_edit.php
===================================================================
--- www/controllers/textblock_edit.php	(revision 1068)
+++ www/controllers/textblock_edit.php	(working copy)
@@ -35,7 +35,7 @@
     $values['title'] = request('title', $page['title']);
     $values['security'] = request('security', $page['security']);
     $values['forum_topic'] = request('forum_topic', $page['forum_topic']);
-    $values['tags'] = request('tags', tag_build_list("textblock", $page_name));
+    $values['tags'] = request('tags', tag_build_list("textblock", $page_name, "tag"));
     $values['creation_timestamp'] = getattr($page, 'creation_timestamp');
     $values['timestamp'] = null;
 
@@ -81,7 +81,7 @@
                                    $new_page['creation_timestamp'],
                                    remote_ip_info());
             if (identity_can('textblock-tag', $new_page)) {
-                tag_update("textblock", $new_page['name'], $values['tags']);
+                tag_update("textblock", $new_page['name'], "tag", $values['tags']);
             }
             flash('Am actualizat continutul');
 
Index: www/views/tags_header.php
===================================================================
--- www/views/tags_header.php	(revision 1068)
+++ www/views/tags_header.php	(working copy)
@@ -41,7 +41,7 @@
     return answer;
 }
 <?php
-$tag_names = tag_get_all_names();
+$tag_names = tag_get_all();
 echo  "collection = [\n";
 foreach ($tag_names as $tag) {
     echo "'".html_escape($tag['name'])."',\n";
