Source for file LibertyTag.php
Documentation is available at LibertyTag.php
* @copyright Copyright (c) 2004-2006, bitweaver.org
* All Rights Reserved. See below for details and a complete list of authors.
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See http://www.gnu.org/copyleft/lesser.html for details.
require_once( KERNEL_PKG_PATH. 'BitBase.php' );
/* Delete when package complete! -wjames5
* get all tags limited by content_id <- load
* get all tags <- getList
* get all content for tag_id <- getContentList
* get tags by map use count <- getList with map refs count
* get tags sorted by tagged date <- getList sorted by map tagged date
* store new tags from tags array
* store new tag-content map for content_id
* expunge tag and all tag-content maps
* expunge tag-content map for content_id
* Load all the tags for a given ContentId
* @param pParamHash be sure to pass by reference in case we need to make modifcations to the hash
INNER JOIN `". BIT_DB_PREFIX. "tags` tg ON tg.`tag_id` = tgc.`tag_id`
WHERE tgc.`content_id`=?";
//$this->mInfo = $this->mDb->query( $query, array( $this->mContentId ) );
$result = $this->mDb->query( $query, array( $this->mContentId ) );
while ($res = $result->fetchRow()) {
$this->mInfo['tags'] = $ret;
if( !empty( $pParamHash['tag_id'] ) && is_numeric( $pParamHash['tag_id'] )) {
$selectSql = ''; $joinSql = ''; $whereSql = '';
$whereSql .= "WHERE tg.`tag_id` = ?";
$bindVars[] = $pParamHash['tag_id'];
if ( $result = $this->mDb->getRow( $query, $bindVars ) ){
* Make sure the data is safe to store
* @param array pParams reference to hash of values that will be used to store the page, they will be modified where necessary
* @return bool TRUE on success, FALSE if verify failed. If FALSE, $this->mErrors will have reason why
function verify( &$pParamHash ) {
global $gBitUser, $gBitSystem;
$pParamHash['tag_store'] = array();
$pParamHash['tag_map_store'] = array();
if(!empty( $pParamHash['tag'])){
$pParamHash['tag_store']['tag'] = $pParamHash['tag'];
if( !empty( $pParamHash['tag_id']) && is_numeric( $pParamHash['tag_id'])){
// $pParamHash['tag_map_store']['tag_id'] = $pParamHash['tag_id'];
$pParamHash['tag_store']['tag_id'] = $pParamHash['tag_id'];
if( isset ( $pParamHash['tagged_on']) ){
$pParamHash['tag_map_store']['tagged_on'] = $pParamHash['tagged_on'];
$pParamHash['tag_map_store']['tagged_on'] = $gBitSystem->getUTCTime();
if( @$this->verifyId( $pParamHash['content_id']) ){
$pParamHash['tag_map_store']['content_id'] = $pParamHash['content_id'];
$this->mErrors['content_id'] = "No content id specified.";
if( $gBitUser->mUserId ){
$pParamHash['tag_map_store']['tagger_id'] = $gBitUser->mUserId;
$this->mErrors['user_id'] = "No user id specified.";
$selectSql = ''; $joinSql = ''; $whereSql = '';
// Bounds checking on tag name length
if( !empty( $pParamHash['tag'] ) && strlen( $pParamHash['tag'] ) > 64 ) {
$pParamHash['tag'] = substr( $pParamHash['tag'], 0, 64 );
// if tag_id supplied, use that
if( !empty( $pParamHash['tag_id'] ) && is_numeric( $pParamHash['tag_id'] )) {
$whereSql .= "WHERE tg.`tag_id` = ?";
$bindVars[] = $pParamHash['tag_id'];
}elseif( isset ( $pParamHash['tag'] ) ) {
$whereSql .= "WHERE tg.`tag` = ?";
$bindVars[] = $pParamHash['tag'];
$query = " SELECT tg.* FROM `". BIT_DB_PREFIX. "tags` tg $whereSql";
if ( $result = $this->mDb->getRow( $query, $bindVars ) ){
$pParamHash['tag_id'] = $result['tag_id'];
$this->mTagId = $result['tag'];
* @param array pParams hash of values that will be used to store the page
* @return bool TRUE on success, FALSE if store could not occur. If FALSE, $this->mErrors will have reason why
function store( &$pParamHash ) {
if( $this->verify( $pParamHash ) ) {
$this->mDb->StartTrans();
if (!empty($pParamHash['tag_store'])) {
if( $this->verifyTag($pParamHash['tag_store']) ) {
$pParamHash['tag_map_store']['tag_id'] = $pParamHash['tag_store']['tag_id'];
$this->mDb->associateInsert( $maptable, $pParamHash['tag_map_store'] );
$pParamHash['tag_store']['tag_id'] = $this->mDb->GenID( 'tags_tag_id_seq' );
$this->mDb->associateInsert( $tagtable, $pParamHash['tag_store'] );
$this->mTagId = $pParamHash['tag_map_store']['tag_id'] = $pParamHash['tag_store']['tag_id'];
$this->mDb->associateInsert( $maptable, $pParamHash['tag_map_store'] );
$this->mDb->CompleteTrans();
// since we use store generally in a loop of several tags we should not load here
if( $this->verify( $pParamHash ) ) {
$this->mDb->StartTrans();
if (!empty($pParamHash['tag_store'])) {
if( isset ($pParamHash['tag_store']['tag_id']) ) {
//this is kind of ugly but it works right
$this->mDb->associateUpdate( $tagtable, array("tag" => $pParamHash['tag_store']['tag']), array( "tag_id" => $pParamHash['tag_id'] ) );
$pParamHash['tag_store']['tag_id'] = $this->mDb->GenID( 'tags_tag_id_seq' );
$this->mDb->associateInsert( $tagtable, $pParamHash['tag_store'] );
$this->mDb->CompleteTrans();
$this->loadTag( $pParamHash['tag_store'] );
if( $gBitSystem->isFeatureActive("tags_strip_spaces") ) {
if( $gBitSystem->isFeatureActive("tags_strip_nonword") ) {
if( $gBitSystem->isFeatureActive("tags_lowercase") ) {
if( $gBitSystem->getConfig('tags_strip_regexp') ) {
$pTag = preg_replace($gBitSystem->getConfig('tags_strip_regexp', $pTag), $gBitSystem->getConfig('tags_strip_replace'), $pTag);
/* make tag data is safe to store
global $gBitUser, $gBitSystem;
$pParamHash['tag_map_store'] = array();
//this is to set the time we add content to a tag.
$timeStamp = $gBitSystem->getUTCTime();
//need to break up this string
$tagMixed = isset ($pParamHash['tags']) ? $pParamHash['tags'] : NULL;
if( !empty( $tagMixed )){
$tagIds = explode( ",", $tagMixed );
$tagIds = array( $tagMixed );
foreach( $tagIds as $value ) {
/* Ignore empty tags like a trailing , generate */
'tagged_on' => $timeStamp,
'user_id' => $gBitUser->mUserId,
$this->mErrors[$value] = "Invalid tag.";
* @param array pParams hash includes mix of tags that will be storeded and associated with a ContentId used by service
* @return bool TRUE on success, FALSE if store could not occur. If FALSE, $this->mErrors will have reason why
foreach ( $pParamHash['tag_map_store'] as $value) {
$result = $this->store( $value );
* check if the mContentId is set and valid
* This function removes a tag entry
$this->mDb->StartTrans();
$query = "DELETE FROM `". BIT_DB_PREFIX. "tags_content_map` WHERE `tag_id` = ?";
if ( $result = $this->mDb->query( $query, array( $tag_id ) ) ){
// remove all references to tag in tags_content_map
$query = "DELETE FROM `". BIT_DB_PREFIX. "tags` WHERE `tag_id` = ?";
if ( $result = $this->mDb->query( $query, array( $tag_id ) ) ) {
//some rollback feature would be nice here
$this->mDb->CompleteTrans();
* This function removes all references to contentid from tags_content_map
$this->mDb->StartTrans();
$query = "DELETE FROM `". BIT_DB_PREFIX. "tags_content_map` WHERE `content_id` = ?";
$result = $this->mDb->query( $query, array( $this->mContentId ) );
$this->mDb->CompleteTrans();
* This function removes all references to contentid from tags_content_map
$this->mDb->StartTrans();
if( !$pObject->hasAdminPermission() ){
$whereSql .= " AND tagger_id = ?";
$bindVars[] = $gBitUser->mUserId;
$query = "DELETE FROM `". BIT_DB_PREFIX. "tags_content_map` WHERE `content_id` = ? $whereSql";
$result = $this->mDb->query( $query, $bindVars );
$this->mDb->CompleteTrans();
* The function removes one or more tag from a piece of content
$this->mDb->StartTrans();
$result = $this->mDb->query( $query, $bind );
foreach( $pTagIdArray as $tagId ) {
if( !$this->mDb->getOne( "SELECT COUNT(*) FROM `". BIT_DB_PREFIX. "tags_content_map` WHERE `tag_id`=?", array( $tagId ) ) ) {
$this->mDb->CompleteTrans();
if( $gBitSystem->isFeatureActive( 'pretty_urls' ) || $gBitSystem->isFeatureActive( 'pretty_urls_extended' ) ) {
$rewrite_tag = $gBitSystem->isFeatureActive( 'pretty_urls_extended' ) ? 'view/': '';
$tag_url = TAGS_PKG_URL. $rewrite_tag. urlencode( $tag );
$tag_url = TAGS_PKG_URL. 'index.php?tags='. urlencode( $tag );
* This function gets a list of tags
global $gBitUser, $gBitSystem;
$joinSql = !empty($pParamHash['join_sql']) ? $pParamHash['join_sql'] : '';
if( !empty( $pParamHash['content_type_guid'] ) ) {
$bindVars[] = $pParamHash['content_type_guid'];
$joinSql = "INNER JOIN `". BIT_DB_PREFIX. "liberty_content` lc ON (tgc.`content_id`=lc.`content_id` AND lc.`content_type_guid`=?) ";
$sort_mode_prefix = 'tg';
//Backward compatability for most popular sort method
if( empty( $pParamHash['sort_mode'] ) ) {
$pParamHash['sort_mode'] = 'tag_asc';
switch( $pParamHash['sort_mode'] ) {
$pParamHash['sort_mode'] = 'tag_asc';
* @TODO this all needs to go in in some other getList type method
* and these are just sketches - need to be different kinds of queries in most cases
// get tags by most hits on content
if ($pParamHash['sort_mode'] == 'hits_desc') {
$joinSql .= "LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content_hits` lch ON lc.`content_id` = lch.`content_id`";
// get tags sorted by tagged date <- getList sorted by map tagged date
if ($pParamHash['sort_mode'] == 'tagged_on_desc') {
$sort_mode_prefix = 'tgc';
$sort_mode = $this->mDb->convertSortmode( $pParamHash['sort_mode'] );
$query = "SELECT tg.`tag_id`, tg.`tag`, COUNT(tgc.`content_id`) AS tag_count
INNER JOIN `". BIT_DB_PREFIX. "tags_content_map` tgc ON ( tgc.`tag_id` = tg.`tag_id` )
GROUP BY tg.`tag_id`,tg.`tag`
$result = $this->mDb->query( $query,$bindVars, ( !empty($pParamHash['max_records']) ? $pParamHash['max_records'] : NULL ) );
$queryCount = "SELECT COUNT( * ) FROM `". BIT_DB_PREFIX. "tags` tg";
$cant = $this->mDb->getOne( $queryCount );
while ($res = $result->fetchRow()) {
// this was really sucky, its now replaced by the slightly lesssucky subselect above. the subselect should prolly be replaced with a count table
// $res['tag_count'] = $this->getPopCount($res['tag_id']);
//get keys for doing sorts
foreach ($ret as $key => $row) {
$popcant[$key] = $row['tag_count'];
$orderedcant[$key] = $row['tag_count'];
//this part creates the tag weight in a scale of 1-10
//get highest count and get lowest count
if (!empty($orderedcant)) {
$lowcant = $orderedcant[0];
$highcant = $orderedcant[ (count($orderedcant) - 1) ];
//hack to prevent us from dividing by zero - this whole weighting thing could use a slightly better formula
if ($highcant == $lowcant){$lowcant -= 1;}
$cantoffset = $highcant - $lowcant;
$tagscale = 9/ $cantoffset;
//@todo make this more sophisticated if the spread is not big enough
$tagscale = 9/ $cantoffset;
//3. (n - low+1)*ratio (n is # to be scaled)
foreach ($ret as $key => $row) {
$ret[$key]['tagscale'] = round((($row['tag_count'] - $lowcant) * $tagscale) + 1, 0);
//trim to max popular count if a limit is asked for
if ( isset ($pParamHash["max_popular"]) && is_numeric($pParamHash["max_popular"])){
$max_popular = array_slice($max_popular, 0, $pParamHash["max_popular"]);
// preserve the sort requested by matching to the original list
$sorted_popular = array();
foreach ( $ret as $retkey => $retrow){
foreach ( $max_popular as $key => $row){
if ( $row['tag_id'] == $retrow['tag_id'] ){
$sorted_popular[] = $retrow;
$pParamHash["data"] = $ret;
$pParamHash["cant"] = $cant;
* This function gets the number of times a tag is used aka Popularity Count
$cant = $this->mDb->getOne($queryCount, array($tag_id) );
* This function gets all content by matching to any tag passed in a group of tags, eliminates dupe records
global $gBitSystem, $gBitSmarty;
$gBitSystem->verifyPermission( 'p_tags_view' );
// some content specific offsets and pagination settings
if( !empty( $pParamHash['sort_mode'] )) {
$content_sort_mode = $pParamHash['sort_mode'];
$gBitSmarty->assign( 'sort_mode', $content_sort_mode );
$max_content = ( !empty( $pParamHash['max_records'] )) ? $pParamHash['max_records'] : $gBitSystem->getConfig( 'max_records' );
$gBitSmarty->assign( 'user_id', @BitBase::verifyId( $pParamHash['user_id'] ) ? $pParamHash['user_id'] : NULL );
// now that we have all the offsets, we can get the content list
$gBitSmarty->assign( 'contentSelect', $contentSelect );
$gBitSmarty->assign( 'contentTypes', $contentTypes );
$contentListHash['parameters']['content_type_guid'] = $contentSelect;
$gBitSmarty->assign( 'listInfo', $contentListHash );
$gBitSmarty->assign( 'content_type_guids', ( isset ( $pParamHash['content_type_guid'] ) ? $pParamHash['content_type_guid'] : NULL ));
if ( isset ($pParamHash['matchtags']) && $pParamHash['matchtags'] == 'all'){
//need some sort of matching function
$gBitSmarty->assign_by_ref('contentList', $distinctdata);
* Used by getContentList to strip out duplicate records in a list
* Lifted from http://us3.php.net/manual/en/function.array-unique.php#57006
* @param $array - nothing to say
* @param $group_keys - columns which have to be grouped - can be STRING or ARRAY (STRING, STRING[, ...])
* @param $sum_keys - columns which have to be summed - can be STRING or ARRAY (STRING, STRING[, ...])
* @param $count_key - must be STRING - count the grouped keys
function array_distinct ($array, $group_keys, $sum_keys = NULL, $count_key = NULL){
if (!is_array ($group_keys)) $group_keys = array ($group_keys);
if (!is_array ($sum_keys)) $sum_keys = array ($sum_keys);
$existing_sub_keys = array ();
foreach ($array as $key => $sub_array){
foreach ($group_keys as $group_key){
$puffer .= $sub_array[$group_key];
if (!in_array ($puffer, $existing_sub_keys)){
$existing_sub_keys[$key] = $puffer;
$output[$key] = $sub_array;
foreach ($sum_keys as $sum_key){
if (is_string ($sum_key)) $output[$puffer][$sum_key] += $sub_array[$sum_key];
if (!array_key_exists ($count_key, $output[$puffer])) $output[$puffer][$count_key] = 1;
if (is_string ($count_key)) $output[$puffer][$count_key]++ ;
/********* SERVICE FUNCTIONS *********/
global $gBitSystem, $gBitSmarty, $gBitUser;
if ( $gBitSystem->isPackageActive( 'tags' ) ) {
$gBitSmarty->assign( 'tagData', !empty( $tag->mInfo['tags'] ) ? $tag->mInfo['tags'] : NULL );
* filter the search with pigeonholes
* @param $pParamHash['tags']['filter'] - a tag or an array of tags
if (isset ($pParamHash['tags']) && !empty($pParamHash['tags'])){
/* slated for removal - makes no sense since content likely has multiple tags */
// $ret['select_sql'] = ", tgc.`tag_id`, tgc.`tagger_id`, tgc.`tagged_on`";
$ret['join_sql'] = " INNER JOIN `". BIT_DB_PREFIX. "tags_content_map` tgc ON ( lc.`content_id`=tgc.`content_id` )
INNER JOIN `". BIT_DB_PREFIX. "tags` tg ON ( tg.`tag_id`=tgc.`tag_id` )";
$tagMixed = $pParamHash['tags']; //need to break up this string
if( !empty( $tagMixed )){
$tagIds = explode( ",", $tagMixed );
$tagIds = array( $tagMixed );
foreach( $tagIds as $value ){
// ignore empty ones created by trailing ,'s
$ret['bind_vars'] = $tags;
// return the values sent for pagination / url purposes
$pParamHash['listInfo']['tags'] = $pParamHash['tags'];
$pParamHash['listInfo']['ihash']['tags'] = $pParamHash['tags'];
global $gBitSystem, $gBitSmarty, $gBitUser;
if ( $gBitSystem->isPackageActive( 'tags' )) {
if( $tag->load() && ($pObject->hasUserPermission( 'p_tags_create' ) || $gBitUser->hasPermission( 'p_tags_moderate' )) ) {
foreach ($tag->mInfo['tags'] as $t) {
if ($t['tagger_id'] == $gBitUser->mUserId || $gBitUser->hasPermission('p_tags_admin') ) {
$gBitSmarty->assign( 'loadTags', TRUE );
$gBitSmarty->assign( 'tagList', $pObject->getField( 'tags' ) );
$gBitSmarty->assign( 'tagData', $tag->getField( 'tags' ) );
* @param includes a string or array of 'tags' and contentid for association.
global $gBitUser, $gBitSystem;
// If a content access system is active, let's call it
if( $gBitSystem->isPackageActive( 'tags' ) && isset ( $pParamHash['tags'] ) ) {
global $gBitUser, $gBitSystem, $gBitSmarty;
if ( $gBitSystem->isPackageActive( 'tags' ) ) {
if (isset ($_REQUEST['tags'])) {
//$pObject->mInfo['tags'] = $_REQUEST['tags'];
$gBitSmarty->assign('tagList', $_REQUEST['tags']);
// make sure all tags from a deleted user are nuked
if( is_a( $pObject, 'BitUser' ) && !empty( $pObject->mUserId ) ) {
$pObject->mDb->StartTrans();
$pObject->mDb->query( "DELETE FROM `". BIT_DB_PREFIX. "tags_content_map` WHERE tagger_id=?", array( $pObject->mUserId ) );
$pObject->mDb->CompleteTrans();
|