View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0009716 | mantisbt | tagging | public | 2008-10-20 10:31 | 2019-06-26 22:46 |
Reporter | thosjo | Assigned To | |||
Priority | normal | Severity | feature | Reproducibility | always |
Status | acknowledged | Resolution | open | ||
Product Version | 1.1.2 | ||||
Summary | 0009716: Seperation of tags between projects | ||||
Description | In 1.1.2 the created tags are shared among all projects, even those the developer isn't a member of. Would like to have the option to keep tags locked to a project so a developer working on X isn't allowed to view the tags assigned to project Y. | ||||
Tags | No tags attached. | ||||
Attached Files | projectSpecificTagsForMaster-1.2.x.patch (22,999 bytes)
From 2e2335841909ef0f25c2616f8b34ed152d726a8e Mon Sep 17 00:00:00 2001 From: Dominik Blunk <dominik@blunk.ch> Date: Mon, 12 Sep 2011 15:49:06 +0200 Subject: [PATCH] Implemented project specific tags @see http://www.mantisbt.org/bugs/view.php?id=9716 To enable this feature run a Mantis update (new column "project_id" in tag table is required) and set the config value $g_tag_project_specific_enabled to ON (defaults to OFF) If project specific tags are enabled the following happens: a) All existing tags will be considered as "global" (tags are saved with project_id = 0). Every "global" tag may be used in all projects. b) Newly created tags are related to the current project (project of the bug precisely). These tags are only available within the same project scope. c) No special handling of "parent projects" - a parent project is equal to a child project. Either tags for the parent project exists or not - no usage of "collected child project tags" d) The tag - project relation may be edited in the tag management section. The tag overview shows only "global" tags and tags for the current project. e) The filter shows only "global" tags and tags for the current project. Therefore filtering issues for specific tag(s) over multiple projects works only with "global" tags. f) When switching $g_tag_project_specific_enabled between ON and OFF the existing tags will keep its project relation. New tags will always be "global" for "all projects". This makes sure that existing "project specific" tags are not unintentionally "globally" available Other improvements: - Added auth_reauthenticate() to the manage_tags_page.php - Added the print_manage_menu() to tag_view_page.php and tag_update_page.php to improve usability (navigation back to tags overview after viewing/editing a tag) --- admin/schema.php | 4 ++ bug_actiongroup_attach_tags_inc.php | 3 +- config_defaults_inc.php | 7 +++ core/tag_api.php | 97 ++++++++++++++++++++++++----------- manage_tags_page.php | 27 ++++++++-- tag_attach.php | 6 ++- tag_create.php | 9 +++- tag_update.php | 8 +++- tag_update_page.php | 24 +++++++-- tag_view_page.php | 22 +++++--- 10 files changed, 150 insertions(+), 57 deletions(-) diff --git a/admin/schema.php b/admin/schema.php index d2b6940..505dd9b 100644 --- a/admin/schema.php +++ b/admin/schema.php @@ -608,3 +608,7 @@ $upgrade[] = Array( 'CreateIndexSQL', Array( 'idx_tag_name', db_get_table( 'mant $upgrade[] = Array( 'CreateIndexSQL', Array( 'idx_bug_tag_tag_id', db_get_table( 'mantis_bug_tag_table' ), 'tag_id' ) ); $upgrade[] = Array( 'CreateIndexSQL', Array( 'idx_email_id', db_get_table( 'mantis_email_table' ), 'email_id', array( 'DROP' ) ), Array( 'db_index_exists', Array( db_get_table( 'mantis_email_table' ), 'idx_email_id') ) ); $upgrade[] = Array( 'UpdateFunction', 'correct_multiselect_custom_fields_db_format' ); + +/* 190 */ +$upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_tag_table' ), "project_id I UNSIGNED NOTNULL DEFAULT '0'" ) ); +$upgrade[] = Array( 'CreateIndexSQL', Array( 'idx_project_id', db_get_table( 'mantis_tag_table' ), 'project_id' ) ); diff --git a/bug_actiongroup_attach_tags_inc.php b/bug_actiongroup_attach_tags_inc.php index 3105acf..fca39b6 100644 --- a/bug_actiongroup_attach_tags_inc.php +++ b/bug_actiongroup_attach_tags_inc.php @@ -108,9 +108,10 @@ global $g_action_attach_tags_attach, $g_action_attach_tags_create; $t_user_id = auth_get_current_user_id(); + $t_bug = bug_get( $p_bug_id ); foreach( $g_action_attach_tags_create as $t_tag_row ) { - $t_tag_row['id'] = tag_create( $t_tag_row['name'], $t_user_id ); + $t_tag_row['id'] = tag_create( $t_tag_row['name'], $t_user_id, '', $t_bug->project_id ); $g_action_attach_tags_attach[] = $t_tag_row; } $g_action_attach_tags_create = array(); diff --git a/config_defaults_inc.php b/config_defaults_inc.php index 59b4764..2261a05 100644 --- a/config_defaults_inc.php +++ b/config_defaults_inc.php @@ -3506,6 +3506,13 @@ /*************** * Bug Tagging * ***************/ + + /** + * When enabled tags will be project specific unless explicitely set to "global" + * in the manage tags section + * @global int $g_tag_project_specific_enabled + */ + $g_tag_project_specific_enabled = OFF; /** * String that will separate tags as entered for input diff --git a/core/tag_api.php b/core/tag_api.php index fd31424..6a1b224 100644 --- a/core/tag_api.php +++ b/core/tag_api.php @@ -65,14 +65,15 @@ function tag_ensure_exists( $p_tag_id ) { * Determine if a given name is unique (not already used). * Uses a case-insensitive search of the database for existing tags with the same name. * @param string Tag name + * @param integer Project ID * @return boolean True if name is unique */ -function tag_is_unique( $p_name ) { +function tag_is_unique( $p_name, $p_project_id = 0 ) { $c_name = trim( $p_name ); $t_tag_table = db_get_table( 'mantis_tag_table' ); - $query = 'SELECT id FROM ' . $t_tag_table . ' WHERE ' . db_helper_like( 'name' ); - $result = db_query_bound( $query, Array( $c_name ) ); + $query = 'SELECT id FROM ' . $t_tag_table . ' WHERE ' . db_helper_like( 'name' ) . " AND project_id = " . db_param(); + $result = db_query_bound( $query, Array( $c_name, $p_project_id ) ); return db_num_rows( $result ) == 0; } @@ -80,9 +81,10 @@ function tag_is_unique( $p_name ) { /** * Ensure that a name is unique. * @param string Tag name + * @param integer Project ID */ -function tag_ensure_unique( $p_name ) { - if( !tag_is_unique( $p_name ) ) { +function tag_ensure_unique( $p_name, $p_project_id = 0 ) { + if( !tag_is_unique( $p_name, $p_project_id ) ) { trigger_error( ERROR_TAG_DUPLICATE, ERROR ); } } @@ -136,9 +138,10 @@ function tag_cmp_name( $p_tag1, $p_tag2 ) { * id = -1 and the tag name, and if the name is invalid, a row is returned with * id = -2 and the tag name. The resulting array is then sorted by tag name. * @param string Input string to parse + * @param int Project ID * @return array Rows of tags parsed from input string */ -function tag_parse_string( $p_string ) { +function tag_parse_string( $p_string, $p_project_id = 0 ) { $t_tags = array(); $t_strings = explode( config_get( 'tag_separator' ), $p_string ); @@ -149,19 +152,27 @@ function tag_parse_string( $p_string ) { } $t_matches = array(); - $t_tag_row = tag_get_by_name( $t_name ); + $t_tag_row = tag_get_by_name( $t_name, $p_project_id ); if( $t_tag_row !== false ) { $t_tags[] = $t_tag_row; } else { - if( tag_name_is_valid( $t_name, $t_matches ) ) { - $t_id = -1; + if ( config_get( 'tag_project_specific_enabled' ) && $p_project_id > 0 ) { + // check if "global" tag exists + $t_tag_row = tag_get_by_name( $t_name, 0 ); + } + if( $t_tag_row !== false ) { + $t_tags[] = $t_tag_row; } else { - $t_id = -2; + if( tag_name_is_valid( $t_name, $t_matches ) ) { + $t_id = -1; + } else { + $t_id = -2; + } + $t_tags[] = array( + 'id' => $t_id, + 'name' => $t_name, + ); } - $t_tags[] = array( - 'id' => $t_id, - 'name' => $t_name, - ); } } usort( $t_tags, 'tag_cmp_name' ); @@ -189,6 +200,10 @@ function tag_parse_filters( $p_string ) { if( !is_blank( $t_name ) && tag_name_is_valid( $t_name, $t_matches, $t_prefix ) ) { $t_tag_row = tag_get_by_name( $t_matches[1] ); + if( $t_tag_row === false && config_get( 'tag_project_specific_enabled' ) ) { + // no "global" tag found -> check project specific tag + $t_tag_row = tag_get_by_name( $t_matches[1], helper_get_current_project() ); + } if( $t_tag_row !== false ) { $t_filter = utf8_substr( $t_name, 0, 1 ); @@ -238,14 +253,17 @@ function tag_get( $p_tag_id ) { /** * Return a tag row for the given name. * @param string Tag name + * @param int Project ID * @return Tag row */ -function tag_get_by_name( $p_name ) { +function tag_get_by_name( $p_name, $p_project_id = 0 ) { $t_tag_table = db_get_table( 'mantis_tag_table' ); + $t_params = array( $p_name, $p_project_id ); $query = "SELECT * FROM $t_tag_table - WHERE " . db_helper_like( 'name' ); - $result = db_query_bound( $query, Array( $p_name ) ); + WHERE " . db_helper_like( 'name' ) ." + and project_id = " . db_param(); + $result = db_query_bound( $query, $t_params ); if( 0 == db_num_rows( $result ) ) { return false; @@ -279,13 +297,14 @@ function tag_get_field( $p_tag_id, $p_field_name ) { * @param string Tag name * @param integer User ID * @param string Description + * @param integer Project ID * @return integer Tag ID */ -function tag_create( $p_name, $p_user_id = null, $p_description = '' ) { +function tag_create( $p_name, $p_user_id = null, $p_description = '', $p_project_id = 0 ) { access_ensure_global_level( config_get( 'tag_create_threshold' ) ); tag_ensure_name_is_valid( $p_name ); - tag_ensure_unique( $p_name ); + tag_ensure_unique( $p_name, $p_project_id ); if( null == $p_user_id ) { $p_used_id = auth_get_current_user_id(); @@ -294,12 +313,17 @@ function tag_create( $p_name, $p_user_id = null, $p_description = '' ) { } $c_user_id = db_prepare_int( $p_user_id ); + $c_project_id = db_prepare_int( $p_project_id ); + if ( !config_get( 'tag_project_specific_enabled' ) ) { + $c_project_id = 0; + } $c_date_created = db_now(); $t_tag_table = db_get_table( 'mantis_tag_table' ); $query = "INSERT INTO $t_tag_table ( user_id, + project_id, name, description, date_created, @@ -310,10 +334,11 @@ function tag_create( $p_name, $p_user_id = null, $p_description = '' ) { " . db_param() . ", " . db_param() . ", " . db_param() . ", + " . db_param() . ", " . db_param() . " )"; - db_query_bound( $query, Array( $c_user_id, trim( $p_name ), trim( $p_description ), $c_date_created, $c_date_created ) ); + db_query_bound( $query, Array( $c_user_id, $c_project_id, trim( $p_name ), trim( $p_description ), $c_date_created, $c_date_created ) ); return db_insert_id( $t_tag_table ); } @@ -322,9 +347,10 @@ function tag_create( $p_name, $p_user_id = null, $p_description = '' ) { * @param integer Tag ID * @param string Tag name * @param integer User ID + * @param integer Project ID * @param string Description */ -function tag_update( $p_tag_id, $p_name, $p_user_id, $p_description ) { +function tag_update( $p_tag_id, $p_name, $p_user_id, $p_description, $p_project_id = 0 ) { user_ensure_exists( $p_user_id ); if( auth_get_current_user_id() == tag_get_field( $p_tag_id, 'user_id' ) ) { @@ -352,11 +378,19 @@ function tag_update( $p_tag_id, $p_name, $p_user_id, $p_description ) { $query = "UPDATE $t_tag_table SET user_id=" . db_param() . ", - name=" . db_param() . ", - description=" . db_param() . ", + name=" . db_param() . ","; + if ( $p_project_id >= 0 ) { + $query .= " project_id=" . db_param() . ","; + } + $query .= " description=" . db_param() . ", date_updated=" . db_param() . " WHERE id=" . db_param(); - db_query_bound( $query, Array( (int)$p_user_id, $p_name, $p_description, $c_date_updated, $c_tag_id ) ); + if ( $p_project_id >= 0 ) { + db_query_bound( $query, Array( (int)$p_user_id, $p_name, $p_project_id, $p_description, $c_date_updated, $c_tag_id ) ); + } + else { + db_query_bound( $query, Array( (int)$p_user_id, $p_name, $p_description, $c_date_updated, $c_tag_id ) ); + } if( $t_rename ) { $t_bugs = tag_get_bugs_attached( $p_tag_id ); @@ -408,12 +442,13 @@ function tag_get_candidates_for_bug( $p_bug_id ) { $t_params = array(); if ( 0 != $p_bug_id ) { $t_bug_tag_table = db_get_table( 'mantis_bug_tag_table' ); - + $t_bug = bug_get( $p_bug_id ); + $t_params[] = $p_bug_id; if ( db_is_mssql() ) { - $t_params[] = $p_bug_id; $query = "SELECT t.id FROM $t_tag_table t LEFT JOIN $t_bug_tag_table b ON t.id=b.tag_id - WHERE b.bug_id IS NULL OR b.bug_id != " . db_param(); + WHERE (b.bug_id IS NULL OR b.bug_id != " . db_param() .") + AND project_id IN (0, " . $t_bug->project_id . ")"; $result = db_query_bound( $query, $t_params ); $t_subquery_results = array(); @@ -426,12 +461,12 @@ function tag_get_candidates_for_bug( $p_bug_id ) { $query = "SELECT id, name, description FROM $t_tag_table WHERE id IN ( SELECT t.id FROM $t_tag_table t LEFT JOIN $t_bug_tag_table b ON t.id=b.tag_id - WHERE b.bug_id IS NULL OR b.bug_id != " . db_param() . - ')'; + WHERE (b.bug_id IS NULL OR b.bug_id != " . db_param() . ") + AND project_id IN (0, " . $t_bug->project_id . ") + )"; } - $t_params[] = $p_bug_id; } else { - $query = 'SELECT id, name, description FROM ' . $t_tag_table; + $query = 'SELECT id, name, description FROM ' . $t_tag_table . " WHERE project_id IN (0, " . helper_get_current_project() . ")"; } $query .= ' ORDER BY name ASC '; diff --git a/manage_tags_page.php b/manage_tags_page.php index 0718160..f0908ad 100644 --- a/manage_tags_page.php +++ b/manage_tags_page.php @@ -32,6 +32,8 @@ require_once( 'tag_api.php' ); require_once( 'user_pref_api.php' ); require_once( 'form_api.php' ); +auth_reauthenticate(); + access_ensure_global_level( config_get( 'tag_edit_threshold' ) ); compress_enable(); @@ -78,11 +80,10 @@ echo '</tr></table>'; $t_where_params = array(); -if ( $f_filter === 'ALL' ) { - $t_where = ''; -} else { +$t_where = "WHERE project_id in (0, " . helper_get_current_project() . ")"; +if ( $f_filter !== 'ALL' ) { $t_where_params[] = $f_filter . '%'; - $t_where = 'WHERE ' . db_helper_like( 'name' ); + $t_where .= ' AND ' . db_helper_like( 'name' ); } # Set the number of Tags per page. @@ -141,7 +142,8 @@ $t_result = db_query_bound( $t_query, $t_where_params, $t_per_page, $t_offset ); </td> </tr> <tr class="row-category"> - <td width="25%"><?php echo lang_get( 'tag_name' ) ?></td> + <td width="20%"><?php echo lang_get( 'tag_name' ) ?></td> + <td width="20%"><?php echo lang_get( 'project_name' ) ?></td> <td width="20%"><?php echo lang_get( 'tag_creator' ) ?></td> <td width="20%"><?php echo lang_get( 'tag_created' ) ?></td> <td width="20%"><?php echo lang_get( 'tag_updated' ) ?></td> @@ -157,6 +159,7 @@ foreach ( $t_result as $t_tag_row ) { <?php } else { ?> <td><?php echo $t_tag_name ?></td> <?php } ?> + <td><?php echo string_display_line( project_get_name( $t_tag_row['project_id'] ) ) ?></td> <td><?php echo string_display_line( user_get_name( $t_tag_row['user_id'] ) ) ?></td> <td><?php echo date( config_get( 'normal_date_format' ), $t_tag_row['date_created'] ) ?></td> <td><?php echo date( config_get( 'normal_date_format' ), $t_tag_row['date_updated'] ) ?></td> @@ -204,7 +207,19 @@ foreach ( $t_result as $t_tag_row ) { <?php echo sprintf( lang_get( 'tag_separate_by' ), config_get( 'tag_separator' ) ); ?> </td> </tr> - <tr class="row-2"> + <?php if ( config_get( 'tag_project_specific_enabled' ) ) { ?> + <tr class="row-2" valign="top"> + <td class="category"> + <?php echo lang_get( 'project_name' ) ?> + </td> + <td> + <select name="project_id"> + <?php print_project_option_list( helper_get_current_project(), true ) ?> + </select> + </td> + </tr> + <?php } ?> + <tr class="row-1"> <td class="category"> <?php echo lang_get( 'tag_description' ) ?> </td> diff --git a/tag_attach.php b/tag_attach.php index 14086f7..09b908b 100644 --- a/tag_attach.php +++ b/tag_attach.php @@ -32,6 +32,8 @@ form_security_validate( 'tag_attach' ); $f_bug_id = gpc_get_int( 'bug_id' ); + $t_bug = bug_get( $f_bug_id ); + $t_project_id = $t_bug->project_id; $f_tag_select = gpc_get_int( 'tag_select' ); $f_tag_string = gpc_get_string( 'tag_string' ); @@ -43,7 +45,7 @@ * to the APIs. This is to allow other clients of the API to support such * functionality. The access level checks should also be moved to the API. */ - $t_tags = tag_parse_string( $f_tag_string ); + $t_tags = tag_parse_string( $f_tag_string, $t_project_id ); $t_can_create = access_has_global_level( config_get( 'tag_create_threshold' ) ); $t_tags_create = array(); @@ -117,7 +119,7 @@ // end failed to attach tag } else { foreach( $t_tags_create as $t_tag_row ) { - $t_tag_row['id'] = tag_create( $t_tag_row['name'], $t_user_id ); + $t_tag_row['id'] = tag_create( $t_tag_row['name'], $t_user_id, '', $t_project_id ); $t_tags_attach[] = $t_tag_row; } diff --git a/tag_create.php b/tag_create.php index fa9662f..83c62fe 100644 --- a/tag_create.php +++ b/tag_create.php @@ -32,6 +32,13 @@ form_security_validate( 'tag_create' ); $f_tag_name = gpc_get_string( 'name' ); $f_tag_description = gpc_get_string( 'description' ); +if ( config_get( 'tag_project_specific_enabled' ) ) { + $f_project_id = gpc_get_int( 'project_id' ); +} +else { + $f_project_id = 0; +} + $t_tag_user = auth_get_current_user_id(); @@ -39,7 +46,7 @@ if ( !is_null( $f_tag_name )) { $t_tags = tag_parse_string( $f_tag_name ); foreach ( $t_tags as $t_tag_row ) { if ( -1 == $t_tag_row['id'] ) { - tag_create( $t_tag_row['name'], $t_tag_user, $f_tag_description ); + tag_create( $t_tag_row['name'], $t_tag_user, $f_tag_description, $f_project_id ); } } } diff --git a/tag_update.php b/tag_update.php index 1252f89..310b40c 100644 --- a/tag_update.php +++ b/tag_update.php @@ -35,6 +35,12 @@ $f_tag_id = gpc_get_int( 'tag_id' ); $t_tag_row = tag_get( $f_tag_id ); + if ( config_get( 'tag_project_specific_enabled' ) ) { + $f_project_id = gpc_get_int( 'project_id' ); + } + else { + $f_project_id = -1; + } if ( !( access_has_global_level( config_get( 'tag_edit_threshold' ) ) || ( auth_get_current_user_id() == $t_tag_row['user_id'] ) @@ -65,7 +71,7 @@ $t_update = true; } - tag_update( $f_tag_id, $f_new_name, $f_new_user_id, $f_new_description ); + tag_update( $f_tag_id, $f_new_name, $f_new_user_id, $f_new_description, $f_project_id ); form_security_purge( 'tag_update' ); diff --git a/tag_update_page.php b/tag_update_page.php index 69de115..323aac6 100644 --- a/tag_update_page.php +++ b/tag_update_page.php @@ -49,6 +49,8 @@ } html_page_top( sprintf( lang_get( 'tag_update' ), $t_name ) ); + + print_manage_menu( '' ); ?> <br /> @@ -69,16 +71,26 @@ <!-- Info --> <tr class="row-category"> - <td width="15%"><?php echo lang_get( 'tag_id' ) ?></td> + <td width="5%"><?php echo lang_get( 'tag_id' ) ?></td> <td width="25%"><?php echo lang_get( 'tag_name' ) ?></td> - <td width="20%"><?php echo lang_get( 'tag_creator' ) ?></td> - <td width="20%"><?php echo lang_get( 'tag_created' ) ?></td> - <td width="20%"><?php echo lang_get( 'tag_updated' ) ?></td> + <td width="25%"><?php echo lang_get( 'project_name' ) ?></td> + <td width="15%"><?php echo lang_get( 'tag_creator' ) ?></td> + <td width="15%"><?php echo lang_get( 'tag_created' ) ?></td> + <td width="15%"><?php echo lang_get( 'tag_updated' ) ?></td> </tr> <tr <?php echo helper_alternate_class() ?>> <td><?php echo $t_tag_row['id'] ?></td> <td><input type="text" <?php echo helper_get_tab_index() ?> name="name" value="<?php echo $t_name ?>"/></td> + <td> + <?php if ( config_get( 'tag_project_specific_enabled' ) ) { ?> + <select name="project_id"> + <?php print_project_option_list( $t_tag_row['project_id'], true ) ?> + </select> + <?php } else { + print( project_get_name( $t_tag_row['project_id'] ) ); + } ?> + </td> <td><?php if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { if ( ON == config_get( 'use_javascript' ) ) { @@ -99,13 +111,13 @@ <!-- spacer --> <tr class="spacer"> - <td colspan="5"></td> + <td colspan="6"></td> </tr> <!-- Description --> <tr <?php echo helper_alternate_class() ?>> <td class="category"><?php echo lang_get( 'tag_description' ) ?></td> - <td colspan="4"> + <td colspan="5"> <textarea name="description" <?php echo helper_get_tab_index() ?> cols="80" rows="6"><?php echo string_textarea( $t_description ) ?></textarea> </td> </tr> diff --git a/tag_view_page.php b/tag_view_page.php index 1eb3e7f..7a811a6 100644 --- a/tag_view_page.php +++ b/tag_view_page.php @@ -39,6 +39,8 @@ $t_description = string_display( $t_tag_row['description'] ); html_page_top( sprintf( lang_get( 'tag_details' ), $t_name ) ); + + print_manage_menu( '' ); ?> <br /> @@ -57,16 +59,18 @@ <!-- Info --> <tr class="row-category"> - <td width="15%"><?php echo lang_get( 'tag_id' ) ?></td> - <td width="25%"><?php echo lang_get( 'tag_name' ) ?></td> - <td width="20%"><?php echo lang_get( 'tag_creator' ) ?></td> - <td width="20%"><?php echo lang_get( 'tag_created' ) ?></td> - <td width="20%"><?php echo lang_get( 'tag_updated' ) ?></td> + <td width="10%"><?php echo lang_get( 'tag_id' ) ?></td> + <td width="30%"><?php echo lang_get( 'tag_name' ) ?></td> + <td width="30%"><?php echo lang_get( 'project_name' ) ?></td> + <td width="10%"><?php echo lang_get( 'tag_creator' ) ?></td> + <td width="10%"><?php echo lang_get( 'tag_created' ) ?></td> + <td width="10%"><?php echo lang_get( 'tag_updated' ) ?></td> </tr> <tr <?php echo helper_alternate_class() ?>> <td><?php echo $t_tag_row['id'] ?></td> <td><?php echo $t_name ?></td> + <td><?php echo string_display_line( project_get_name( $t_tag_row['project_id'] ) ) ?></td> <td><?php echo string_display_line( user_get_name($t_tag_row['user_id']) ) ?></td> <td><?php echo date( config_get( 'normal_date_format' ), $t_tag_row['date_created'] ) ?> </td> <td><?php echo date( config_get( 'normal_date_format' ), $t_tag_row['date_updated'] ) ?> </td> @@ -74,13 +78,13 @@ <!-- spacer --> <tr class="spacer"> - <td colspan="5"></td> + <td colspan="6"></td> </tr> <!-- Description --> <tr <?php echo helper_alternate_class() ?>> <td class="category"><?php echo lang_get( 'tag_description' ) ?></td> - <td colspan="4"><?php echo $t_description ?></td> + <td colspan="5"><?php echo $t_description ?></td> </tr> <!-- Statistics --> @@ -98,7 +102,7 @@ echo ( $i > 0 ? '<tr '.helper_alternate_class().'>' : '' ); echo "<td><a href='tag_view_page.php?tag_id=$t_tag[id]' title='$t_description'>$t_name</a></td>\n"; - echo '<td colspan="3">'; + echo '<td colspan="4">'; print_bracket_link( 'search.php?tag_string='.urlencode("+$t_tag_row[name]".config_get('tag_separator')."+$t_name"), sprintf( lang_get( 'tag_related_issues' ), $t_tag['count'] ) ); echo '</td></tr>'; @@ -109,7 +113,7 @@ <!-- Buttons --> <tr> - <td colspan="5"> + <td colspan="6"> <?php $t_can_edit = access_has_global_level( config_get( 'tag_edit_threshold' ) ); $t_can_edit_own = $t_can_edit || auth_get_current_user_id() == tag_get_field( $f_tag_id, 'user_id' ) -- 1.7.4.msysgit.0 | ||||
Is it true? It's not in the standard?! |
|
No, tags are not separated by project because that would create duplication, and lots of extra complexity and complications, which I don't think is really feasible. |
|
I think tags should be similar to categories -- global tags, per-project tags, copying tags from one project to another. |
|
I really would like to see this implemented. I don't see why it's necessarily complicated. You could just add a property to tags for a project id or All Projects; and when placing a tag, if it's a new one, have a confirmation screen for "You're adding a new tag - are you sure?" and a checkbox for "Make it global" versus "Apply to the current project only" (I think the confirmation is necessary anyway, to differentiate typos of existing tags from legitimate new ones). Then in the drop-down list of tags only select tags for All Projects + the current project (but still allow anything to by typed). I have several projects on my system which are completely different (different types, e.g. hardware vs software vs support; some are for internal use only, others are accessible to external customers). I keep them completely separated: all projects are private, and all users are globally viewers. Tagging is a very nice feature and I'd like to use it, but without being able to separate tags between projects it's useless to me. By the way, I'm planning on allowing only project managers to create new tags, but reporters & above will be able to use existing tags. |
|
Olegos, it's not as simple as you make it out to be. The entire tagging system for Mantis works under the assumption that tags are global, including the API, all views and forms, and the database (the database schema being the least of my worries). The problems and complications arise when you try to move this system into a project-specific manner. Not only would most of the API, pages, and forms need to be reworked (some completely rewritten), but you also introduce many added layers of complexity:
It's not as easy as it seems to you, and I don't see why tags are automatically "useless" just because they're global. Is it really a show-stopper just because a software guy might see a hardware-related tag? If you really need per-project organization, categories are always available and already include the proper API and interfaces elements for handling them per-project or globally. At this point in development, tags are global, and no offense, but that's most likely how it will stay. Categories have been overhauled in 1.2 to offer the option of project inheritence and global categorization, and it basically required a complete rewrite to get those features; I'd have to believe that (as the person who wrote the tagging system), that it would be the same thing for tags, and quite frankly, it's not worth it to me and most people who use them. I hate to be the bearer of bad news, but what you ask will require a lot of effort to implement; if you really want the features, then I urge you to implement it yourself and submit a patch (or series of patches) for us to review and consider. |
|
@jreese: That's a bummer. Yes, it's a show-stopper for us to for example have customers see developers' tags on another project (and in fact I was going to propose that each tag should have access level associated with it, so that some tags could be used only by developers, and be invisible to reporters -- but that's a separate feature). We don't even want reporters on one project knowing what technologies are being used on other projects. Categories are good, but a big advantage of tags is that more than one can be applied. But as far as project/subproject/global behavior, I think categories have similar problems to tags and do reasonable things, and tags should be modeled after them. Specifically, about your questions:
I think you're wrong about most people not caring about tags being always global. By the way, I would be happy with something simple like tags always being per-project, no inheritance, but allowing them to be copied in bulk between projects (hey, this sounds a lot like categories before the overhaul). Maybe with a config option for "tags are global", for those who prefer the current behavior. |
|
Slightly off-ball idea... what about a Custom Field that is of a Tags type? So for this custom field you could enter in a series of tags, delete them, etc. And Custom Fields already have project-level control embedded in Mantis, so you can expose this field on whichever projects you choose. |
|
Olegos: |
|
I agree with olegos. It will be a nice nice thing to have project separated tags. |
|
Attached a possible solution: To enable this feature run a Mantis update (new column "project_id" in tag table is required) If project specific tags are enabled the following happens: a) All existing tags will be considered as "global" (tags are saved with project_id = 0). b) Newly created tags are related to the current project (project of the bug precisely). c) No special handling of "parent projects" - a parent project is equal to a child project. d) The tag - project relation may be edited in the tag management section. The tag overview e) The filter shows only "global" tags and tags for the current project. Therefore filtering f) When switching $g_tag_project_specific_enabled between ON and OFF the existing tags will keep Other improvements:
Feedback appreciated - Would be nice if this "feature" can be integrated into the official version... |
|
dominik, thank you for the suggested patch. Would you consider submitting pull requests for this functionality? This would increase the speed of including these changes in MantisBT. Before going on you should write to the developer mailing list to get some more feedback of other developers. |
|
Hi, |
|
@ilsaul I commented your PR https://github.com/mantisbt/mantisbt/pull/814 You should also consider what I wrote to dominik at 0009716:0029765
|
|
I really would like to see this implemented. It would be helpful to have Tags based on the project and not global. Is there a new update for this ticket? |
|
Hi, We would like to work on this feature as well. Are database schema changes authorized in the current Mantis version ? Thanks in advance |
|
Database schema changes are not allowed in version 2.x. |
|
Hi, In case there are no updates on the subject, here is another idea :
Would it be feasible ? |
|
Everything is feasible, the question is the effort required to achieve the goal ;-) I am not opposed to the idea, but moving tags management to a plugin would would not be a small undertaking, first to decouple the code from Mantis core, then creating the necessary events, write the plugin and handle the data migration... |
|