ElementSearcherQueryBuilder.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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 && $this->user_id)
  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())) && $this->es->getTags() != '[]')
  191. {
  192. if (is_null($tags) && !count($tags))
  193. {
  194. return;
  195. }
  196. if (is_string($tags))
  197. {
  198. if (is_null(trim($tags)))
  199. {
  200. return;
  201. }
  202. }
  203. // On a besoin de récupérer la liste des element_id qui ont les tags
  204. // demandés.
  205. $tag_ids = '';
  206. if (count($tags))
  207. {
  208. foreach ($tags as $tag_id => $tag_name)
  209. {
  210. if ($tag_ids === '')
  211. {
  212. $tag_ids .= (int)$tag_id;
  213. }
  214. else
  215. {
  216. $tag_ids .= ','.(int)$tag_id;
  217. }
  218. }
  219. if (!is_null(trim($tag_ids)))
  220. {
  221. $sql = "SELECT et.element_id FROM elements_tag et "
  222. ."WHERE et.tag_id IN ($tag_ids) group by et.element_id "
  223. ."having count(distinct et.tag_id) = ".count($tags);
  224. $rsm = new \Doctrine\ORM\Query\ResultSetMapping;
  225. $rsm->addScalarResult('element_id', 'element_id');
  226. $strict_element_ids_result = $this->em
  227. ->createNativeQuery($sql, $rsm)
  228. //->setParameter('ids', $tag_ids)
  229. ->getScalarResult()
  230. ;
  231. $strict_element_ids = array();
  232. if (count($strict_element_ids_result))
  233. {
  234. foreach ($strict_element_ids_result as $strict_id)
  235. {
  236. $strict_element_ids[] = $strict_id['element_id'];
  237. }
  238. }
  239. if (count($strict_element_ids))
  240. {
  241. $this->query_ids->andWhere('e.id IN (:tag_strict_ids)');
  242. $this->parameters_ids['tag_strict_ids'] = $strict_element_ids;
  243. }
  244. // Ce else palie au bug du au cas ou $strict_element_ids est egal a array();
  245. else
  246. {
  247. return false;
  248. }
  249. }
  250. }
  251. }
  252. }
  253. private function buildNeedTags()
  254. {
  255. // Si id_limit est précisé c'est que l'on demande "la suite" ou "les nouveaux"
  256. if ($this->es->isNeedTags())
  257. {
  258. $this->query_ids->andWhere("e.need_tags = '1'");
  259. }
  260. }
  261. private function buildIdsLimits()
  262. {
  263. if ($this->es->hasIds())
  264. {
  265. $this->query_ids->andWhere('e.id IN (:limiteds_ids)');
  266. $this->parameters_ids['limiteds_ids'] = $this->es->getIds();
  267. }
  268. }
  269. /**
  270. *
  271. * @param boolean $disable_limit
  272. */
  273. protected function proceedIdsQuery($disable_limit = false)
  274. {
  275. $this->query_ids = $this->em->createQueryBuilder()
  276. ->select('e.id')
  277. ->from('MuzichCoreBundle:Element', 'e')
  278. ->groupBy('e.id')
  279. ->orderBy('e.created', 'DESC')
  280. ->addOrderBy('e.id', 'DESC')
  281. ;
  282. if (!$disable_limit)
  283. {
  284. $this->query_ids->setMaxResults($this->es->getCount());
  285. }
  286. // Prise en compte des tags
  287. $this->buildTags();
  288. // Si on effectue une recherche avec un string
  289. $this->buildString();
  290. // Paramètrage en fonction du réseau
  291. $this->buildNetwork();
  292. // Si on recherche des elements d'user ou de groupe
  293. $this->buildUserAndGroup();
  294. // Si on recherche des elements mis en favoris
  295. $this->buildFavorite();
  296. // Pour les demandes de "more" ou "nouveaux" elements.
  297. $this->buildLimits();
  298. // Si on recherche les tags de manière stricte
  299. if ($this->buildStrict() === false)
  300. {
  301. return false;
  302. }
  303. // Si on recherche des partages en demande de tags
  304. $this->buildNeedTags();
  305. // Si on a fournis des ids dés le départ
  306. $this->buildIdsLimits();
  307. $this->query_ids->setParameters($this->parameters_ids);
  308. }
  309. /**
  310. *
  311. * @param boolean $disable_limit
  312. * @return \Doctrine\ORM\Query
  313. * @throws \Exception
  314. */
  315. public function getIdsQuery($disable_limit = false)
  316. {
  317. //// Contrôle de la demande
  318. //if ($this->es->hasIds())
  319. //{
  320. // throw new \Exception("Vous demandez un Query_ids avec un ElementSearcher "
  321. // ."possédant déjà une liste d'ids");
  322. //}
  323. if ($this->proceedIdsQuery($disable_limit) === false)
  324. {
  325. return false;
  326. }
  327. return $this->query_ids->getQuery();
  328. }
  329. protected function proceedElementsQuery()
  330. {
  331. // On récupère les ids d'éléments
  332. if (($ids_query = $this->getIdsQuery()) === false)
  333. {
  334. return false;
  335. }
  336. // On va récupérer les ids en base en fonction des paramètres
  337. $q_ids = $ids_query->getArrayResult();
  338. $element_ids = array();
  339. if (count($q_ids))
  340. {
  341. // On prépare les ids pour la requete des éléments
  342. foreach ($q_ids as $r_id)
  343. {
  344. $element_ids[] = $r_id['id'];
  345. }
  346. }
  347. if (!count($element_ids))
  348. {
  349. // Si on a pas d'ids on retourne une requete qui ne donnera rien
  350. return false;
  351. }
  352. // On prépare des paramètres de la requete d'éléments
  353. $this->parameters_elements['ids'] = $element_ids;
  354. // On prépare la requete des elements
  355. $this->query_elements = $this->em->createQueryBuilder();
  356. if ($this->user_id)
  357. {
  358. $this->query_elements->select('e', 'p', 'po', 't', 'o', 'g', 'fav');
  359. }
  360. else{
  361. $this->query_elements->select('e', 'p', 'po', 't', 'o', 'g');
  362. }
  363. $this->query_elements
  364. ->from('MuzichCoreBundle:Element', 'e')
  365. ->leftJoin('e.group', 'g')
  366. ->leftJoin('e.parent', 'p')
  367. ->leftJoin('p.owner', 'po')
  368. ;
  369. if ($this->user_id)
  370. {
  371. $this->parameters_elements['uidt'] = '%"'. $this->user_id.'"%';
  372. $this->parameters_elements['uid'] = $this->user_id;
  373. $this->query_elements
  374. ->leftJoin('e.tags', 't', Join::WITH,
  375. "(t.tomoderate = 'FALSE' OR t.tomoderate IS NULL OR t.privateids LIKE :uidt)")
  376. ->leftJoin('e.elements_favorites', 'fav', Join::WITH,
  377. 'fav.user = :uid')
  378. ;
  379. }
  380. else
  381. {
  382. $this->query_elements
  383. ->leftJoin('e.tags', 't', Join::WITH,
  384. "(t.tomoderate = 'FALSE' OR t.tomoderate IS NULL)")
  385. ;
  386. }
  387. $this->query_elements
  388. ->join('e.owner', 'o')
  389. ->where('e.id IN (:ids)')
  390. ->orderBy("e.created", 'DESC')
  391. ->addOrderBy("e.id", 'DESC')
  392. ;
  393. // Ce code est désactivé: Les ids ont déjà été filtré par la id_query.
  394. // // Ce cas de figure se présente lorsque l'on fait un ajax de "plus d'éléments"
  395. // if (($id_limit = $this->es->getIdLimit()))
  396. // {
  397. // $this->query_elements->andWhere("e.id < :id_limit");
  398. // $this->query_elements->setMaxResults($this->es->getCount());
  399. // $this->parameters_elements['id_limit'] = $id_limit;
  400. // }
  401. // Lorsque l'on impose les ids (typiquement affichage des éléments avec un commentaire etc)
  402. // On charge les tags proposés dés la requete pour économiser les échanges avec la bdd
  403. if (($ids_display = $this->es->getIdsDisplay()))
  404. {
  405. $this->query_elements
  406. ->addSelect('tp', 'tpu', 'tpt')
  407. ->leftJoin('e.tags_propositions', 'tp')
  408. ->leftJoin('tp.user', 'tpu')
  409. ->leftJoin('tp.tags', 'tpt')
  410. ;
  411. }
  412. $this->query_elements->setParameters($this->parameters_elements);
  413. }
  414. public function getElementsQuery()
  415. {
  416. if ($this->proceedElementsQuery() === false)
  417. {
  418. return $this->em
  419. ->createQuery("SELECT e FROM MuzichCoreBundle:Element e WHERE 1 = 2")
  420. ;
  421. }
  422. return $this->query_elements->getQuery();
  423. }
  424. }