1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-03 09:49:30 +02:00
ampache/lib/class/tag.class.php
2013-11-23 01:11:19 +01:00

656 lines
20 KiB
PHP

<?php
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
/**
*
* LICENSE: GNU General Public License, version 2 (GPLv2)
* Copyright 2001 - 2013 Ampache.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License v2
* as published by the Free Software Foundation.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
/**
* Tag Class
*
* This class hnadles all of the tag relation operations
*
*/
class Tag extends database_object
{
public $id;
public $name;
// constructed
public $weight=0;
public $count=0;
public $owner=0;
/**
* constructor
* This takes a tag id and returns all of the relevent information
*/
public function __construct($id)
{
if (!$id) { return false; }
$info = $this->get_info($id);
foreach ($info as $key=>$value) {
$this->$key = $value;
} // end foreach
} // constructor
/**
* construct_from_name
* This attempts to construct the tag from a name, rather then the ID
*/
public static function construct_from_name($name)
{
$tag_id = self::tag_exists($name);
$tag = new Tag($tag_id);
return $tag;
} // construct_from_name
/**
* format
* This makes the tag presentable to the great humans that use this program, other life forms
* will just have to fend for themselves
*/
public function format($type=0,$object_id=0)
{
if ($type AND !self::validate_type($type)) { return false; }
if ($type) {
$this->set_object($type,$object_id);
}
$size = 3 + ($this->weight-1) - ($this->count-1);
if (abs($size) > 4) { $size = 4; }
if (abs($size) < 1) { $size = 1; }
if ($this->owner == $GLOBALS['user']->id) {
$action = '?page=tag&action=remove_tag&type=' . scrub_out($type) . '&tag_id=' . intval($this->id) . '&object_id=' . intval($object_id);
$class = "hover-remove ";
} else {
$action = '?page=tag&action=add_tag&type=' . scrub_out($type) . '&tag_id=' . intval($this->id) . '&object_id=' . intval($object_id);
$class = "hover-add ";
}
$class .= 'tag_size' . $size;
$this->f_class = $class;
$this->f_name = Ajax::text($action,$this->name,'modify_tag_' . $this->id . '_' . $object_id,'',$class);
} // format
/**
* set_object
* This associates the tag with a specified object, we try to get the
* data from the map cache, otherwise I guess we'll just have to look it
* up.
*/
public function set_object($type,$object_id)
{
if (parent::is_cached('tag_top_' . $type,$object_id)) {
$data = parent::get_from_cache('tag_top_' . $type,$object_id);
} else {
$data = self::get_top_tags($type,$object_id);
}
// If nothing is found, then go ahead and return false
if (!is_array($data) OR !count($data)) { return false; }
$this->weight = count($data[$this->id]['users']);
if (in_array($GLOBALS['user']->id,$data[$this->id]['users'])) {
$this->owner = $GLOBALS['user']->id;
}
$this->count = count($data);
} // set_object
/**
* build_cache
* This takes an array of object ids and caches all of their information
* in a single query, cuts down on the connections
*/
public static function build_cache($ids)
{
if (!is_array($ids) OR !count($ids)) { return false; }
$idlist = '(' . implode(',',$ids) . ')';
$sql = "SELECT * FROM `tag` WHERE `id` IN $idlist";
$db_results = Dba::read($sql);
while ($row = Dba::fetch_assoc($db_results)) {
parent::add_to_cache('tag',$row['id'],$row);
}
return true;
} // build_cache
/**
* build_map_cache
* This builds a cache of the mappings for the specified object, no limit is given
*/
public static function build_map_cache($type,$ids)
{
if (!is_array($ids) OR !count($ids)) { return false; }
$type = self::validate_type($type);
$idlist = '(' . implode(',',$ids) . ')';
$sql = "SELECT `tag_map`.`id`,`tag_map`.`tag_id`,`tag_map`.`object_id`,`tag_map`.`user` FROM `tag_map` " .
"WHERE `tag_map`.`object_type`='$type' AND `tag_map`.`object_id` IN $idlist ";
$db_results = Dba::read($sql);
$tags = array();
while ($row = Dba::fetch_assoc($db_results)) {
$tags[$row['object_id']][$row['tag_id']]['users'][] = $row['user'];
$tag_map[$row['object_id']] = array('id'=>$row['id'],'tag_id'=>$row['tag_id'],'user'=>$row['user'],'object_type'=>$type,'object_id'=>$row['object_id']);
}
// Run through our original ids as we also want to cache NULL
// results
foreach ($ids as $id) {
if (!isset($tags[$id])) {
$tags[$id] = null;
$tag_map[$id] = null;
}
parent::add_to_cache('tag_top_' . $type, $id, $tags[$id]);
parent::add_to_cache('tag_map_' . $type, $id, $tag_map[$id]);
}
return true;
} // build_map_cache
/**
* add
* This is a wrapper function, it figures out what we need to add, be it a tag
* and map, or just the mapping
*/
public static function add($type,$id,$value,$user=false)
{
// Validate the tag type
if (!self::validate_type($type)) { return false; }
if (!is_numeric($id)) { return false; }
$cleaned_value = self::clean_tag($value);
if (!strlen($cleaned_value)) { return false; }
$uid = ($user === false) ? intval($user) : intval($GLOBALS['user']->id);
// Check and see if the tag exists, if not create it, we need the tag id from this
if (!$tag_id = self::tag_exists($cleaned_value)) {
$tag_id = self::add_tag($cleaned_value);
}
if (!$tag_id) {
debug_event('Error','Error unable to create tag value:' . $cleaned_value . ' unknown error','1');
return false;
}
// We've got the tag id, let's see if it's already got a map, if not then create the map and return the value
if (!$map_id = self::tag_map_exists($type,$id,$tag_id,$user)) {
$map_id = self::add_tag_map($type,$id,$tag_id,$user);
}
return $map_id;
} // add
/**
* add_tag
* This function adds a new tag, for now we're going to limit the tagging a bit
*/
public static function add_tag($value)
{
// Clean it up and make it tagish
$value = self::clean_tag($value);
if (!strlen($value)) { return false; }
$value = Dba::escape($value);
$sql = "REPLACE INTO `tag` SET `name`='$value'";
$db_results = Dba::write($sql);
$insert_id = Dba::insert_id();
parent::add_to_cache('tag_name',$value,$insert_id);
return $insert_id;
} // add_tag
/**
* add_tag_map
* This adds a specific tag to the map for specified object
*/
public static function add_tag_map($type,$object_id,$tag_id,$user='')
{
$uid = ($user == '') ? intval($GLOBALS['user']->id) : intval($user);
$tag_id = intval($tag_id);
if (!self::validate_type($type)) { return false; }
$id = intval($object_id);
if (!$tag_id || !$id) { return false; }
$sql = "INSERT INTO `tag_map` (`tag_id`,`user`,`object_type`,`object_id`) " .
"VALUES ('$tag_id','$uid','$type','$id')";
$db_results = Dba::write($sql);
$insert_id = Dba::insert_id();
parent::add_to_cache('tag_map_' . $type,$insert_id,array('tag_id'=>$tag_id,'user'=>$uid,'object_type'=>$type,'object_id'=>$id));
return $insert_id;
} // add_tag_map
/**
* gc
*
* This cleans out tag_maps that are obsolete and then removes tags that
* have no maps.
*/
public static function gc()
{
$sql = "DELETE FROM `tag_map` USING `tag_map` LEFT JOIN `song` ON `song`.`id`=`tag_map`.`object_id` " .
"WHERE `tag_map`.`object_type`='song' AND `song`.`id` IS NULL";
$db_results = Dba::write($sql);
$sql = "DELETE FROM `tag_map` USING `tag_map` LEFT JOIN `album` ON `album`.`id`=`tag_map`.`object_id` " .
"WHERE `tag_map`.`object_type`='album' AND `album`.`id` IS NULL";
$db_results = Dba::write($sql);
$sql = "DELETE FROM `tag_map` USING `tag_map` LEFT JOIN `artist` ON `artist`.`id`=`tag_map`.`object_id` " .
"WHERE `tag_map`.`object_type`='artist' AND `artist`.`id` IS NULL";
$db_results = Dba::write($sql);
$sql = "DELETE FROM `tag_map` USING `tag_map` LEFT JOIN `video` ON `video`.`id`=`tag_map`.`object_id` " .
"WHERE `tag_map`.`object_type`='video' AND `video`.`id` IS NULL";
$db_results = Dba::write($sql);
// Now nuke the tags themselves
$sql = "DELETE FROM `tag` USING `tag` LEFT JOIN `tag_map` ON `tag`.`id`=`tag_map`.`tag_id` " .
"WHERE `tag_map`.`id` IS NULL";
$db_results = Dba::write($sql);
}
/**
* tag_exists
* This checks to see if a tag exists, this has nothing to do with objects or maps
*/
public static function tag_exists($value)
{
if (parent::is_cached('tag_name',$value)) {
return parent::get_from_cache('tag_name',$value);
}
$value = Dba::escape($value);
$sql = "SELECT * FROM `tag` WHERE `name`='$value'";
$db_results = Dba::read($sql);
$results = Dba::fetch_assoc($db_results);
parent::add_to_cache('tag_name',$results['name'],$results['id']);
return $results['id'];
} // tag_exists
/**
* tag_map_exists
* This looks to see if the current mapping of the current object of the current tag of the current
* user exists, lots of currents... taste good in scones.
*/
public static function tag_map_exists($type,$object_id,$tag_id,$user)
{
if (!self::validate_type($type)) { return false; }
if (parent::is_cached('tag_map_' . $type,$object_id)) {
$data = parent::get_from_cache('tag_map_' . $type,$object_id);
return $data['id'];
}
$object_id = Dba::escape($object_id);
$tag_id = Dba::escape($tag_id);
$user = Dba::escape($user);
$type = Dba::escape($type);
$sql = "SELECT * FROM `tag_map` WHERE `tag_id`='$tag_id' AND `user`='$user' AND `object_id`='$object_id' AND `object_type`='$type'";
$db_results = Dba::read($sql);
$results = Dba::fetch_assoc($db_results);
parent::add_to_cache('tag_map_' . $type,$results['id'],$results);
return $results['id'];
} // tag_map_exists
/**
* get_top_tags
* This gets the top tags for the specified object using limit
*/
public static function get_top_tags($type, $object_id, $limit = 10)
{
if (!self::validate_type($type)) { return false; }
if (parent::is_cached('tag_top_' . $type,$object_id)) {
return parent::get_from_cache('tag_top_' . $type,$object_id);
}
$object_id = intval($object_id);
$limit = intval($limit);
$sql = "SELECT `tag_id`, `user` FROM `tag_map` " .
"WHERE `object_type`='$type' AND `object_id`='$object_id' " .
"LIMIT $limit";
$db_results = Dba::read($sql);
$results = array();
while ($row = Dba::fetch_assoc($db_results)) {
$results[$row['tag_id']]['users'][] = $row['user'];
}
parent::add_to_cache('tag_top_' . $type,$object_id,$results);
return $results;
} // get_top_tags
/**
* get_object_tags
* Display all tags that apply to maching target type of the specified id
* UNUSED
*/
public static function get_object_tags($type, $id)
{
if (!self::validate_type($type)) { return array(); }
$id = Dba::escape($id);
$sql = "SELECT `tag_map`.`id`, `tag`.`name`, `tag_map`.`user` FROM `tag` " .
"LEFT JOIN `tag_map` ON `tag_map`.`tag_id`=`tag`.`id` " .
"WHERE `tag_map`.`object_type`='$type' AND `tag_map`.`object_id`='$id'";
$results = array();
$db_results = Dba::read($sql);
while ($row = Dba::fetch_assoc($db_results)) {
$results[] = $row;
}
return $results;
} // get_object_tags
/**
* get_tag_objects
* This gets the objects from a specified tag and returns an array of object ids, nothing more
*/
public static function get_tag_objects($type,$tag_id,$count='',$offset='')
{
if (!self::validate_type($type)) { return array(); }
if ($count) {
$limit_sql = "LIMIT ";
if ($offset) $limit_sql .= intval($offset) . ',';
$limit_sql .= intval($count);
}
$sql = "SELECT DISTINCT `tag_map`.`object_id` FROM `tag_map` " .
"WHERE `tag_map`.`tag_id` = ? AND `tag_map`.`object_type` = ? $limit_sql";
$db_results = Dba::read($sql, array($tag_id, $type));
$results = array();
while ($row = Dba::fetch_assoc($db_results)) {
$results[] = $row['object_id'];
}
return $results;
} // get_tag_objects
/**
* get_tags
* This is a non-object non type depedent function that just returns tags
* we've got, it can take filters (this is used by the tag cloud)
*/
public static function get_tags($limit = 0,$filters=array())
{
$sql = "SELECT `tag_map`.`tag_id`,COUNT(`tag_map`.`object_id`) AS `count` " .
"FROM `tag_map` " .
"LEFT JOIN `tag` ON `tag`.`id`=`tag_map`.`tag_id` " .
"GROUP BY `tag`.`name` ORDER BY `count` DESC ";
if ($limit >0) {
$sql .= " LIMIT $limit";
}
$db_results = Dba::read($sql);
$results = array();
while ($row = Dba::fetch_assoc($db_results)) {
if ($row['count'] > $top) { $top = $row['count']; }
$results[$row['tag_id']] = array('id'=>$row['tag_id'],'count'=>$row['count']);
$count+= $row['count'];
}
// Do something with this
$min = $row['count'];
return $results;
} // get_tags
/**
* get_display
* This returns a human formated version of the tags that we are given
* it also takes a type so that it knows how to return it, this is used
* by the formating functions of the different objects
*/
public static function get_display($tags)
{
if (!is_array($tags)) { return ''; }
$results = '';
// Iterate through the tags, format them according to type and element id
foreach ($tags as $tag_id=>$value) {
$tag = new Tag($tag_id);
$results .= $tag->name . ', ';
}
$results = rtrim($results,', ');
return $results;
} // get_display
/**
* update_tag_list
* Update the tags list based on commated list (ex. tag1,tag2,tag3,..)
*/
public static function update_tag_list($tags_comma, $type, $object_id)
{
debug_event('tag.class', 'Updating tags for values {'.$tags_comma.'} type {'.$type.'} object_id {'.$object_id.'}', '5');
$ctags = Tag::get_top_tags($type, $object_id);
$editedTags = explode(",", $tags_comma);
foreach ($ctags as $ctid => $ctv) {
$ctag = new Tag($ctid);
foreach ($editedTags as $tk => $tv) {
if ($ctag->name == $tv) {
debug_event('tag.class', 'Tag {'.$ctag->name.'} already found. Do nothing.', '5');
// Removing the tag from the new tags array
unset($editedTags[$tk]);
break;
} else {
debug_event('tag.class', 'Tag {'.$ctag->name.'} not found in the new list. Delete it.', '5');
$ctag->remove_map($type, $object_id);
break;
}
}
}
// Look if we need to add some new tags
foreach ($editedTags as $tk => $tv) {
debug_event('tag.class', 'Adding new tag {'.$tv.'}', '5');
Tag::add($type, $object_id, $tv, false);
}
} // update_tag_list
/**
* count
* This returns the count for the all objects associated with this tag
* If a type is specific only counts for said type are returned
*/
public function count($type='')
{
if ($type) {
$filter_sql = " AND `object_type`='" . Dba::escape($type) . "'";
}
$results = array();
$sql = "SELECT COUNT(`id`) AS `count`,`object_type` FROM `tag_map` WHERE `tag_id`='" . Dba::escape($this->id) . "'" . $filter_sql . " GROUP BY `object_type`";
$db_results = Dba::read($sql);
while ($row = Dba::fetch_assoc($db_results)) {
$results[$row['object_type']] = $row['count'];
}
return $results;
} // count
/**
* filter_with_prefs
* This filters the tags based on the users preference
*/
public static function filter_with_prefs($l)
{
$colors = array('#0000FF',
'#00FF00', '#FFFF00', '#00FFFF','#FF00FF','#FF0000');
$prefs = 'tag company';
// $prefs = Config::get('tags_userlist');
$ulist = explode(' ', $prefs);
$req = '';
foreach ($ulist as $i) {
$req .= "'" . Dba::escape($i) . "',";
}
$req = rtrim($req, ',');
$sql = 'SELECT `id`,`username` FROM `user` WHERE ';
if ($prefs=='all') {
$sql .= '1';
} else {
$sql .= 'username in ('.$req.')';
}
$db_results = Dba::read($sql);
$uids=array();
$usernames = array();
$p = 0;
while ($r = Dba::fetch_assoc($db_results)) {
$usernames[$r['id']] = $r['username'];
$uids[$r['id']] = $colors[$p];
$p++;
if ($p == sizeof($colors)) {
$p = 0;
}
}
$res = array();
foreach ($l as $i) {
if ($GLOBALS['user']->id == $i['user']) {
$res[] = $i;
} elseif (isset($uids[$i['user']])) {
$i['color'] = $uids[$i['user']];
$i['username'] = $usernames[$i['user']];
$res[] = $i;
}
}
return $res;
} // filter_with_prefs
/**
* remove_map
* This will only remove tag maps for the current user
*/
public function remove_map($type,$object_id)
{
if (!self::validate_type($type)) { return false; }
$type = Dba::escape($type);
$tag_id = Dba::escape($this->id);
$object_id = Dba::escape($object_id);
$user_id = Dba::escape($GLOBALS['user']->id);
$sql = "DELETE FROM `tag_map` WHERE `tag_id`='$tag_id' AND `object_type`='$type' AND `object_id`='$object_id' AND `user`='$user_id'";
$db_results = Dba::write($sql);
return true;
} // remove_map
/**
* validate_type
* This validates the type of the object the user wants to tag, we limit this to types
* we currently support
*/
public static function validate_type($type)
{
$valid_array = array('song','artist','album','video','playlist','live_stream');
if (in_array($type,$valid_array)) { return $type; }
return false;
} // validate_type
/**
* clean_tag
* This takes a string and makes it Tagish
*/
public static function clean_tag($value)
{
$tag = preg_replace("/[^\w\_\-\s\&]/","",$value);
return $tag;
} // clean_tag
} // end of Tag class