Created
February 4, 2009 03:26
-
-
Save speedmax/57906 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* To implment tagging behavior to be able to plug into any data model in the system | |
* | |
* Requirements | |
* You are required to create "tags" table to hold all tag entries | |
* You are required to create a "[tableName]_tags" table for each object you | |
* want to tag. for example, to implement tags for pages u need this table | |
* pages_tags (page_id : int, tag_id) | |
* | |
* | |
* Example Setup | |
* class Page extends AppModel { | |
* var $actsAs = array('Taggable'); | |
* var $hasAndBelongsToMany => array('Tag' => array('with'=>'PageTag','joinTable'=>'pages_tags'); | |
* } | |
* | |
* Done | |
* now all tagging related features can be used in your models. | |
* $this->Page->findPopularTags(); | |
* $this->Page->findRelatedTags(array('news')); | |
* | |
* Once $this->Page->id is set, you can all pages related to current page. | |
* $this->Page->findRelatedTagged(10); | |
* | |
* | |
*/ | |
class TaggableBehavior extends ModelBehavior { | |
function tag(&$model, $name = null) { | |
if (empty($name)) return false; | |
if ($existing = $model->Tag->find(array('name'=>$name), array('id'))) { | |
return $existing['Tag']['id']; | |
} else { | |
$handle = Inflector::slug($name, '-'); | |
$model->Tag->create(array('name'=>$name,'handle'=>$handle)); | |
$model->Tag->save(); | |
return $model->Tag->getLastInsertId(); | |
} | |
} | |
function afterFind(&$model, $results, $primary = false) { | |
foreach($results as &$result) { | |
if (isset($result['Tag'])) { | |
$tags = join(', ', Set::extract($result['Tag'], '{n}.name')); | |
$result[$model->alias]['tags'] = $tags; | |
} | |
} | |
return $results; | |
} | |
function beforeSave(&$model) { | |
if (isset($model->data[$model->alias]['tags'])) { | |
$tags = Set::normalize($model->data[$model->alias]['tags'], false); | |
$ids = array(); | |
foreach ($tags as $tag) { | |
$ids[] = $this->tag($model, $tag); | |
} | |
$model->data['Tag']['Tag'] = $ids; | |
} | |
return true; | |
} | |
/** | |
* Finds other records that share the most tags with the record passed as the | |
* related parameter. | |
* Useful for constructing �Related� or �See Also� boxes and lists. | |
* | |
* Require Model::$data to be set so it points to current entry. | |
* @param Model $model | |
* @param integer $limit | |
* @param integer $page | |
* @return array - list of related entries | |
*/ | |
function findRelatedTagged(&$model, $limit = 20, $page = 1) { | |
if (!isset($model->hasAndBelongsToMany['Tag']) || !$model->id) { | |
return false; | |
} | |
extract($model->hasAndBelongsToMany['Tag'], EXTR_SKIP); | |
$prefix = $model->tablePrefix; | |
$tableName = Inflector::tableize($className); | |
$offset = $limit * ($page-1); | |
# SQL query to get related items share most of tags. | |
$sql = "SELECT {$model->alias}.*, COUNT( joinTable2.{$foreignKey} ) AS count | |
FROM {$prefix}{$model->useTable} {$model->alias}, | |
{$prefix}{$joinTable} joinTable, | |
{$prefix}{$joinTable} joinTable2, | |
{$prefix}{$tableName} Tag | |
WHERE joinTable.{$foreignKey} = {$model->id} | |
AND Tag.{$model->primaryKey} = joinTable.{$associationForeignKey} | |
AND joinTable2.{$foreignKey} != joinTable.{$foreignKey} | |
AND joinTable2.{$associationForeignKey} = joinTable.{$associationForeignKey} | |
AND {$model->alias}.{$model->primaryKey} = joinTable2.{$foreignKey} | |
AND Tag.site_id = ".Configure::read('Site.id')." | |
GROUP BY joinTable2.{$foreignKey} ORDER BY count DESC | |
LIMIT {$offset}, {$limit};"; | |
$related = $model->query($sql); | |
# Filter out count field from result | |
if (!empty($related)) { | |
foreach ($related as $i => $item) unset($related[$i][0]); | |
} | |
return $related; | |
} | |
/** | |
* Finds other tags that are related to the tags passed thru the tags parameter, | |
* by finding common records that share similar sets of tags. | |
* Useful for constructing �Related tags� lists. | |
* | |
* @param Model $model - instance of target model | |
* @param mixed $tags - list of tags in string or array format | |
* @return array - list of related Tags | |
*/ | |
function findRelatedTags(&$model, $tags = array()) { | |
if (!isset($model->hasAndBelongsToMany['Tag']) || !$tags ) { | |
return false; | |
} | |
extract($model->hasAndBelongsToMany['Tag'], EXTR_SKIP); | |
if (is_string($tags)) { | |
$tags = Set::normalize($tags, false); | |
} | |
$prefix = $model->tablePrefix; | |
$tableName = Inflector::tableize($className); | |
$tagsString = "'".join("', '", $tags)."'"; | |
$tagsCount = count($tags); | |
# Sql find related tags | |
$sql = "SELECT Tag.*, COUNT(joinTable.{$foreignKey}) AS count | |
FROM {$prefix}{$joinTable} joinTable, | |
{$prefix}{$tableName} Tag | |
WHERE joinTable.{$foreignKey} IN ( | |
SELECT joinTable.{$foreignKey} | |
FROM {$prefix}{$joinTable} joinTable, | |
{$prefix}{$tableName} Tags | |
WHERE joinTable.{$associationForeignKey} = Tags.id | |
AND Tags.name IN ({$tagsString}) | |
GROUP BY joinTable.{$foreignKey} | |
HAVING COUNT(joinTable.{$foreignKey}) = {$tagsCount} | |
) | |
AND Tag.name NOT IN ({$tagsString}) | |
AND Tag.id = joinTable.{$associationForeignKey} | |
AND Tag.site_id = ".Configure::read('Site.id')." | |
GROUP BY joinTable.{$associationForeignKey} | |
ORDER BY count DESC"; | |
$related = $model->query($sql); | |
# Filter out count field from result | |
if (!empty($related)) { | |
foreach ($related as $i => $item) unset($related[$i][0]); | |
} | |
return $related; | |
} | |
/** | |
* Find most popular tags in relation to current model | |
* | |
* @param unknown_type $model | |
* @param unknown_type $limit | |
* @param unknown_type $page | |
* @return unknown | |
*/ | |
function findPopularTags(&$model, $limit = 20, $page = 1) { | |
extract($model->hasAndBelongsToMany['Tag'], EXTR_SKIP); | |
$prefix = $model->tablePrefix; | |
$tableName = Inflector::tableize($className); | |
$sql = "SELECT Tag.*, COUNT( joinTable.{$foreignKey} ) AS count | |
FROM {$prefix}{$joinTable} joinTable, | |
{$prefix}{$tableName} Tag | |
WHERE Tag.{$model->primaryKey} = joinTable.{$associationForeignKey} | |
AND Tag.site_id = ".Configure::read('Site.id')." | |
GROUP BY joinTable.{$associationForeignKey} | |
ORDER BY count DESC"; | |
if ($limit != false) { | |
$offset = $limit * ($page - 1); | |
$sql .= " LIMIT {$offset}, {$limit}"; | |
} | |
$popular = $model->query($sql); | |
foreach ($popular as &$tag) { | |
$tag = array('Tag' => array_merge($tag['Tag'], $tag[0])); | |
} | |
return $popular; | |
} | |
/** | |
* Find all associated entry with tags | |
* | |
* @param Model $model | |
* @param Array $tags - Find entries with these tags | |
* @return $results | |
*/ | |
function findTaggedWith(&$model, $tags = array(), $type = 'name') { | |
extract($model->hasAndBelongsToMany['Tag'], EXTR_SKIP); | |
if (is_string($tags)) { | |
$tags = array($tags); | |
} | |
if (!in_array($type, array('name', 'handle'))) { | |
throw new Exception('Tags can only be search using name or handle'); | |
} | |
$Tag = ClassRegistry::init('Tag'); | |
$ids = Set::extract($Tag->findAll(array($type=>$tags), array('id')), '{n}.Tag.id'); | |
if (!isset($model->{$with}->belongsTo[$model->name])) { | |
$model->{$with}->bindModel(array( | |
'belongsTo'=> array($model->name => compact('foreignKey'), 'conditions'=>$conditions, 'Tag')) | |
); | |
} | |
$results = $model->{$with}->findAll(array("{$with}.tag_id"=>$ids, "{$model->name}.id >" => 0)); | |
return $results; | |
} | |
/** | |
* Count the number of tags associated to current Model entry | |
* Require Model::$data to be set so it points to current entry. | |
* | |
* @param Model $model | |
* @return integer - Number of tags | |
*/ | |
function tagsCount(&$model) { | |
if(!isset($model->id)) { | |
return 0; | |
} | |
extract($model->hasAndBelongsToMany['Tag'], EXTR_SKIP); | |
$results = $model->{$with}->findCount(array( | |
"{$with}.page_id" => $model->id | |
)); | |
return $results; | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment