/datatypes/eztags/eztags.php

https://github.com/alafon/eztags · PHP · 440 lines · 284 code · 50 blank · 106 comment · 44 complexity · 02096893b1631304eec2fc0c3aaa9d90 MD5 · raw file

  1. <?php
  2. /**
  3. * eZTags class implements functions used by eztags datatype
  4. *
  5. */
  6. class eZTags
  7. {
  8. /**
  9. * Contains IDs of the tags
  10. *
  11. * @var array $ParentArray
  12. */
  13. private $IDArray = array();
  14. /**
  15. * Contains the keywords in the same order as IDs
  16. *
  17. * @var array $KeywordArray
  18. */
  19. private $KeywordArray = array();
  20. /**
  21. * Contains parent IDs in same order as IDs
  22. *
  23. * @var array $ParentArray
  24. */
  25. private $ParentArray = array();
  26. /**
  27. * Returns an array with attributes that are available
  28. *
  29. * @return array
  30. */
  31. function attributes()
  32. {
  33. return array( 'tags',
  34. 'tag_ids',
  35. 'id_string',
  36. 'keyword_string',
  37. 'parent_string' );
  38. }
  39. /**
  40. * Returns true if the provided attribute exists
  41. *
  42. * @param string $name
  43. * @return bool
  44. */
  45. function hasAttribute( $name )
  46. {
  47. return in_array( $name, $this->attributes() );
  48. }
  49. /**
  50. * Returns the specified attribute
  51. *
  52. * @param string $name
  53. * @return mixed
  54. */
  55. function attribute( $name )
  56. {
  57. switch ( $name )
  58. {
  59. case 'tags' :
  60. {
  61. return $this->tags();
  62. } break;
  63. case 'tag_ids' :
  64. {
  65. return $this->IDArray;
  66. } break;
  67. case 'id_string' :
  68. {
  69. return $this->idString();
  70. } break;
  71. case 'keyword_string' :
  72. {
  73. return $this->keywordString();
  74. } break;
  75. case 'parent_string' :
  76. {
  77. return $this->parentString();
  78. } break;
  79. default:
  80. {
  81. eZDebug::writeError( "Attribute '$name' does not exist", "eZTags::attribute" );
  82. return null;
  83. } break;
  84. }
  85. }
  86. /**
  87. * Initializes the tags
  88. *
  89. * @param string $idString
  90. * @param string $keywordString
  91. * @param string $parentString
  92. */
  93. function createFromStrings( $idString, $keywordString, $parentString )
  94. {
  95. $idArray = explode( '|#', $idString );
  96. $keywordArray = explode( '|#', $keywordString );
  97. $parentArray = explode( '|#', $parentString );
  98. $wordArray = array();
  99. foreach ( array_keys( $idArray ) as $key )
  100. {
  101. $wordArray[] = trim( $idArray[$key] ) . "|#" . trim( $keywordArray[$key] ) . "|#" . trim( $parentArray[$key] );
  102. }
  103. $wordArray = array_unique( $wordArray );
  104. foreach ( $wordArray as $wordKey )
  105. {
  106. $word = explode( '|#', $wordKey );
  107. if ($word[0] != '')
  108. {
  109. $this->IDArray[] = (int) $word[0];
  110. $this->KeywordArray[] = $word[1];
  111. $this->ParentArray[] = (int) $word[2];
  112. }
  113. }
  114. }
  115. /**
  116. * Fetches the tags for the given attribute
  117. *
  118. * @param eZContentObjectAttribute $attribute
  119. */
  120. function createFromAttribute( $attribute )
  121. {
  122. if ( !( $attribute instanceof eZContentObjectAttribute && is_numeric( $attribute->attribute( 'id' ) ) ) )
  123. {
  124. return;
  125. }
  126. $classAttribute = $attribute->contentClassAttribute();
  127. $maxTags = (int) $classAttribute->attribute( eZTagsType::MAX_TAGS_FIELD );
  128. if ( $maxTags > 0 && count( $this->IDArray ) > $maxTags )
  129. {
  130. $this->IDArray = array_slice( $this->IDArray, 0, $maxTags );
  131. $this->KeywordArray = array_slice( $this->KeywordArray, 0, $maxTags );
  132. $this->ParentArray = array_slice( $this->ParentArray, 0, $maxTags );
  133. }
  134. $db = eZDB::instance();
  135. $words = $db->arrayQuery( "SELECT eztags.id, eztags.keyword, eztags.parent_id FROM eztags_attribute_link, eztags
  136. WHERE eztags_attribute_link.keyword_id = eztags.id AND
  137. eztags_attribute_link.objectattribute_id = " . $attribute->attribute( 'id' ) . " AND
  138. eztags_attribute_link.objectattribute_version = " . $attribute->attribute( 'version' ) );
  139. $wordArray = array();
  140. foreach ( $words as $w )
  141. {
  142. $wordArray[] = trim( $w['id'] ) . "|#" . trim( $w['keyword'] ) . "|#" . trim( $w['parent_id'] );
  143. }
  144. $wordArray = array_unique( $wordArray );
  145. foreach ( $wordArray as $wordKey )
  146. {
  147. $word = explode( '|#', $wordKey );
  148. if ($word[0] != '')
  149. {
  150. $this->IDArray[] = (int) $word[0];
  151. $this->KeywordArray[] = $word[1];
  152. $this->ParentArray[] = (int) $word[2];
  153. }
  154. }
  155. }
  156. /**
  157. * Stores the tags to database
  158. *
  159. * @param eZContentObjectAttribute $attribute
  160. */
  161. function store( $attribute )
  162. {
  163. if ( !( $attribute instanceof eZContentObjectAttribute && is_numeric( $attribute->attribute( 'id' ) ) ) )
  164. {
  165. return;
  166. }
  167. $attributeID = $attribute->attribute( 'id' );
  168. $attributeVersion = $attribute->attribute( 'version' );
  169. $objectID = $attribute->attribute( 'contentobject_id' );
  170. $db = eZDB::instance();
  171. $currentTime = time();
  172. //get existing tags for object attribute
  173. $existingTagIDs = array();
  174. $existingTags = $db->arrayQuery( "SELECT DISTINCT keyword_id FROM eztags_attribute_link WHERE objectattribute_id = $attributeID AND objectattribute_version = $attributeVersion" );
  175. if ( is_array($existingTags ) )
  176. {
  177. foreach ( $existingTags as $t )
  178. {
  179. $existingTagIDs[] = (int) $t['keyword_id'];
  180. }
  181. }
  182. //get tags to delete from object attribute
  183. $tagsToDelete = array();
  184. $tempIDArray = array();
  185. // if for some reason already existing tags are added with ID = 0 with fromString
  186. // check to see if they really exist, so we don't delete them by mistake
  187. foreach ( array_keys( $this->IDArray ) as $key )
  188. {
  189. if ( $this->IDArray[$key] == 0 )
  190. {
  191. $existing = eZTagsObject::fetchList( array( 'keyword' => array( 'like', trim( $this->KeywordArray[$key] ) ), 'parent_id' => $this->ParentArray[$key] ) );
  192. if ( is_array( $existing ) && !empty( $existing ) )
  193. $tempIDArray[] = $existing[0]->attribute( 'id' );
  194. }
  195. else
  196. {
  197. $tempIDArray[] = $this->IDArray[$key];
  198. }
  199. }
  200. foreach ( $existingTagIDs as $tid )
  201. {
  202. if ( !in_array( $tid, $tempIDArray ) )
  203. {
  204. $tagsToDelete[] = $tid;
  205. }
  206. }
  207. //and delete them
  208. if ( !empty( $tagsToDelete ) )
  209. {
  210. $dbString = $db->generateSQLINStatement( $tagsToDelete, 'keyword_id', false, true, 'int' );
  211. $db->query( "DELETE FROM eztags_attribute_link WHERE $dbString AND eztags_attribute_link.objectattribute_id = $attributeID AND eztags_attribute_link.objectattribute_version = $attributeVersion" );
  212. }
  213. //get tags that are new to the object attribute
  214. $newTags = array();
  215. $tagsToLink = array();
  216. foreach ( array_keys( $this->IDArray ) as $key )
  217. {
  218. if ( !in_array( $this->IDArray[$key], $existingTagIDs ) )
  219. {
  220. if ( $this->IDArray[$key] == 0 )
  221. {
  222. // We won't allow adding tags to the database that already exist, but instead, we link to the existing tags
  223. $existing = eZTagsObject::fetchList( array( 'keyword' => array( 'like', trim( $this->KeywordArray[$key] ) ), 'parent_id' => $this->ParentArray[$key] ) );
  224. if ( is_array( $existing ) && !empty( $existing ) )
  225. {
  226. if ( !in_array( $existing[0]->attribute( 'id' ), $existingTagIDs ) )
  227. $tagsToLink[] = $existing[0]->attribute( 'id' );
  228. }
  229. else
  230. {
  231. $newTags[] = array( 'id' => $this->IDArray[$key], 'keyword' => $this->KeywordArray[$key], 'parent_id' => $this->ParentArray[$key] );
  232. }
  233. }
  234. else
  235. $tagsToLink[] = $this->IDArray[$key];
  236. }
  237. }
  238. //we need to check if user really has access to tags/add, taking into account policy and subtree limits
  239. $attributeSubTreeLimit = $attribute->contentClassAttribute()->attribute( eZTagsType::SUBTREE_LIMIT_FIELD );
  240. $userLimitations = eZTagsTemplateFunctions::getSimplifiedUserAccess('tags', 'add');
  241. if ( $userLimitations['accessWord'] != 'no' && !empty( $newTags ) )
  242. {
  243. //first we need to fetch all locations user has access to
  244. $userLimitations = isset( $userLimitations['simplifiedLimitations']['Tag'] ) ? $userLimitations['simplifiedLimitations']['Tag'] : array();
  245. $allowedLocations = self::getAllowedLocations( $attributeSubTreeLimit, $userLimitations );
  246. foreach ( $newTags as $t )
  247. {
  248. //and then for each tag check if user can save in one of the allowed locations
  249. $parentTag = eZTagsObject::fetch( $t['parent_id'] );
  250. $pathString = ( $parentTag instanceof eZTagsObject ) ? $parentTag->attribute( 'path_string' ) : '/';
  251. $depth = ( $parentTag instanceof eZTagsObject ) ? (int) $parentTag->attribute( 'depth' ) + 1 : 1;
  252. if ( self::canSave( $pathString, $allowedLocations ) )
  253. {
  254. $db->query( "INSERT INTO eztags ( parent_id, main_tag_id, keyword, depth, path_string, modified, remote_id ) VALUES ( " .
  255. $t['parent_id'] . ", 0, '" . $db->escapeString( trim( $t['keyword'] ) ) . "', $depth, '$pathString', 0, '" . eZTagsObject::generateRemoteID() . "' )" );
  256. $tagID = (int) $db->lastSerialID( 'eztags', 'id' );
  257. $db->query( "UPDATE eztags SET path_string = CONCAT(path_string, CAST($tagID AS CHAR), '/') WHERE id = $tagID" );
  258. $pathArray = explode( '/', trim( $pathString, '/' ) );
  259. array_push( $pathArray, $tagID );
  260. $db->query( "UPDATE eztags SET modified = $currentTime WHERE " . $db->generateSQLINStatement( $pathArray, 'id', false, true, 'int' ) );
  261. $tagsToLink[] = $tagID;
  262. }
  263. }
  264. }
  265. //link tags to objects taking into account subtree limit
  266. if ( !empty( $tagsToLink ) )
  267. {
  268. $dbString = $db->generateSQLINStatement( $tagsToLink, 'id', false, true, 'int' );
  269. $tagsToLink = $db->arrayQuery( "SELECT id, path_string FROM eztags WHERE $dbString" );
  270. if ( is_array( $tagsToLink ) && !empty( $tagsToLink ) )
  271. {
  272. foreach ( $tagsToLink as $t )
  273. {
  274. if ( $attributeSubTreeLimit == 0 || ( $attributeSubTreeLimit > 0 &&
  275. strpos( $t['path_string'], '/' . $attributeSubTreeLimit . '/' ) !== false ) )
  276. {
  277. $db->query( "INSERT INTO eztags_attribute_link ( keyword_id, objectattribute_id, objectattribute_version, object_id ) VALUES ( " . $t['id'] . ", $attributeID, $attributeVersion, $objectID )" );
  278. }
  279. }
  280. }
  281. }
  282. }
  283. /**
  284. * Returns all allowed locations user has access to
  285. *
  286. * @static
  287. * @param int $attributeSubTreeLimit
  288. * @param array $userLimitations
  289. * @return bool
  290. */
  291. private static function getAllowedLocations( $attributeSubTreeLimit, $userLimitations )
  292. {
  293. if ( empty( $userLimitations ) )
  294. return array( (string) $attributeSubTreeLimit );
  295. else
  296. {
  297. if ( $attributeSubTreeLimit == 0 )
  298. return $userLimitations;
  299. else
  300. {
  301. $limitTag = eZTagsObject::fetch( $attributeSubTreeLimit );
  302. $pathString = ( $limitTag instanceof eZTagsObject ) ? $limitTag->attribute( 'path_string' ) : '/';
  303. foreach ( $userLimitations as $l )
  304. {
  305. if ( strpos( $pathString, '/' . $l . '/' ) !== false )
  306. return array( (string) $attributeSubTreeLimit );
  307. }
  308. }
  309. }
  310. return array();
  311. }
  312. /**
  313. * Checks if tag (described by its path string) can be saved
  314. * to one of the allowed locations
  315. *
  316. * @static
  317. * @param string $pathString
  318. * @param array $userLimitations
  319. * @return bool
  320. */
  321. private static function canSave( $pathString, $allowedLocations )
  322. {
  323. if ( !empty( $allowedLocations ) )
  324. {
  325. if ( $allowedLocations[0] == 0 )
  326. {
  327. return true;
  328. }
  329. else
  330. {
  331. foreach ( $allowedLocations as $l )
  332. {
  333. if ( strpos( $pathString, '/' . $l . '/' ) !== false )
  334. {
  335. return true;
  336. }
  337. }
  338. }
  339. }
  340. return false;
  341. }
  342. /**
  343. * Returns tags within this instance
  344. *
  345. * @return array
  346. */
  347. function tags()
  348. {
  349. if ( !is_array( $this->IDArray ) || empty( $this->IDArray ) )
  350. return array();
  351. return eZTagsObject::fetchList( array( 'id' => array( $this->IDArray ) ) );
  352. }
  353. /**
  354. * Returns the tags ID array
  355. *
  356. * @return array
  357. */
  358. function idArray()
  359. {
  360. return $this->IDArray;
  361. }
  362. /**
  363. * Returns the IDs as a string
  364. *
  365. * @return string
  366. */
  367. function idString()
  368. {
  369. return implode( '|#', $this->IDArray );
  370. }
  371. /**
  372. * Returns the keywords as a string
  373. *
  374. * @return string
  375. */
  376. function keywordString( $separator = '|#' )
  377. {
  378. return implode( $separator, $this->KeywordArray );
  379. }
  380. /**
  381. * Returns the parent IDs as a string
  382. *
  383. * @return string
  384. */
  385. function parentString()
  386. {
  387. return implode( '|#', $this->ParentArray );
  388. }
  389. }
  390. ?>