View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0008274 | mantisbt | tagging | public | 2007-08-17 17:57 | 2019-09-09 03:53 |
Reporter | jreese | Assigned To | jreese | ||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | closed | Resolution | fixed | ||
Product Version | 1.1.0a4 | ||||
Target Version | 1.1.0rc1 | Fixed in Version | 1.1.0rc1 | ||
Summary | 0008274: Implement Keyword Tagging Features | ||||
Description | From the requirements page: "One of the biggest movements in the modern web communities is the push towards ‘tagging’ articles and topics with short descriptive keywords that represent the content and can be used to find items with the same subject. Mantis currently can only classify issues by project and category, which can be limiting, and only allows one classification per issue. By implementing tagging, it would allow users to attach multiple keywords to each report, which could be new tags typed in manually or existing tags selected from a list, either through AJAX auto-completion, or a popup window of some fashion. "Because of the ‘metadata’ nature of tagging, it would not be primary method of classification for issues (which would still be left to categories), and could allow for lower permission requirements for tagging reports, such as allowing any registered reporters to add tags at any time. It could also allow for potential custom auto-tagging features, where certain tags could be automatically attached to any bug containing special characteristics, like a report with version control checkins." | ||||
Additional Information | Requirements page: http://www.mantisbt.org/wiki/doku.php/mantisbt:tagging_requirements | ||||
Tags | No tags attached. | ||||
Attached Files | mantis-tagging-2007-08-17.patch (52,788 bytes)
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/admin/schema.php mantis-tagging/admin/schema.php --- mantis-cvs/admin/schema.php 2007-08-07 10:54:56.000000000 -0400 +++ mantis-tagging/admin/schema.php 2007-08-17 15:19:22.000000000 -0400 @@ -330,6 +330,21 @@ $upgrade[] = Array('CreateIndexSQL',Array('idx_diskfile',config_get('mantis_bug_file_table'),'diskfile')); $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_user_print_pref_table' ), "print_pref C(64) NOTNULL" ) ); $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_bug_history_table' ), "field_name C(64) NOTNULL" ) ); +$upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_tag_table' ), " + id I UNSIGNED NOTNULL PRIMARY AUTOINCREMENT, + user_id I UNSIGNED NOTNULL DEFAULT '0', + name C(100) NOTNULL DEFAULT \" '' \", + description XL NOTNULL, + date_created T NOTNULL DEFAULT '1970-01-01 00:00:01', + date_updated T NOTNULL DEFAULT '1970-01-01 00:00:01' + ", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) ); +$upgrade[] = Array('CreateIndexSQL', Array( 'idx_tag_name', config_get( 'mantis_tag_table' ), 'name', Array( 'UNIQUE' ) ) ); +$upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_bug_tag_table' ), " + bug_id I UNSIGNED NOTNULL PRIMARY DEFAULT '0', + tag_id I UNSIGNED NOTNULL PRIMARY DEFAULT '0', + user_id I UNSIGNED NOTNULL DEFAULT '0', + date_attached T NOTNULL DEFAULT '1970-01-01 00:00:01' + ", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) ); # Release marker: 1.1.0a4 diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/bug_view_advanced_page.php mantis-tagging/bug_view_advanced_page.php --- mantis-cvs/bug_view_advanced_page.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/bug_view_advanced_page.php 2007-08-17 11:29:28.000000000 -0400 @@ -20,6 +20,7 @@ require_once( $t_core_path.'date_api.php' ); require_once( $t_core_path.'relationship_api.php' ); require_once( $t_core_path.'last_visited_api.php' ); + require_once( $t_core_path.'tag_api.php' ); $f_bug_id = gpc_get_int( 'bug_id' ); $f_history = gpc_get_bool( 'history', config_get( 'history_default_visible' ) ); @@ -464,13 +465,35 @@ </td> </tr> +<!-- Tagging --> +<?php if ( access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tags' ) ?></td> + <td colspan="5"> +<?php + tag_display_attached( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag_view access ?> + +<?php if ( access_has_global_level( config_get( 'tag_attach_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td colspan="5"> +<?php + print_tag_attach_form( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag attach access ?> + <!-- spacer --> <tr class="spacer"> <td colspan="6"></td> </tr> - <!-- Custom Fields --> <?php $t_custom_fields_found = false; diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/bug_view_page.php mantis-tagging/bug_view_page.php --- mantis-cvs/bug_view_page.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/bug_view_page.php 2007-08-17 15:36:02.000000000 -0400 @@ -22,6 +22,7 @@ require_once( $t_core_path.'date_api.php' ); require_once( $t_core_path.'relationship_api.php' ); require_once( $t_core_path.'last_visited_api.php' ); + require_once( $t_core_path.'tag_api.php' ); ?> <?php $f_bug_id = gpc_get_int( 'bug_id' ); @@ -341,6 +342,29 @@ </td> </tr> +<!-- Tagging --> +<?php if ( access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tags' ) ?></td> + <td colspan="5"> +<?php + tag_display_attached( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag_view access ?> + +<?php if ( access_has_global_level( config_get( 'tag_attach_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td colspan="5"> +<?php + print_tag_attach_form( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag attach access ?> + <!-- spacer --> <tr class="spacer"> diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/config_defaults_inc.php mantis-tagging/config_defaults_inc.php --- mantis-cvs/config_defaults_inc.php 2007-08-16 13:23:56.000000000 -0400 +++ mantis-tagging/config_defaults_inc.php 2007-08-16 13:48:44.000000000 -0400 @@ -1331,6 +1331,7 @@ $g_mantis_bug_monitor_table = '%db_table_prefix%_bug_monitor%db_table_suffix%'; $g_mantis_bug_relationship_table = '%db_table_prefix%_bug_relationship%db_table_suffix%'; $g_mantis_bug_table = '%db_table_prefix%_bug%db_table_suffix%'; + $g_mantis_bug_tag_table = '%db_table_prefix%_bug_tag%db_table_suffix%'; $g_mantis_bug_text_table = '%db_table_prefix%_bug_text%db_table_suffix%'; $g_mantis_bugnote_table = '%db_table_prefix%_bugnote%db_table_suffix%'; $g_mantis_bugnote_text_table = '%db_table_prefix%_bugnote_text%db_table_suffix%'; @@ -1340,6 +1341,7 @@ $g_mantis_project_table = '%db_table_prefix%_project%db_table_suffix%'; $g_mantis_project_user_list_table = '%db_table_prefix%_project_user_list%db_table_suffix%'; $g_mantis_project_version_table = '%db_table_prefix%_project_version%db_table_suffix%'; + $g_mantis_tag_table = '%db_table_prefix%_tag%db_table_suffix%'; $g_mantis_user_table = '%db_table_prefix%_user%db_table_suffix%'; $g_mantis_user_profile_table = '%db_table_prefix%_user_profile%db_table_suffix%'; $g_mantis_user_pref_table = '%db_table_prefix%_user_pref%db_table_suffix%'; @@ -1835,6 +1837,34 @@ $g_recently_visited_count = 5; ##################### + # Bug Tagging + ##################### + + # String that will separate tags as entered for input + $g_tag_separator = ','; + + # Access level required to view tags attached to a bug + $g_tag_view_threshold = VIEWER; + + # Access level required to attach tags to a bug + $g_tag_attach_threshold = REPORTER; + + # Access level required to detach tags from a bug + $g_tag_detach_threshold = DEVELOPER; + + # Access level required to detach tags attached by the same user + $g_tag_detach_own_threshold = REPORTER; + + # Access level required to create new tags + $g_tag_create_threshold = REPORTER; + + # Access level required to edit tag names and descriptions + $g_tag_edit_threshold = DEVELOPER; + + # Access level required to edit descriptions by the creating user + $g_tag_edit_own_threshold = REPORTER; + + ##################### # Time tracking ##################### diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/constant_inc.php mantis-tagging/core/constant_inc.php --- mantis-cvs/core/constant_inc.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/constant_inc.php 2007-08-16 13:48:43.000000000 -0400 @@ -153,6 +153,9 @@ define( 'CHECKIN', 22 ); define( 'BUG_REPLACE_RELATIONSHIP', 23 ); define( 'BUG_PAID_SPONSORSHIP', 24 ); + define( 'TAG_ATTACHED', 25 ); + define( 'TAG_DETACHED', 26 ); + define( 'TAG_RENAMED', 27 ); # bug relationship constants define( 'BUG_DUPLICATE', 0 ); diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/filter_api.php mantis-tagging/core/filter_api.php --- mantis-cvs/core/filter_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/filter_api.php 2007-08-17 14:31:51.000000000 -0400 @@ -56,6 +56,7 @@ define( 'FILTER_PROPERTY_FILTER_BY_DATE', 'do_filter_by_date' ); define( 'FILTER_PROPERTY_RELATIONSHIP_TYPE', 'relationship_type' ); define( 'FILTER_PROPERTY_RELATIONSHIP_BUG', 'relationship_bug' ); + define( 'FILTER_PROPERTY_TAG_STRING', 'tag_string' ); ########################################################################### # Filter Query Parameter Names @@ -96,6 +97,7 @@ define( 'FILTER_SEARCH_FILTER_BY_DATE', 'filter_by_date' ); define( 'FILTER_SEARCH_RELATIONSHIP_TYPE', 'relationship_type' ); define( 'FILTER_SEARCH_RELATIONSHIP_BUG', 'relationship_bug' ); + define( 'FILTER_SEARCH_TAG_STRING', 'tag_string' ); # Checks the supplied value to see if it is an ANY value. # $p_field_value - The value to check. @@ -306,6 +308,10 @@ $t_query[] = filter_encode_field_and_value( FILTER_SEARCH_OS_BUILD, $p_custom_filter[FILTER_PROPERTY_OS_BUILD] ); } + if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ) ) { + $t_query[] = filter_encode_field_and_value( FILTER_SEARCH_TAG_STRING, $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ); + } + if ( isset( $p_custom_filter['custom_fields'] ) ) { foreach( $p_custom_filter['custom_fields'] as $t_custom_field_id => $t_custom_field_values ) { if ( !filter_str_field_is_any( $t_custom_field_values ) ) { @@ -1057,6 +1063,59 @@ array_push( $t_where_clauses, '('. implode( ' OR ', $t_clauses ) .')' ); } + # tags + $c_tag_string = trim( $t_filter['tag_string'] ); + if ( !is_blank( $c_tag_string ) ) { + require_once( $t_core_path . 'tag_api.php' ); + $t_tags = tag_parse_filters( $c_tag_string ); + + if ( !count( $t_tags ) ) { break; } + + $t_tags_all = array(); + $t_tags_any = array(); + $t_tags_none = array(); + + foreach( $t_tags as $t_tag_row ) { + switch ( $t_tag_row['filter'] ) { + case 1: + $t_tags_all[] = $t_tag_row; + break; + case 0: + $t_tags_any[] = $t_tag_row; + break; + case -1: + $t_tags_none[] = $t_tag_row; + break; + } + } + + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + if ( count( $t_tags_all ) ) { + $t_clauses = array(); + foreach ( $t_tags_all as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_table.id IN ( SELECT bug_id FROM $t_bug_tag_table WHERE $t_bug_tag_table.tag_id = $t_tag_row[id] )" ); + } + array_push( $t_where_clauses, '('. implode( ' AND ', $t_clauses ) .')' ); + } + + if ( count( $t_tags_any ) ) { + $t_clauses = array(); + foreach ( $t_tags_any as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_tag_table.tag_id = $t_tag_row[id]" ); + } + array_push( $t_where_clauses, "$t_bug_table.id IN ( SELECT bug_id FROM $t_bug_tag_table WHERE ( ". implode( ' OR ', $t_clauses ) .') )' ); + } + + if ( count( $t_tags_none ) ) { + $t_clauses = array(); + foreach ( $t_tags_none as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_tag_table.tag_id = $t_tag_row[id]" ); + } + array_push( $t_where_clauses, "$t_bug_table.id NOT IN ( SELECT bug_id FROM $t_bug_tag_table WHERE ( ". implode( ' OR ', $t_clauses ) .') )' ); + } + } + # custom field filters if( ON == config_get( 'filter_by_custom_fields' ) ) { # custom field filtering @@ -2291,6 +2350,7 @@ <a href="<?php PRINT $t_filters_url . 'os_build'; ?>" id="os_build_filter"><?php echo lang_get( 'os_version' ) ?>:</a> </td> <td class="small-caption" valign="top" colspan="5"> + <a href="<?php PRINT $t_filters_url . 'tag_string'; ?>" id="tag_string_filter"><?php echo lang_get( 'tags' ) ?>:</a> </td> <?php if ( $t_filter_cols > 8 ) { echo '<td class="small-caption" valign="top" colspan="' . ( $t_filter_cols - 8 ) . '"> </td>'; @@ -2312,7 +2372,12 @@ print_multivalue_field( FILTER_PROPERTY_OS_BUILD, $t_filter[FILTER_PROPERTY_OS_BUILD] ); ?> </td> - <td class="small-caption" colspan="5"> + <td class="small-caption" valign="top" id="tag_string_filter_target" colspan="5"> + <?php PRINT $t_filter['tag_string'] ?> + <input type="hidden" name="tag_string" value="<?php echo $t_filter['tag_string'] ?>"/> + <?php + //print_tag_input(); + ?> </td> </tr> <?php @@ -3025,6 +3090,9 @@ if ( !isset( $p_filter_arr['target_version'] ) ) { $p_filter_arr['target_version'] = META_FILTER_ANY; } + if ( !isset( $p_filter_arr['tag_string'] ) ) { + $p_filter_arr['tag_string'] = gpc_get_string( 'tag_string', '' ); + } $t_custom_fields = custom_field_get_ids(); # @@@ (thraxisp) This should really be the linked ids, but we don't know the project $f_custom_fields_data = array(); @@ -3531,6 +3599,17 @@ } + function print_filter_tag_string() { + global $t_filter; + ?> + <input type="hidden" id="tag_separator" value="<?php echo config_get( 'tag_separator' ) ?>" /> + <input type="text" name="tag_string" id="tag_string" size="40" value="<?php echo $t_filter['tag_string'] ?>" /> + <select <?php echo helper_get_tab_index() ?> name="tag_select" id="tag_select"> + <?php print_tag_option_list( $p_bug_id ); ?> + </select> + <?php + } + function print_filter_custom_field($p_field_id){ global $t_filter, $t_accessible_custom_fields_names, $t_accessible_custom_fields_types, $t_accessible_custom_fields_values, $t_accessible_custom_fields_ids, $t_select_modifier; diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/history_api.php mantis-tagging/core/history_api.php --- mantis-cvs/core/history_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/history_api.php 2007-08-17 15:41:19.000000000 -0400 @@ -165,6 +165,15 @@ } } + // tags + if ( $v_type == TAG_ATTACHED || + $v_type == TAG_DETACHED || + $v_type == TAG_RENAMED ) { + if ( !access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { + continue; + } + } + $raw_history[$j]['date'] = db_unixtimestamp( $v_date_modified ); $raw_history[$j]['userid'] = $v_user_id; @@ -390,6 +399,16 @@ case CHECKIN: $t_note = lang_get( 'checkin' ); break; + case TAG_ATTACHED: + $t_note = lang_get( 'tag_history_attached' ) .': '. $p_old_value; + break; + case TAG_DETACHED: + $t_note = lang_get( 'tag_history_detached' ) .': '. $p_old_value; + break; + case TAG_RENAMED: + $t_note = lang_get( 'tag_history_renamed' ); + $t_change = $p_old_value . ' => ' . $p_new_value; + break; } } diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/html_api.php mantis-tagging/core/html_api.php --- mantis-cvs/core/html_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/html_api.php 2007-08-17 11:56:56.000000000 -0400 @@ -1218,4 +1218,24 @@ echo '</tr></table>'; } + + function html_button_tag_update( $p_tag_id ) { + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == tag_get_field( $p_tag_id, 'user_id' ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + html_button( 'tag_update_page.php', lang_get( 'tag_update_button' ), array( 'tag_id' => $p_tag_id ) ); + } + } + + function html_button_tag_delete( $p_tag_id ) { + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + html_button( 'tag_delete.php', lang_get( 'tag_delete_button' ), array( 'tag_id' => $p_tag_id ) ); + } + } + + function html_buttons_tag_view_page( $p_tag_id ) { + html_button_tag_update( $p_tag_id ); + html_button_tag_delete( $p_tag_id ); + } ?> diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/print_api.php mantis-tagging/core/print_api.php --- mantis-cvs/core/print_api.php 2007-08-14 10:12:41.000000000 -0400 +++ mantis-tagging/core/print_api.php 2007-08-17 11:02:32.000000000 -0400 @@ -11,6 +11,7 @@ $t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR; + require_once( $t_core_dir . 'ajax_api.php' ); require_once( $t_core_dir . 'current_user_api.php' ); require_once( $t_core_dir . 'string_api.php' ); require_once( $t_core_dir . 'prepare_api.php' ); @@ -255,6 +256,54 @@ PRINT "<option value=\"$t_duplicate_id\">".$t_duplicate_id."</option>"; } } + + function print_tag_attach_form( $p_bug_id, $p_string="" ) { + ?> + <small><?php echo sprintf( lang_get( 'tag_separate_by' ), config_get('tag_separator') ) ?></small> + <form method="post" action="tag_attach.php"> + <input type="hidden" name="bug_id" value="<?php echo $p_bug_id ?>" /> + <?php + print_tag_input( $p_bug_id, $p_string ); + ?> + <input type="submit" value="<?php echo lang_get( 'tag_attach' ) ?>" class="button" /> + </form> + <?php + return true; + } + + function print_tag_input( $p_bug_id = 0, $p_string="" ) { + ?> + <input type="hidden" id="tag_separator" value="<?php echo config_get( 'tag_separator' ) ?>" /> + <input type="text" name="tag_string" id="tag_string" size="40" value="<?php echo $p_string ?>" /> + <select <?php echo helper_get_tab_index() ?> name="tag_select" id="tag_select"> + <?php print_tag_option_list( $p_bug_id ); ?> + </select> + <?php + + return true; + } + + function print_tag_option_list( $p_bug_id = 0 ) { + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT id, name FROM $t_tag_table "; + if ( 0 != $p_bug_id ) { + $c_bug_id = db_prepare_int( $p_bug_id ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query .= " WHERE id NOT IN ( + SELECT tag_id FROM $t_bug_tag_table WHERE bug_id='$c_bug_id' ) "; + } + + $query .= " ORDER BY name ASC "; + $result = db_query( $query ); + + echo '<option value="0">',lang_get( 'tag_existing' ),'</option>'; + while ( $row = db_fetch_array( $result ) ) { + echo '<option value="',$row['id'],'" onclick="tag_string_append(\'',$row['name'],'\')">',$row['name'],'</option>'; + } + } + # -------------------- # Get current headlines and id prefix with v_ function print_news_item_option_list() { diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/tag_api.php mantis-tagging/core/tag_api.php --- mantis-cvs/core/tag_api.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/core/tag_api.php 2007-08-17 17:01:13.000000000 -0400 @@ -0,0 +1,474 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: tag_api.php,v 1.3 2007/04/20 08:28:23 vboctor Exp $ + # -------------------------------------------------------- + + $t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR; + + require_once( $t_core_dir . 'bug_api.php' ); + require_once( $t_core_dir . 'history_api.php' ); + + ### Tag API ### + + function tag_exists( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table WHERE id='$c_tag_id'"; + $result = db_query( $query ) ; + + return db_num_rows( $result ) > 0; + } + + function tag_ensure_exists( $p_tag_id ) { + if ( !tag_exists( $p_tag_id ) ) { + error_parameters( $p_tag_id ); + trigger_error( ERROR_TAG_NOT_FOUND, ERROR ); + } + } + + function tag_is_unique( $p_name ) { + $c_name = trim( db_prepare_string( $p_name ) ); + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT id FROM $t_tag_table WHERE name like '$c_name'"; + $result = db_query( $query ) ; + + return db_num_rows( $result ) == 0; + } + + function tag_ensure_unique( $p_name ) { + if ( !tag_is_unique( $p_name ) ) { + trigger_error( ERROR_TAG_DUPLICATE, ERROR ); + } + } + + # Name must start with letter/number and consist of letters, numbers, hyphen, underscore, period, or spaces + function tag_name_is_valid( $p_name, $p_prefix="", &$p_matches=null ) { + $t_pattern = "/^$p_prefix([a-zA-Z0-9][a-zA-Z0-9-_. ]*)$/"; + return preg_match( $t_pattern, $p_name, $p_matches ); + } + + function tag_ensure_name_is_valid( $p_name ) { + if ( !tag_name_is_valid( $p_name ) ) { + trigger_error( ERROR_TAG_NAME_INVALID, ERROR ); + } + } + + function tag_cmp_name( $p_tag1, $p_tag2 ) { + return strcasecmp( $p_tag1['name'], $p_tag2['name'] ); + } + + function tag_parse_string( $p_string ) { + $t_tags = array(); + + $t_strings = explode( config_get( 'tag_separator' ), $p_string ); + foreach( $t_strings as $t_name ) { + $t_name = trim( $t_name ); + if ( "" == trim( $t_name ) ) { continue; } + + $t_tag_row = tag_get_by_name( $t_name ); + if ( $t_tag_row !== false ) { + $t_tags[] = $t_tag_row; + } else { + if ( tag_name_is_valid( $t_name ) ) { + $t_id = -1; + } else { + $t_id = -2; + } + $t_tags[] = array( 'id' => $t_id, 'name' => $t_name ); + } + } + usort( $t_tags, "tag_cmp_name" ); + return $t_tags; + } + + function tag_parse_filters( $p_string ) { + $t_tags = array(); + $t_prefix = "[+-]{0,1}"; + + $t_strings = explode( config_get( 'tag_separator' ), $p_string ); + foreach( $t_strings as $t_name ) { + $t_name = trim( $t_name ); + if ( "" == trim( $t_name ) || !tag_name_is_valid( $t_name, $t_prefix ) ) { continue; } + + $t_matches = array(); + if ( tag_name_is_valid( $t_name, $t_prefix, $t_matches ) ) { + $t_tag_row = tag_get_by_name( $t_matches[1] ); + if ( $t_tag_row !== false ) { + $t_filter = substr( $t_name, 0, 1 ); + + if ( "+" == $t_filter ) { + $t_tag_row['filter'] = 1; + } elseif ( "-" == $t_filter ) { + $t_tag_row['filter'] = -1; + } else { + $t_tag_row['filter'] = 0; + } + + $t_tags[] = $t_tag_row; + } + } + } + usort( $t_tags, "tag_cmp_name" ); + return $t_tags; + } + + # CRUD + + function tag_get( $p_tag_id ) { + tag_ensure_exists( $p_tag_id ); + + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table + WHERE id='$c_tag_id'"; + $result = db_query( $query ); + + if ( 0 == db_num_rows( $result ) ) { + return false; + } + $row = db_fetch_array( $result ); + + return $row; + } + + function tag_get_by_name( $p_name ) { + $c_name = db_prepare_string( $p_name ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table + WHERE name LIKE '$c_name'"; + $result = db_query( $query ); + + if ( 0 == db_num_rows( $result ) ) { + return false; + } + $row = db_fetch_array( $result ); + + return $row; + } + + function tag_get_field( $p_tag_id, $p_field_name ) { + $row = tag_get( $p_tag_id ); + + if ( isset( $row[$p_field_name] ) ) { + return $row[$p_field_name]; + } else { + error_parameters( $p_field_name ); + trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING ); + return ''; + } + } + + function tag_create( $p_name, $p_user_id, $p_description='' ) { + + tag_ensure_name_is_valid( $p_name ); + tag_ensure_unique( $p_name ); + + $c_name = trim( db_prepare_string( $p_name ) ); + $c_description = db_prepare_string( $p_description ); + $c_user_id = db_prepare_int( $p_user_id ); + $c_date_created = db_now(); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "INSERT INTO $t_tag_table + ( user_id, + name, + description, + date_created, + date_updated + ) + VALUES + ( '$c_user_id', + '$c_name', + '$c_description', + ".$c_date_created.", + ".$c_date_created." + )"; + + db_query( $query ); + return db_insert_id( $t_tag_table ); + } + + function tag_update( $p_tag_id, $p_name, $p_user_id, $p_description ) { + tag_ensure_exists( $p_tag_id ); + user_ensure_exists( $p_user_id ); + + tag_ensure_name_is_valid( $p_name ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + + $t_rename = false; + if ( strtolower($p_name) != strtolower($t_tag_name) ) { + tag_ensure_unique( $p_name ); + $t_rename = true; + } + + $c_tag_id = trim( db_prepare_int( $p_tag_id ) ); + $c_user_id = db_prepare_string( $p_user_id ); + $c_name = db_prepare_string( $p_name ); + $c_description = db_prepare_string( $p_description ); + $c_date_updated = db_now(); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "UPDATE $t_tag_table + SET user_id='$c_user_id', + name='$c_name', + description='$c_description', + date_updated=".$c_date_updated." + WHERE id='$c_tag_id'"; + db_query( $query ); + + if ( $t_rename ) { + $t_bugs = tag_get_bugs_attached( $p_tag_id ); + + foreach ( $t_bugs as $t_bug_id ) { + history_log_event_special( $t_bug_id, TAG_RENAMED, $t_tag_name, $c_name ); + } + } + + return true; + } + + function tag_delete( $p_tag_id ) { + tag_ensure_exists( $p_tag_id ); + + $t_bugs = tag_get_bugs_attached( $p_tag_id ); + foreach ( $t_bugs as $t_bug_id ) { + tag_bug_detach( $p_tag_id, $t_bug_id ); + } + + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query = "DELETE FROM $t_tag_table + WHERE id='$c_tag_id'"; + db_query( $query ); + + return true; + } + + # Associative + + function tag_bug_is_attached( $p_tag_id, $p_bug_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + $result = db_query( $query ); + return ( db_num_rows( $result ) > 0 ); + } + + function tag_bug_get_row( $p_tag_id, $p_bug_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + $result = db_query( $query ); + + if ( db_num_rows( $result ) == 0 ) { + trigger_error( TAG_NOT_ATTACHED, ERROR ); + } + return db_fetch_array( $result ); + } + + function tag_bug_get_attached( $p_bug_id ) { + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT t.*, b.user_id as user_attached, b.date_attached + FROM $t_tag_table as t + LEFT JOIN $t_bug_tag_table as b + on t.id=b.tag_id + WHERE b.bug_id='$c_bug_id'"; + $result = db_query( $query ); + + $rows = array(); + while ( $row = db_fetch_array( $result ) ) { + $rows[] = $row; + } + + usort( $rows, "tag_cmp_name" ); + return $rows; + } + + function tag_get_bugs_attached( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT bug_id FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id'"; + $result = db_query( $query ); + + $bugs = array(); + while ( $row = db_fetch_array( $result ) ) { + $bugs[] = $row['bug_id']; + } + + return $bugs; + } + + function tag_bug_attach( $p_tag_id, $p_bug_id, $p_user_id ) { + tag_ensure_exists( $p_tag_id ); + bug_ensure_exists( $p_bug_id ); + user_ensure_exists( $p_user_id ); + + if ( tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) { + trigger_error( TAG_ALREADY_ATTACHED, ERROR ); + } + + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + $c_user_id = db_prepare_int( $p_user_id ); + $c_date_attached= db_now(); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "INSERT INTO $t_bug_tag_table + ( tag_id, + bug_id, + user_id, + date_attached + ) + VALUES + ( '$c_tag_id', + '$c_bug_id', + '$c_user_id', + ".$c_date_attached." + )"; + db_query( $query ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + history_log_event_special( $p_bug_id, TAG_ATTACHED, $t_tag_name ); + + return true; + } + + function tag_bug_detach( $p_tag_id, $p_bug_id ) { + tag_ensure_exists( $p_tag_id ); + bug_ensure_exists( $p_bug_id ); + + if ( !tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) { + trigger_error( TAG_NOT_ATTACHED, ERROR ); + } + + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "DELETE FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + db_query( $query ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + history_log_event_special( $p_bug_id, TAG_DETACHED, $t_tag_name ); + + return true; + } + + # Display + + function tag_display_link( $p_tag_row, $p_bug_id=0 ) { + if ( auth_get_current_user_id() == $p_tag_row[user_attached] ) { + $t_detach = config_get( 'tag_detach_own_threshold' ); + } else { + $t_detach = config_get( 'tag_detach_threshold' ); + } + + $t_name = string_display_line( $p_tag_row['name'] ); + $t_description = string_display_line( $p_tag_row['description'] ); + + echo "<a href='tag_view_page.php?tag_id=$p_tag_row[id]' title='$t_description'>$t_name</a>"; + + if ( access_has_global_level($t_detach) ) { + $t_tooltip = sprintf( lang_get( 'tag_detach' ), $t_name ); + echo " [<a href='tag_detach.php?bug_id=$p_bug_id&tag_id=$p_tag_row[id]' title='$t_tooltip'>x</a>]"; + } + + return true; + } + + function tag_display_attached( $p_bug_id ) { + $t_tag_rows = tag_bug_get_attached( $p_bug_id ); + + if ( count( $t_tag_rows ) == 0 ) { + echo lang_get( 'tag_none_attached' ); + } else { + $i = 0; + foreach ( $t_tag_rows as $t_tag ) { + echo ( $i > 0 ? config_get('tag_separator')." " : "" ); + tag_display_link( $t_tag, $p_bug_id ); + $i++; + } + } + + return true; + } + + # Statistics + + function tag_stats_attached( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT bug_id FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id'"; + $result = db_query( $query ); + + return db_num_rows( $result ); + } + + function tag_stats_related( $p_tag_id, $p_limit=5 ) { + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + $c_tag_id = db_prepare_int( $p_tag_id ); + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id != $c_tag_id + AND bug_id IN ( SELECT bug_id FROM $t_bug_tag_table + WHERE tag_id=$c_tag_id ) "; + $result = db_query( $query ); + + $t_tag_counts = array(); + while ( $row = db_fetch_array( $result ) ) { + $t_tag_counts[$row['tag_id']]++; + } + + asort( $t_tag_counts ); + + $t_tags = array(); + $i = 1; + foreach ( $t_tag_counts as $t_tag_id => $t_count ) { + $t_tag_row = tag_get($t_tag_id); + $t_tag_row['count'] = $t_count; + $t_tags[] = $t_tag_row; + $i++; + if ( $i > $p_limit ) { break; } + } + + return $t_tags; + } Binary files mantis-cvs/core/.tag_api.php.swp and mantis-tagging/core/.tag_api.php.swp differ diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/xmlhttprequest_api.php mantis-tagging/core/xmlhttprequest_api.php --- mantis-cvs/core/xmlhttprequest_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/xmlhttprequest_api.php 2007-08-16 13:48:44.000000000 -0400 @@ -31,6 +31,15 @@ echo '</select>'; } + function xmlhttprequest_user_combobox() { + $f_user_id = gpc_get_int( 'user_id' ); + $f_user_access = gpc_get_int( 'access_level' ); + + echo '<select name="user_id">'; + print_user_option_list( $f_user_id, ALL_PROJECTS, $f_user_access ); + echo '</select>'; + } + # --------------- # Echos a serialized list of platforms starting with the prefix specified in the $_POST function xmlhttprequest_platform_get_with_prefix() { diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/javascript/common.js mantis-tagging/javascript/common.js --- mantis-cvs/javascript/common.js 2007-08-14 10:12:43.000000000 -0400 +++ mantis-tagging/javascript/common.js 2007-08-16 14:57:40.000000000 -0400 @@ -163,3 +163,16 @@ setDisplay( idTag, (document.getElementById(idTag).style.display == 'none')?1:0 ); } +/* Tag functionality */ +function tag_string_append( p_string ) { + t_tag_separator = document.getElementById('tag_separator').value; + t_tag_string = document.getElementById('tag_string'); + t_tag_select = document.getElementById('tag_select'); + if ( t_tag_string.value != '' ) { + t_tag_string.value = t_tag_string.value + t_tag_separator + p_string; + } else { + t_tag_string.value = t_tag_string.value + p_string; + } + t_tag_select.selectedIndex=0; +} + diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/lang/strings_english.txt mantis-tagging/lang/strings_english.txt --- mantis-cvs/lang/strings_english.txt 2007-08-16 13:24:00.000000000 -0400 +++ mantis-tagging/lang/strings_english.txt 2007-08-17 16:59:16.000000000 -0400 @@ -275,6 +275,11 @@ $MANTIS_ERROR[ERROR_USER_CHANGE_LAST_ADMIN] = 'You cannot change the access level of the only ADMINISTRATOR in the system.'; $MANTIS_ERROR[ERROR_PAGE_REDIRECTION] = 'Page redirection error, ensure that there are no spaces outside the PHP block (<?php ?>) in config_inc.php or custom_*.php files.'; $MANTIS_ERROR[ERROR_TWITTER_NO_CURL_EXT] = 'Twitter integration requires PHP CURL extension which is not installed.'; +$MANTIS_ERROR[ERROR_TAG_NOT_FOUND] = 'Could not find a tag with that name.'; +$MANTIS_ERROR[ERROR_TAG_DUPLICATE] = 'A tag already exists with that name.'; +$MANTIS_ERROR[ERROR_TAG_NAME_INVALID] = 'That tag name is invalid.'; +$MANTIS_ERROR[ERROR_TAG_NOT_ATTACHED] = 'That tag is not attached to that bug.'; +$MANTIS_ERROR[ERROR_TAG_ALREADY_ATTACHED] = 'That tag already attached to that bug.'; $s_login_error = 'Your account may be disabled or blocked or the username/password you entered is incorrect.'; $s_login_cookies_disabled = 'Your browser either doesn\'t know how to handle cookies, or refuses to handle them.'; @@ -1347,6 +1352,38 @@ # wiki related strings $s_wiki = 'Wiki'; +# Tagging +$s_tags = 'Tags'; +$s_tag_details = 'Tag Details: %s'; +$s_tag_id = 'Tag ID'; +$s_tag_name = 'Name'; +$s_tag_creator = 'Creator'; +$s_tag_created = 'Date Created'; +$s_tag_updated = 'Last Updated'; +$s_tag_description = 'Tag Description'; +$s_tag_statistics = 'Usage Statistics'; +$s_tag_update = 'Update Tag: %s'; +$s_tag_update_return = 'Back to Tag'; +$s_tag_update_button = 'Update Tag'; +$s_tag_delete_button = 'Delete Tag'; +$s_tag_delete_message = 'Are you sure you wish to delete this tag?'; +$s_tag_existing = 'Existing tags'; +$s_tag_none_attached = 'No tags attached.'; +$s_tag_attach = 'Attach'; +$s_tag_attach_long = 'Attach Tags'; +$s_tag_attach_failed = 'Tag attachment failed.'; +$s_tag_detach = 'Detach %s'; +$s_tag_separate_by = "(Separate by '%s')"; +$s_tag_invalid_name = 'Invalid tag name.'; +$s_tag_create_denied = 'Create permission denied.'; +$s_tag_filter_default = 'Attached Issues (%s)'; +$s_tag_history_attached = 'Tag Attached'; +$s_tag_history_detached = 'Tag Detached'; +$s_tag_history_renamed = 'Tag Renamed'; +$s_tag_related = 'Related Tags'; +$s_tag_related_issues = 'Shared Issues'; +$s_tag_stats_attached = 'Issues attached: %s'; + # Time Tracking $s_time_tracking_billing_link = 'Billing'; $s_time_tracking = 'Time tracking'; diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_attach.php mantis-tagging/tag_attach.php --- mantis-cvs/tag_attach.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_attach.php 2007-08-17 11:30:39.000000000 -0400 @@ -0,0 +1,103 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + $f_bug_id = gpc_get_int( 'bug_id' ); + $t_user_id = auth_get_current_user_id(); + + access_ensure_global_level( config_get( 'tag_attach_threshold' ) ); + + $t_tags = tag_parse_string( gpc_get_string( 'tag_string' ) ); + $t_can_create = access_has_global_level( config_get( 'tag_create_threshold' ) ); + + $t_tags_create = array(); + $t_tags_attach = array(); + $t_tags_failed = array(); + + foreach ( $t_tags as $t_tag_row ) { + if ( -1 == $t_tag_row['id'] ) { + if ( $t_can_create ) { + $t_tags_create[] = $t_tag_row; + } else { + $t_tags_failed[] = $t_tag_row; + } + } elseif ( -2 == $t_tag_row['id'] ) { + $t_tags_failed[] = $t_tag_row; + } else { + $t_tags_attach[] = $t_tag_row; + } + } + + if ( count( $t_tags_failed ) > 0 ) { + html_page_top1( lang_get( 'tag_attach_long' ).' '.bug_format_summary( $f_bug_id, SUMMARY_CAPTION ) ); + html_page_top2(); +?> +<br/> +<table class="width75" align="center"> + <tr class="row-category"> + <td colspan="2"><?php echo lang_get( 'tag_attach_failed' ) ?></td> + </tr> + <tr class="spacer"><td colspan="2"></td></tr> +<?php + $t_tag_string = ""; + foreach( $t_tags_attach as $t_tag_row ) { + if ( "" != $t_tag_string ) { + $t_tag_string .= config_get( 'tag_separator' ); + } + $t_tag_string .= $t_tag_row['name']; + } + + foreach( $t_tags_failed as $t_tag_row ) { + echo '<tr ',helper_alternate_class(),'>'; + if ( -1 == $t_tag_row['id'] ) { + echo '<td class="category">',lang_get( 'tag_invalid_name' ),'</td>'; + } elseif ( -2 == $t_tag_row['id'] ) { + echo '<td class="category">',lang_get( 'tag_create_denied' ),'</td>'; + } + echo '<td>',$t_tag_row['name'],'</td></tr>'; + + if ( "" != $t_tag_string ) { + $t_tag_string .= config_get( 'tag_separator' ); + } + $t_tag_string .= $t_tag_row['name']; + } +?> + <tr class="spacer"><td colspan="2"></td></tr> + <tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td> +<?php + print_tag_input( $f_bug_id, $t_tag_string ); +?> + </td> + </tr> +</table> +<?php + html_page_bottom1(__FILE__); + } else { + foreach( $t_tags_create as $t_tag_row ) { + $t_tag_row['id'] = tag_create( $t_tag_row['name'], $t_user_id ); + $t_tags_attach[] = $t_tag_row; + } + + foreach( $t_tags_attach as $t_tag_row ) { + if ( ! tag_bug_is_attached( $t_tag_row['id'], $f_bug_id ) ) { + tag_bug_attach( $t_tag_row['id'], $f_bug_id, $t_user_id ); + } + } + + print_successful_redirect_to_bug( $f_bug_id ); + } diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_delete.php mantis-tagging/tag_delete.php --- mantis-cvs/tag_delete.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_delete.php 2007-08-17 13:34:13.000000000 -0400 @@ -0,0 +1,27 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + access_ensure_global_level( config_get( 'tag_edit_threshold' ) ); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + helper_ensure_confirmed( lang_get( 'tag_delete_message' ), lang_get( 'tag_delete_button' ) ); + + tag_delete( $f_tag_id ); + + print_successful_redirect( config_get( 'default_home_page' ) ); diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_detach.php mantis-tagging/tag_detach.php --- mantis-cvs/tag_detach.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_detach.php 2007-08-17 11:34:04.000000000 -0400 @@ -0,0 +1,33 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $f_bug_id = gpc_get_int( 'bug_id' ); + + $t_tag_row = tag_get( $f_tag_id ); + $t_tag_bug_row = tag_bug_get_row( $f_tag_id, $f_bug_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_detach_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_bug_row['user_id'] ) + && access_has_global_level( config_get( 'tag_detach_own_threshold' ) ) ) ) + { + access_denied(); + } + + tag_bug_detach( $f_tag_id, $f_bug_id ); + + print_successful_redirect_to_bug( $f_bug_id ); diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_update_page.php mantis-tagging/tag_update_page.php --- mantis-cvs/tag_update_page.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_update_page.php 2007-08-17 13:37:45.000000000 -0400 @@ -0,0 +1,105 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'ajax_api.php' ); + require_once( $t_core_path . 'tag_api.php' ); + + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_row['user_id'] ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + access_denied(); + } + + html_page_top1( sprintf( lang_get( 'tag_update' ), $t_tag_row['name'] ) ); + html_page_top2(); +?> + +<br/> +<form method="post" action="tag_update.php"> +<table class="width100" cellspacing="1"> + +<!-- Title --> +<tr> + <td class="form-title" colspan="2"> + <?php echo sprintf( lang_get( 'tag_update' ), $t_tag_row['name'] ) ?> + <input type="hidden" name="tag_id" value="<?php echo $f_tag_id ?>"/> + </td> + <td class="right" colspan="3"> + <?php print_bracket_link( 'tag_view_page.php?tag_id='.$f_tag_id, lang_get( 'tag_update_return' ) ); ?> + </td> +</tr> + +<!-- 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> +</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_tag_row['name'] ?>"/></td> + <td><?php + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + if ( ON == config_get( 'use_javascript' ) ) { + $t_username = prepare_user_name( $t_tag_row['user_id'] ); + echo ajax_click_to_edit( $t_username, 'user_id', 'entrypoint=user_combobox&user_id=' . $t_tag_row['user_id'] . '&access_level=' . config_get( 'tag_create_threshold' ) ); + } else { + echo '<select ', helper_get_tab_index(), ' name="user_id">'; + print_user_option_list( $t_tag_row['user_id'], ALL_PROJECTS, config_get( 'tag_create_threshold' ) ); + echo '</select>'; + } + } else { + echo user_get_name($t_tag_row['user_id']); + } + ?></td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_created'] ) ) ?> </td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_updated'] ) ) ?> </td> +</tr> + +<!-- spacer --> +<tr class="spacer"> + <td colspan="5"></td> +</tr> + +<!-- Description --> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_description' ) ?></td> + <td colspan="4"> + <textarea name="description" <?php echo helper_get_tab_index() ?> cols="80" rows="6"><?php echo $t_tag_row['description'] ?></textarea> + </td> +</tr> + +<!-- Submit Button --> +<tr> + <td class="center" colspan="6"> + <input <?php echo helper_get_tab_index() ?> type="submit" class="button" value="<?php echo lang_get( 'tag_update_button' ) ?>" /> + </td> +</tr> + +</table> +</form> + +<?php + html_page_bottom1( __FILE__ ); +?> diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_update.php mantis-tagging/tag_update.php --- mantis-cvs/tag_update.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_update.php 2007-08-16 13:48:44.000000000 -0400 @@ -0,0 +1,55 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_row['user_id'] ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + access_denied(); + } + + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + $f_new_user_id = gpc_get_int( 'user_id', $t_tag_row['user_id'] ); + } else { + $f_new_user_id = $t_tag_row['user_id']; + } + + $f_new_name = gpc_get_string( 'name', $t_tag_row['name'] ); + $f_new_description = gpc_get_string( 'description', $t_tag_row['description'] ); + + $t_update = false; + + if ( $t_tag_row['user_id'] != $f_new_user_id ) { + user_ensure_exists( $f_new_user_id ); + $t_update = true; + } + + if ( $t_tag_row['name'] != $f_new_name || + $t_tag_row['description'] != $f_new_description ) { + + $t_update = true; + } + + tag_update( $f_tag_id, $f_new_name, $f_new_user_id, $f_new_description ); + + $t_url = 'tag_view_page.php?tag_id='.$f_tag_id; + print_successful_redirect( $t_url ); diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_view_page.php mantis-tagging/tag_view_page.php --- mantis-cvs/tag_view_page.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_view_page.php 2007-08-17 17:00:45.000000000 -0400 @@ -0,0 +1,107 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + access_ensure_global_level( config_get( 'tag_view_threshold' ) ); + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + $t_name = string_display_line( $t_tag_row['name'] ); + $t_description = string_display( $t_tag_row['description'] ); + + html_page_top1( sprintf( lang_get( 'tag_details' ), $t_tag_row['name'] ) ); + html_page_top2(); +?> + +<br/> +<table class="width100" cellspacing="1"> + +<!-- Title --> +<tr> + <td class="form-title" colspan="2"> + <?php echo sprintf( lang_get( 'tag_details' ), $t_tag_row['name'] ) ?> + + </td> + <td class="right" colspan="3"> + <?php print_bracket_link( 'search.php?hide_status_id=90&tag_string='.urlencode($t_tag_row['name']), sprintf( lang_get( 'tag_filter_default' ), tag_stats_attached( $f_tag_id ) ) ); ?> + </td> +</tr> + +<!-- 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> +</tr> + +<tr <?php echo helper_alternate_class() ?>> + <td><?php echo $t_tag_row['id'] ?></td> + <td><?php echo $t_name ?></td> + <td><?php echo user_get_name($t_tag_row['user_id']) ?></td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_created'] ) ) ?> </td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_updated'] ) ) ?> </td> +</tr> + +<!-- spacer --> +<tr class="spacer"> + <td colspan="5"></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> +</tr> + +<!-- Statistics --> +<?php + $t_tags_related = tag_stats_related( $f_tag_id ); + if ( count( $t_tags_related ) ) { + echo '<tr ',helper_alternate_class(),'>'; + echo '<td class="category" rowspan="',count( $t_tags_related ),'">',lang_get( 'tag_related' ),'</td>'; + + $i = 0; + foreach( $t_tags_related as $t_tag ) { + $t_name = string_display_line( $t_tag['name'] ); + $t_description = string_display_line( $t_tag['description'] ); + $t_count = $t_tag['count']; + + 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">'; + print_bracket_link( "search.php?hide_status_is=90&tag_string=+$t_tag_row[name]".config_get('tag_separator')."+$t_name", lang_get( 'tag_related_issues' ) ); + echo '</a></td></tr>'; + + $i++; + } + } +?> + +<!-- Buttons --> +<tr> + <td colspan="5"> + <?php html_buttons_tag_view_page( $f_tag_id ); ?> + </td> +</tr> + +</table> +<?php + html_page_bottom1( __FILE__ ); +?> Binary files mantis-cvs/.tag_view_page.php.swp and mantis-tagging/.tag_view_page.php.swp differ diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/view_all_set.php mantis-tagging/view_all_set.php --- mantis-cvs/view_all_set.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/view_all_set.php 2007-08-16 16:27:27.000000000 -0400 @@ -203,6 +203,8 @@ $f_do_filter_by_date = gpc_get_bool( 'do_filter_by_date' ); $f_view_state = gpc_get_int( 'view_state', META_FILTER_ANY ); + $f_tag_string = gpc_get_string( 'tag_string', '' ); + $t_custom_fields = custom_field_get_ids(); # @@@ (thraxisp) This should really be the linked ids, but we don't know the project $f_custom_fields_data = array(); if ( is_array( $t_custom_fields ) && ( sizeof( $t_custom_fields ) > 0 ) ) { @@ -414,6 +416,7 @@ $t_setting_arr['platform'] = $f_platform; $t_setting_arr['os'] = $f_os; $t_setting_arr['os_build'] = $f_os_build; + $t_setting_arr['tag_string'] = $f_tag_string; break; # Set the sort order and direction case '2': mantis-tagging-2007-08-20.patch (55,957 bytes)
diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/admin/schema.php mantis-tagging/admin/schema.php --- mantis-cvs/admin/schema.php 2007-08-07 10:54:56.000000000 -0400 +++ mantis-tagging/admin/schema.php 2007-08-17 15:19:22.000000000 -0400 @@ -330,6 +330,21 @@ $upgrade[] = Array('CreateIndexSQL',Array('idx_diskfile',config_get('mantis_bug_file_table'),'diskfile')); $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_user_print_pref_table' ), "print_pref C(64) NOTNULL" ) ); $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_bug_history_table' ), "field_name C(64) NOTNULL" ) ); +$upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_tag_table' ), " + id I UNSIGNED NOTNULL PRIMARY AUTOINCREMENT, + user_id I UNSIGNED NOTNULL DEFAULT '0', + name C(100) NOTNULL DEFAULT \" '' \", + description XL NOTNULL, + date_created T NOTNULL DEFAULT '1970-01-01 00:00:01', + date_updated T NOTNULL DEFAULT '1970-01-01 00:00:01' + ", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) ); +$upgrade[] = Array('CreateIndexSQL', Array( 'idx_tag_name', config_get( 'mantis_tag_table' ), 'name', Array( 'UNIQUE' ) ) ); +$upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_bug_tag_table' ), " + bug_id I UNSIGNED NOTNULL PRIMARY DEFAULT '0', + tag_id I UNSIGNED NOTNULL PRIMARY DEFAULT '0', + user_id I UNSIGNED NOTNULL DEFAULT '0', + date_attached T NOTNULL DEFAULT '1970-01-01 00:00:01' + ", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) ); # Release marker: 1.1.0a4 diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/bug_view_advanced_page.php mantis-tagging/bug_view_advanced_page.php --- mantis-cvs/bug_view_advanced_page.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/bug_view_advanced_page.php 2007-08-17 11:29:28.000000000 -0400 @@ -20,6 +20,7 @@ require_once( $t_core_path.'date_api.php' ); require_once( $t_core_path.'relationship_api.php' ); require_once( $t_core_path.'last_visited_api.php' ); + require_once( $t_core_path.'tag_api.php' ); $f_bug_id = gpc_get_int( 'bug_id' ); $f_history = gpc_get_bool( 'history', config_get( 'history_default_visible' ) ); @@ -464,13 +465,35 @@ </td> </tr> +<!-- Tagging --> +<?php if ( access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tags' ) ?></td> + <td colspan="5"> +<?php + tag_display_attached( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag_view access ?> + +<?php if ( access_has_global_level( config_get( 'tag_attach_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td colspan="5"> +<?php + print_tag_attach_form( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag attach access ?> + <!-- spacer --> <tr class="spacer"> <td colspan="6"></td> </tr> - <!-- Custom Fields --> <?php $t_custom_fields_found = false; diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/bug_view_page.php mantis-tagging/bug_view_page.php --- mantis-cvs/bug_view_page.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/bug_view_page.php 2007-08-17 15:36:02.000000000 -0400 @@ -22,6 +22,7 @@ require_once( $t_core_path.'date_api.php' ); require_once( $t_core_path.'relationship_api.php' ); require_once( $t_core_path.'last_visited_api.php' ); + require_once( $t_core_path.'tag_api.php' ); ?> <?php $f_bug_id = gpc_get_int( 'bug_id' ); @@ -341,6 +342,29 @@ </td> </tr> +<!-- Tagging --> +<?php if ( access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tags' ) ?></td> + <td colspan="5"> +<?php + tag_display_attached( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag_view access ?> + +<?php if ( access_has_global_level( config_get( 'tag_attach_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td colspan="5"> +<?php + print_tag_attach_form( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag attach access ?> + <!-- spacer --> <tr class="spacer"> diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/config_defaults_inc.php mantis-tagging/config_defaults_inc.php --- mantis-cvs/config_defaults_inc.php 2007-08-16 13:23:56.000000000 -0400 +++ mantis-tagging/config_defaults_inc.php 2007-08-16 13:48:44.000000000 -0400 @@ -1331,6 +1331,7 @@ $g_mantis_bug_monitor_table = '%db_table_prefix%_bug_monitor%db_table_suffix%'; $g_mantis_bug_relationship_table = '%db_table_prefix%_bug_relationship%db_table_suffix%'; $g_mantis_bug_table = '%db_table_prefix%_bug%db_table_suffix%'; + $g_mantis_bug_tag_table = '%db_table_prefix%_bug_tag%db_table_suffix%'; $g_mantis_bug_text_table = '%db_table_prefix%_bug_text%db_table_suffix%'; $g_mantis_bugnote_table = '%db_table_prefix%_bugnote%db_table_suffix%'; $g_mantis_bugnote_text_table = '%db_table_prefix%_bugnote_text%db_table_suffix%'; @@ -1340,6 +1341,7 @@ $g_mantis_project_table = '%db_table_prefix%_project%db_table_suffix%'; $g_mantis_project_user_list_table = '%db_table_prefix%_project_user_list%db_table_suffix%'; $g_mantis_project_version_table = '%db_table_prefix%_project_version%db_table_suffix%'; + $g_mantis_tag_table = '%db_table_prefix%_tag%db_table_suffix%'; $g_mantis_user_table = '%db_table_prefix%_user%db_table_suffix%'; $g_mantis_user_profile_table = '%db_table_prefix%_user_profile%db_table_suffix%'; $g_mantis_user_pref_table = '%db_table_prefix%_user_pref%db_table_suffix%'; @@ -1835,6 +1837,34 @@ $g_recently_visited_count = 5; ##################### + # Bug Tagging + ##################### + + # String that will separate tags as entered for input + $g_tag_separator = ','; + + # Access level required to view tags attached to a bug + $g_tag_view_threshold = VIEWER; + + # Access level required to attach tags to a bug + $g_tag_attach_threshold = REPORTER; + + # Access level required to detach tags from a bug + $g_tag_detach_threshold = DEVELOPER; + + # Access level required to detach tags attached by the same user + $g_tag_detach_own_threshold = REPORTER; + + # Access level required to create new tags + $g_tag_create_threshold = REPORTER; + + # Access level required to edit tag names and descriptions + $g_tag_edit_threshold = DEVELOPER; + + # Access level required to edit descriptions by the creating user + $g_tag_edit_own_threshold = REPORTER; + + ##################### # Time tracking ##################### diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/constant_inc.php mantis-tagging/core/constant_inc.php --- mantis-cvs/core/constant_inc.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/constant_inc.php 2007-08-16 13:48:43.000000000 -0400 @@ -153,6 +153,9 @@ define( 'CHECKIN', 22 ); define( 'BUG_REPLACE_RELATIONSHIP', 23 ); define( 'BUG_PAID_SPONSORSHIP', 24 ); + define( 'TAG_ATTACHED', 25 ); + define( 'TAG_DETACHED', 26 ); + define( 'TAG_RENAMED', 27 ); # bug relationship constants define( 'BUG_DUPLICATE', 0 ); diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/filter_api.php mantis-tagging/core/filter_api.php --- mantis-cvs/core/filter_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/filter_api.php 2007-08-17 23:52:18.000000000 -0400 @@ -16,6 +16,7 @@ require_once( $t_core_dir . 'bug_api.php' ); require_once( $t_core_dir . 'collapse_api.php' ); require_once( $t_core_dir . 'relationship_api.php' ); + require_once( $t_core_dir . 'tag_api.php' ); ########################################################################### # Filter Property Names @@ -56,6 +57,8 @@ define( 'FILTER_PROPERTY_FILTER_BY_DATE', 'do_filter_by_date' ); define( 'FILTER_PROPERTY_RELATIONSHIP_TYPE', 'relationship_type' ); define( 'FILTER_PROPERTY_RELATIONSHIP_BUG', 'relationship_bug' ); + define( 'FILTER_PROPERTY_TAG_STRING', 'tag_string' ); + define( 'FILTER_PROPERTY_TAG_SELECT', 'tag_select' ); ########################################################################### # Filter Query Parameter Names @@ -96,6 +99,8 @@ define( 'FILTER_SEARCH_FILTER_BY_DATE', 'filter_by_date' ); define( 'FILTER_SEARCH_RELATIONSHIP_TYPE', 'relationship_type' ); define( 'FILTER_SEARCH_RELATIONSHIP_BUG', 'relationship_bug' ); + define( 'FILTER_SEARCH_TAG_STRING', 'tag_string' ); + define( 'FILTER_SEARCH_TAG_SELECT', 'tag_select' ); # Checks the supplied value to see if it is an ANY value. # $p_field_value - The value to check. @@ -306,6 +311,14 @@ $t_query[] = filter_encode_field_and_value( FILTER_SEARCH_OS_BUILD, $p_custom_filter[FILTER_PROPERTY_OS_BUILD] ); } + if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ) ) { + $t_query[] = filter_encode_field_and_value( FILTER_SEARCH_TAG_STRING, $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ); + } + + if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] ) ) { + $t_query[] = filter_encode_field_and_value( FILTER_SEARCH_TAG_SELECT, $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] ); + } + if ( isset( $p_custom_filter['custom_fields'] ) ) { foreach( $p_custom_filter['custom_fields'] as $t_custom_field_id => $t_custom_field_values ) { if ( !filter_str_field_is_any( $t_custom_field_values ) ) { @@ -1057,6 +1070,63 @@ array_push( $t_where_clauses, '('. implode( ' OR ', $t_clauses ) .')' ); } + # tags + $c_tag_string = trim( $t_filter['tag_string'] ); + if ( !is_blank( $c_tag_string ) ) { + require_once( $t_core_path . 'tag_api.php' ); + $t_tags = tag_parse_filters( $c_tag_string ); + + if ( !count( $t_tags ) ) { break; } + + $t_tags_all = array(); + $t_tags_any = array(); + $t_tags_none = array(); + + foreach( $t_tags as $t_tag_row ) { + switch ( $t_tag_row['filter'] ) { + case 1: + $t_tags_all[] = $t_tag_row; + break; + case 0: + $t_tags_any[] = $t_tag_row; + break; + case -1: + $t_tags_none[] = $t_tag_row; + break; + } + } + + if ( 0 < $t_filter['tag_select'] && tag_exists( $t_filter['tag_select'] ) ) { + $t_tags_any[] = tag_get( $t_filter['tag_select'] ); + } + + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + if ( count( $t_tags_all ) ) { + $t_clauses = array(); + foreach ( $t_tags_all as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_table.id IN ( SELECT bug_id FROM $t_bug_tag_table WHERE $t_bug_tag_table.tag_id = $t_tag_row[id] )" ); + } + array_push( $t_where_clauses, '('. implode( ' AND ', $t_clauses ) .')' ); + } + + if ( count( $t_tags_any ) ) { + $t_clauses = array(); + foreach ( $t_tags_any as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_tag_table.tag_id = $t_tag_row[id]" ); + } + array_push( $t_where_clauses, "$t_bug_table.id IN ( SELECT bug_id FROM $t_bug_tag_table WHERE ( ". implode( ' OR ', $t_clauses ) .') )' ); + } + + if ( count( $t_tags_none ) ) { + $t_clauses = array(); + foreach ( $t_tags_none as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_tag_table.tag_id = $t_tag_row[id]" ); + } + array_push( $t_where_clauses, "$t_bug_table.id NOT IN ( SELECT bug_id FROM $t_bug_tag_table WHERE ( ". implode( ' OR ', $t_clauses ) .') )' ); + } + } + # custom field filters if( ON == config_get( 'filter_by_custom_fields' ) ) { # custom field filtering @@ -2291,6 +2361,7 @@ <a href="<?php PRINT $t_filters_url . 'os_build'; ?>" id="os_build_filter"><?php echo lang_get( 'os_version' ) ?>:</a> </td> <td class="small-caption" valign="top" colspan="5"> + <a href="<?php PRINT $t_filters_url . 'tag_string'; ?>" id="tag_string_filter"><?php echo lang_get( 'tags' ) ?>:</a> </td> <?php if ( $t_filter_cols > 8 ) { echo '<td class="small-caption" valign="top" colspan="' . ( $t_filter_cols - 8 ) . '"> </td>'; @@ -2312,7 +2383,16 @@ print_multivalue_field( FILTER_PROPERTY_OS_BUILD, $t_filter[FILTER_PROPERTY_OS_BUILD] ); ?> </td> - <td class="small-caption" colspan="5"> + <td class="small-caption" valign="top" id="tag_string_filter_target" colspan="5"> + <?php + $t_tag_string = $t_filter['tag_string']; + if ( $t_filter['tag_select'] != 0 ) { + $t_tag_string .= ( is_blank( $t_tag_string ) ? '' : config_get( 'tag_separator' ) ); + $t_tag_string .= tag_get_field( $t_filter['tag_select'], 'name' ); + } + PRINT $t_tag_string + ?> + <input type="hidden" name="tag_string" value="<?php echo $t_tag_string ?>"/> </td> </tr> <?php @@ -3025,6 +3105,9 @@ if ( !isset( $p_filter_arr['target_version'] ) ) { $p_filter_arr['target_version'] = META_FILTER_ANY; } + if ( !isset( $p_filter_arr['tag_string'] ) ) { + $p_filter_arr['tag_string'] = gpc_get_string( 'tag_string', '' ); + } $t_custom_fields = custom_field_get_ids(); # @@@ (thraxisp) This should really be the linked ids, but we don't know the project $f_custom_fields_data = array(); @@ -3531,6 +3614,22 @@ } + function print_filter_tag_string() { + global $t_filter; + $t_tag_string = $t_filter['tag_string']; + if ( $t_filter['tag_select'] != 0 ) { + $t_tag_string .= ( is_blank( $t_tag_string ) ? '' : config_get( 'tag_separator' ) ); + $t_tag_string .= tag_get_field( $t_filter['tag_select'], 'name' ); + } + ?> + <input type="hidden" id="tag_separator" value="<?php echo config_get( 'tag_separator' ) ?>" /> + <input type="text" name="tag_string" id="tag_string" size="40" value="<?php echo $t_tag_string ?>" /> + <select <?php echo helper_get_tab_index() ?> name="tag_select" id="tag_select"> + <?php print_tag_option_list( $p_bug_id ); ?> + </select> + <?php + } + function print_filter_custom_field($p_field_id){ global $t_filter, $t_accessible_custom_fields_names, $t_accessible_custom_fields_types, $t_accessible_custom_fields_values, $t_accessible_custom_fields_ids, $t_select_modifier; diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/history_api.php mantis-tagging/core/history_api.php --- mantis-cvs/core/history_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/history_api.php 2007-08-17 15:41:19.000000000 -0400 @@ -165,6 +165,15 @@ } } + // tags + if ( $v_type == TAG_ATTACHED || + $v_type == TAG_DETACHED || + $v_type == TAG_RENAMED ) { + if ( !access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { + continue; + } + } + $raw_history[$j]['date'] = db_unixtimestamp( $v_date_modified ); $raw_history[$j]['userid'] = $v_user_id; @@ -390,6 +399,16 @@ case CHECKIN: $t_note = lang_get( 'checkin' ); break; + case TAG_ATTACHED: + $t_note = lang_get( 'tag_history_attached' ) .': '. $p_old_value; + break; + case TAG_DETACHED: + $t_note = lang_get( 'tag_history_detached' ) .': '. $p_old_value; + break; + case TAG_RENAMED: + $t_note = lang_get( 'tag_history_renamed' ); + $t_change = $p_old_value . ' => ' . $p_new_value; + break; } } diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/html_api.php mantis-tagging/core/html_api.php --- mantis-cvs/core/html_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/html_api.php 2007-08-17 11:56:56.000000000 -0400 @@ -1218,4 +1218,24 @@ echo '</tr></table>'; } + + function html_button_tag_update( $p_tag_id ) { + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == tag_get_field( $p_tag_id, 'user_id' ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + html_button( 'tag_update_page.php', lang_get( 'tag_update_button' ), array( 'tag_id' => $p_tag_id ) ); + } + } + + function html_button_tag_delete( $p_tag_id ) { + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + html_button( 'tag_delete.php', lang_get( 'tag_delete_button' ), array( 'tag_id' => $p_tag_id ) ); + } + } + + function html_buttons_tag_view_page( $p_tag_id ) { + html_button_tag_update( $p_tag_id ); + html_button_tag_delete( $p_tag_id ); + } ?> diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/print_api.php mantis-tagging/core/print_api.php --- mantis-cvs/core/print_api.php 2007-08-14 10:12:41.000000000 -0400 +++ mantis-tagging/core/print_api.php 2007-08-17 11:02:32.000000000 -0400 @@ -11,6 +11,7 @@ $t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR; + require_once( $t_core_dir . 'ajax_api.php' ); require_once( $t_core_dir . 'current_user_api.php' ); require_once( $t_core_dir . 'string_api.php' ); require_once( $t_core_dir . 'prepare_api.php' ); @@ -255,6 +256,54 @@ PRINT "<option value=\"$t_duplicate_id\">".$t_duplicate_id."</option>"; } } + + function print_tag_attach_form( $p_bug_id, $p_string="" ) { + ?> + <small><?php echo sprintf( lang_get( 'tag_separate_by' ), config_get('tag_separator') ) ?></small> + <form method="post" action="tag_attach.php"> + <input type="hidden" name="bug_id" value="<?php echo $p_bug_id ?>" /> + <?php + print_tag_input( $p_bug_id, $p_string ); + ?> + <input type="submit" value="<?php echo lang_get( 'tag_attach' ) ?>" class="button" /> + </form> + <?php + return true; + } + + function print_tag_input( $p_bug_id = 0, $p_string="" ) { + ?> + <input type="hidden" id="tag_separator" value="<?php echo config_get( 'tag_separator' ) ?>" /> + <input type="text" name="tag_string" id="tag_string" size="40" value="<?php echo $p_string ?>" /> + <select <?php echo helper_get_tab_index() ?> name="tag_select" id="tag_select"> + <?php print_tag_option_list( $p_bug_id ); ?> + </select> + <?php + + return true; + } + + function print_tag_option_list( $p_bug_id = 0 ) { + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT id, name FROM $t_tag_table "; + if ( 0 != $p_bug_id ) { + $c_bug_id = db_prepare_int( $p_bug_id ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query .= " WHERE id NOT IN ( + SELECT tag_id FROM $t_bug_tag_table WHERE bug_id='$c_bug_id' ) "; + } + + $query .= " ORDER BY name ASC "; + $result = db_query( $query ); + + echo '<option value="0">',lang_get( 'tag_existing' ),'</option>'; + while ( $row = db_fetch_array( $result ) ) { + echo '<option value="',$row['id'],'" onclick="tag_string_append(\'',$row['name'],'\')">',$row['name'],'</option>'; + } + } + # -------------------- # Get current headlines and id prefix with v_ function print_news_item_option_list() { diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/tag_api.php mantis-tagging/core/tag_api.php --- mantis-cvs/core/tag_api.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/core/tag_api.php 2007-08-20 09:34:48.000000000 -0400 @@ -0,0 +1,474 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: tag_api.php,v 1.3 2007/04/20 08:28:23 vboctor Exp $ + # -------------------------------------------------------- + + $t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR; + + require_once( $t_core_dir . 'bug_api.php' ); + require_once( $t_core_dir . 'history_api.php' ); + + ### Tag API ### + + function tag_exists( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table WHERE id='$c_tag_id'"; + $result = db_query( $query ) ; + + return db_num_rows( $result ) > 0; + } + + function tag_ensure_exists( $p_tag_id ) { + if ( !tag_exists( $p_tag_id ) ) { + error_parameters( $p_tag_id ); + trigger_error( ERROR_TAG_NOT_FOUND, ERROR ); + } + } + + function tag_is_unique( $p_name ) { + $c_name = trim( db_prepare_string( $p_name ) ); + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT id FROM $t_tag_table WHERE name like '$c_name'"; + $result = db_query( $query ) ; + + return db_num_rows( $result ) == 0; + } + + function tag_ensure_unique( $p_name ) { + if ( !tag_is_unique( $p_name ) ) { + trigger_error( ERROR_TAG_DUPLICATE, ERROR ); + } + } + + # Name must start with letter/number and consist of letters, numbers, hyphen, underscore, period, or spaces + function tag_name_is_valid( $p_name, $p_prefix="", &$p_matches=null ) { + $t_pattern = "/^$p_prefix([a-zA-Z0-9][a-zA-Z0-9-_. ]*)$/"; + return preg_match( $t_pattern, $p_name, $p_matches ); + } + + function tag_ensure_name_is_valid( $p_name ) { + if ( !tag_name_is_valid( $p_name ) ) { + trigger_error( ERROR_TAG_NAME_INVALID, ERROR ); + } + } + + function tag_cmp_name( $p_tag1, $p_tag2 ) { + return strcasecmp( $p_tag1['name'], $p_tag2['name'] ); + } + + function tag_parse_string( $p_string ) { + $t_tags = array(); + + $t_strings = explode( config_get( 'tag_separator' ), $p_string ); + foreach( $t_strings as $t_name ) { + $t_name = trim( $t_name ); + if ( "" == trim( $t_name ) ) { continue; } + + $t_tag_row = tag_get_by_name( $t_name ); + if ( $t_tag_row !== false ) { + $t_tags[] = $t_tag_row; + } else { + if ( tag_name_is_valid( $t_name ) ) { + $t_id = -1; + } else { + $t_id = -2; + } + $t_tags[] = array( 'id' => $t_id, 'name' => $t_name ); + } + } + usort( $t_tags, "tag_cmp_name" ); + return $t_tags; + } + + function tag_parse_filters( $p_string ) { + $t_tags = array(); + $t_prefix = "[+-]{0,1}"; + + $t_strings = explode( config_get( 'tag_separator' ), $p_string ); + foreach( $t_strings as $t_name ) { + $t_name = trim( $t_name ); + if ( "" == trim( $t_name ) || !tag_name_is_valid( $t_name, $t_prefix ) ) { continue; } + + $t_matches = array(); + if ( tag_name_is_valid( $t_name, $t_prefix, $t_matches ) ) { + $t_tag_row = tag_get_by_name( $t_matches[1] ); + if ( $t_tag_row !== false ) { + $t_filter = substr( $t_name, 0, 1 ); + + if ( "+" == $t_filter ) { + $t_tag_row['filter'] = 1; + } elseif ( "-" == $t_filter ) { + $t_tag_row['filter'] = -1; + } else { + $t_tag_row['filter'] = 0; + } + + $t_tags[] = $t_tag_row; + } + } + } + usort( $t_tags, "tag_cmp_name" ); + return $t_tags; + } + + # CRUD + + function tag_get( $p_tag_id ) { + tag_ensure_exists( $p_tag_id ); + + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table + WHERE id='$c_tag_id'"; + $result = db_query( $query ); + + if ( 0 == db_num_rows( $result ) ) { + return false; + } + $row = db_fetch_array( $result ); + + return $row; + } + + function tag_get_by_name( $p_name ) { + $c_name = db_prepare_string( $p_name ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table + WHERE name LIKE '$c_name'"; + $result = db_query( $query ); + + if ( 0 == db_num_rows( $result ) ) { + return false; + } + $row = db_fetch_array( $result ); + + return $row; + } + + function tag_get_field( $p_tag_id, $p_field_name ) { + $row = tag_get( $p_tag_id ); + + if ( isset( $row[$p_field_name] ) ) { + return $row[$p_field_name]; + } else { + error_parameters( $p_field_name ); + trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING ); + return ''; + } + } + + function tag_create( $p_name, $p_user_id, $p_description='' ) { + + tag_ensure_name_is_valid( $p_name ); + tag_ensure_unique( $p_name ); + + $c_name = trim( db_prepare_string( $p_name ) ); + $c_description = db_prepare_string( $p_description ); + $c_user_id = db_prepare_int( $p_user_id ); + $c_date_created = db_now(); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "INSERT INTO $t_tag_table + ( user_id, + name, + description, + date_created, + date_updated + ) + VALUES + ( '$c_user_id', + '$c_name', + '$c_description', + ".$c_date_created.", + ".$c_date_created." + )"; + + db_query( $query ); + return db_insert_id( $t_tag_table ); + } + + function tag_update( $p_tag_id, $p_name, $p_user_id, $p_description ) { + tag_ensure_exists( $p_tag_id ); + user_ensure_exists( $p_user_id ); + + tag_ensure_name_is_valid( $p_name ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + + $t_rename = false; + if ( strtolower($p_name) != strtolower($t_tag_name) ) { + tag_ensure_unique( $p_name ); + $t_rename = true; + } + + $c_tag_id = trim( db_prepare_int( $p_tag_id ) ); + $c_user_id = db_prepare_string( $p_user_id ); + $c_name = db_prepare_string( $p_name ); + $c_description = db_prepare_string( $p_description ); + $c_date_updated = db_now(); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "UPDATE $t_tag_table + SET user_id='$c_user_id', + name='$c_name', + description='$c_description', + date_updated=".$c_date_updated." + WHERE id='$c_tag_id'"; + db_query( $query ); + + if ( $t_rename ) { + $t_bugs = tag_get_bugs_attached( $p_tag_id ); + + foreach ( $t_bugs as $t_bug_id ) { + history_log_event_special( $t_bug_id, TAG_RENAMED, $t_tag_name, $c_name ); + } + } + + return true; + } + + function tag_delete( $p_tag_id ) { + tag_ensure_exists( $p_tag_id ); + + $t_bugs = tag_get_bugs_attached( $p_tag_id ); + foreach ( $t_bugs as $t_bug_id ) { + tag_bug_detach( $p_tag_id, $t_bug_id ); + } + + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query = "DELETE FROM $t_tag_table + WHERE id='$c_tag_id'"; + db_query( $query ); + + return true; + } + + # Associative + + function tag_bug_is_attached( $p_tag_id, $p_bug_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + $result = db_query( $query ); + return ( db_num_rows( $result ) > 0 ); + } + + function tag_bug_get_row( $p_tag_id, $p_bug_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + $result = db_query( $query ); + + if ( db_num_rows( $result ) == 0 ) { + trigger_error( TAG_NOT_ATTACHED, ERROR ); + } + return db_fetch_array( $result ); + } + + function tag_bug_get_attached( $p_bug_id ) { + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT t.*, b.user_id as user_attached, b.date_attached + FROM $t_tag_table as t + LEFT JOIN $t_bug_tag_table as b + on t.id=b.tag_id + WHERE b.bug_id='$c_bug_id'"; + $result = db_query( $query ); + + $rows = array(); + while ( $row = db_fetch_array( $result ) ) { + $rows[] = $row; + } + + usort( $rows, "tag_cmp_name" ); + return $rows; + } + + function tag_get_bugs_attached( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT bug_id FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id'"; + $result = db_query( $query ); + + $bugs = array(); + while ( $row = db_fetch_array( $result ) ) { + $bugs[] = $row['bug_id']; + } + + return $bugs; + } + + function tag_bug_attach( $p_tag_id, $p_bug_id, $p_user_id ) { + tag_ensure_exists( $p_tag_id ); + bug_ensure_exists( $p_bug_id ); + user_ensure_exists( $p_user_id ); + + if ( tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) { + trigger_error( TAG_ALREADY_ATTACHED, ERROR ); + } + + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + $c_user_id = db_prepare_int( $p_user_id ); + $c_date_attached= db_now(); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "INSERT INTO $t_bug_tag_table + ( tag_id, + bug_id, + user_id, + date_attached + ) + VALUES + ( '$c_tag_id', + '$c_bug_id', + '$c_user_id', + ".$c_date_attached." + )"; + db_query( $query ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + history_log_event_special( $p_bug_id, TAG_ATTACHED, $t_tag_name ); + + return true; + } + + function tag_bug_detach( $p_tag_id, $p_bug_id ) { + tag_ensure_exists( $p_tag_id ); + bug_ensure_exists( $p_bug_id ); + + if ( !tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) { + trigger_error( TAG_NOT_ATTACHED, ERROR ); + } + + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "DELETE FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + db_query( $query ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + history_log_event_special( $p_bug_id, TAG_DETACHED, $t_tag_name ); + + return true; + } + + # Display + + function tag_display_link( $p_tag_row, $p_bug_id=0 ) { + if ( auth_get_current_user_id() == $p_tag_row[user_attached] ) { + $t_detach = config_get( 'tag_detach_own_threshold' ); + } else { + $t_detach = config_get( 'tag_detach_threshold' ); + } + + $t_name = string_display_line( $p_tag_row['name'] ); + $t_description = string_display_line( $p_tag_row['description'] ); + + echo "<a href='tag_view_page.php?tag_id=$p_tag_row[id]' title='$t_description'>$t_name</a>"; + + if ( access_has_global_level($t_detach) ) { + $t_tooltip = sprintf( lang_get( 'tag_detach' ), $t_name ); + echo " [<a href='tag_detach.php?bug_id=$p_bug_id&tag_id=$p_tag_row[id]' title='$t_tooltip'>x</a>]"; + } + + return true; + } + + function tag_display_attached( $p_bug_id ) { + $t_tag_rows = tag_bug_get_attached( $p_bug_id ); + + if ( count( $t_tag_rows ) == 0 ) { + echo lang_get( 'tag_none_attached' ); + } else { + $i = 0; + foreach ( $t_tag_rows as $t_tag ) { + echo ( $i > 0 ? config_get('tag_separator')." " : "" ); + tag_display_link( $t_tag, $p_bug_id ); + $i++; + } + } + + return true; + } + + # Statistics + + function tag_stats_attached( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT COUNT(*) FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id'"; + $result = db_query( $query ); + + return db_result( $result ); + } + + function tag_stats_related( $p_tag_id, $p_limit=5 ) { + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + $c_tag_id = db_prepare_int( $p_tag_id ); + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id != $c_tag_id + AND bug_id IN ( SELECT bug_id FROM $t_bug_tag_table + WHERE tag_id=$c_tag_id ) "; + $result = db_query( $query ); + + $t_tag_counts = array(); + while ( $row = db_fetch_array( $result ) ) { + $t_tag_counts[$row['tag_id']]++; + } + + arsort( $t_tag_counts ); + + $t_tags = array(); + $i = 1; + foreach ( $t_tag_counts as $t_tag_id => $t_count ) { + $t_tag_row = tag_get($t_tag_id); + $t_tag_row['count'] = $t_count; + $t_tags[] = $t_tag_row; + $i++; + if ( $i > $p_limit ) { break; } + } + + return $t_tags; + } diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/core/xmlhttprequest_api.php mantis-tagging/core/xmlhttprequest_api.php --- mantis-cvs/core/xmlhttprequest_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/xmlhttprequest_api.php 2007-08-16 13:48:44.000000000 -0400 @@ -31,6 +31,15 @@ echo '</select>'; } + function xmlhttprequest_user_combobox() { + $f_user_id = gpc_get_int( 'user_id' ); + $f_user_access = gpc_get_int( 'access_level' ); + + echo '<select name="user_id">'; + print_user_option_list( $f_user_id, ALL_PROJECTS, $f_user_access ); + echo '</select>'; + } + # --------------- # Echos a serialized list of platforms starting with the prefix specified in the $_POST function xmlhttprequest_platform_get_with_prefix() { diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/javascript/common.js mantis-tagging/javascript/common.js --- mantis-cvs/javascript/common.js 2007-08-14 10:12:43.000000000 -0400 +++ mantis-tagging/javascript/common.js 2007-08-16 14:57:40.000000000 -0400 @@ -163,3 +163,16 @@ setDisplay( idTag, (document.getElementById(idTag).style.display == 'none')?1:0 ); } +/* Tag functionality */ +function tag_string_append( p_string ) { + t_tag_separator = document.getElementById('tag_separator').value; + t_tag_string = document.getElementById('tag_string'); + t_tag_select = document.getElementById('tag_select'); + if ( t_tag_string.value != '' ) { + t_tag_string.value = t_tag_string.value + t_tag_separator + p_string; + } else { + t_tag_string.value = t_tag_string.value + p_string; + } + t_tag_select.selectedIndex=0; +} + diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/lang/strings_english.txt mantis-tagging/lang/strings_english.txt --- mantis-cvs/lang/strings_english.txt 2007-08-16 13:24:00.000000000 -0400 +++ mantis-tagging/lang/strings_english.txt 2007-08-20 09:28:47.000000000 -0400 @@ -275,6 +275,11 @@ $MANTIS_ERROR[ERROR_USER_CHANGE_LAST_ADMIN] = 'You cannot change the access level of the only ADMINISTRATOR in the system.'; $MANTIS_ERROR[ERROR_PAGE_REDIRECTION] = 'Page redirection error, ensure that there are no spaces outside the PHP block (<?php ?>) in config_inc.php or custom_*.php files.'; $MANTIS_ERROR[ERROR_TWITTER_NO_CURL_EXT] = 'Twitter integration requires PHP CURL extension which is not installed.'; +$MANTIS_ERROR[ERROR_TAG_NOT_FOUND] = 'Could not find a tag with that name.'; +$MANTIS_ERROR[ERROR_TAG_DUPLICATE] = 'A tag already exists with that name.'; +$MANTIS_ERROR[ERROR_TAG_NAME_INVALID] = 'That tag name is invalid.'; +$MANTIS_ERROR[ERROR_TAG_NOT_ATTACHED] = 'That tag is not attached to that bug.'; +$MANTIS_ERROR[ERROR_TAG_ALREADY_ATTACHED] = 'That tag already attached to that bug.'; $s_login_error = 'Your account may be disabled or blocked or the username/password you entered is incorrect.'; $s_login_cookies_disabled = 'Your browser either doesn\'t know how to handle cookies, or refuses to handle them.'; @@ -1347,6 +1352,38 @@ # wiki related strings $s_wiki = 'Wiki'; +# Tagging +$s_tags = 'Tags'; +$s_tag_details = 'Tag Details: %s'; +$s_tag_id = 'Tag ID'; +$s_tag_name = 'Name'; +$s_tag_creator = 'Creator'; +$s_tag_created = 'Date Created'; +$s_tag_updated = 'Last Updated'; +$s_tag_description = 'Tag Description'; +$s_tag_statistics = 'Usage Statistics'; +$s_tag_update = 'Update Tag: %s'; +$s_tag_update_return = 'Back to Tag'; +$s_tag_update_button = 'Update Tag'; +$s_tag_delete_button = 'Delete Tag'; +$s_tag_delete_message = 'Are you sure you wish to delete this tag?'; +$s_tag_existing = 'Existing tags'; +$s_tag_none_attached = 'No tags attached.'; +$s_tag_attach = 'Attach'; +$s_tag_attach_long = 'Attach Tags'; +$s_tag_attach_failed = 'Tag attachment failed.'; +$s_tag_detach = 'Detach %s'; +$s_tag_separate_by = "(Separate by '%s')"; +$s_tag_invalid_name = 'Invalid tag name.'; +$s_tag_create_denied = 'Create permission denied.'; +$s_tag_filter_default = 'Attached Issues (%s)'; +$s_tag_history_attached = 'Tag Attached'; +$s_tag_history_detached = 'Tag Detached'; +$s_tag_history_renamed = 'Tag Renamed'; +$s_tag_related = 'Related Tags'; +$s_tag_related_issues = 'Shared Issues (%s)'; +$s_tag_stats_attached = 'Issues attached: %s'; + # Time Tracking $s_time_tracking_billing_link = 'Billing'; $s_time_tracking = 'Time tracking'; diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_attach.php mantis-tagging/tag_attach.php --- mantis-cvs/tag_attach.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_attach.php 2007-08-20 08:43:02.000000000 -0400 @@ -0,0 +1,108 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + $f_bug_id = gpc_get_int( 'bug_id' ); + $f_tag_select = gpc_get_int( 'tag_select' ); + $t_user_id = auth_get_current_user_id(); + + access_ensure_global_level( config_get( 'tag_attach_threshold' ) ); + + $t_tags = tag_parse_string( gpc_get_string( 'tag_string' ) ); + $t_can_create = access_has_global_level( config_get( 'tag_create_threshold' ) ); + + $t_tags_create = array(); + $t_tags_attach = array(); + $t_tags_failed = array(); + + foreach ( $t_tags as $t_tag_row ) { + if ( -1 == $t_tag_row['id'] ) { + if ( $t_can_create ) { + $t_tags_create[] = $t_tag_row; + } else { + $t_tags_failed[] = $t_tag_row; + } + } elseif ( -2 == $t_tag_row['id'] ) { + $t_tags_failed[] = $t_tag_row; + } else { + $t_tags_attach[] = $t_tag_row; + } + } + + if ( 0 < $f_tag_select && tag_exists( $f_tag_select ) ) { + $t_tags_attach[] = tag_get( $f_tag_select ); + } + + if ( count( $t_tags_failed ) > 0 ) { + html_page_top1( lang_get( 'tag_attach_long' ).' '.bug_format_summary( $f_bug_id, SUMMARY_CAPTION ) ); + html_page_top2(); +?> +<br/> +<table class="width75" align="center"> + <tr class="row-category"> + <td colspan="2"><?php echo lang_get( 'tag_attach_failed' ) ?></td> + </tr> + <tr class="spacer"><td colspan="2"></td></tr> +<?php + $t_tag_string = ""; + foreach( $t_tags_attach as $t_tag_row ) { + if ( "" != $t_tag_string ) { + $t_tag_string .= config_get( 'tag_separator' ); + } + $t_tag_string .= $t_tag_row['name']; + } + + foreach( $t_tags_failed as $t_tag_row ) { + echo '<tr ',helper_alternate_class(),'>'; + if ( -1 == $t_tag_row['id'] ) { + echo '<td class="category">',lang_get( 'tag_invalid_name' ),'</td>'; + } elseif ( -2 == $t_tag_row['id'] ) { + echo '<td class="category">',lang_get( 'tag_create_denied' ),'</td>'; + } + echo '<td>',$t_tag_row['name'],'</td></tr>'; + + if ( "" != $t_tag_string ) { + $t_tag_string .= config_get( 'tag_separator' ); + } + $t_tag_string .= $t_tag_row['name']; + } +?> + <tr class="spacer"><td colspan="2"></td></tr> + <tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td> +<?php + print_tag_input( $f_bug_id, $t_tag_string ); +?> + </td> + </tr> +</table> +<?php + html_page_bottom1(__FILE__); + } else { + foreach( $t_tags_create as $t_tag_row ) { + $t_tag_row['id'] = tag_create( $t_tag_row['name'], $t_user_id ); + $t_tags_attach[] = $t_tag_row; + } + + foreach( $t_tags_attach as $t_tag_row ) { + if ( ! tag_bug_is_attached( $t_tag_row['id'], $f_bug_id ) ) { + tag_bug_attach( $t_tag_row['id'], $f_bug_id, $t_user_id ); + } + } + + print_successful_redirect_to_bug( $f_bug_id ); + } diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_delete.php mantis-tagging/tag_delete.php --- mantis-cvs/tag_delete.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_delete.php 2007-08-17 13:34:13.000000000 -0400 @@ -0,0 +1,27 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + access_ensure_global_level( config_get( 'tag_edit_threshold' ) ); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + helper_ensure_confirmed( lang_get( 'tag_delete_message' ), lang_get( 'tag_delete_button' ) ); + + tag_delete( $f_tag_id ); + + print_successful_redirect( config_get( 'default_home_page' ) ); diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_detach.php mantis-tagging/tag_detach.php --- mantis-cvs/tag_detach.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_detach.php 2007-08-17 11:34:04.000000000 -0400 @@ -0,0 +1,33 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $f_bug_id = gpc_get_int( 'bug_id' ); + + $t_tag_row = tag_get( $f_tag_id ); + $t_tag_bug_row = tag_bug_get_row( $f_tag_id, $f_bug_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_detach_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_bug_row['user_id'] ) + && access_has_global_level( config_get( 'tag_detach_own_threshold' ) ) ) ) + { + access_denied(); + } + + tag_bug_detach( $f_tag_id, $f_bug_id ); + + print_successful_redirect_to_bug( $f_bug_id ); diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_update_page.php mantis-tagging/tag_update_page.php --- mantis-cvs/tag_update_page.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_update_page.php 2007-08-17 13:37:45.000000000 -0400 @@ -0,0 +1,105 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'ajax_api.php' ); + require_once( $t_core_path . 'tag_api.php' ); + + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_row['user_id'] ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + access_denied(); + } + + html_page_top1( sprintf( lang_get( 'tag_update' ), $t_tag_row['name'] ) ); + html_page_top2(); +?> + +<br/> +<form method="post" action="tag_update.php"> +<table class="width100" cellspacing="1"> + +<!-- Title --> +<tr> + <td class="form-title" colspan="2"> + <?php echo sprintf( lang_get( 'tag_update' ), $t_tag_row['name'] ) ?> + <input type="hidden" name="tag_id" value="<?php echo $f_tag_id ?>"/> + </td> + <td class="right" colspan="3"> + <?php print_bracket_link( 'tag_view_page.php?tag_id='.$f_tag_id, lang_get( 'tag_update_return' ) ); ?> + </td> +</tr> + +<!-- 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> +</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_tag_row['name'] ?>"/></td> + <td><?php + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + if ( ON == config_get( 'use_javascript' ) ) { + $t_username = prepare_user_name( $t_tag_row['user_id'] ); + echo ajax_click_to_edit( $t_username, 'user_id', 'entrypoint=user_combobox&user_id=' . $t_tag_row['user_id'] . '&access_level=' . config_get( 'tag_create_threshold' ) ); + } else { + echo '<select ', helper_get_tab_index(), ' name="user_id">'; + print_user_option_list( $t_tag_row['user_id'], ALL_PROJECTS, config_get( 'tag_create_threshold' ) ); + echo '</select>'; + } + } else { + echo user_get_name($t_tag_row['user_id']); + } + ?></td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_created'] ) ) ?> </td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_updated'] ) ) ?> </td> +</tr> + +<!-- spacer --> +<tr class="spacer"> + <td colspan="5"></td> +</tr> + +<!-- Description --> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_description' ) ?></td> + <td colspan="4"> + <textarea name="description" <?php echo helper_get_tab_index() ?> cols="80" rows="6"><?php echo $t_tag_row['description'] ?></textarea> + </td> +</tr> + +<!-- Submit Button --> +<tr> + <td class="center" colspan="6"> + <input <?php echo helper_get_tab_index() ?> type="submit" class="button" value="<?php echo lang_get( 'tag_update_button' ) ?>" /> + </td> +</tr> + +</table> +</form> + +<?php + html_page_bottom1( __FILE__ ); +?> diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_update.php mantis-tagging/tag_update.php --- mantis-cvs/tag_update.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_update.php 2007-08-16 13:48:44.000000000 -0400 @@ -0,0 +1,55 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2006 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_row['user_id'] ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + access_denied(); + } + + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + $f_new_user_id = gpc_get_int( 'user_id', $t_tag_row['user_id'] ); + } else { + $f_new_user_id = $t_tag_row['user_id']; + } + + $f_new_name = gpc_get_string( 'name', $t_tag_row['name'] ); + $f_new_description = gpc_get_string( 'description', $t_tag_row['description'] ); + + $t_update = false; + + if ( $t_tag_row['user_id'] != $f_new_user_id ) { + user_ensure_exists( $f_new_user_id ); + $t_update = true; + } + + if ( $t_tag_row['name'] != $f_new_name || + $t_tag_row['description'] != $f_new_description ) { + + $t_update = true; + } + + tag_update( $f_tag_id, $f_new_name, $f_new_user_id, $f_new_description ); + + $t_url = 'tag_view_page.php?tag_id='.$f_tag_id; + print_successful_redirect( $t_url ); diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/tag_view_page.php mantis-tagging/tag_view_page.php --- mantis-cvs/tag_view_page.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_view_page.php 2007-08-20 09:31:56.000000000 -0400 @@ -0,0 +1,107 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + access_ensure_global_level( config_get( 'tag_view_threshold' ) ); + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + $t_name = string_display_line( $t_tag_row['name'] ); + $t_description = string_display( $t_tag_row['description'] ); + + html_page_top1( sprintf( lang_get( 'tag_details' ), $t_tag_row['name'] ) ); + html_page_top2(); +?> + +<br/> +<table class="width100" cellspacing="1"> + +<!-- Title --> +<tr> + <td class="form-title" colspan="2"> + <?php echo sprintf( lang_get( 'tag_details' ), $t_tag_row['name'] ) ?> + + </td> + <td class="right" colspan="3"> + <?php print_bracket_link( 'search.php?hide_status_id=90&tag_string='.urlencode($t_tag_row['name']), sprintf( lang_get( 'tag_filter_default' ), tag_stats_attached( $f_tag_id ) ) ); ?> + </td> +</tr> + +<!-- 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> +</tr> + +<tr <?php echo helper_alternate_class() ?>> + <td><?php echo $t_tag_row['id'] ?></td> + <td><?php echo $t_name ?></td> + <td><?php echo user_get_name($t_tag_row['user_id']) ?></td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_created'] ) ) ?> </td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_updated'] ) ) ?> </td> +</tr> + +<!-- spacer --> +<tr class="spacer"> + <td colspan="5"></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> +</tr> + +<!-- Statistics --> +<?php + $t_tags_related = tag_stats_related( $f_tag_id ); + if ( count( $t_tags_related ) ) { + echo '<tr ',helper_alternate_class(),'>'; + echo '<td class="category" rowspan="',count( $t_tags_related ),'">',lang_get( 'tag_related' ),'</td>'; + + $i = 0; + foreach( $t_tags_related as $t_tag ) { + $t_name = string_display_line( $t_tag['name'] ); + $t_description = string_display_line( $t_tag['description'] ); + $t_count = $t_tag['count']; + + 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">'; + print_bracket_link( 'search.php?hide_status_id=90&tag_string='.urlencode("+$t_tag_row[name]".config_get('tag_separator')."+$t_name"), sprintf( lang_get( 'tag_related_issues' ), $t_tag['count'] ) ); + echo '</a></td></tr>'; + + $i++; + } + } +?> + +<!-- Buttons --> +<tr> + <td colspan="5"> + <?php html_buttons_tag_view_page( $f_tag_id ); ?> + </td> +</tr> + +</table> +<?php + html_page_bottom1( __FILE__ ); +?> diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/view_all_set.php mantis-tagging/view_all_set.php --- mantis-cvs/view_all_set.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/view_all_set.php 2007-08-17 22:43:15.000000000 -0400 @@ -203,6 +203,9 @@ $f_do_filter_by_date = gpc_get_bool( 'do_filter_by_date' ); $f_view_state = gpc_get_int( 'view_state', META_FILTER_ANY ); + $f_tag_string = gpc_get_string( 'tag_string', '' ); + $f_tag_select = gpc_get_int( 'tag_select', '0' ); + $t_custom_fields = custom_field_get_ids(); # @@@ (thraxisp) This should really be the linked ids, but we don't know the project $f_custom_fields_data = array(); if ( is_array( $t_custom_fields ) && ( sizeof( $t_custom_fields ) > 0 ) ) { @@ -414,6 +417,8 @@ $t_setting_arr['platform'] = $f_platform; $t_setting_arr['os'] = $f_os; $t_setting_arr['os_build'] = $f_os_build; + $t_setting_arr['tag_string'] = $f_tag_string; + $t_setting_arr['tag_select'] = $f_tag_select; break; # Set the sort order and direction case '2': diff -urN --exclude=CVS --exclude=.svn --exclude=.swp --exclude='.git*' --exclude=config_inc.php mantis-cvs/view_filters_page.php mantis-tagging/view_filters_page.php --- mantis-cvs/view_filters_page.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/view_filters_page.php 2007-08-17 22:21:01.000000000 -0400 @@ -16,6 +16,7 @@ require_once( $t_core_path.'bug_api.php' ); require_once( $t_core_path.'string_api.php' ); require_once( $t_core_path.'date_api.php' ); + require_once( $t_core_path.'tag_api.php' ); auth_ensure_user_authenticated(); @@ -406,7 +407,8 @@ </tr> <tr class="row-category2"> <td class="small-caption" colspan="<?php echo ( 1 * $t_custom_cols ); ?>"><?php echo lang_get( 'search' ) ?></td> -<td class="small-caption" colspan="<?php echo ( ( $t_filter_cols - 1 ) * $t_custom_cols ); ?>"></td> +<td class="small-caption" colspan="<?php echo ( ( $t_filter_cols - 2 ) * $t_custom_cols ); ?>"><?php echo lang_get( 'tags' ) ?></td> +<td class="small-caption" colspan="<?php echo ( 1 * $t_custom_cols ); ?>"></td> </tr> <tr> <!-- Search field --> @@ -414,13 +416,12 @@ <input type="text" size="16" name="search" value="<?php echo string_html_specialchars( $t_filter['search'] ); ?>" /> </td> - <td class="small-caption" colspan="<?php echo ( ( $t_filter_cols - 3 ) * $t_custom_cols ); ?>"></td> + <td class="small-caption" colspan="<?php echo ( ( $t_filter_cols - 2 ) * $t_custom_cols ); ?>"><?php print_filter_tag_string() ?></td> <!-- Submit button --> <td class="right" colspan="<?php echo ( 1 * $t_custom_cols ); ?>"> <input type="submit" name="filter" class="button" value="<?php echo lang_get( 'filter_button' ) ?>" /> </td> - <td class="small-caption" colspan="<?php echo ( 1 * $t_custom_cols ); ?>"></td> </tr> </table> </form> mantis-tagging-2007-08-24.patch (69,246 bytes)
diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/admin/schema.php mantis-tagging/admin/schema.php --- mantis-cvs/admin/schema.php 2007-08-07 10:54:56.000000000 -0400 +++ mantis-tagging/admin/schema.php 2007-08-24 13:25:17.000000000 -0400 @@ -330,6 +330,20 @@ $upgrade[] = Array('CreateIndexSQL',Array('idx_diskfile',config_get('mantis_bug_file_table'),'diskfile')); $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_user_print_pref_table' ), "print_pref C(64) NOTNULL" ) ); $upgrade[] = Array('AlterColumnSQL', Array( config_get( 'mantis_bug_history_table' ), "field_name C(64) NOTNULL" ) ); +$upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_tag_table' ), " + id I UNSIGNED NOTNULL PRIMARY AUTOINCREMENT, + user_id I UNSIGNED NOTNULL DEFAULT '0', + name C(100) NOTNULL PRIMARY DEFAULT \" '' \", + description XL NOTNULL, + date_created T NOTNULL DEFAULT '1970-01-01 00:00:01', + date_updated T NOTNULL DEFAULT '1970-01-01 00:00:01' + ", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) ); +$upgrade[] = Array('CreateTableSQL', Array( config_get( 'mantis_bug_tag_table' ), " + bug_id I UNSIGNED NOTNULL PRIMARY DEFAULT '0', + tag_id I UNSIGNED NOTNULL PRIMARY DEFAULT '0', + user_id I UNSIGNED NOTNULL DEFAULT '0', + date_attached T NOTNULL DEFAULT '1970-01-01 00:00:01' + ", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) ); # Release marker: 1.1.0a4 diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/bug_actiongroup_attach_tags_inc.php mantis-tagging/bug_actiongroup_attach_tags_inc.php --- mantis-cvs/bug_actiongroup_attach_tags_inc.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/bug_actiongroup_attach_tags_inc.php 2007-08-24 11:00:23.000000000 -0400 @@ -0,0 +1,101 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + $t_core_path = config_get( 'core_path' ); + require_once( $t_core_path . 'tag_api.php' ); + + /** + * Prints the title for the custom action page. + */ + function action_attach_tags_print_title() { + echo '<tr class="form-title">'; + echo '<td colspan="2">'; + echo lang_get( 'tag_attach_long' ); + echo '</td></tr>'; + } + + /** + * Prints the table and form for the Attach Tags group action page. + */ + function action_attach_tags_print_fields() { + echo '<tr ',helper_alternate_class(),'><td class="category">',lang_get('tag_attach_long'),'</td><td>'; + print_tag_input(); + echo '<input type="submit" class="button" value="' . lang_get( 'tag_attach' ) . ' " /></td></tr>'; + } + + /** + * Validates the Attach Tags group action. + * Gets called for every bug, but performs the real tag validation only + * the first time. Any invalid tags will be skipped, as there is no simple + * or clean method of presenting these errors to the user. + * @param integer Bug ID + * @return boolean True + */ + function action_attach_tags_validate( $p_bug_id ) { + global $g_action_attach_tags_valid; + if ( !isset( $g_action_attach_tags_valid ) ) { + $f_tag_string = gpc_get_string( 'tag_string' ); + $f_tag_select = gpc_get_string( 'tag_select' ); + + global $g_action_attach_tags_attach, $g_action_attach_tags_create, $g_action_attach_tags_failed; + $g_action_attach_tags_attach = array(); + $g_action_attach_tags_create = array(); + $g_action_attach_tags_failed = array(); + + $t_tags = tag_parse_string( $f_tag_string ); + $t_can_create = access_has_global_level( config_get( 'tag_create_threshold' ) ); + + foreach ( $t_tags as $t_tag_row ) { + if ( -1 == $t_tag_row['id'] ) { + if ( $t_can_create ) { + $g_action_attach_tags_create[] = $t_tag_row; + } else { + $g_action_attach_tags_failed[] = $t_tag_row; + } + } elseif ( -2 == $t_tag_row['id'] ) { + $g_action_attach_tags_failed[] = $t_tag_row; + } else { + $g_action_attach_tags_attach[] = $t_tag_row; + } + } + + if ( 0 < $f_tag_select && tag_exists( $f_tag_select ) ) { + $g_action_attach_tags_attach[] = tag_get( $f_tag_select ); + } + + } + + global $g_action_attach_tags_attach, $g_action_attach_tags_create, $g_action_attach_tags_failed; + + return true; + } + + /** + * Attaches all the tags to each bug in the group action. + * @param integer Bug ID + * @return boolean True if all tags attach properly + */ + function action_attach_tags_process( $p_bug_id ) { + global $g_action_attach_tags_attach, $g_action_attach_tags_create; + + foreach( $g_action_attach_tags_create as $t_tag_row ) { + $t_tag_row['id'] = tag_create( $t_tag_row['name'] ); + $g_action_attach_tags_attach[] = $t_tag_row; + } + + foreach( $g_action_attach_tags_attach as $t_tag_row ) { + if ( ! tag_bug_is_attached( $t_tag_row['id'], $p_bug_id ) ) { + tag_bug_attach( $t_tag_row['id'], $p_bug_id ); + } + } + + return true; + } diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/bug_view_advanced_page.php mantis-tagging/bug_view_advanced_page.php --- mantis-cvs/bug_view_advanced_page.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/bug_view_advanced_page.php 2007-08-24 11:00:22.000000000 -0400 @@ -20,6 +20,7 @@ require_once( $t_core_path.'date_api.php' ); require_once( $t_core_path.'relationship_api.php' ); require_once( $t_core_path.'last_visited_api.php' ); + require_once( $t_core_path.'tag_api.php' ); $f_bug_id = gpc_get_int( 'bug_id' ); $f_history = gpc_get_bool( 'history', config_get( 'history_default_visible' ) ); @@ -464,13 +465,35 @@ </td> </tr> +<!-- Tagging --> +<?php if ( access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tags' ) ?></td> + <td colspan="5"> +<?php + tag_display_attached( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag_view access ?> + +<?php if ( access_has_global_level( config_get( 'tag_attach_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td colspan="5"> +<?php + print_tag_attach_form( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag attach access ?> + <!-- spacer --> <tr class="spacer"> <td colspan="6"></td> </tr> - <!-- Custom Fields --> <?php $t_custom_fields_found = false; diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/bug_view_page.php mantis-tagging/bug_view_page.php --- mantis-cvs/bug_view_page.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/bug_view_page.php 2007-08-24 11:00:22.000000000 -0400 @@ -22,6 +22,7 @@ require_once( $t_core_path.'date_api.php' ); require_once( $t_core_path.'relationship_api.php' ); require_once( $t_core_path.'last_visited_api.php' ); + require_once( $t_core_path.'tag_api.php' ); ?> <?php $f_bug_id = gpc_get_int( 'bug_id' ); @@ -341,6 +342,29 @@ </td> </tr> +<!-- Tagging --> +<?php if ( access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tags' ) ?></td> + <td colspan="5"> +<?php + tag_display_attached( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag_view access ?> + +<?php if ( access_has_global_level( config_get( 'tag_attach_threshold' ) ) ) { ?> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td colspan="5"> +<?php + print_tag_attach_form( $f_bug_id ); +?> + </td> +</tr> +<?php } # has tag attach access ?> + <!-- spacer --> <tr class="spacer"> diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/config_defaults_inc.php mantis-tagging/config_defaults_inc.php --- mantis-cvs/config_defaults_inc.php 2007-08-24 10:54:10.000000000 -0400 +++ mantis-tagging/config_defaults_inc.php 2007-08-24 11:00:21.000000000 -0400 @@ -1334,6 +1334,7 @@ $g_mantis_bug_monitor_table = '%db_table_prefix%_bug_monitor%db_table_suffix%'; $g_mantis_bug_relationship_table = '%db_table_prefix%_bug_relationship%db_table_suffix%'; $g_mantis_bug_table = '%db_table_prefix%_bug%db_table_suffix%'; + $g_mantis_bug_tag_table = '%db_table_prefix%_bug_tag%db_table_suffix%'; $g_mantis_bug_text_table = '%db_table_prefix%_bug_text%db_table_suffix%'; $g_mantis_bugnote_table = '%db_table_prefix%_bugnote%db_table_suffix%'; $g_mantis_bugnote_text_table = '%db_table_prefix%_bugnote_text%db_table_suffix%'; @@ -1343,6 +1344,7 @@ $g_mantis_project_table = '%db_table_prefix%_project%db_table_suffix%'; $g_mantis_project_user_list_table = '%db_table_prefix%_project_user_list%db_table_suffix%'; $g_mantis_project_version_table = '%db_table_prefix%_project_version%db_table_suffix%'; + $g_mantis_tag_table = '%db_table_prefix%_tag%db_table_suffix%'; $g_mantis_user_table = '%db_table_prefix%_user%db_table_suffix%'; $g_mantis_user_profile_table = '%db_table_prefix%_user_profile%db_table_suffix%'; $g_mantis_user_pref_table = '%db_table_prefix%_user_pref%db_table_suffix%'; @@ -1824,6 +1826,34 @@ $g_recently_visited_count = 5; ##################### + # Bug Tagging + ##################### + + # String that will separate tags as entered for input + $g_tag_separator = ','; + + # Access level required to view tags attached to a bug + $g_tag_view_threshold = VIEWER; + + # Access level required to attach tags to a bug + $g_tag_attach_threshold = REPORTER; + + # Access level required to detach tags from a bug + $g_tag_detach_threshold = DEVELOPER; + + # Access level required to detach tags attached by the same user + $g_tag_detach_own_threshold = REPORTER; + + # Access level required to create new tags + $g_tag_create_threshold = REPORTER; + + # Access level required to edit tag names and descriptions + $g_tag_edit_threshold = DEVELOPER; + + # Access level required to edit descriptions by the creating user + $g_tag_edit_own_threshold = REPORTER; + + ##################### # Time tracking ##################### diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/core/bug_api.php mantis-tagging/core/bug_api.php --- mantis-cvs/core/bug_api.php 2007-08-24 10:54:11.000000000 -0400 +++ mantis-tagging/core/bug_api.php 2007-08-24 11:00:23.000000000 -0400 @@ -18,6 +18,7 @@ require_once( $t_core_dir . 'string_api.php' ); require_once( $t_core_dir . 'sponsorship_api.php' ); require_once( $t_core_dir . 'twitter_api.php' ); + require_once( $t_core_dir . 'tag_api.php' ); # MASC RELATIONSHIP require_once( $t_core_dir.'relationship_api.php' ); @@ -708,6 +709,12 @@ # Delete files file_delete_attachments( $p_bug_id ); + # Detach tags + $t_tags = tag_bug_get_attached( $p_bug_id ); + foreach ( $t_tags as $t_tag_row ) { + tag_detach( $t_tag_row['id'], $p_bug_id ); + } + # Delete the bug history history_delete( $p_bug_id ); diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/core/constant_inc.php mantis-tagging/core/constant_inc.php --- mantis-cvs/core/constant_inc.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/constant_inc.php 2007-08-24 11:00:23.000000000 -0400 @@ -153,6 +153,9 @@ define( 'CHECKIN', 22 ); define( 'BUG_REPLACE_RELATIONSHIP', 23 ); define( 'BUG_PAID_SPONSORSHIP', 24 ); + define( 'TAG_ATTACHED', 25 ); + define( 'TAG_DETACHED', 26 ); + define( 'TAG_RENAMED', 27 ); # bug relationship constants define( 'BUG_DUPLICATE', 0 ); @@ -295,6 +298,13 @@ # ERROR_TWITTER_* define( 'ERROR_TWITTER_NO_CURL_EXT', 2100 ); + # ERROR_TAG_* + define( 'ERROR_TAG_NOT_FOUND', 2200 ); + define( 'ERROR_TAG_DUPLICATE', 2201 ); + define( 'ERROR_TAG_NAME_INVALID', 2202 ); + define( 'ERROR_TAG_NOT_ATTACHED', 2203 ); + define( 'ERROR_TAG_ALREADY_ATTACHED', 2204 ); + # Status Legend Position define( 'STATUS_LEGEND_POSITION_TOP', 1); define( 'STATUS_LEGEND_POSITION_BOTTOM', 2); diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/core/filter_api.php mantis-tagging/core/filter_api.php --- mantis-cvs/core/filter_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/filter_api.php 2007-08-24 11:00:22.000000000 -0400 @@ -16,6 +16,7 @@ require_once( $t_core_dir . 'bug_api.php' ); require_once( $t_core_dir . 'collapse_api.php' ); require_once( $t_core_dir . 'relationship_api.php' ); + require_once( $t_core_dir . 'tag_api.php' ); ########################################################################### # Filter Property Names @@ -56,6 +57,8 @@ define( 'FILTER_PROPERTY_FILTER_BY_DATE', 'do_filter_by_date' ); define( 'FILTER_PROPERTY_RELATIONSHIP_TYPE', 'relationship_type' ); define( 'FILTER_PROPERTY_RELATIONSHIP_BUG', 'relationship_bug' ); + define( 'FILTER_PROPERTY_TAG_STRING', 'tag_string' ); + define( 'FILTER_PROPERTY_TAG_SELECT', 'tag_select' ); ########################################################################### # Filter Query Parameter Names @@ -96,6 +99,8 @@ define( 'FILTER_SEARCH_FILTER_BY_DATE', 'filter_by_date' ); define( 'FILTER_SEARCH_RELATIONSHIP_TYPE', 'relationship_type' ); define( 'FILTER_SEARCH_RELATIONSHIP_BUG', 'relationship_bug' ); + define( 'FILTER_SEARCH_TAG_STRING', 'tag_string' ); + define( 'FILTER_SEARCH_TAG_SELECT', 'tag_select' ); # Checks the supplied value to see if it is an ANY value. # $p_field_value - The value to check. @@ -306,6 +311,14 @@ $t_query[] = filter_encode_field_and_value( FILTER_SEARCH_OS_BUILD, $p_custom_filter[FILTER_PROPERTY_OS_BUILD] ); } + if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ) ) { + $t_query[] = filter_encode_field_and_value( FILTER_SEARCH_TAG_STRING, $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ); + } + + if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] ) ) { + $t_query[] = filter_encode_field_and_value( FILTER_SEARCH_TAG_SELECT, $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] ); + } + if ( isset( $p_custom_filter['custom_fields'] ) ) { foreach( $p_custom_filter['custom_fields'] as $t_custom_field_id => $t_custom_field_values ) { if ( !filter_str_field_is_any( $t_custom_field_values ) ) { @@ -1057,6 +1070,63 @@ array_push( $t_where_clauses, '('. implode( ' OR ', $t_clauses ) .')' ); } + # tags + $c_tag_string = trim( $t_filter['tag_string'] ); + if ( !is_blank( $c_tag_string ) ) { + require_once( $t_core_path . 'tag_api.php' ); + $t_tags = tag_parse_filters( $c_tag_string ); + + if ( !count( $t_tags ) ) { break; } + + $t_tags_all = array(); + $t_tags_any = array(); + $t_tags_none = array(); + + foreach( $t_tags as $t_tag_row ) { + switch ( $t_tag_row['filter'] ) { + case 1: + $t_tags_all[] = $t_tag_row; + break; + case 0: + $t_tags_any[] = $t_tag_row; + break; + case -1: + $t_tags_none[] = $t_tag_row; + break; + } + } + + if ( 0 < $t_filter['tag_select'] && tag_exists( $t_filter['tag_select'] ) ) { + $t_tags_any[] = tag_get( $t_filter['tag_select'] ); + } + + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + if ( count( $t_tags_all ) ) { + $t_clauses = array(); + foreach ( $t_tags_all as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_table.id IN ( SELECT bug_id FROM $t_bug_tag_table WHERE $t_bug_tag_table.tag_id = $t_tag_row[id] )" ); + } + array_push( $t_where_clauses, '('. implode( ' AND ', $t_clauses ) .')' ); + } + + if ( count( $t_tags_any ) ) { + $t_clauses = array(); + foreach ( $t_tags_any as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_tag_table.tag_id = $t_tag_row[id]" ); + } + array_push( $t_where_clauses, "$t_bug_table.id IN ( SELECT bug_id FROM $t_bug_tag_table WHERE ( ". implode( ' OR ', $t_clauses ) .') )' ); + } + + if ( count( $t_tags_none ) ) { + $t_clauses = array(); + foreach ( $t_tags_none as $t_tag_row ) { + array_push( $t_clauses, "$t_bug_tag_table.tag_id = $t_tag_row[id]" ); + } + array_push( $t_where_clauses, "$t_bug_table.id NOT IN ( SELECT bug_id FROM $t_bug_tag_table WHERE ( ". implode( ' OR ', $t_clauses ) .') )' ); + } + } + # custom field filters if( ON == config_get( 'filter_by_custom_fields' ) ) { # custom field filtering @@ -2291,6 +2361,7 @@ <a href="<?php PRINT $t_filters_url . 'os_build'; ?>" id="os_build_filter"><?php echo lang_get( 'os_version' ) ?>:</a> </td> <td class="small-caption" valign="top" colspan="5"> + <a href="<?php PRINT $t_filters_url . 'tag_string'; ?>" id="tag_string_filter"><?php echo lang_get( 'tags' ) ?>:</a> </td> <?php if ( $t_filter_cols > 8 ) { echo '<td class="small-caption" valign="top" colspan="' . ( $t_filter_cols - 8 ) . '"> </td>'; @@ -2312,7 +2383,16 @@ print_multivalue_field( FILTER_PROPERTY_OS_BUILD, $t_filter[FILTER_PROPERTY_OS_BUILD] ); ?> </td> - <td class="small-caption" colspan="5"> + <td class="small-caption" valign="top" id="tag_string_filter_target" colspan="5"> + <?php + $t_tag_string = $t_filter['tag_string']; + if ( $t_filter['tag_select'] != 0 ) { + $t_tag_string .= ( is_blank( $t_tag_string ) ? '' : config_get( 'tag_separator' ) ); + $t_tag_string .= tag_get_field( $t_filter['tag_select'], 'name' ); + } + PRINT $t_tag_string + ?> + <input type="hidden" name="tag_string" value="<?php echo $t_tag_string ?>"/> </td> </tr> <?php @@ -3025,6 +3105,9 @@ if ( !isset( $p_filter_arr['target_version'] ) ) { $p_filter_arr['target_version'] = META_FILTER_ANY; } + if ( !isset( $p_filter_arr['tag_string'] ) ) { + $p_filter_arr['tag_string'] = gpc_get_string( 'tag_string', '' ); + } $t_custom_fields = custom_field_get_ids(); # @@@ (thraxisp) This should really be the linked ids, but we don't know the project $f_custom_fields_data = array(); @@ -3531,6 +3614,22 @@ } + function print_filter_tag_string() { + global $t_filter; + $t_tag_string = $t_filter['tag_string']; + if ( $t_filter['tag_select'] != 0 ) { + $t_tag_string .= ( is_blank( $t_tag_string ) ? '' : config_get( 'tag_separator' ) ); + $t_tag_string .= tag_get_field( $t_filter['tag_select'], 'name' ); + } + ?> + <input type="hidden" id="tag_separator" value="<?php echo config_get( 'tag_separator' ) ?>" /> + <input type="text" name="tag_string" id="tag_string" size="40" value="<?php echo $t_tag_string ?>" /> + <select <?php echo helper_get_tab_index() ?> name="tag_select" id="tag_select"> + <?php print_tag_option_list( $p_bug_id ); ?> + </select> + <?php + } + function print_filter_custom_field($p_field_id){ global $t_filter, $t_accessible_custom_fields_names, $t_accessible_custom_fields_types, $t_accessible_custom_fields_values, $t_accessible_custom_fields_ids, $t_select_modifier; diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/core/history_api.php mantis-tagging/core/history_api.php --- mantis-cvs/core/history_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/history_api.php 2007-08-24 11:00:22.000000000 -0400 @@ -165,6 +165,15 @@ } } + // tags + if ( $v_type == TAG_ATTACHED || + $v_type == TAG_DETACHED || + $v_type == TAG_RENAMED ) { + if ( !access_has_global_level( config_get( 'tag_view_threshold' ) ) ) { + continue; + } + } + $raw_history[$j]['date'] = db_unixtimestamp( $v_date_modified ); $raw_history[$j]['userid'] = $v_user_id; @@ -390,6 +399,16 @@ case CHECKIN: $t_note = lang_get( 'checkin' ); break; + case TAG_ATTACHED: + $t_note = lang_get( 'tag_history_attached' ) .': '. $p_old_value; + break; + case TAG_DETACHED: + $t_note = lang_get( 'tag_history_detached' ) .': '. $p_old_value; + break; + case TAG_RENAMED: + $t_note = lang_get( 'tag_history_renamed' ); + $t_change = $p_old_value . ' => ' . $p_new_value; + break; } } diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/core/html_api.php mantis-tagging/core/html_api.php --- mantis-cvs/core/html_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/html_api.php 2007-08-24 11:00:22.000000000 -0400 @@ -1218,4 +1218,24 @@ echo '</tr></table>'; } + + function html_button_tag_update( $p_tag_id ) { + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == tag_get_field( $p_tag_id, 'user_id' ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + html_button( 'tag_update_page.php', lang_get( 'tag_update_button' ), array( 'tag_id' => $p_tag_id ) ); + } + } + + function html_button_tag_delete( $p_tag_id ) { + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + html_button( 'tag_delete.php', lang_get( 'tag_delete_button' ), array( 'tag_id' => $p_tag_id ) ); + } + } + + function html_buttons_tag_view_page( $p_tag_id ) { + html_button_tag_update( $p_tag_id ); + html_button_tag_delete( $p_tag_id ); + } ?> diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/core/print_api.php mantis-tagging/core/print_api.php --- mantis-cvs/core/print_api.php 2007-08-24 10:54:11.000000000 -0400 +++ mantis-tagging/core/print_api.php 2007-08-24 11:00:23.000000000 -0400 @@ -11,6 +11,7 @@ $t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR; + require_once( $t_core_dir . 'ajax_api.php' ); require_once( $t_core_dir . 'current_user_api.php' ); require_once( $t_core_dir . 'string_api.php' ); require_once( $t_core_dir . 'prepare_api.php' ); @@ -255,6 +256,54 @@ PRINT "<option value=\"$t_duplicate_id\">".$t_duplicate_id."</option>"; } } + + function print_tag_attach_form( $p_bug_id, $p_string="" ) { + ?> + <small><?php echo sprintf( lang_get( 'tag_separate_by' ), config_get('tag_separator') ) ?></small> + <form method="post" action="tag_attach.php"> + <input type="hidden" name="bug_id" value="<?php echo $p_bug_id ?>" /> + <?php + print_tag_input( $p_bug_id, $p_string ); + ?> + <input type="submit" value="<?php echo lang_get( 'tag_attach' ) ?>" class="button" /> + </form> + <?php + return true; + } + + function print_tag_input( $p_bug_id = 0, $p_string="" ) { + ?> + <input type="hidden" id="tag_separator" value="<?php echo config_get( 'tag_separator' ) ?>" /> + <input type="text" name="tag_string" id="tag_string" size="40" value="<?php echo $p_string ?>" /> + <select <?php echo helper_get_tab_index() ?> name="tag_select" id="tag_select"> + <?php print_tag_option_list( $p_bug_id ); ?> + </select> + <?php + + return true; + } + + function print_tag_option_list( $p_bug_id = 0 ) { + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT id, name FROM $t_tag_table "; + if ( 0 != $p_bug_id ) { + $c_bug_id = db_prepare_int( $p_bug_id ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query .= " WHERE id NOT IN ( + SELECT tag_id FROM $t_bug_tag_table WHERE bug_id='$c_bug_id' ) "; + } + + $query .= " ORDER BY name ASC "; + $result = db_query( $query ); + + echo '<option value="0">',lang_get( 'tag_existing' ),'</option>'; + while ( $row = db_fetch_array( $result ) ) { + echo '<option value="',$row['id'],'" onclick="tag_string_append(\'',$row['name'],'\')">',$row['name'],'</option>'; + } + } + # -------------------- # Get current headlines and id prefix with v_ function print_news_item_option_list() { @@ -925,7 +974,9 @@ 'UP_STATUS' => lang_get('actiongroup_menu_update_status'), 'UP_CATEGORY' => lang_get('actiongroup_menu_update_category'), 'VIEW_STATUS' => lang_get( 'actiongroup_menu_update_view_status' ), - 'EXT_ADD_NOTE' => lang_get( 'actiongroup_menu_add_note' ) ); + 'EXT_ADD_NOTE' => lang_get( 'actiongroup_menu_add_note' ), + 'EXT_ATTACH_TAGS' => lang_get( 'actiongroup_menu_attach_tags' ), + ); $t_project_id = helper_get_current_project(); diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/core/tag_api.php mantis-tagging/core/tag_api.php --- mantis-cvs/core/tag_api.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/core/tag_api.php 2007-08-24 13:30:34.000000000 -0400 @@ -0,0 +1,678 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + /** + * Tag API + * + * @package TagAPI + * @author John Reese + */ + + $t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR; + + require_once( $t_core_dir . 'bug_api.php' ); + require_once( $t_core_dir . 'history_api.php' ); + + ### Tag API ### + + /** + * Determine if a tag exists with the given ID. + * @param integer Tag ID + * @return boolean True if tag exists + */ + function tag_exists( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table WHERE id='$c_tag_id'"; + $result = db_query( $query ) ; + + return db_num_rows( $result ) > 0; + } + + /** + * Ensure a tag exists with the given ID. + * @param integer Tag ID + */ + function tag_ensure_exists( $p_tag_id ) { + if ( !tag_exists( $p_tag_id ) ) { + error_parameters( $p_tag_id ); + trigger_error( ERROR_TAG_NOT_FOUND, ERROR ); + } + } + + /** + * 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 + * @return boolean True if name is unique + */ + function tag_is_unique( $p_name ) { + $c_name = trim( db_prepare_string( $p_name ) ); + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT id FROM $t_tag_table WHERE ".db_helper_like( 'name', $c_name ); + $result = db_query( $query ) ; + + return db_num_rows( $result ) == 0; + } + + /** + * Ensure that a name is unique. + * @param string Tag name + */ + function tag_ensure_unique( $p_name ) { + if ( !tag_is_unique( $p_name ) ) { + trigger_error( ERROR_TAG_DUPLICATE, ERROR ); + } + } + + /** + * Determine if a given name is valid. + * Name must start with letter/number and consist of letters, numbers, + * hyphens, underscores, periods, or spaces. The prefix parameter is optional, + * but allows you to prefix the regex check, which is useful for filters, etc. + * The matches parameter allows you to also receive an array of regex matches, + * which by default only includes the valid tag name itself. + * @param string Tag name + * @param string Prefix regex pattern + * @param array Array reference for regex matches + * @return boolean True if the name is valid + */ + function tag_name_is_valid( $p_name, $p_prefix="", &$p_matches=null ) { + $t_pattern = "/^$p_prefix([a-zA-Z0-9][a-zA-Z0-9-_. ]*)$/"; + return preg_match( $t_pattern, $p_name, $p_matches ); + } + + /** + * Ensure a tag name is valid. + * @param string Tag name + */ + function tag_ensure_name_is_valid( $p_name ) { + if ( !tag_name_is_valid( $p_name ) ) { + trigger_error( ERROR_TAG_NAME_INVALID, ERROR ); + } + } + + /** + * Compare two tag rows based on tag name. + * @param array Tag row 1 + * @param array Tag row 2 + * @return integer -1 when Tag 1 < Tag 2, 1 when Tag 1 > Tag 2, 0 otherwise + */ + function tag_cmp_name( $p_tag1, $p_tag2 ) { + return strcasecmp( $p_tag1['name'], $p_tag2['name'] ); + } + + /** + * Parse a form input string to extract existing and new tags. + * When given a string, parses for tag names separated by configured separator, + * then returns an array of tag rows for each tag. Existing tags get the full + * row of information returned. If the tag does not exist, a row is returned with + * 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 + * @return array Rows of tags parsed from input string + */ + function tag_parse_string( $p_string ) { + $t_tags = array(); + + $t_strings = explode( config_get( 'tag_separator' ), $p_string ); + foreach( $t_strings as $t_name ) { + $t_name = trim( $t_name ); + if ( is_blank( $t_name ) ) { continue; } + + $t_tag_row = tag_get_by_name( $t_name ); + if ( $t_tag_row !== false ) { + $t_tags[] = $t_tag_row; + } else { + if ( tag_name_is_valid( $t_name ) ) { + $t_id = -1; + } else { + $t_id = -2; + } + $t_tags[] = array( 'id' => $t_id, 'name' => $t_name ); + } + } + usort( $t_tags, "tag_cmp_name" ); + return $t_tags; + } + + /** + * Parse a filter string to extract existing and new tags. + * When given a string, parses for tag names separated by configured separator, + * then returns an array of tag rows for each tag. Existing tags get the full + * row of information returned. If the tag does not exist, a row is returned with + * 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 Filter string to parse + * @return array Rows of tags parsed from filter string + */ + function tag_parse_filters( $p_string ) { + $t_tags = array(); + $t_prefix = "[+-]{0,1}"; + + $t_strings = explode( config_get( 'tag_separator' ), $p_string ); + foreach( $t_strings as $t_name ) { + $t_name = trim( $t_name ); + if ( is_blank( $t_name ) || !tag_name_is_valid( $t_name, $t_prefix ) ) { continue; } + + $t_matches = array(); + if ( tag_name_is_valid( $t_name, $t_prefix, $t_matches ) ) { + $t_tag_row = tag_get_by_name( $t_matches[1] ); + if ( $t_tag_row !== false ) { + $t_filter = substr( $t_name, 0, 1 ); + + if ( "+" == $t_filter ) { + $t_tag_row['filter'] = 1; + } elseif ( "-" == $t_filter ) { + $t_tag_row['filter'] = -1; + } else { + $t_tag_row['filter'] = 0; + } + + $t_tags[] = $t_tag_row; + } + } + } + usort( $t_tags, "tag_cmp_name" ); + return $t_tags; + } + + # CRUD + + /** + * Return a tag row for the given ID. + * @param integer Tag ID + * @return array Tag row + */ + function tag_get( $p_tag_id ) { + tag_ensure_exists( $p_tag_id ); + + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table + WHERE id='$c_tag_id'"; + $result = db_query( $query ); + + if ( 0 == db_num_rows( $result ) ) { + return false; + } + $row = db_fetch_array( $result ); + + return $row; + } + + /** + * Return a tag row for the given name. + * @param string Tag name + * @return Tag row + */ + function tag_get_by_name( $p_name ) { + $c_name = db_prepare_string( $p_name ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "SELECT * FROM $t_tag_table + WHERE ".db_helper_like( 'name', $c_name ); + $result = db_query( $query ); + + if ( 0 == db_num_rows( $result ) ) { + return false; + } + $row = db_fetch_array( $result ); + + return $row; + } + + /** + * Return a single field from a tag row for the given ID. + * @param integer Tag ID + * @param string Field name + * @return mixed Field value + */ + function tag_get_field( $p_tag_id, $p_field_name ) { + $row = tag_get( $p_tag_id ); + + if ( isset( $row[$p_field_name] ) ) { + return $row[$p_field_name]; + } else { + error_parameters( $p_field_name ); + trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING ); + return ''; + } + } + + /** + * Create a tag with the given name, creator, and description. + * Defaults to the currently logged in user, and a blank description. + * @param string Tag name + * @param integer User ID + * @param string Description + * @return integer Tag ID + */ + function tag_create( $p_name, $p_user_id=null, $p_description='' ) { + access_ensure_has_global_level( config_get( 'tag_create_threshold' ) ); + + tag_ensure_name_is_valid( $p_name ); + tag_ensure_unique( $p_name ); + + if ( null == $p_user_id ) { + $p_used_id = auth_get_current_user_id(); + } else { + user_ensure_exists( $p_user_id ); + } + + $c_name = trim( db_prepare_string( $p_name ) ); + $c_description = db_prepare_string( $p_description ); + $c_user_id = db_prepare_int( $p_user_id ); + $c_date_created = db_now(); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "INSERT INTO $t_tag_table + ( user_id, + name, + description, + date_created, + date_updated + ) + VALUES + ( '$c_user_id', + '$c_name', + '$c_description', + ".$c_date_created.", + ".$c_date_created." + )"; + + db_query( $query ); + return db_insert_id( $t_tag_table ); + } + + /** + * Update a tag with given name, creator, and description. + * @param integer Tag ID + * @param string Tag name + * @param integer User ID + * @param string Description + */ + function tag_update( $p_tag_id, $p_name, $p_user_id, $p_description ) { + tag_ensure_exists( $p_tag_id ); + user_ensure_exists( $p_user_id ); + + if ( auth_get_current_user_id() == tag_get_field( $p_tag_id, 'user_id' ) ) { + $t_update_level = config_get( 'tag_edit_own_threshold' ); + } else { + $t_update_level = config_get( 'tag_edit_threshold' ); + } + access_ensure_has_global_level( $t_update_level ); + + tag_ensure_name_is_valid( $p_name ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + + $t_rename = false; + if ( strtolower($p_name) != strtolower($t_tag_name) ) { + tag_ensure_unique( $p_name ); + $t_rename = true; + } + + $c_tag_id = trim( db_prepare_int( $p_tag_id ) ); + $c_user_id = db_prepare_string( $p_user_id ); + $c_name = db_prepare_string( $p_name ); + $c_description = db_prepare_string( $p_description ); + $c_date_updated = db_now(); + + $t_tag_table = config_get( 'mantis_tag_table' ); + + $query = "UPDATE $t_tag_table + SET user_id='$c_user_id', + name='$c_name', + description='$c_description', + date_updated=".$c_date_updated." + WHERE id='$c_tag_id'"; + db_query( $query ); + + if ( $t_rename ) { + $t_bugs = tag_get_bugs_attached( $p_tag_id ); + + foreach ( $t_bugs as $t_bug_id ) { + history_log_event_special( $t_bug_id, TAG_RENAMED, $t_tag_name, $c_name ); + } + } + + return true; + } + + /** + * Delete a tag with the given ID. + * @param integer Tag ID + */ + function tag_delete( $p_tag_id ) { + tag_ensure_exists( $p_tag_id ); + + access_ensure_has_global_level( config_get( 'tag_edit_threshold' ) ); + + $t_bugs = tag_get_bugs_attached( $p_tag_id ); + foreach ( $t_bugs as $t_bug_id ) { + tag_bug_detach( $p_tag_id, $t_bug_id ); + } + + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query = "DELETE FROM $t_tag_table + WHERE id='$c_tag_id'"; + db_query( $query ); + + return true; + } + + # Associative + + /** + * Determine if a tag is attached to a bug. + * @param integer Tag ID + * @param integer Bug ID + * @return boolean True if the tag is attached + */ + function tag_bug_is_attached( $p_tag_id, $p_bug_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + $result = db_query( $query ); + return ( db_num_rows( $result ) > 0 ); + } + + /** + * Return the tag attachment row. + * @param integer Tag ID + * @param integer Bug ID + * @return array Tag attachment row + */ + function tag_bug_get_row( $p_tag_id, $p_bug_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + $result = db_query( $query ); + + if ( db_num_rows( $result ) == 0 ) { + trigger_error( TAG_NOT_ATTACHED, ERROR ); + } + return db_fetch_array( $result ); + } + + /** + * Return an array of tags attached to a given bug sorted by tag name. + * @param Bug ID + * @return array Array of tag rows with attachement information + */ + function tag_bug_get_attached( $p_bug_id ) { + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT t.*, b.user_id as user_attached, b.date_attached + FROM $t_tag_table as t + LEFT JOIN $t_bug_tag_table as b + on t.id=b.tag_id + WHERE b.bug_id='$c_bug_id'"; + $result = db_query( $query ); + + $rows = array(); + while ( $row = db_fetch_array( $result ) ) { + $rows[] = $row; + } + + usort( $rows, "tag_cmp_name" ); + return $rows; + } + + /** + * Return an array of bugs that a tag is attached to. + * @param integer Tag ID + * @return array Array of bug ID's. + */ + function tag_get_bugs_attached( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT bug_id FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id'"; + $result = db_query( $query ); + + $bugs = array(); + while ( $row = db_fetch_array( $result ) ) { + $bugs[] = $row['bug_id']; + } + + return $bugs; + } + + /** + * Attach a tag to a bug. + * @param integer Tag ID + * @param integer Bug ID + * @param integer User ID + */ + function tag_bug_attach( $p_tag_id, $p_bug_id, $p_user_id=null ) { + tag_ensure_exists( $p_tag_id ); + bug_ensure_exists( $p_bug_id ); + if ( null == $p_user_id ) { + $p_user_id = auth_get_current_user_id(); + } else { + user_ensure_exists( $p_user_id ); + } + + access_ensure_global_level( config_get( 'tag_detach_threshold' ) ); + + if ( tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) { + trigger_error( TAG_ALREADY_ATTACHED, ERROR ); + } + + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + $c_user_id = db_prepare_int( $p_user_id ); + $c_date_attached= db_now(); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "INSERT INTO $t_bug_tag_table + ( tag_id, + bug_id, + user_id, + date_attached + ) + VALUES + ( '$c_tag_id', + '$c_bug_id', + '$c_user_id', + ".$c_date_attached." + )"; + db_query( $query ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + history_log_event_special( $p_bug_id, TAG_ATTACHED, $t_tag_name ); + + return true; + } + + /** + * Detach a tag from a bug. + * @param integer Tag ID + * @param integer Bug ID + */ + function tag_bug_detach( $p_tag_id, $p_bug_id ) { + tag_ensure_exists( $p_tag_id ); + bug_ensure_exists( $p_bug_id ); + + if ( !tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) { + trigger_error( TAG_NOT_ATTACHED, ERROR ); + } + + if ( auth_get_current_user_id() == tag_get_field( $p_tag_id, 'user_id' ) ) { + $t_detach_level = config_get( 'tag_detach_own_threshold' ); + } else { + $t_detach_level = config_get( 'tag_detach_threshold' ); + } + access_ensure_has_global_level( $t_detach_level ); + + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_bug_id = db_prepare_int( $p_bug_id ); + + $t_bug_tag_table= config_get( 'mantis_bug_tag_table' ); + + $query = "DELETE FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id' AND bug_id='$c_bug_id'"; + db_query( $query ); + + $t_tag_name = tag_get_field( $p_tag_id, 'name' ); + history_log_event_special( $p_bug_id, TAG_DETACHED, $t_tag_name ); + + return true; + } + + # Display + + /** + * Display a tag hyperlink. + * If a bug ID is passed, the tag link will include a detach link if the + * user has appropriate privileges. + * @param array Tag row + * @param integer Bug ID + */ + function tag_display_link( $p_tag_row, $p_bug_id=0 ) { + if ( auth_get_current_user_id() == $p_tag_row[user_attached] ) { + $t_detach = config_get( 'tag_detach_own_threshold' ); + } else { + $t_detach = config_get( 'tag_detach_threshold' ); + } + + $t_name = string_display_line( $p_tag_row['name'] ); + $t_description = string_display_line( $p_tag_row['description'] ); + + echo "<a href='tag_view_page.php?tag_id=$p_tag_row[id]' title='$t_description'>$t_name</a>"; + + if ( access_has_global_level($t_detach) ) { + $t_tooltip = sprintf( lang_get( 'tag_detach' ), $t_name ); + echo " <a href='tag_detach.php?bug_id=$p_bug_id&tag_id=$p_tag_row[id]'><img src='images/delete.png' class='delete-icon' title=\"$t_tooltip\"/></a>"; + } + + return true; + } + + /** + * Display a list of attached tag hyperlinks separated by the configured hyperlinks. + * @param Bug ID + */ + function tag_display_attached( $p_bug_id ) { + $t_tag_rows = tag_bug_get_attached( $p_bug_id ); + + if ( count( $t_tag_rows ) == 0 ) { + echo lang_get( 'tag_none_attached' ); + } else { + $i = 0; + foreach ( $t_tag_rows as $t_tag ) { + echo ( $i > 0 ? config_get('tag_separator')." " : "" ); + tag_display_link( $t_tag, $p_bug_id ); + $i++; + } + } + + return true; + } + + # Statistics + + /** + * Get the number of bugs a given tag is attached to. + * @param integer Tag ID + * @return integer Number of attached bugs + */ + function tag_stats_attached( $p_tag_id ) { + $c_tag_id = db_prepare_int( $p_tag_id ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + + $query = "SELECT COUNT(*) FROM $t_bug_tag_table + WHERE tag_id='$c_tag_id'"; + $result = db_query( $query ); + + return db_result( $result ); + } + + /** + * Get a list of related tags. + * Returns a list of tags that are the most related to the given tag, + * based on the number of times they have been attached to the same bugs. + * Defaults to a list of five tags. + * @param integer Tag ID + * @param integer List size + * @return array Array of tag rows, with share count added + */ + function tag_stats_related( $p_tag_id, $p_limit=5 ) { + $t_bug_table = config_get( 'mantis_bug_table' ); + $t_tag_table = config_get( 'mantis_tag_table' ); + $t_bug_tag_table = config_get( 'mantis_bug_tag_table' ); + $t_project_user_list_table = config_get( 'mantis_project_user_list_table' ); + $t_user_table = config_get( 'mantis_user_table' ); + + $c_tag_id = db_prepare_int( $p_tag_id ); + $c_user_id = auth_get_current_user_id(); + + $subquery = "SELECT b.id FROM $t_bug_table AS b + LEFT JOIN $t_project_user_list_table AS p + ON p.project_id=b.project_id AND p.user_id=$c_user_id + JOIN $t_user_table AS u + ON u.id=$c_user_id + JOIN $t_bug_tag_table AS t + ON t.bug_id=b.id + WHERE ( p.access_level>b.view_state OR u.access_level>b.view_state ) + AND t.tag_id=$c_tag_id"; + + $query = "SELECT * FROM $t_bug_tag_table + WHERE tag_id != $c_tag_id + AND bug_id IN ( $subquery ) "; + + $result = db_query( $query ); + + $t_tag_counts = array(); + while ( $row = db_fetch_array( $result ) ) { + $t_tag_counts[$row['tag_id']]++; + } + + arsort( $t_tag_counts ); + + $t_tags = array(); + $i = 1; + foreach ( $t_tag_counts as $t_tag_id => $t_count ) { + $t_tag_row = tag_get($t_tag_id); + $t_tag_row['count'] = $t_count; + $t_tags[] = $t_tag_row; + $i++; + if ( $i > $p_limit ) { break; } + } + + return $t_tags; + } diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/core/xmlhttprequest_api.php mantis-tagging/core/xmlhttprequest_api.php --- mantis-cvs/core/xmlhttprequest_api.php 2007-08-07 10:47:13.000000000 -0400 +++ mantis-tagging/core/xmlhttprequest_api.php 2007-08-24 11:00:21.000000000 -0400 @@ -31,6 +31,15 @@ echo '</select>'; } + function xmlhttprequest_user_combobox() { + $f_user_id = gpc_get_int( 'user_id' ); + $f_user_access = gpc_get_int( 'access_level' ); + + echo '<select name="user_id">'; + print_user_option_list( $f_user_id, ALL_PROJECTS, $f_user_access ); + echo '</select>'; + } + # --------------- # Echos a serialized list of platforms starting with the prefix specified in the $_POST function xmlhttprequest_platform_get_with_prefix() { diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/css/default.css mantis-tagging/css/default.css --- mantis-cvs/css/default.css 2007-08-09 08:19:07.000000000 -0400 +++ mantis-tagging/css/default.css 2007-08-24 11:00:23.000000000 -0400 @@ -100,6 +100,7 @@ img {} img.icon { width: 11px; height: 11px; } +img.delete-icon { position: relative; top: 5px; border: 0; } div { padding: 3px; } div.menu { background-color: #e8e8e8; color: #000000; text-align: center; width: 100%; padding: 1px; } Binary files mantis-cvs/images/delete.png and mantis-tagging/images/delete.png differ diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/javascript/common.js mantis-tagging/javascript/common.js --- mantis-cvs/javascript/common.js 2007-08-14 10:12:43.000000000 -0400 +++ mantis-tagging/javascript/common.js 2007-08-24 11:00:22.000000000 -0400 @@ -163,3 +163,16 @@ setDisplay( idTag, (document.getElementById(idTag).style.display == 'none')?1:0 ); } +/* Tag functionality */ +function tag_string_append( p_string ) { + t_tag_separator = document.getElementById('tag_separator').value; + t_tag_string = document.getElementById('tag_string'); + t_tag_select = document.getElementById('tag_select'); + if ( t_tag_string.value != '' ) { + t_tag_string.value = t_tag_string.value + t_tag_separator + p_string; + } else { + t_tag_string.value = t_tag_string.value + p_string; + } + t_tag_select.selectedIndex=0; +} + diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/lang/strings_english.txt mantis-tagging/lang/strings_english.txt --- mantis-cvs/lang/strings_english.txt 2007-08-16 13:24:00.000000000 -0400 +++ mantis-tagging/lang/strings_english.txt 2007-08-24 11:00:23.000000000 -0400 @@ -39,6 +39,7 @@ $s_actiongroup_menu_update_target_version = 'Update Target Version'; $s_actiongroup_menu_update_fixed_in_version = 'Update Fixed in Version'; $s_actiongroup_menu_add_note = 'Add Note'; +$s_actiongroup_menu_attach_tags = 'Attach Tags'; $s_actiongroup_bugs = 'Selected Issues'; # new strings: @@ -275,6 +276,11 @@ $MANTIS_ERROR[ERROR_USER_CHANGE_LAST_ADMIN] = 'You cannot change the access level of the only ADMINISTRATOR in the system.'; $MANTIS_ERROR[ERROR_PAGE_REDIRECTION] = 'Page redirection error, ensure that there are no spaces outside the PHP block (<?php ?>) in config_inc.php or custom_*.php files.'; $MANTIS_ERROR[ERROR_TWITTER_NO_CURL_EXT] = 'Twitter integration requires PHP CURL extension which is not installed.'; +$MANTIS_ERROR[ERROR_TAG_NOT_FOUND] = 'Could not find a tag with that name.'; +$MANTIS_ERROR[ERROR_TAG_DUPLICATE] = 'A tag already exists with that name.'; +$MANTIS_ERROR[ERROR_TAG_NAME_INVALID] = 'That tag name is invalid.'; +$MANTIS_ERROR[ERROR_TAG_NOT_ATTACHED] = 'That tag is not attached to that bug.'; +$MANTIS_ERROR[ERROR_TAG_ALREADY_ATTACHED] = 'That tag already attached to that bug.'; $s_login_error = 'Your account may be disabled or blocked or the username/password you entered is incorrect.'; $s_login_cookies_disabled = 'Your browser either doesn\'t know how to handle cookies, or refuses to handle them.'; @@ -1347,6 +1353,38 @@ # wiki related strings $s_wiki = 'Wiki'; +# Tagging +$s_tags = 'Tags'; +$s_tag_details = 'Tag Details: %s'; +$s_tag_id = 'Tag ID'; +$s_tag_name = 'Name'; +$s_tag_creator = 'Creator'; +$s_tag_created = 'Date Created'; +$s_tag_updated = 'Last Updated'; +$s_tag_description = 'Tag Description'; +$s_tag_statistics = 'Usage Statistics'; +$s_tag_update = 'Update Tag: %s'; +$s_tag_update_return = 'Back to Tag'; +$s_tag_update_button = 'Update Tag'; +$s_tag_delete_button = 'Delete Tag'; +$s_tag_delete_message = 'Are you sure you wish to delete this tag?'; +$s_tag_existing = 'Existing tags'; +$s_tag_none_attached = 'No tags attached.'; +$s_tag_attach = 'Attach'; +$s_tag_attach_long = 'Attach Tags'; +$s_tag_attach_failed = 'Tag attachment failed.'; +$s_tag_detach = 'Detach \'%s\''; +$s_tag_separate_by = "(Separate by '%s')"; +$s_tag_invalid_name = 'Invalid tag name.'; +$s_tag_create_denied = 'Create permission denied.'; +$s_tag_filter_default = 'Attached Issues (%s)'; +$s_tag_history_attached = 'Tag Attached'; +$s_tag_history_detached = 'Tag Detached'; +$s_tag_history_renamed = 'Tag Renamed'; +$s_tag_related = 'Related Tags'; +$s_tag_related_issues = 'Shared Issues (%s)'; +$s_tag_stats_attached = 'Issues attached: %s'; + # Time Tracking $s_time_tracking_billing_link = 'Billing'; $s_time_tracking = 'Time tracking'; diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/tag_attach.php mantis-tagging/tag_attach.php --- mantis-cvs/tag_attach.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_attach.php 2007-08-24 13:27:04.000000000 -0400 @@ -0,0 +1,108 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + $f_bug_id = gpc_get_int( 'bug_id' ); + $f_tag_select = gpc_get_int( 'tag_select' ); + $t_user_id = auth_get_current_user_id(); + + access_ensure_global_level( config_get( 'tag_attach_threshold' ) ); + + $t_tags = tag_parse_string( gpc_get_string( 'tag_string' ) ); + $t_can_create = access_has_global_level( config_get( 'tag_create_threshold' ) ); + + $t_tags_create = array(); + $t_tags_attach = array(); + $t_tags_failed = array(); + + foreach ( $t_tags as $t_tag_row ) { + if ( -1 == $t_tag_row['id'] ) { + if ( $t_can_create ) { + $t_tags_create[] = $t_tag_row; + } else { + $t_tags_failed[] = $t_tag_row; + } + } elseif ( -2 == $t_tag_row['id'] ) { + $t_tags_failed[] = $t_tag_row; + } else { + $t_tags_attach[] = $t_tag_row; + } + } + + if ( 0 < $f_tag_select && tag_exists( $f_tag_select ) ) { + $t_tags_attach[] = tag_get( $f_tag_select ); + } + + if ( count( $t_tags_failed ) > 0 ) { + html_page_top1( lang_get( 'tag_attach_long' ).' '.bug_format_summary( $f_bug_id, SUMMARY_CAPTION ) ); + html_page_top2(); +?> +<br/> +<table class="width75" align="center"> + <tr class="row-category"> + <td colspan="2"><?php echo lang_get( 'tag_attach_failed' ) ?></td> + </tr> + <tr class="spacer"><td colspan="2"></td></tr> +<?php + $t_tag_string = ""; + foreach( $t_tags_attach as $t_tag_row ) { + if ( "" != $t_tag_string ) { + $t_tag_string .= config_get( 'tag_separator' ); + } + $t_tag_string .= $t_tag_row['name']; + } + + foreach( $t_tags_failed as $t_tag_row ) { + echo '<tr ',helper_alternate_class(),'>'; + if ( -1 == $t_tag_row['id'] ) { + echo '<td class="category">',lang_get( 'tag_invalid_name' ),'</td>'; + } elseif ( -2 == $t_tag_row['id'] ) { + echo '<td class="category">',lang_get( 'tag_create_denied' ),'</td>'; + } + echo '<td>',$t_tag_row['name'],'</td></tr>'; + + if ( "" != $t_tag_string ) { + $t_tag_string .= config_get( 'tag_separator' ); + } + $t_tag_string .= $t_tag_row['name']; + } +?> + <tr class="spacer"><td colspan="2"></td></tr> + <tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_attach_long' ) ?></td> + <td> +<?php + print_tag_input( $f_bug_id, $t_tag_string ); +?> + </td> + </tr> +</table> +<?php + html_page_bottom1(__FILE__); + } else { + foreach( $t_tags_create as $t_tag_row ) { + $t_tag_row['id'] = tag_create( $t_tag_row['name'], $t_user_id ); + $t_tags_attach[] = $t_tag_row; + } + + foreach( $t_tags_attach as $t_tag_row ) { + if ( ! tag_bug_is_attached( $t_tag_row['id'], $f_bug_id ) ) { + tag_bug_attach( $t_tag_row['id'], $f_bug_id, $t_user_id ); + } + } + + print_successful_redirect_to_bug( $f_bug_id ); + } diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/tag_delete.php mantis-tagging/tag_delete.php --- mantis-cvs/tag_delete.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_delete.php 2007-08-24 13:27:16.000000000 -0400 @@ -0,0 +1,27 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + access_ensure_global_level( config_get( 'tag_edit_threshold' ) ); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + helper_ensure_confirmed( lang_get( 'tag_delete_message' ), lang_get( 'tag_delete_button' ) ); + + tag_delete( $f_tag_id ); + + print_successful_redirect( config_get( 'default_home_page' ) ); diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/tag_detach.php mantis-tagging/tag_detach.php --- mantis-cvs/tag_detach.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_detach.php 2007-08-24 13:27:26.000000000 -0400 @@ -0,0 +1,33 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $f_bug_id = gpc_get_int( 'bug_id' ); + + $t_tag_row = tag_get( $f_tag_id ); + $t_tag_bug_row = tag_bug_get_row( $f_tag_id, $f_bug_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_detach_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_bug_row['user_id'] ) + && access_has_global_level( config_get( 'tag_detach_own_threshold' ) ) ) ) + { + access_denied(); + } + + tag_bug_detach( $f_tag_id, $f_bug_id ); + + print_successful_redirect_to_bug( $f_bug_id ); diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/tag_update_page.php mantis-tagging/tag_update_page.php --- mantis-cvs/tag_update_page.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_update_page.php 2007-08-24 13:27:45.000000000 -0400 @@ -0,0 +1,105 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'ajax_api.php' ); + require_once( $t_core_path . 'tag_api.php' ); + + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_row['user_id'] ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + access_denied(); + } + + html_page_top1( sprintf( lang_get( 'tag_update' ), $t_tag_row['name'] ) ); + html_page_top2(); +?> + +<br/> +<form method="post" action="tag_update.php"> +<table class="width100" cellspacing="1"> + +<!-- Title --> +<tr> + <td class="form-title" colspan="2"> + <?php echo sprintf( lang_get( 'tag_update' ), $t_tag_row['name'] ) ?> + <input type="hidden" name="tag_id" value="<?php echo $f_tag_id ?>"/> + </td> + <td class="right" colspan="3"> + <?php print_bracket_link( 'tag_view_page.php?tag_id='.$f_tag_id, lang_get( 'tag_update_return' ) ); ?> + </td> +</tr> + +<!-- 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> +</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_tag_row['name'] ?>"/></td> + <td><?php + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + if ( ON == config_get( 'use_javascript' ) ) { + $t_username = prepare_user_name( $t_tag_row['user_id'] ); + echo ajax_click_to_edit( $t_username, 'user_id', 'entrypoint=user_combobox&user_id=' . $t_tag_row['user_id'] . '&access_level=' . config_get( 'tag_create_threshold' ) ); + } else { + echo '<select ', helper_get_tab_index(), ' name="user_id">'; + print_user_option_list( $t_tag_row['user_id'], ALL_PROJECTS, config_get( 'tag_create_threshold' ) ); + echo '</select>'; + } + } else { + echo user_get_name($t_tag_row['user_id']); + } + ?></td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_created'] ) ) ?> </td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_updated'] ) ) ?> </td> +</tr> + +<!-- spacer --> +<tr class="spacer"> + <td colspan="5"></td> +</tr> + +<!-- Description --> +<tr <?php echo helper_alternate_class() ?>> + <td class="category"><?php echo lang_get( 'tag_description' ) ?></td> + <td colspan="4"> + <textarea name="description" <?php echo helper_get_tab_index() ?> cols="80" rows="6"><?php echo $t_tag_row['description'] ?></textarea> + </td> +</tr> + +<!-- Submit Button --> +<tr> + <td class="center" colspan="6"> + <input <?php echo helper_get_tab_index() ?> type="submit" class="button" value="<?php echo lang_get( 'tag_update_button' ) ?>" /> + </td> +</tr> + +</table> +</form> + +<?php + html_page_bottom1( __FILE__ ); +?> diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/tag_update.php mantis-tagging/tag_update.php --- mantis-cvs/tag_update.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_update.php 2007-08-24 13:27:35.000000000 -0400 @@ -0,0 +1,55 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + if ( ! ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) + || ( auth_get_current_user_id() == $t_tag_row['user_id'] ) + && access_has_global_level( config_get( 'tag_edit_own_threshold' ) ) ) ) + { + access_denied(); + } + + if ( access_has_global_level( config_get( 'tag_edit_threshold' ) ) ) { + $f_new_user_id = gpc_get_int( 'user_id', $t_tag_row['user_id'] ); + } else { + $f_new_user_id = $t_tag_row['user_id']; + } + + $f_new_name = gpc_get_string( 'name', $t_tag_row['name'] ); + $f_new_description = gpc_get_string( 'description', $t_tag_row['description'] ); + + $t_update = false; + + if ( $t_tag_row['user_id'] != $f_new_user_id ) { + user_ensure_exists( $f_new_user_id ); + $t_update = true; + } + + if ( $t_tag_row['name'] != $f_new_name || + $t_tag_row['description'] != $f_new_description ) { + + $t_update = true; + } + + tag_update( $f_tag_id, $f_new_name, $f_new_user_id, $f_new_description ); + + $t_url = 'tag_view_page.php?tag_id='.$f_tag_id; + print_successful_redirect( $t_url ); diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/tag_view_page.php mantis-tagging/tag_view_page.php --- mantis-cvs/tag_view_page.php 1969-12-31 19:00:00.000000000 -0500 +++ mantis-tagging/tag_view_page.php 2007-08-24 11:00:23.000000000 -0400 @@ -0,0 +1,107 @@ +<?php + # Mantis - a php based bugtracking system + # Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org + # Copyright (C) 2002 - 2007 Mantis Team - mantisbt-dev@lists.sourceforge.net + # This program is distributed under the terms and conditions of the GPL + # See the README and LICENSE files for details + + # -------------------------------------------------------- + # $Id: $ + # -------------------------------------------------------- + + require_once( 'core.php' ); + + $t_core_path = config_get( 'core_path' ); + + require_once( $t_core_path . 'tag_api.php' ); + + access_ensure_global_level( config_get( 'tag_view_threshold' ) ); + compress_enable(); + + $f_tag_id = gpc_get_int( 'tag_id' ); + $t_tag_row = tag_get( $f_tag_id ); + + $t_name = string_display_line( $t_tag_row['name'] ); + $t_description = string_display( $t_tag_row['description'] ); + + html_page_top1( sprintf( lang_get( 'tag_details' ), $t_tag_row['name'] ) ); + html_page_top2(); +?> + +<br/> +<table class="width100" cellspacing="1"> + +<!-- Title --> +<tr> + <td class="form-title" colspan="2"> + <?php echo sprintf( lang_get( 'tag_details' ), $t_tag_row['name'] ) ?> + + </td> + <td class="right" colspan="3"> + <?php print_bracket_link( 'search.php?hide_status_id=90&tag_string='.urlencode($t_tag_row['name']), sprintf( lang_get( 'tag_filter_default' ), tag_stats_attached( $f_tag_id ) ) ); ?> + </td> +</tr> + +<!-- 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> +</tr> + +<tr <?php echo helper_alternate_class() ?>> + <td><?php echo $t_tag_row['id'] ?></td> + <td><?php echo $t_name ?></td> + <td><?php echo user_get_name($t_tag_row['user_id']) ?></td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_created'] ) ) ?> </td> + <td><?php echo print_date( config_get( 'normal_date_format' ), db_unixtimestamp( $t_tag_row['date_updated'] ) ) ?> </td> +</tr> + +<!-- spacer --> +<tr class="spacer"> + <td colspan="5"></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> +</tr> + +<!-- Statistics --> +<?php + $t_tags_related = tag_stats_related( $f_tag_id ); + if ( count( $t_tags_related ) ) { + echo '<tr ',helper_alternate_class(),'>'; + echo '<td class="category" rowspan="',count( $t_tags_related ),'">',lang_get( 'tag_related' ),'</td>'; + + $i = 0; + foreach( $t_tags_related as $t_tag ) { + $t_name = string_display_line( $t_tag['name'] ); + $t_description = string_display_line( $t_tag['description'] ); + $t_count = $t_tag['count']; + + 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">'; + print_bracket_link( 'search.php?hide_status_id=90&tag_string='.urlencode("+$t_tag_row[name]".config_get('tag_separator')."+$t_name"), sprintf( lang_get( 'tag_related_issues' ), $t_tag['count'] ) ); + echo '</a></td></tr>'; + + $i++; + } + } +?> + +<!-- Buttons --> +<tr> + <td colspan="5"> + <?php html_buttons_tag_view_page( $f_tag_id ); ?> + </td> +</tr> + +</table> +<?php + html_page_bottom1( __FILE__ ); +?> diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/view_all_set.php mantis-tagging/view_all_set.php --- mantis-cvs/view_all_set.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/view_all_set.php 2007-08-24 11:00:22.000000000 -0400 @@ -203,6 +203,9 @@ $f_do_filter_by_date = gpc_get_bool( 'do_filter_by_date' ); $f_view_state = gpc_get_int( 'view_state', META_FILTER_ANY ); + $f_tag_string = gpc_get_string( 'tag_string', '' ); + $f_tag_select = gpc_get_int( 'tag_select', '0' ); + $t_custom_fields = custom_field_get_ids(); # @@@ (thraxisp) This should really be the linked ids, but we don't know the project $f_custom_fields_data = array(); if ( is_array( $t_custom_fields ) && ( sizeof( $t_custom_fields ) > 0 ) ) { @@ -414,6 +417,8 @@ $t_setting_arr['platform'] = $f_platform; $t_setting_arr['os'] = $f_os; $t_setting_arr['os_build'] = $f_os_build; + $t_setting_arr['tag_string'] = $f_tag_string; + $t_setting_arr['tag_select'] = $f_tag_select; break; # Set the sort order and direction case '2': diff -urN --exclude=CVS --exclude=.svn --exclude='.git*' --exclude='*.swp' --exclude=config_inc.php mantis-cvs/view_filters_page.php mantis-tagging/view_filters_page.php --- mantis-cvs/view_filters_page.php 2007-08-07 10:47:14.000000000 -0400 +++ mantis-tagging/view_filters_page.php 2007-08-24 11:00:22.000000000 -0400 @@ -16,6 +16,7 @@ require_once( $t_core_path.'bug_api.php' ); require_once( $t_core_path.'string_api.php' ); require_once( $t_core_path.'date_api.php' ); + require_once( $t_core_path.'tag_api.php' ); auth_ensure_user_authenticated(); @@ -406,7 +407,8 @@ </tr> <tr class="row-category2"> <td class="small-caption" colspan="<?php echo ( 1 * $t_custom_cols ); ?>"><?php echo lang_get( 'search' ) ?></td> -<td class="small-caption" colspan="<?php echo ( ( $t_filter_cols - 1 ) * $t_custom_cols ); ?>"></td> +<td class="small-caption" colspan="<?php echo ( ( $t_filter_cols - 2 ) * $t_custom_cols ); ?>"><?php echo lang_get( 'tags' ) ?></td> +<td class="small-caption" colspan="<?php echo ( 1 * $t_custom_cols ); ?>"></td> </tr> <tr> <!-- Search field --> @@ -414,13 +416,12 @@ <input type="text" size="16" name="search" value="<?php echo string_html_specialchars( $t_filter['search'] ); ?>" /> </td> - <td class="small-caption" colspan="<?php echo ( ( $t_filter_cols - 3 ) * $t_custom_cols ); ?>"></td> + <td class="small-caption" colspan="<?php echo ( ( $t_filter_cols - 2 ) * $t_custom_cols ); ?>"><?php print_filter_tag_string() ?></td> <!-- Submit button --> <td class="right" colspan="<?php echo ( 1 * $t_custom_cols ); ?>"> <input type="submit" name="filter" class="button" value="<?php echo lang_get( 'filter_button' ) ?>" /> </td> - <td class="small-caption" colspan="<?php echo ( 1 * $t_custom_cols ); ?>"></td> </tr> </table> </form> | ||||
Patch 'mantis-tagging-2007-08-17.patch' was created from CVS head on Friday, Aug 17, 2007. Test site is available at http://leetcode.homeip.net/mantis-tagging |
|
Comments on the patch
|
|
|
|
Patch 'mantis-tagging-2007-08-20.patch' was created from CVS head on Monday, Aug 20, 2007. Major changes from previous patch:
|
|
Good job. I've tested the sample website after your second patch and following are my comments (I haven't looked at the code yet):
|
|
1 & 2: Will do.
|
|
Replies to your replies:
|
|
I have a quick look at the code, it looks good. Following are my comments:
|
|
I've just remembered one more needed feature. We should be able to attach/detach tags as a group action. For example, in the case of the official Mantis Bug Tracker, we will define some tags and then apply them to the applicable issues. Without this feature, it will be very hard to get started with this feature. Group attaching should ignore issues that already have the tag and group detaching should ignore issues that don't have the tag. I had a look at the wiki requirements now, and found the following: We should add group actions to add/remove tags. (vboctor)Did you also implement the following one: What will happen when a user is deleted? What will happen to the tags assigned by him? I think we should keep them, we should handle this the same way this is handled in other places. I think we keep the user id, and refer to the user as user10 or whatever the id was. (vboctor) |
|
Multiline descriptions are handled by the string_display_line() function, so that newlines are stripped. However, I did not specifically handle long descriptions, because all browsers show different amounts of tooltip information. If, for example, you want to only show the first 200 characters, that should just a substr() added before string_display_line(). Regarding information, that's a good argument, so I will alter the SQL queries appropriately, and include that in the next patch. As for the extra combo-box, that's part of the 'standard' tag form I created, and it would allow someone to pick existing tags if, for example, they lack creation privileges and need to see what they can choose from. 1,2,4,5,6,7,9: I will do this tomorrow when I get in to work.
I will look into adding tagging to the group actions set. Am I assuming correctly that this is only used from the View Issues page, or are there more locations I would need to modify for this feature? Finally, I was under the impression that the user_get_name() function would handle this situation, and that is what I used in all places to display the appropriate username. Is there a different approach I should be using for this? |
|
|
|
Patch 'mantis-tagging-2007-08-24.patch' was created from CVS head on Friday, Aug 24, 2007. This should be a feature complete patch, including group actions, no private information disclosure, fixed/optimized queries, API permissions checks, API documentation, and many other various fixes and modifications. If nobody finds any major issues with this patch, I am prepared to commit it sometime this afternoon or evening. Otherwise, I will be on the road all day tomorrow traveling to Ohio and will need to wait for Sunday or Monday to be online from my in-laws' house. |
|
Looks good. Some minor comments then go ahead and commit.
Another question that we need to look at: (Don't hold the commit for that, but it may require some follow up work). Are nest queries (or sub queries) supported in other DBMSes that we are supporting? If so, then there are other places that we should use. Specifically in areas like filter_api we can get a huge improvement in performance by using them at least for dbs that support them. I was thinking of db_nested_queries_supported() in database_api.php. |
|
I was under the impression that MySQL before 4.1 was one of the few DBMS choices that didn't support subqueries. AFAIK, all of the other ADOdb DBMS's support subqueries. And I will go ahead and comment all the functions, and make your suggested bug_delete() changes, and then I will commit the resulting code after I do the final testing. |
|
Testing has completed successfully, and feature has been committed to CVS. |
|