Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1026 lines (876 sloc) 29.1 KB
<?php
# MantisBT - A PHP based bugtracking system
# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT. If not, see <http://www.gnu.org/licenses/>.
/**
* Tag API
*
* @package CoreAPI
* @subpackage TagAPI
* @author John Reese
* @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
* @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net
* @link http://www.mantisbt.org
*
* @uses access_api.php
* @uses antispam_api.php
* @uses authentication_api.php
* @uses bug_api.php
* @uses config_api.php
* @uses constant_inc.php
* @uses database_api.php
* @uses error_api.php
* @uses form_api.php
* @uses history_api.php
* @uses lang_api.php
* @uses string_api.php
* @uses user_api.php
* @uses utility_api.php
*/
require_api( 'access_api.php' );
require_api( 'antispam_api.php' );
require_api( 'authentication_api.php' );
require_api( 'bug_api.php' );
require_api( 'config_api.php' );
require_api( 'constant_inc.php' );
require_api( 'database_api.php' );
require_api( 'error_api.php' );
require_api( 'form_api.php' );
require_api( 'history_api.php' );
require_api( 'lang_api.php' );
require_api( 'string_api.php' );
require_api( 'user_api.php' );
require_api( 'utility_api.php' );
# cache the tag definitions, indexed by tag id
# tag ids that dont exist are stored as 'false', to avoid repeated searches
$g_cache_tags = array();
# cache the bug tags indexed by [bug_id, tag_id]. Items stored are rows (arrays) fetched from table {bug_tags}
# bugs with no tags will be stored as 'false'.
$g_cache_bug_tags = array();
/**
* Loads into cache a set of tag definitions from tag table
* Non existant ids are cached as 'false'
* @global array $g_cache_tags
* @param array $p_tag_ids Array of tag ids
* @return void
*/
function tag_cache_rows( array $p_tag_ids ) {
global $g_cache_tags;
$t_ids_to_seach = array();
foreach( $p_tag_ids as $t_id ) {
if( !isset( $g_cache_tags[(int)$t_id]) ) {
$t_ids_to_seach[(int)$t_id] = (int)$t_id;
}
}
if( empty( $t_ids_to_seach ) ) {
return;
}
db_param_push();
$t_sql_in_params = array();
$t_params = array();
foreach( $t_ids_to_seach as $t_id ) {
$t_sql_in_params[] = db_param();
$t_params[] = $t_id;
}
$t_query = 'SELECT * FROM {tag} where id IN (' . implode( ',', $t_sql_in_params ) . ')';
$t_result = db_query( $t_query, $t_params );
while( $t_row = db_fetch_array( $t_result ) ) {
$c_id = (int)$t_row['id'];
$g_cache_tags[$c_id] = $t_row;
unset( $t_ids_to_seach[$c_id] );
}
# mark the non existant ids
foreach( $t_ids_to_seach as $t_id ) {
$g_cache_tags[$t_id] = false;
}
}
/**
* Loads into cache the tags associated to a set of bug ids
* A bug id that has no tags will be cached as 'false'
* @global array $g_cache_bug_tags
* @param array $p_bug_ids Array of bug ids
* @return void
*/
function tag_cache_bug_tag_rows( array $p_bug_ids ) {
global $g_cache_bug_tags;
$t_ids_to_seach = array();
foreach( $p_bug_ids as $t_id ) {
if( !isset( $g_cache_bug_tags[(int)$t_id]) ) {
$t_ids_to_seach[] = (int)$t_id;
}
}
if( empty( $t_ids_to_seach ) ) {
return;
}
db_param_push();
$t_sql_in_params = array();
$t_params = array();
foreach( $t_ids_to_seach as $t_id ) {
$t_sql_in_params[] = db_param();
$t_params[] = $t_id;
}
$t_query = 'SELECT B.id AS bug_id, BT.tag_id, BT.user_id, BT.date_attached FROM {bug} B LEFT OUTER JOIN {bug_tag} BT ON B.id=BT.bug_id'
. ' WHERE B.id IN (' . implode( ',', $t_sql_in_params ) . ')';
$t_result = db_query( $t_query, $t_params );
$t_found_tags = array();
while( $t_row = db_fetch_array( $t_result ) ) {
$c_bug_id = (int)$t_row['bug_id'];
$t_has_tags = null !== $t_row['tag_id'];
# create a bug index if needed
if( !isset( $g_cache_bug_tags[$c_bug_id] ) ) {
$g_cache_bug_tags[$c_bug_id] = $t_has_tags? array() : false;
}
if( $t_has_tags ) {
$c_tag_id = (int)$t_row['tag_id'];
$g_cache_bug_tags[$c_bug_id][$c_tag_id] = $t_row;
$t_found_tags[$c_tag_id] = $c_tag_id;
}
}
# also cache the tags founds
if( !empty( $t_found_tags ) ) {
tag_cache_rows( $t_found_tags );
}
}
/**
* Clear the bug tags cache (or just the given bug id if specified)
* @global array $g_cache_bug_tags
* @param integer $p_bug_id Bug id
* @return void
*/
function tag_clear_cache_bug_tags( $p_bug_id = null ) {
global $g_cache_bug_tags;
if( null === $p_bug_id ) {
$g_cache_bug_tags = array();
} else {
if( isset( $g_cache_bug_tags[(int)$p_bug_id] ) ) {
unset( $g_cache_bug_tags[(int)$p_bug_id] );
}
}
}
/**
* Determine if a tag exists with the given ID.
* @param integer $p_tag_id A tag ID to check.
* @return boolean True if tag exists
*/
function tag_exists( $p_tag_id ) {
return ( tag_get( $p_tag_id ) !== false );
}
/**
* Ensure a tag exists with the given ID.
* @param integer $p_tag_id A tag ID to check.
* @return void
*/
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 $p_name The tag name to check.
* @return boolean True if name is unique
*/
function tag_is_unique( $p_name ) {
$c_name = trim( $p_name );
$t_query = 'SELECT id FROM {tag} WHERE ' . db_helper_like( 'name' );
$t_result = db_query( $t_query, array( $c_name ) );
if( db_result( $t_result ) ) {
return false;
}
return true;
}
/**
* Ensure that a name is unique.
* @param string $p_name The tag name to check.
* @return void
*/
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 not begin with '+' and '-' characters (they are used for
* filters) and must not contain the configured tag separator.
* The matches parameter allows to also receive an array of regex matches,
* which by default only includes the valid tag name itself.
* The prefix parameter is optional, but allows you to prefix the regex
* check, which is useful for filters, etc.
* @param string $p_name The tag name to check.
* @param array &$p_matches Array reference for regex matches.
* @param string $p_prefix The regex pattern to use as a prefix.
* @return boolean True if the name is valid.
*/
function tag_name_is_valid( $p_name, array &$p_matches, $p_prefix = '' ) {
$t_separator = config_get( 'tag_separator' );
$t_pattern = '/^' . $p_prefix . '([^\+\-' . $t_separator . '][^' . $t_separator . ']*)$/';
return preg_match( $t_pattern, $p_name, $p_matches );
}
/**
* Ensure a tag name is valid.
* @param string $p_name The tag name to check.
* @return void
*/
function tag_ensure_name_is_valid( $p_name ) {
$t_matches = array();
if( !tag_name_is_valid( $p_name, $t_matches ) ) {
trigger_error( ERROR_TAG_NAME_INVALID, ERROR );
}
}
/**
* Compare two tag rows based on tag name.
* @param array $p_tag1 The first tag row to compare.
* @param array $p_tag2 The second tag row to compare.
* @return int -1 when Tag 1 < Tag 2, 1 when Tag 1 > Tag 2, 0 otherwise
*/
function tag_cmp_name( array $p_tag1, array $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 $p_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_matches = array();
$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_matches ) ) {
$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;
}
/**
* Attaches a bunch of tags to the specified issue.
*
* @param int $p_bug_id The bug id.
* @param string $p_tag_string String of tags separated by configured separator.
* @param int $p_tag_id Tag id to add or 0 to skip.
* @return array|bool true for success, otherwise array of failures. The array elements follow the tag_parse_string()
* format.
*/
function tag_attach_many( $p_bug_id, $p_tag_string, $p_tag_id = 0 ) {
# If no work, then there is no need to do access check.
if( $p_tag_id === 0 && is_blank( $p_tag_string ) ) {
return true;
}
access_ensure_bug_level( config_get( 'tag_attach_threshold' ), $p_bug_id );
$t_tags = tag_parse_string( $p_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;
}
} else if( -2 == $t_tag_row['id'] ) {
$t_tags_failed[] = $t_tag_row;
} else {
$t_tags_attach[] = $t_tag_row;
}
}
if( 0 < $p_tag_id && tag_exists( $p_tag_id ) ) {
$t_tags_attach[] = tag_get( $p_tag_id );
}
# failed to attach at least one tag
if( count( $t_tags_failed ) > 0 ) {
return $t_tags_failed;
}
foreach( $t_tags_create as $t_tag_row ) {
$t_tag_row['id'] = tag_create( $t_tag_row['name'] );
$t_tags_attach[] = $t_tag_row;
}
foreach( $t_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 );
}
}
event_signal( 'EVENT_TAG_ATTACHED', array( $p_bug_id, $t_tags_attach ) );
return true;
}
/**
* 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 $p_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 );
$t_matches = array();
if( !is_blank( $t_name ) && tag_name_is_valid( $t_name, $t_matches, $t_prefix ) ) {
$t_tag_row = tag_get_by_name( $t_matches[1] );
if( $t_tag_row !== false ) {
$t_filter = utf8_substr( $t_name, 0, 1 );
if( '+' == $t_filter ) {
$t_tag_row['filter'] = 1;
} else if( '-' == $t_filter ) {
$t_tag_row['filter'] = -1;
} else {
$t_tag_row['filter'] = 0;
}
$t_tags[] = $t_tag_row;
}
} else {
continue;
}
}
usort( $t_tags, 'tag_cmp_name' );
return $t_tags;
}
/**
* Returns all available tags
*
* @param integer $p_name_filter A string to match the beginning of the tag name.
* @param integer $p_count The number of tags to return.
* @param integer $p_offset The offset of the result.
*
* @return ADORecordSet|boolean Tags sorted by name, or false if the query failed.
*/
function tag_get_all( $p_name_filter, $p_count, $p_offset ) {
$t_where = '';
$t_where_params = array();
if( !is_blank( $p_name_filter ) ) {
$t_where = 'WHERE ' . db_helper_like( 'name' );
$t_where_params[] = $p_name_filter . '%';
}
$t_query = 'SELECT * FROM {tag} ' . $t_where . ' ORDER BY name';
return db_query( $t_query, $t_where_params, $p_count, $p_offset );
}
/**
* Counts all available tags
* @param integer $p_name_filter A string to match the beginning of the tag name.
* @return integer
*/
function tag_count( $p_name_filter ) {
$t_where = '';
$t_where_params = array();
if( $p_name_filter ) {
$t_where = ' WHERE ' . db_helper_like( 'name' );
$t_where_params[] = $p_name_filter . '%';
}
$t_query = 'SELECT count(*) FROM {tag}' . $t_where;
$t_result = db_query( $t_query, $t_where_params );
$t_row = db_fetch_array( $t_result );
return (int)db_result( $t_result );
}
/**
* Return a tag row for the given ID.
* @param integer $p_tag_id The tag ID to retrieve from the database.
* @return boolean|array Tag row, or false if not found
*/
function tag_get( $p_tag_id ) {
global $g_cache_tags;
$c_tag_id = (int)$p_tag_id;
if( !isset( $g_cache_tags[$c_tag_id] ) ) {
tag_cache_rows( array( $c_tag_id ) );
}
$t_tag = $g_cache_tags[$c_tag_id];
if( null === $t_tag ) {
return false;
} else {
return $t_tag;
}
}
/**
* Return a tag row for the given name.
* @param string $p_name The tag name to retrieve from the database.
* @return array|boolean Tag row
*/
function tag_get_by_name( $p_name ) {
db_param_push();
$t_query = 'SELECT * FROM {tag} WHERE ' . db_helper_like( 'name' );
$t_result = db_query( $t_query, array( $p_name ) );
$t_row = db_fetch_array( $t_result );
if( !$t_row ) {
return false;
}
return $t_row;
}
/**
* Return a single field from a tag row for the given ID.
* @param integer $p_tag_id The tag id to lookup.
* @param string $p_field_name The field name to retrieve from the tag.
* @return array Field value
*/
function tag_get_field( $p_tag_id, $p_field_name ) {
$t_row = tag_get( $p_tag_id );
if( isset( $t_row[$p_field_name] ) ) {
return $t_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 $p_name The tag name to create.
* @param integer $p_user_id The user ID to link the new tag to.
* @param string $p_description A Description for the tag.
* @return int Tag ID
*/
function tag_create( $p_name, $p_user_id = null, $p_description = '' ) {
access_ensure_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_user_id = auth_get_current_user_id();
} else {
user_ensure_exists( $p_user_id );
}
$c_date_created = db_now();
db_param_push();
$t_query = 'INSERT INTO {tag}
( user_id, name, description, date_created, date_updated )
VALUES
( ' . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ')';
db_query( $t_query, array( $p_user_id, trim( $p_name ), trim( $p_description ), $c_date_created, $c_date_created ) );
return db_insert_id( db_get_table( 'tag' ) );
}
/**
* Update a tag with given name, creator, and description.
* @param integer $p_tag_id The tag ID which is being updated.
* @param string $p_name The name of the tag.
* @param integer $p_user_id The user ID to set when updating the tag. Note: This replaces the existing user id.
* @param string $p_description An updated description for the tag.
* @return boolean
*/
function tag_update( $p_tag_id, $p_name, $p_user_id, $p_description ) {
$t_tag_row = tag_get( $p_tag_id );
$t_tag_name = $t_tag_row['name'];
if( $t_tag_name == $p_name &&
$t_tag_row['description'] == $p_description &&
$t_tag_row['user_id'] == $p_user_id ) {
# nothing has changed
return true;
}
user_ensure_exists( $p_user_id );
if( auth_get_current_user_id() == $t_tag_row['user_id'] ) {
$t_update_level = config_get( 'tag_edit_own_threshold' );
} else {
$t_update_level = config_get( 'tag_edit_threshold' );
}
access_ensure_global_level( $t_update_level );
tag_ensure_name_is_valid( $p_name );
$t_rename = false;
if( utf8_strtolower( $p_name ) != utf8_strtolower( $t_tag_name ) ) {
tag_ensure_unique( $p_name );
$t_rename = true;
}
$c_date_updated = db_now();
db_param_push();
$t_query = 'UPDATE {tag}
SET user_id=' . db_param() . ',
name=' . db_param() . ',
description=' . db_param() . ',
date_updated=' . db_param() . '
WHERE id=' . db_param();
db_query( $t_query, array( (int)$p_user_id, $p_name, $p_description, $c_date_updated, $p_tag_id ) );
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, $p_name );
}
}
return true;
}
/**
* Delete a tag with the given ID.
* @param integer $p_tag_id The tag ID to delete.
* @return boolean
*/
function tag_delete( $p_tag_id ) {
tag_ensure_exists( $p_tag_id );
access_ensure_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 );
}
db_param_push();
$t_query = 'DELETE FROM {tag} WHERE id=' . db_param();
db_query( $t_query, array( $p_tag_id ) );
return true;
}
/**
* Gets the candidates for the specified bug. These are existing tags
* that are not associated with the bug already.
*
* @param integer $p_bug_id The bug id, if 0 returns all tags.
* @return array The array of tag rows, each with id, name, and description.
*/
function tag_get_candidates_for_bug( $p_bug_id ) {
db_param_push();
$t_params = array();
if( 0 != $p_bug_id ) {
$t_params[] = $p_bug_id;
if( config_get_global( 'db_type' ) == 'odbc_mssql' ) {
db_param_push();
$t_query = 'SELECT t.id FROM {tag} t
LEFT JOIN {bug_tag} b ON t.id=b.tag_id
WHERE b.bug_id IS NULL OR b.bug_id != ' . db_param();
$t_result = db_query( $t_query, $t_params );
$t_params = null;
$t_subquery_results = array();
while( $t_row = db_fetch_array( $t_result ) ) {
$t_subquery_results[] = (int)$t_row['id'];
}
if( count( $t_subquery_results ) == 0 ) {
db_param_pop();
return array();
}
$t_query = 'SELECT id, name, description FROM {tag} WHERE id IN ( ' . implode( ', ', $t_subquery_results ) . ')';
} else {
$t_query = 'SELECT id, name, description FROM {tag} WHERE id IN (
SELECT t.id FROM {tag} t
LEFT JOIN {bug_tag} b ON t.id=b.tag_id
WHERE b.bug_id IS NULL OR b.bug_id != ' . db_param() .
')';
}
} else {
$t_query = 'SELECT id, name, description FROM {tag}';
}
$t_query .= ' ORDER BY name ASC ';
$t_result = db_query( $t_query, $t_params );
$t_results_to_return = array();
while( $t_row = db_fetch_array( $t_result ) ) {
$t_results_to_return[] = $t_row;
}
return $t_results_to_return;
}
/**
* Determine if a tag is attached to a bug.
* @param integer $p_tag_id The tag ID to check.
* @param integer $p_bug_id The bug ID to check.
* @return boolean True if the tag is attached
*/
function tag_bug_is_attached( $p_tag_id, $p_bug_id ) {
db_param_push();
$t_query = 'SELECT bug_id FROM {bug_tag} WHERE tag_id=' . db_param() . ' AND bug_id=' . db_param();
$t_result = db_query( $t_query, array( $p_tag_id, $p_bug_id ) );
return( db_result( $t_result ) !== false );
}
/**
* Return the tag attachment row.
* @param integer $p_tag_id The tag ID to check.
* @param integer $p_bug_id The bug ID to check.
* @return array Tag attachment row
*/
function tag_bug_get_row( $p_tag_id, $p_bug_id ) {
global $g_cache_bug_tags;
$c_bug_id = (int)$p_bug_id;
if( !isset( $g_cache_bug_tags[$c_bug_id] ) ) {
tag_cache_bug_tag_rows( array( $c_bug_id ) );
}
$t_bug_tags = $g_cache_bug_tags[$c_bug_id];
if( !$t_bug_tags || !isset( $t_bug_tags[$p_tag_id] ) ) {
trigger_error( TAG_NOT_ATTACHED, ERROR );
}
return $t_bug_tags[$p_tag_id];
}
/**
* Return an array of tags attached to a given bug sorted by tag name.
* @param integer $p_bug_id The bug ID to check.
* @return array Array of tag rows with attachment information
*/
function tag_bug_get_attached( $p_bug_id ) {
global $g_cache_bug_tags;
$c_bug_id = (int)$p_bug_id;
if( !isset( $g_cache_bug_tags[$c_bug_id] ) ) {
tag_cache_bug_tag_rows( array( $c_bug_id ) );
}
$t_bug_tags = $g_cache_bug_tags[$c_bug_id];
if( !$t_bug_tags ) {
return array();
}
$t_tag_info_rows = array();
foreach( $t_bug_tags as $t_row ) {
$t_tag_data = tag_get($t_row['tag_id']);
$t_tag_data['user_attached'] = $t_row['user_id'];
$t_tag_data['date_attached'] = $t_row['date_attached'];
$t_tag_info_rows[] = $t_tag_data;
}
usort( $t_tag_info_rows, 'tag_cmp_name' );
return $t_tag_info_rows;
}
/**
* Return an array of bugs that a tag is attached to.
* @param integer $p_tag_id The tag ID to check.
* @return array Array of bug ID's.
*/
function tag_get_bugs_attached( $p_tag_id ) {
db_param_push();
$t_query = 'SELECT bug_id FROM {bug_tag} WHERE tag_id=' . db_param();
$t_result = db_query( $t_query, array( $p_tag_id ) );
$t_bugs = array();
while( $t_row = db_fetch_array( $t_result ) ) {
$t_bugs[] = $t_row['bug_id'];
}
return $t_bugs;
}
/**
* Attach a tag to a bug.
* @param integer $p_tag_id The tag ID to attach.
* @param integer $p_bug_id The bug ID to attach.
* @param integer $p_user_id The user ID to attach.
* @return boolean
*/
function tag_bug_attach( $p_tag_id, $p_bug_id, $p_user_id = null ) {
antispam_check();
access_ensure_bug_level( config_get( 'tag_attach_threshold' ), $p_bug_id, $p_user_id );
tag_ensure_exists( $p_tag_id );
if( tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) {
trigger_error( TAG_ALREADY_ATTACHED, ERROR );
}
if( null == $p_user_id ) {
$p_user_id = auth_get_current_user_id();
} else {
user_ensure_exists( $p_user_id );
}
db_param_push();
$t_query = 'INSERT INTO {bug_tag}
( tag_id, bug_id, user_id, date_attached )
VALUES
( ' . db_param() . ',' . db_param() . ',' . db_param() . ',' . db_param() . ')';
db_query( $t_query, array( $p_tag_id, $p_bug_id, $p_user_id, db_now() ) );
tag_clear_cache_bug_tags( $p_bug_id );
$t_tag_name = tag_get_field( $p_tag_id, 'name' );
history_log_event_special( $p_bug_id, TAG_ATTACHED, $t_tag_name );
# updated the last_updated date
bug_update_date( $p_bug_id );
return true;
}
/**
* Detach a tag from a bug.
* @param integer $p_tag_id The tag ID to detach.
* @param integer $p_bug_id The bug ID to detach.
* @param boolean $p_add_history Add history entries to bug.
* @param integer $p_user_id User Id (or null for current logged in user).
* @return boolean
*/
function tag_bug_detach( $p_tag_id, $p_bug_id, $p_add_history = true, $p_user_id = null ) {
if( $p_user_id === null ) {
$t_user_id = auth_get_current_user_id();
} else {
$t_user_id = $p_user_id;
}
if( !tag_bug_is_attached( $p_tag_id, $p_bug_id ) ) {
trigger_error( TAG_NOT_ATTACHED, ERROR );
}
$t_tag_row = tag_bug_get_row( $p_tag_id, $p_bug_id );
if( $t_user_id == tag_get_field( $p_tag_id, 'user_id' ) || $t_user_id == $t_tag_row['user_id'] ) {
$t_detach_level = config_get( 'tag_detach_own_threshold' );
} else {
$t_detach_level = config_get( 'tag_detach_threshold' );
}
access_ensure_bug_level( $t_detach_level, $p_bug_id, $t_user_id );
db_param_push();
$t_query = 'DELETE FROM {bug_tag} WHERE tag_id=' . db_param() . ' AND bug_id=' . db_param();
db_query( $t_query, array( $p_tag_id, $p_bug_id ) );
tag_clear_cache_bug_tags( $p_bug_id );
if( $p_add_history ) {
$t_tag_name = tag_get_field( $p_tag_id, 'name' );
history_log_event_special( $p_bug_id, TAG_DETACHED, $t_tag_name );
}
# updated the last_updated date
bug_update_date( $p_bug_id );
return true;
}
/**
* Detach all tags from a given bug.
* @param integer $p_bug_id The bug ID to detach.
* @param boolean $p_add_history Add history entries to bug.
* @param integer $p_user_id User Id (or null for current logged in user).
* @return void
*/
function tag_bug_detach_all( $p_bug_id, $p_add_history = true, $p_user_id = null ) {
$t_tags = tag_bug_get_attached( $p_bug_id );
foreach( $t_tags as $t_tag_row ) {
tag_bug_detach( $t_tag_row['id'], $p_bug_id, $p_add_history, $p_user_id );
}
}
/**
* Builds a hyperlink to the Tag Detail page
* @param array $p_tag_row Tag row.
* @return string
*/
function tag_get_link( array $p_tag_row ) {
return sprintf(
'<a class="btn btn-xs btn-primary btn-white btn-round" href="tag_view_page.php?tag_id=%s" title="%s">%s</a>',
$p_tag_row['id'],
string_display_line( $p_tag_row['description'] ),
string_display_line( $p_tag_row['name'] )
);
}
/**
* 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 $p_tag_row Tag row.
* @param integer $p_bug_id The bug ID to display.
* @return boolean
*/
function tag_display_link( array $p_tag_row, $p_bug_id = 0 ) {
static $s_security_token = null;
if( is_null( $s_security_token ) ) {
$s_security_token = htmlspecialchars( form_security_param( 'tag_detach' ) );
}
echo tag_get_link( $p_tag_row );
if( isset( $p_tag_row['user_attached'] ) && auth_get_current_user_id() == $p_tag_row['user_attached']
|| auth_get_current_user_id() == $p_tag_row['user_id']
) {
$t_detach = config_get( 'tag_detach_own_threshold' );
} else {
$t_detach = config_get( 'tag_detach_threshold' );
}
if( $p_bug_id > 0 && access_has_bug_level( $t_detach, $p_bug_id ) ) {
$t_tooltip = string_html_specialchars( sprintf( lang_get( 'tag_detach' ), string_display_line( $p_tag_row['name'] ) ) );
$t_href = 'tag_detach.php?bug_id=' . $p_bug_id . '&amp;tag_id=' . $p_tag_row['id'] . $s_security_token;
echo ' <a class="btn btn-xs btn-primary btn-white btn-round" title="' . $t_tooltip . '" href="' . $t_href . '">';
echo '<i class="fa fa-times"></i>';
echo '</a>';
}
return true;
}
/**
* Display a list of attached tag hyperlinks separated by the configured hyperlinks.
* @param integer $p_bug_id The bug ID to display.
* @return boolean
*/
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;
}
/**
* Get all attached tags separated by the Tag Separator.
* @param integer $p_bug_id The bug ID to display.
* @return string tags separated by the configured Tag Separator
*/
function tag_bug_get_all( $p_bug_id ) {
$t_tag_rows = tag_bug_get_attached( $p_bug_id );
$t_value = '';
$i = 0;
foreach( $t_tag_rows as $t_tag ) {
$t_value .= ( $i > 0 ? config_get( 'tag_separator' ) . ' ' : '' );
$t_value .= $t_tag['name'];
$i++;
}
return $t_value;
}
/**
* Get the number of bugs a given tag is attached to.
* @param integer $p_tag_id The tag ID to retrieve statistics on.
* @return int Number of attached bugs
*/
function tag_stats_attached( $p_tag_id ) {
db_param_push();
$t_query = 'SELECT COUNT(*) FROM {bug_tag} WHERE tag_id=' . db_param();
$t_result = db_query( $t_query, array( $p_tag_id ) );
return db_result( $t_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 $p_tag_id The tag ID to retrieve statistics on.
* @param integer $p_limit List size.
* @return array Array of tag rows, with share count added
*/
function tag_stats_related( $p_tag_id, $p_limit = 5 ) {
$c_user_id = auth_get_current_user_id();
db_param_push();
$t_query = 'SELECT * FROM {bug_tag}
WHERE tag_id != ' . db_param(); # 1st Param
$t_subquery = 'SELECT b.id FROM {bug} b
LEFT JOIN {project_user_list} p
ON p.project_id=b.project_id AND p.user_id=' . db_param() . # 2nd Param
' JOIN {user} u
ON u.id=' . db_param() . # 3rd Param
' JOIN {bug_tag} 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=' . db_param(); # 4th Param
$t_query .= ' AND bug_id IN ( ' . $t_subquery . ' ) ';
$t_result = db_query( $t_query, array( $p_tag_id, $c_user_id, $c_user_id, $p_tag_id ) );
$t_tag_counts = array();
while( $t_row = db_fetch_array( $t_result ) ) {
if( !isset( $t_tag_counts[$t_row['tag_id']] ) ) {
$t_tag_counts[$t_row['tag_id']] = 1;
} else {
$t_tag_counts[$t_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;
}