PageRenderTime 25ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Backend/Modules/Tags/Engine/Model.php

https://github.com/forkcms/forkcms
PHP | 364 lines | 219 code | 45 blank | 100 comment | 18 complexity | 1955772a2d94eafd8d00714e5f5b587e MD5 | raw file
  1. <?php
  2. namespace Backend\Modules\Tags\Engine;
  3. use Common\Uri as CommonUri;
  4. use Backend\Core\Language\Language as BL;
  5. use Backend\Core\Engine\Model as BackendModel;
  6. use Backend\Modules\Search\Engine\Model as BackendSearchModel;
  7. /**
  8. * In this file we store all generic functions that we will be using in the TagsModule
  9. */
  10. class Model
  11. {
  12. const QUERY_DATAGRID_BROWSE =
  13. 'SELECT i.id, i.tag, i.number AS num_tags
  14. FROM tags AS i
  15. WHERE i.language = ?
  16. GROUP BY i.id';
  17. /**
  18. * Delete one or more tags.
  19. *
  20. * @param int|int[] $ids The ids to delete.
  21. */
  22. public static function delete($ids): void
  23. {
  24. // get database
  25. $database = BackendModel::getContainer()->get('database');
  26. // make sure $ids is an array of integers
  27. $ids = array_map('intval', (array) $ids);
  28. // delete tags
  29. $database->delete('tags', 'id IN (' . implode(',', $ids) . ')');
  30. $database->delete('modules_tags', 'tag_id IN (' . implode(',', $ids) . ')');
  31. }
  32. public static function exists(int $id): bool
  33. {
  34. return (bool) BackendModel::getContainer()->get('database')->getVar(
  35. 'SELECT i.id
  36. FROM tags AS i
  37. WHERE i.id = ?',
  38. [$id]
  39. );
  40. }
  41. public static function existsTag(string $tag): bool
  42. {
  43. return (BackendModel::getContainer()->get('database')->getVar(
  44. 'SELECT i.tag FROM tags AS i WHERE i.tag = ?',
  45. [$tag]
  46. ) != '');
  47. }
  48. public static function get(int $id): array
  49. {
  50. return (array) BackendModel::getContainer()->get('database')->getRecord(
  51. 'SELECT i.tag AS name
  52. FROM tags AS i
  53. WHERE i.id = ?',
  54. [$id]
  55. );
  56. }
  57. public static function getAll(string $language = null): array
  58. {
  59. return (array) BackendModel::getContainer()->get('database')->getRecords(
  60. 'SELECT i.tag AS name
  61. FROM tags AS i
  62. WHERE i.language = ?',
  63. [$language ?? BL::getWorkingLanguage()]
  64. );
  65. }
  66. public static function getTagNames(string $language = null): array
  67. {
  68. return (array) BackendModel::getContainer()->get('database')->getColumn(
  69. 'SELECT tag
  70. FROM tags
  71. WHERE language = ?',
  72. [$language ?? BL::getWorkingLanguage()]
  73. );
  74. }
  75. /**
  76. * Get tags that start with the given string
  77. *
  78. * @param string $term The searchstring.
  79. * @param string $language The language to use, if not provided use the working language.
  80. *
  81. * @return array
  82. */
  83. public static function getStartsWith(string $term, string $language = null): array
  84. {
  85. return (array) BackendModel::getContainer()->get('database')->getRecords(
  86. 'SELECT i.tag AS name, i.tag AS value
  87. FROM tags AS i
  88. WHERE i.language = ? AND i.tag LIKE ?
  89. ORDER BY i.tag ASC',
  90. [$language ?? BL::getWorkingLanguage(), $term . '%']
  91. );
  92. }
  93. /**
  94. * Get tags for an item
  95. *
  96. * @param string $module The module wherein will be searched.
  97. * @param int $otherId The id of the record.
  98. * @param string $type The type of the returnvalue, possible values are: array, string (tags will be joined by ,).
  99. * @param string $language The language to use, if not provided the working language will be used.
  100. *
  101. * @return mixed
  102. */
  103. public static function getTags(string $module, int $otherId, string $type = 'string', string $language = null)
  104. {
  105. $type = (string) \SpoonFilter::getValue($type, ['string', 'array'], 'string');
  106. // fetch tags
  107. $tags = (array) BackendModel::getContainer()->get('database')->getColumn(
  108. 'SELECT i.tag
  109. FROM tags AS i
  110. INNER JOIN modules_tags AS mt ON i.id = mt.tag_id
  111. WHERE mt.module = ? AND mt.other_id = ? AND i.language = ?
  112. ORDER BY i.tag ASC',
  113. [$module, $otherId, $language ?? BL::getWorkingLanguage()]
  114. );
  115. // return as an imploded string
  116. if ($type === 'string') {
  117. return implode(',', $tags);
  118. }
  119. // return as array
  120. return $tags;
  121. }
  122. /**
  123. * Get a unique URL for a tag
  124. *
  125. * @param string $url The URL to use as a base.
  126. * @param int|null $id The ID to ignore.
  127. *
  128. * @return string
  129. */
  130. public static function getUrl(string $url, int $id = null): string
  131. {
  132. $url = CommonUri::getUrl($url);
  133. $language = BL::getWorkingLanguage();
  134. // get database
  135. $database = BackendModel::getContainer()->get('database');
  136. // no specific id
  137. if ($id === null) {
  138. // get number of tags with the specified url
  139. $number = (int) $database->getVar(
  140. 'SELECT 1
  141. FROM tags AS i
  142. WHERE i.url = ? AND i.language = ?
  143. LIMIT 1',
  144. [$url, $language]
  145. );
  146. // there are items so, call this method again.
  147. if ($number != 0) {
  148. // add a number
  149. $url = BackendModel::addNumber($url);
  150. // recall this method, but with a new url
  151. $url = self::getUrl($url, $id);
  152. }
  153. } else {
  154. // specific id given
  155. // get number of tags with the specified url
  156. $number = (int) $database->getVar(
  157. 'SELECT 1
  158. FROM tags AS i
  159. WHERE i.url = ? AND i.language = ? AND i.id != ?
  160. LIMIT 1',
  161. [$url, $language, $id]
  162. );
  163. // there are items so, call this method again.
  164. if ($number != 0) {
  165. // add a number
  166. $url = BackendModel::addNumber($url);
  167. // recall this method, but with a new url
  168. $url = self::getUrl($url, $id);
  169. }
  170. }
  171. return $url;
  172. }
  173. /**
  174. * Insert a new tag
  175. *
  176. * @param string $tag The data for the tag.
  177. * @param string $language The language wherein the tag will be inserted,
  178. * if not provided the workinglanguage will be used.
  179. *
  180. * @return int
  181. */
  182. public static function insert(string $tag, string $language = null): int
  183. {
  184. return (int) BackendModel::getContainer()->get('database')->insert(
  185. 'tags',
  186. [
  187. 'language' => $language ?? BL::getWorkingLanguage(),
  188. 'tag' => $tag,
  189. 'number' => 0,
  190. 'url' => self::getUrl($tag),
  191. ]
  192. );
  193. }
  194. /**
  195. * Save the tags
  196. *
  197. * @param int $otherId The id of the item to tag.
  198. * @param mixed $tags The tags for the item.
  199. * @param string $module The module wherein the item is located.
  200. * @param string|null $language The language wherein the tags will be inserted,
  201. * if not provided the workinglanguage will be used.
  202. */
  203. public static function saveTags(int $otherId, $tags, string $module, string $language = null)
  204. {
  205. $language = $language ?? BL::getWorkingLanguage();
  206. // redefine the tags as an array
  207. if (!is_array($tags)) {
  208. $tags = (array) explode(',', (string) $tags);
  209. }
  210. // make sure the list of tags contains only unique and non-empty elements in a case insensitive way
  211. $tags = array_filter(
  212. array_intersect_key(
  213. $tags,
  214. array_unique(
  215. array_map(
  216. static function (string $tag): string {
  217. return strtolower(trim($tag));
  218. },
  219. $tags
  220. )
  221. )
  222. )
  223. );
  224. // get database
  225. $database = BackendModel::getContainer()->get('database');
  226. // get current tags for item
  227. $currentTags = (array) $database->getPairs(
  228. 'SELECT i.tag, i.id
  229. FROM tags AS i
  230. INNER JOIN modules_tags AS mt ON i.id = mt.tag_id
  231. WHERE mt.module = ? AND mt.other_id = ? AND i.language = ?',
  232. [$module, $otherId, $language]
  233. );
  234. // remove old links
  235. if (!empty($currentTags)) {
  236. $database->delete(
  237. 'modules_tags',
  238. 'tag_id IN (' . implode(', ', array_values($currentTags)) . ') AND other_id = ? AND module = ?',
  239. [$otherId, $module]
  240. );
  241. }
  242. if (!empty($tags)) {
  243. // loop tags
  244. foreach ($tags as $key => $tag) {
  245. // cleanup
  246. $tag = mb_strtolower(trim($tag));
  247. // unset if the tag is empty
  248. if ($tag == '') {
  249. unset($tags[$key]);
  250. } else {
  251. $tags[$key] = $tag;
  252. }
  253. }
  254. // don't do a regular implode, mysql injection might be possible
  255. $placeholders = array_fill(0, count($tags), '?');
  256. // get tag ids
  257. $tagsAndIds = (array) $database->getPairs(
  258. 'SELECT LOWER(i.tag), i.id
  259. FROM tags AS i
  260. WHERE i.tag IN (' . implode(',', $placeholders) . ') AND i.language = ?',
  261. array_merge($tags, [$language])
  262. );
  263. // loop again and create tags that don't already exist
  264. foreach ($tags as $tag) {
  265. // doesn' exist yet
  266. if (!isset($tagsAndIds[$tag])) {
  267. // insert tag
  268. $tagsAndIds[$tag] = self::insert($tag, $language);
  269. }
  270. }
  271. // init items to insert
  272. $rowsToInsert = [];
  273. // loop again
  274. foreach ($tags as $tag) {
  275. // get tagId
  276. $tagId = (int) $tagsAndIds[$tag];
  277. // not linked before so increment the counter
  278. if (!isset($currentTags[$tag])) {
  279. $database->execute(
  280. 'UPDATE tags SET number = number + 1 WHERE id = ?',
  281. $tagId
  282. );
  283. }
  284. // add to insert array
  285. $rowsToInsert[] = ['module' => $module, 'tag_id' => $tagId, 'other_id' => $otherId];
  286. }
  287. // insert the rows at once if there are items to insert
  288. if (!empty($rowsToInsert)) {
  289. $database->insert('modules_tags', $rowsToInsert);
  290. }
  291. }
  292. // add to search index
  293. BackendSearchModel::saveIndex($module, $otherId, ['tags' => implode(' ', (array) $tags)], $language);
  294. // decrement number
  295. foreach ($currentTags as $tag => $tagId) {
  296. // if the tag can't be found in the new tags we lower the number of tags by one
  297. if (array_search($tag, $tags) === false) {
  298. $database->execute(
  299. 'UPDATE tags SET number = number - 1 WHERE id = ?',
  300. $tagId
  301. );
  302. }
  303. }
  304. // remove all tags that don't have anything linked
  305. $database->delete('tags', 'number = ?', 0);
  306. }
  307. /**
  308. * Update a tag
  309. * Remark: $tag['id'] should be available.
  310. *
  311. * @param array $tag The new data for the tag.
  312. *
  313. * @return int
  314. */
  315. public static function update(array $tag): int
  316. {
  317. return BackendModel::getContainer()->get('database')->update('tags', $tag, 'id = ?', $tag['id']);
  318. }
  319. }