ElementSearcherQueryBuilder.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <?php
  2. namespace Muzich\CoreBundle\Searcher;
  3. use Doctrine\ORM\EntityManager;
  4. use Doctrine\ORM\QueryBuilder;
  5. use Doctrine\ORM\Query\Expr\Join;
  6. class ElementSearcherQueryBuilder
  7. {
  8. /**
  9. *
  10. * @var \Doctrine\ORM\EntityManager
  11. */
  12. protected $em;
  13. protected $user_id;
  14. /**
  15. *
  16. * @var array
  17. */
  18. protected $parameters_ids = array();
  19. /**
  20. *
  21. * @var array
  22. */
  23. protected $parameters_elements = array();
  24. /**
  25. *
  26. * @var ElementSearcher
  27. */
  28. protected $es;
  29. /**
  30. *
  31. * @var QueryBuilder
  32. */
  33. protected $query_ids;
  34. /**
  35. *
  36. * @var QueryBuilder
  37. */
  38. protected $query_elements;
  39. /**
  40. *
  41. * @var array
  42. */
  43. protected $builder_params = array();
  44. /**
  45. *
  46. * @param EntityManager $em
  47. * @param ElementSearcher $es
  48. */
  49. public function __construct(EntityManager $em, ElementSearcher $es, $user_id, $builder_params = array())
  50. {
  51. $this->em = $em;
  52. $this->es = $es;
  53. $this->user_id = $user_id;
  54. $this->builder_params = $builder_params;
  55. }
  56. private function buildTags()
  57. {
  58. if (count($tags = $this->es->getTags()))
  59. {
  60. // Un truc pas propre fait que des fois (quand ajax on dirais)
  61. // on a du string a la place du tableau
  62. if (!is_array($tags))
  63. {
  64. $tags_decoded = json_decode($tags);
  65. $tags = array();
  66. foreach ($tags_decoded as $tag_id)
  67. {
  68. $tags[$tag_id] = $tag_id;
  69. }
  70. }
  71. if (count($tags))
  72. {
  73. $str_or = $this->query_ids->expr()->orx();
  74. $this->query_ids->leftJoin('e.tags', 't');
  75. foreach ($tags as $tag_id => $tag_name)
  76. {
  77. $str_or->add($this->query_ids->expr()->eq('t.id', ":itag".$tag_id));
  78. $this->parameters_ids["itag".$tag_id] = $tag_id;
  79. }
  80. $this->query_ids->andWhere($str_or);
  81. }
  82. }
  83. }
  84. private function buildString()
  85. {
  86. if (($string = $this->es->getString()))
  87. {
  88. // On prépare notre liste de mots
  89. $words = array_unique(array_merge(
  90. explode(' ', $string),
  91. explode('-', $string),
  92. explode('- ', $string),
  93. explode(' -', $string),
  94. explode(' - ', $string),
  95. explode(',', $string),
  96. explode(', ', $string),
  97. explode(' ,', $string),
  98. explode(' , ', $string)
  99. ));
  100. // On récupère les ids des elements correspondants
  101. $word_min_length = 0;
  102. if (isset($this->builder_params['word_min_length']))
  103. {
  104. $word_min_length = $this->builder_params['word_min_length'];
  105. }
  106. // On prépare un sous-where avec des or
  107. $str_or = $this->query_ids->expr()->orx();
  108. foreach ($words as $i => $word)
  109. {
  110. if (strlen($word) >= $word_min_length)
  111. {
  112. // On ajoute un or pour chaque mots
  113. $str_or->add($this->query_ids->expr()->like('UPPER(e.name)', ":str".$i));
  114. $this->parameters_ids['str'.$i] = '%'.strtoupper($word).'%';
  115. }
  116. }
  117. $this->query_ids->andWhere($str_or);
  118. }
  119. }
  120. private function buildNetwork()
  121. {
  122. if ($this->es->getNetwork() == ElementSearcher::NETWORK_PERSONAL)
  123. {
  124. $this->query_ids
  125. ->join('e.owner', 'o')
  126. ->leftJoin('e.group', 'g')
  127. ->leftJoin('o.followers_users', 'f')
  128. ->leftJoin('g.followers', 'gf')
  129. ;
  130. $this->query_ids->andWhere('f.follower = :userid OR gf.follower = :useridg');
  131. $this->parameters_ids['userid'] = $this->user_id;
  132. $this->parameters_ids['useridg'] = $this->user_id;
  133. }
  134. }
  135. private function buildFavorite()
  136. {
  137. if ($this->es->isFavorite())
  138. {
  139. if (($favorite_user_id = $this->es->getUserId()) && !$this->es->getGroupId())
  140. {
  141. $this->query_ids->leftJoin('e.elements_favorites', 'fav2');
  142. $this->query_ids->andWhere('fav2.user = :fuid');
  143. $this->parameters_ids['fuid'] = $favorite_user_id;
  144. }
  145. else if (($favorite_group_id = $this->es->getGroupId()) && !$this->es->getUserId())
  146. {
  147. // TODO: Faire en sorte que ça affiche les favoris des gens suivant
  148. // le groupe
  149. }
  150. else
  151. {
  152. throw new Exception('For use favorite search element, you must specify an user_id or group_id');
  153. }
  154. }
  155. }
  156. private function buildUserAndGroup()
  157. {
  158. // (flou) Si on recherche les élements d'un user
  159. if (($search_user_id = $this->es->getUserId()) && !$this->es->isFavorite())
  160. {
  161. $this->query_ids->andWhere('e.owner = :suid');
  162. $this->parameters_ids['suid'] = $search_user_id;
  163. }
  164. // (flou) Si on recherche les éléments d'un groupe
  165. if (($search_group_id = $this->es->getGroupId()) && !$this->es->isFavorite())
  166. {
  167. $this->query_ids->andWhere('e.group = :sgid');
  168. $this->parameters_ids['sgid'] = $search_group_id;
  169. }
  170. }
  171. private function buildLimits()
  172. {
  173. // Si id_limit est précisé c'est que l'on demande "la suite" ou "les nouveaux"
  174. if (($id_limit = $this->es->getIdLimit()) && !$this->es->isSearchingNew())
  175. {
  176. $this->query_ids->andWhere("e.id < :id_limit");
  177. $this->parameters_ids['id_limit'] = $id_limit;
  178. }
  179. elseif ($id_limit && $this->es->isSearchingNew())
  180. {
  181. $this->query_ids->andWhere("e.id > :id_limit");
  182. $this->parameters_ids['id_limit'] = $id_limit;
  183. $this->query_ids->orderBy("e.created", 'ASC')
  184. ->addOrderBy("e.id", 'ASC');
  185. }
  186. }
  187. private function buildStrict()
  188. {
  189. // Recherche strict ou non ?
  190. if ($this->es->getTagStrict() && count(($tags = $this->es->getTags())))
  191. {
  192. // On a besoin de récupérer la liste des element_id qui ont les tags
  193. // demandés.
  194. $tag_ids = '';
  195. foreach ($tags as $tag_id => $tag_name)
  196. {
  197. if ($tag_ids === '')
  198. {
  199. $tag_ids .= (int)$tag_id;
  200. }
  201. else
  202. {
  203. $tag_ids .= ','.(int)$tag_id;
  204. }
  205. }
  206. $sql = "SELECT et.element_id FROM elements_tag et "
  207. ."WHERE et.tag_id IN ($tag_ids) group by et.element_id "
  208. ."having count(distinct et.tag_id) = ".count($tags);
  209. $rsm = new \Doctrine\ORM\Query\ResultSetMapping;
  210. $rsm->addScalarResult('element_id', 'element_id');
  211. $strict_element_ids_result = $this->em
  212. ->createNativeQuery($sql, $rsm)
  213. //->setParameter('ids', $tag_ids)
  214. ->getScalarResult()
  215. ;
  216. $strict_element_ids = array();
  217. foreach ($strict_element_ids_result as $strict_id)
  218. {
  219. $strict_element_ids[] = $strict_id['element_id'];
  220. }
  221. if (count($strict_element_ids))
  222. {
  223. $this->query_ids->andWhere('e.id IN (:tag_strict_ids)');
  224. $this->parameters_ids['tag_strict_ids'] = $strict_element_ids;
  225. }
  226. // Ce else palie au bug du au cas ou $strict_element_ids est egal a array();
  227. else
  228. {
  229. return false;
  230. }
  231. }
  232. }
  233. private function buildNeedTags()
  234. {
  235. // Si id_limit est précisé c'est que l'on demande "la suite" ou "les nouveaux"
  236. if ($this->es->isNeedTags())
  237. {
  238. $this->query_ids->andWhere("e.need_tags = '1'");
  239. }
  240. }
  241. private function buildIdsLimits()
  242. {
  243. if ($this->es->hasIds())
  244. {
  245. $this->query_ids->andWhere('e.id IN (:limiteds_ids)');
  246. $this->parameters_ids['limiteds_ids'] = $this->es->getIds();
  247. }
  248. }
  249. /**
  250. *
  251. * @param boolean $disable_limit
  252. */
  253. protected function proceedIdsQuery($disable_limit = false)
  254. {
  255. $this->query_ids = $this->em->createQueryBuilder()
  256. ->select('e.id')
  257. ->from('MuzichCoreBundle:Element', 'e')
  258. ->groupBy('e.id')
  259. ->orderBy('e.created', 'DESC')
  260. ->addOrderBy('e.id', 'DESC')
  261. ;
  262. if (!$disable_limit)
  263. {
  264. $this->query_ids->setMaxResults($this->es->getCount());
  265. }
  266. // Prise en compte des tags
  267. $this->buildTags();
  268. // Si on effectue une recherche avec un string
  269. $this->buildString();
  270. // Paramètrage en fonction du réseau
  271. $this->buildNetwork();
  272. // Si on recherche des elements d'user ou de groupe
  273. $this->buildUserAndGroup();
  274. // Si on recherche des elements mis en favoris
  275. $this->buildFavorite();
  276. // Pour les demandes de "more" ou "nouveaux" elements.
  277. $this->buildLimits();
  278. // Si on recherche les tags de manière stricte
  279. if ($this->buildStrict() === false)
  280. {
  281. return false;
  282. }
  283. // Si on recherche des partages en demande de tags
  284. $this->buildNeedTags();
  285. // Si on a fournis des ids dés le départ
  286. $this->buildIdsLimits();
  287. $this->query_ids->setParameters($this->parameters_ids);
  288. }
  289. /**
  290. *
  291. * @param boolean $disable_limit
  292. * @return \Doctrine\ORM\Query
  293. * @throws \Exception
  294. */
  295. public function getIdsQuery($disable_limit = false)
  296. {
  297. //// Contrôle de la demande
  298. //if ($this->es->hasIds())
  299. //{
  300. // throw new \Exception("Vous demandez un Query_ids avec un ElementSearcher "
  301. // ."possédant déjà une liste d'ids");
  302. //}
  303. if ($this->proceedIdsQuery($disable_limit) === false)
  304. {
  305. return false;
  306. }
  307. return $this->query_ids->getQuery();
  308. }
  309. protected function proceedElementsQuery()
  310. {
  311. // On récupère les ids d'éléments
  312. if (($ids_query = $this->getIdsQuery()) === false)
  313. {
  314. return false;
  315. }
  316. // On va récupérer les ids en base en fonction des paramètres
  317. $q_ids = $ids_query->getArrayResult();
  318. $element_ids = array();
  319. if (count($q_ids))
  320. {
  321. // On prépare les ids pour la requete des éléments
  322. foreach ($q_ids as $r_id)
  323. {
  324. $element_ids[] = $r_id['id'];
  325. }
  326. }
  327. if (!count($element_ids))
  328. {
  329. // Si on a pas d'ids on retourne une requete qui ne donnera rien
  330. return false;
  331. }
  332. // On prépare des paramètres de la requete d'éléments
  333. $this->parameters_elements['uidt'] = '%"'. $this->user_id.'"%';
  334. $this->parameters_elements['uid'] = $this->user_id;
  335. $this->parameters_elements['ids'] = $element_ids;
  336. // On prépare la requete des elements
  337. $this->query_elements = $this->em->createQueryBuilder()
  338. ->select('e', 'p', 'po', 't', 'o', 'g', 'fav')
  339. ->from('MuzichCoreBundle:Element', 'e')
  340. ->leftJoin('e.group', 'g')
  341. ->leftJoin('e.parent', 'p')
  342. ->leftJoin('p.owner', 'po')
  343. ->leftJoin('e.tags', 't', Join::WITH,
  344. "(t.tomoderate = 'FALSE' OR t.tomoderate IS NULL OR t.privateids LIKE :uidt)")
  345. ->leftJoin('e.elements_favorites', 'fav', Join::WITH,
  346. 'fav.user = :uid')
  347. ->join('e.owner', 'o')
  348. ->where('e.id IN (:ids)')
  349. ->orderBy("e.created", 'DESC')
  350. ->addOrderBy("e.id", 'DESC')
  351. ;
  352. // Ce code est désactivé: Les ids ont déjà été filtré par la id_query.
  353. // // Ce cas de figure se présente lorsque l'on fait un ajax de "plus d'éléments"
  354. // if (($id_limit = $this->es->getIdLimit()))
  355. // {
  356. // $this->query_elements->andWhere("e.id < :id_limit");
  357. // $this->query_elements->setMaxResults($this->es->getCount());
  358. // $this->parameters_elements['id_limit'] = $id_limit;
  359. // }
  360. // Lorsque l'on impose les ids (typiquement affichage des éléments avec un commentaire etc)
  361. // On charge les tags proposés dés la requete pour économiser les échanges avec la bdd
  362. if (($ids_display = $this->es->getIdsDisplay()))
  363. {
  364. $this->query_elements
  365. ->addSelect('tp', 'tpu', 'tpt')
  366. ->leftJoin('e.tags_propositions', 'tp')
  367. ->leftJoin('tp.user', 'tpu')
  368. ->leftJoin('tp.tags', 'tpt')
  369. ;
  370. }
  371. $this->query_elements->setParameters($this->parameters_elements);
  372. }
  373. public function getElementsQuery()
  374. {
  375. if ($this->proceedElementsQuery() === false)
  376. {
  377. return $this->em
  378. ->createQuery("SELECT e FROM MuzichCoreBundle:Element e WHERE 1 = 2")
  379. ;
  380. }
  381. return $this->query_elements->getQuery();
  382. }
  383. }