PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/bedita-app/models/category.php

http://bedita.googlecode.com/
PHP | 507 lines | 331 code | 64 blank | 112 comment | 48 complexity | 274b037a1b1249ed3a83a1355a0116fb MD5 | raw file
Possible License(s): AGPL-1.0, AGPL-3.0, LGPL-3.0, LGPL-2.1, GPL-3.0
  1. <?php
  2. /*-----8<--------------------------------------------------------------------
  3. *
  4. * BEdita - a semantic content management framework
  5. *
  6. * Copyright 2008 ChannelWeb Srl, Chialab Srl
  7. *
  8. * This file is part of BEdita: you can redistribute it and/or modify
  9. * it under the terms of the GNU Lesser General Public License as published
  10. * by the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. * BEdita is distributed WITHOUT ANY WARRANTY; without even the implied
  13. * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  14. * See the GNU Lesser General Public License for more details.
  15. * You should have received a copy of the GNU Lesser General Public License
  16. * version 3 along with BEdita (see LICENSE.LGPL).
  17. * If not, see <http://gnu.org/licenses/lgpl-3.0.html>.
  18. *
  19. *------------------------------------------------------------------->8-----
  20. */
  21. /**
  22. * Generic category
  23. *
  24. * @version $Revision: 3905 $
  25. * @modifiedby $LastChangedBy: ste $
  26. * @lastmodified $LastChangedDate: 2012-08-08 18:21:17 +0200 (Wed, 08 Aug 2012) $
  27. *
  28. * $Id: category.php 3905 2012-08-08 16:21:17Z ste $
  29. */
  30. class Category extends BEAppModel {
  31. var $actsAs = array(
  32. 'CompactResult' => array()
  33. );
  34. var $validate = array(
  35. 'label' => array(
  36. 'rule' => 'notEmpty'
  37. ),
  38. 'status' => array(
  39. 'rule' => 'notEmpty'
  40. ),
  41. 'name' => array(
  42. 'rule' => 'notEmpty'
  43. )
  44. ) ;
  45. // static vars used by reorderTag static function
  46. static $dirTag, $orderTag;
  47. function afterFind($result) {
  48. foreach ($result as &$res) {
  49. if(isset($res['name']))
  50. $res['url_label'] = $res['name'];
  51. }
  52. return $result;
  53. }
  54. public function tagLabelPresent($label, $exclude_id=null) {
  55. $tagDB = $this->find("first",
  56. array("conditions" => "object_type_id IS NULL AND name='".addslashes($label)."' " . $this->collateStatment() ) );
  57. if (!empty($exclude_id) && $exclude_id == $tagDB["id"])
  58. return false;
  59. return !empty($tagDB);
  60. }
  61. /**
  62. * Get tag label from unique name
  63. *
  64. * @param string $name
  65. */
  66. public function tagLabelFromName($name) {
  67. $tagDB = $this->find("first",
  68. array("conditions" => "object_type_id IS NULL AND name='$name'"));
  69. return !empty($tagDB) ? $tagDB['label'] : "";
  70. }
  71. /**
  72. * Define a unique name from label: lowercase, trimmed, etc...
  73. *
  74. * @param string $label
  75. */
  76. public function uniqueLabelName($label) {
  77. $baseName = $name = BeLib::getInstance()->friendlyUrlString($label);
  78. // search for already used label
  79. // suffix counter
  80. $i = 1;
  81. // if it's a category
  82. if (!empty($this->data[$this->alias]["object_type_id"])) {
  83. // if name is in mediaTypes
  84. if (in_array($name, Configure::read("mediaTypes"))) {
  85. // if multimedia object return baseName
  86. if (in_array($this->data[$this->alias]["object_type_id"], Configure::read("objectTypes.multimedia.id"))) {
  87. return $baseName;
  88. } else {
  89. $name = $baseName . "-" . $i++;
  90. }
  91. }
  92. $conditions["NOT"] = array("object_type_id" => $this->data[$this->alias]["object_type_id"]);
  93. $conditions[] = "object_type_id IS NOT NULL";
  94. // if it's a tag
  95. } else {
  96. $conditions[] = "object_type_id IS NULL";
  97. }
  98. $conditions["name"] = $name;
  99. // exclude itself if already present
  100. if (!empty($this->data[$this->alias]["id"])) {
  101. $conditions["NOT"]["id"] = $this->data[$this->alias]["id"];
  102. }
  103. $count = $this->find("count", array("conditions" => $conditions));
  104. if ($count > 0) {
  105. $freeName = false;
  106. while (!$freeName) {
  107. $conditions["name"] = $baseName . "-" . $i++;
  108. $count = $this->find("count", array("conditions" => $conditions));
  109. if ($count == 0) {
  110. $freeName = true;
  111. $name = $conditions["name"];
  112. }
  113. }
  114. }
  115. return $name;
  116. }
  117. /**
  118. * Define default values
  119. */
  120. function beforeValidate() {
  121. $data = &$this->data[$this->name] ;
  122. $data['name'] = $this->uniqueLabelName($data["label"]);
  123. return true;
  124. }
  125. /**
  126. * Get all categories of some object type and order them by area
  127. *
  128. * @param int $objectType
  129. * @return array(
  130. * "area" => array(
  131. * nomearea => array(categories in that area)),
  132. * "noarea" => array(categories aren't in any area)
  133. * )
  134. */
  135. public function getCategoriesByArea($objectType) {
  136. $categories = $this->find("all", array(
  137. "conditions" => array("Category.object_type_id" => $objectType), "order" => "label"
  138. ));
  139. $objModel = ClassRegistry::init("BEObject");
  140. $areaList = $objModel->find('list', array(
  141. "conditions" => "object_type_id=" . Configure::read("objectTypes.area.id"),
  142. "order" => "title",
  143. "fields" => "BEObject.title"
  144. )
  145. );
  146. $areaCategory = array();
  147. foreach ($categories as $cat) {
  148. if (array_key_exists($cat["area_id"],$areaList)) {
  149. $areaCategory["area"][$areaList[$cat["area_id"]]][] = $cat;
  150. } else {
  151. $areaCategory["noarea"][] = $cat;
  152. }
  153. }
  154. return $areaCategory;
  155. }
  156. private function collateStatment() {
  157. $res = "";
  158. // #MYSQL
  159. if($this->getDriver() == "mysql") {
  160. $res = "collate utf8_bin";
  161. }
  162. return $res;
  163. }
  164. /**
  165. * save a list of comma separated tag
  166. *
  167. * @param comma separated string $tagList
  168. * @return array of tags' id
  169. */
  170. public function saveTagList($tagList) {
  171. $arrIdTag = array();
  172. if (!empty($tagList)) {
  173. $tags = explode(",", $tagList);
  174. foreach ($tags as $tag) {
  175. $tag = trim($tag);
  176. if (!empty($tag)) {
  177. $tagDB = $this->find("first", array(
  178. "conditions" => "object_type_id IS NULL AND label='".addslashes($tag)."' " .
  179. $this->collateStatment()
  180. )
  181. );
  182. if (empty($tagDB)) {
  183. $tagDB["label"] = $tag;
  184. $tagDB["status"] = "on";
  185. $this->create();
  186. if (!$this->save($tagDB)) {
  187. throw new BeditaException(__("Error saving tags", true));
  188. }
  189. }
  190. $id_tag = (!empty($tagDB["id"]))? $tagDB["id"] : $this->getLastInsertID();
  191. if (!in_array($id_tag,$arrIdTag)) {
  192. $arrIdTag[$id_tag] = $id_tag;
  193. }
  194. }
  195. }
  196. }
  197. return $arrIdTag;
  198. }
  199. /**
  200. * return list of tags with their weight
  201. *
  202. * @param array $options
  203. * "showOrphans" => true, show all tags also not associated
  204. * "status" => null, string or array (on, off, draft). if area_id is setted "status" is used also to related objects
  205. * "cloud" => false, true to set a css class for cloud view
  206. * "coeff" => 12, coeffiecient for calculate the distribution
  207. * "order" => "label", order by field
  208. * "dir" => 1, asc(1), desc(0)
  209. * "area_id"=> null get tags only associated to objects that are in "area_id" publication
  210. *
  211. * @return array
  212. */
  213. public function getTags(array $options = array()) {
  214. $options = array_merge(
  215. array("showOrphans" => true, "status" => null, "cloud" => false,
  216. "coeff" => 12, "order" => "label", "dir" => 1, "area_id"=> null, "section_id" => null),
  217. (array)$options
  218. );
  219. $conditions = array();
  220. $conditions[] = "Category.object_type_id IS NULL";
  221. if(!empty($options["status"])) {
  222. $conditions["Category.status"] = $options["status"];
  223. }
  224. $orderSql = ($options["order"] != "weight")? $options["order"] : "label";
  225. $dirSql = ($options["dir"])? "ASC" : "DESC";
  226. $joinsBEObject = array();
  227. $joins = array();
  228. // get tags associated to objects that are in $area_id publication or $section_id section
  229. if (!empty($options["area_id"]) || !empty($options["section_id"])) {
  230. $joinsBEObject = array(
  231. 'table' => 'objects',
  232. 'alias' => 'BEObject',
  233. 'type' => 'inner',
  234. 'conditions'=> array(
  235. 'BEObject.id = ObjectCategory.object_id'
  236. )
  237. );
  238. if (!empty($options["status"])) {
  239. $joinsBEObject["conditions"]['BEObject.status'] = $options["status"];
  240. }
  241. $treeCondition = array('Tree.id = BEObject.id');
  242. if(!empty($options["section_id"])) {
  243. $treeCondition['Tree.parent_id'] = $options["section_id"];
  244. } else {
  245. $treeCondition['Tree.area_id'] = $options["area_id"];
  246. }
  247. $joinsTree = array(
  248. 'table' => 'trees',
  249. 'alias' => 'Tree',
  250. 'type' => 'inner',
  251. 'conditions'=> $treeCondition
  252. );
  253. $joins = array(
  254. array(
  255. 'table' => 'object_categories',
  256. 'alias' => 'ObjectCategory',
  257. 'type' => 'inner',
  258. 'conditions'=> array('ObjectCategory.category_id = Category.id')
  259. ),
  260. $joinsBEObject,
  261. $joinsTree
  262. );
  263. }
  264. $allTags = $this->find('all', array(
  265. 'conditions'=> $conditions,
  266. 'order' => array("Category." . $orderSql => $dirSql),
  267. 'group' => $this->fieldsString("Category"),
  268. 'joins' => $joins
  269. ));
  270. $tags = array();
  271. foreach ($allTags as $t) {
  272. $tags[$t['id']] = $t;
  273. }
  274. $category_ids = Set::extract('/id', $allTags);
  275. $objCatModel = ClassRegistry::init("ObjectCategory");
  276. $joins = (empty($joinsBEObject))? array() : array($joinsBEObject, $joinsTree);
  277. $res = $objCatModel->find("all", array(
  278. 'fields' => array("DISTINCT ObjectCategory.category_id", "ObjectCategory.object_id"),
  279. 'conditions' => array("ObjectCategory.category_id" => $category_ids),
  280. 'joins' => $joins
  281. ));
  282. // calculate weights
  283. $weights = array(0);
  284. foreach ($res as $val) {
  285. if (empty($weights[$val["ObjectCategory"]["category_id"]])) {
  286. $weights[$val["ObjectCategory"]["category_id"]] = 1;
  287. } else {
  288. $weights[$val["ObjectCategory"]["category_id"]]++;
  289. }
  290. }
  291. if ($options["cloud"]) {
  292. $max = max($weights);
  293. $min = min($weights);
  294. $distribution = ($max - $min) / $options["coeff"];
  295. }
  296. foreach ($res as $r) {
  297. $key = !empty($r['ObjectCategory']['category_id']) ? $r['ObjectCategory']['category_id'] : $r[0]['category_id'] ;
  298. $w = $weights[$r["ObjectCategory"]["category_id"]];
  299. $tags[$key]['weight'] = $w;
  300. if ($options["cloud"]) {
  301. if ($w == $min)
  302. $tags[$key]['class'] = "smallestTag";
  303. elseif ($w == $max)
  304. $tags[$key]['class'] = "largestTag";
  305. elseif ($w > ($min + ($distribution * 2)))
  306. $tags[$key]['class'] = "largeTag";
  307. elseif ($w > ($min + $distribution))
  308. $tags[$key]['class'] = "mediumTag";
  309. else
  310. $tags[$key]['class'] = "smallTag";
  311. }
  312. }
  313. // remove orphans or set weight = 0, create the non-associative array
  314. $tagsArray = array();
  315. foreach ($tags as $k => $t) {
  316. $tags[$k]['url_label'] = $t['name'];
  317. if(!isset($t['weight'])) {
  318. if($options["showOrphans"] === false) {
  319. unset($tags[$k]);
  320. } else {
  321. $tags[$k]['weight'] = 0;
  322. $tagsArray[]= $tags[$k];
  323. }
  324. } else {
  325. $tagsArray[]= $tags[$k];
  326. }
  327. }
  328. // if order by weight reorder tags
  329. if ($options["order"] == "weight") {
  330. Category::$orderTag = $options["order"];
  331. Category::$dirTag = $options["dir"];
  332. usort($tagsArray, array('Category', 'reorderTag'));
  333. }
  334. // pr($tagsArray);exit;
  335. return $tagsArray;
  336. }
  337. public function getContentsByTag($name) {
  338. // bind association on the fly
  339. $hasAndBelongsToMany = array(
  340. 'BEObject' =>
  341. array(
  342. 'className' => 'BEObject',
  343. 'joinTable' => 'object_categories',
  344. 'foreignKey' => 'category_id',
  345. 'associationForeignKey' => 'object_id',
  346. 'unique' => true
  347. )
  348. );
  349. $this->bindModel( array(
  350. 'hasAndBelongsToMany' => $hasAndBelongsToMany
  351. )
  352. );
  353. // don't compact find result
  354. $this->bviorCompactResults = false;
  355. $tag = $this->find("first", array(
  356. "conditions" => "object_type_id IS NULL AND name='".addslashes($name)."' ".
  357. $this->collateStatment(),
  358. "contain" => array("BEObject" => array("ObjectType"))
  359. )
  360. );
  361. // reset to default compact result
  362. $this->bviorCompactResults = true;
  363. return empty($tag["BEObject"]) ? array() : $tag["BEObject"];
  364. }
  365. /**
  366. * USED for multimedia objects
  367. * check if exists $mediatype in categories for an object type. If not, create the category
  368. *
  369. * @param int $object_type_id
  370. * @return mixed, false if not mediatype in the form else return array of Category
  371. */
  372. public function checkMediaType($object_type_id, $mediatype) {
  373. if (empty($mediatype)) {
  374. return false;
  375. }
  376. $category = $this->find("first",
  377. array(
  378. "conditions" => array(
  379. "name" => $mediatype,
  380. "object_type_id" => $object_type_id
  381. )
  382. )
  383. );
  384. if(empty($category)) { // if media category doesn't exists, create it
  385. $data = array(
  386. "name"=>$mediatype,
  387. "label"=>ucfirst($mediatype),
  388. "object_type_id"=>$object_type_id,
  389. "status"=>"on"
  390. );
  391. $this->create();
  392. if(!$this->save($data)) {
  393. throw new BeditaException(__("Error saving category", true), $this->validationErrors);
  394. }
  395. $category['id']=$this->id;
  396. }
  397. $categoryArr = array($category['id']=>$category['id']);
  398. return $categoryArr;
  399. }
  400. /**
  401. * compare two array elements defined by $orderTag var and return -1,0,1
  402. * $dirTag is used for define order of comparison
  403. *
  404. * @param array $e1
  405. * @param array $e2
  406. * @return int (-1,0,1)
  407. */
  408. private static function reorderTag($e1, $e2) {
  409. $d1 = $e1[Category::$orderTag];
  410. $d2 = $e2[Category::$orderTag];
  411. return (Category::$dirTag)? strcmp($d1,$d2) : strcmp($d2,$d1);
  412. }
  413. /**
  414. * Search for category names, create if not already present, and
  415. * return array of corresponding id
  416. *
  417. * @param array $names, category names to search/create
  418. * @param int $objTypeId, category object type id
  419. * @return array of corresponding id-category
  420. */
  421. public function findCreateCategories(array &$names, $objTypeId) {
  422. $res = array();
  423. // if not exists create
  424. foreach ($names as $n) {
  425. $this->create();
  426. $n = trim($n);
  427. $this->bviorCompactResults = false;
  428. $idCat = $this->field('id', array('name' => $n, 'object_type_id' => $objTypeId));
  429. $this->bviorCompactResults = true;
  430. if(empty($idCat)) {
  431. $dataCat = array('name'=> $n,'label' => $n,
  432. 'object_type_id' => $objTypeId, 'status'=>'on');
  433. if(!$this->save($dataCat)) {
  434. throw new BeditaException(__("Error saving category") . ": " . print_r($dataCat, true));
  435. }
  436. $idCat = $this->id;
  437. }
  438. $res[] = $idCat;
  439. }
  440. return $res;
  441. }
  442. }
  443. ?>