/extension/eztags/datatypes/eztags/eztags.php

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