SavedSearches.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. <?php
  2. /**
  3. * Saved searches managing
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin;
  7. use function count;
  8. use function intval;
  9. use function is_string;
  10. use function json_decode;
  11. use function json_encode;
  12. use function max;
  13. use function min;
  14. /**
  15. * Saved searches managing
  16. */
  17. class SavedSearches
  18. {
  19. /**
  20. * Global configuration
  21. *
  22. * @var array
  23. */
  24. private $config = null;
  25. /**
  26. * Id
  27. *
  28. * @var int|null
  29. */
  30. private $id = null;
  31. /**
  32. * Username
  33. *
  34. * @var string
  35. */
  36. private $username = null;
  37. /**
  38. * DB name
  39. *
  40. * @var string
  41. */
  42. private $dbname = null;
  43. /**
  44. * Saved search name
  45. *
  46. * @var string
  47. */
  48. private $searchName = null;
  49. /**
  50. * Criterias
  51. *
  52. * @var array
  53. */
  54. private $criterias = null;
  55. /** @var Relation */
  56. private $relation;
  57. /**
  58. * @param array $config Global configuration
  59. * @param Relation $relation Relation instance
  60. */
  61. public function __construct(array $config, Relation $relation)
  62. {
  63. $this->setConfig($config);
  64. $this->relation = $relation;
  65. }
  66. /**
  67. * Setter of id
  68. *
  69. * @param int|null $searchId Id of search
  70. *
  71. * @return static
  72. */
  73. public function setId($searchId)
  74. {
  75. $searchId = (int) $searchId;
  76. if (empty($searchId)) {
  77. $searchId = null;
  78. }
  79. $this->id = $searchId;
  80. return $this;
  81. }
  82. /**
  83. * Getter of id
  84. *
  85. * @return int|null
  86. */
  87. public function getId()
  88. {
  89. return $this->id;
  90. }
  91. /**
  92. * Setter of searchName
  93. *
  94. * @param string $searchName Saved search name
  95. *
  96. * @return static
  97. */
  98. public function setSearchName($searchName)
  99. {
  100. $this->searchName = $searchName;
  101. return $this;
  102. }
  103. /**
  104. * Getter of searchName
  105. *
  106. * @return string
  107. */
  108. public function getSearchName()
  109. {
  110. return $this->searchName;
  111. }
  112. /**
  113. * Setter of config
  114. *
  115. * @param array $config Global configuration
  116. *
  117. * @return static
  118. */
  119. public function setConfig(array $config)
  120. {
  121. $this->config = $config;
  122. return $this;
  123. }
  124. /**
  125. * Getter of config
  126. *
  127. * @return array
  128. */
  129. public function getConfig()
  130. {
  131. return $this->config;
  132. }
  133. /**
  134. * Setter for criterias
  135. *
  136. * @param array|string $criterias Criterias of saved searches
  137. * @param bool $json Criterias are in JSON format
  138. *
  139. * @return static
  140. */
  141. public function setCriterias($criterias, $json = false)
  142. {
  143. if ($json === true && is_string($criterias)) {
  144. $this->criterias = json_decode($criterias, true);
  145. return $this;
  146. }
  147. $aListFieldsToGet = [
  148. 'criteriaColumn',
  149. 'criteriaSort',
  150. 'criteriaShow',
  151. 'criteria',
  152. 'criteriaAndOrRow',
  153. 'criteriaAndOrColumn',
  154. 'rows',
  155. 'TableList',
  156. ];
  157. $data = [];
  158. $data['criteriaColumnCount'] = count($criterias['criteriaColumn']);
  159. foreach ($aListFieldsToGet as $field) {
  160. if (! isset($criterias[$field])) {
  161. continue;
  162. }
  163. $data[$field] = $criterias[$field];
  164. }
  165. /* Limit amount of rows */
  166. if (! isset($data['rows'])) {
  167. $data['rows'] = 0;
  168. } else {
  169. $data['rows'] = min(
  170. max(0, intval($data['rows'])),
  171. 100
  172. );
  173. }
  174. for ($i = 0; $i <= $data['rows']; $i++) {
  175. $data['Or' . $i] = $criterias['Or' . $i];
  176. }
  177. $this->criterias = $data;
  178. return $this;
  179. }
  180. /**
  181. * Getter for criterias
  182. *
  183. * @return array
  184. */
  185. public function getCriterias()
  186. {
  187. return $this->criterias;
  188. }
  189. /**
  190. * Setter for username
  191. *
  192. * @param string $username Username
  193. *
  194. * @return static
  195. */
  196. public function setUsername($username)
  197. {
  198. $this->username = $username;
  199. return $this;
  200. }
  201. /**
  202. * Getter for username
  203. *
  204. * @return string
  205. */
  206. public function getUsername()
  207. {
  208. return $this->username;
  209. }
  210. /**
  211. * Setter for DB name
  212. *
  213. * @param string $dbname DB name
  214. *
  215. * @return static
  216. */
  217. public function setDbname($dbname)
  218. {
  219. $this->dbname = $dbname;
  220. return $this;
  221. }
  222. /**
  223. * Getter for DB name
  224. *
  225. * @return string
  226. */
  227. public function getDbname()
  228. {
  229. return $this->dbname;
  230. }
  231. /**
  232. * Save the search
  233. *
  234. * @return bool
  235. */
  236. public function save()
  237. {
  238. global $dbi;
  239. if ($this->getSearchName() == null) {
  240. $message = Message::error(
  241. __('Please provide a name for this bookmarked search.')
  242. );
  243. $response = Response::getInstance();
  244. $response->setRequestStatus($message->isSuccess());
  245. $response->addJSON('fieldWithError', 'searchName');
  246. $response->addJSON('message', $message);
  247. exit;
  248. }
  249. if ($this->getUsername() == null
  250. || $this->getDbname() == null
  251. || $this->getSearchName() == null
  252. || $this->getCriterias() == null
  253. ) {
  254. $message = Message::error(
  255. __('Missing information to save the bookmarked search.')
  256. );
  257. $response = Response::getInstance();
  258. $response->setRequestStatus($message->isSuccess());
  259. $response->addJSON('message', $message);
  260. exit;
  261. }
  262. $savedSearchesTbl
  263. = Util::backquote($this->config['cfgRelation']['db']) . '.'
  264. . Util::backquote($this->config['cfgRelation']['savedsearches']);
  265. //If it's an insert.
  266. if ($this->getId() === null) {
  267. $wheres = [
  268. "search_name = '" . $dbi->escapeString($this->getSearchName())
  269. . "'",
  270. ];
  271. $existingSearches = $this->getList($wheres);
  272. if (! empty($existingSearches)) {
  273. $message = Message::error(
  274. __('An entry with this name already exists.')
  275. );
  276. $response = Response::getInstance();
  277. $response->setRequestStatus($message->isSuccess());
  278. $response->addJSON('fieldWithError', 'searchName');
  279. $response->addJSON('message', $message);
  280. exit;
  281. }
  282. $sqlQuery = 'INSERT INTO ' . $savedSearchesTbl
  283. . '(`username`, `db_name`, `search_name`, `search_data`)'
  284. . ' VALUES ('
  285. . "'" . $dbi->escapeString($this->getUsername()) . "',"
  286. . "'" . $dbi->escapeString($this->getDbname()) . "',"
  287. . "'" . $dbi->escapeString($this->getSearchName()) . "',"
  288. . "'" . $dbi->escapeString(json_encode($this->getCriterias()))
  289. . "')";
  290. $result = (bool) $this->relation->queryAsControlUser($sqlQuery);
  291. if (! $result) {
  292. return false;
  293. }
  294. $this->setId($dbi->insertId());
  295. return true;
  296. }
  297. //Else, it's an update.
  298. $wheres = [
  299. 'id != ' . $this->getId(),
  300. "search_name = '" . $dbi->escapeString($this->getSearchName()) . "'",
  301. ];
  302. $existingSearches = $this->getList($wheres);
  303. if (! empty($existingSearches)) {
  304. $message = Message::error(
  305. __('An entry with this name already exists.')
  306. );
  307. $response = Response::getInstance();
  308. $response->setRequestStatus($message->isSuccess());
  309. $response->addJSON('fieldWithError', 'searchName');
  310. $response->addJSON('message', $message);
  311. exit;
  312. }
  313. $sqlQuery = 'UPDATE ' . $savedSearchesTbl
  314. . "SET `search_name` = '"
  315. . $dbi->escapeString($this->getSearchName()) . "', "
  316. . "`search_data` = '"
  317. . $dbi->escapeString(json_encode($this->getCriterias())) . "' "
  318. . 'WHERE id = ' . $this->getId();
  319. return (bool) $this->relation->queryAsControlUser($sqlQuery);
  320. }
  321. /**
  322. * Delete the search
  323. *
  324. * @return bool
  325. */
  326. public function delete()
  327. {
  328. global $dbi;
  329. if ($this->getId() == null) {
  330. $message = Message::error(
  331. __('Missing information to delete the search.')
  332. );
  333. $response = Response::getInstance();
  334. $response->setRequestStatus($message->isSuccess());
  335. $response->addJSON('fieldWithError', 'searchId');
  336. $response->addJSON('message', $message);
  337. exit;
  338. }
  339. $savedSearchesTbl
  340. = Util::backquote($this->config['cfgRelation']['db']) . '.'
  341. . Util::backquote($this->config['cfgRelation']['savedsearches']);
  342. $sqlQuery = 'DELETE FROM ' . $savedSearchesTbl
  343. . "WHERE id = '" . $dbi->escapeString((string) $this->getId()) . "'";
  344. return (bool) $this->relation->queryAsControlUser($sqlQuery);
  345. }
  346. /**
  347. * Load the current search from an id.
  348. *
  349. * @return bool Success
  350. */
  351. public function load()
  352. {
  353. global $dbi;
  354. if ($this->getId() == null) {
  355. $message = Message::error(
  356. __('Missing information to load the search.')
  357. );
  358. $response = Response::getInstance();
  359. $response->setRequestStatus($message->isSuccess());
  360. $response->addJSON('fieldWithError', 'searchId');
  361. $response->addJSON('message', $message);
  362. exit;
  363. }
  364. $savedSearchesTbl = Util::backquote($this->config['cfgRelation']['db'])
  365. . '.'
  366. . Util::backquote($this->config['cfgRelation']['savedsearches']);
  367. $sqlQuery = 'SELECT id, search_name, search_data '
  368. . 'FROM ' . $savedSearchesTbl . ' '
  369. . "WHERE id = '" . $dbi->escapeString((string) $this->getId()) . "' ";
  370. $resList = $this->relation->queryAsControlUser($sqlQuery);
  371. $oneResult = $dbi->fetchArray($resList);
  372. if ($oneResult === false) {
  373. $message = Message::error(__('Error while loading the search.'));
  374. $response = Response::getInstance();
  375. $response->setRequestStatus($message->isSuccess());
  376. $response->addJSON('fieldWithError', 'searchId');
  377. $response->addJSON('message', $message);
  378. exit;
  379. }
  380. $this->setSearchName($oneResult['search_name'])
  381. ->setCriterias($oneResult['search_data'], true);
  382. return true;
  383. }
  384. /**
  385. * Get the list of saved searches of a user on a DB
  386. *
  387. * @param string[] $wheres List of filters
  388. *
  389. * @return array List of saved searches or empty array on failure
  390. */
  391. public function getList(array $wheres = [])
  392. {
  393. global $dbi;
  394. if ($this->getUsername() == null
  395. || $this->getDbname() == null
  396. ) {
  397. return [];
  398. }
  399. $savedSearchesTbl = Util::backquote($this->config['cfgRelation']['db'])
  400. . '.'
  401. . Util::backquote($this->config['cfgRelation']['savedsearches']);
  402. $sqlQuery = 'SELECT id, search_name '
  403. . 'FROM ' . $savedSearchesTbl . ' '
  404. . 'WHERE '
  405. . "username = '" . $dbi->escapeString($this->getUsername()) . "' "
  406. . "AND db_name = '" . $dbi->escapeString($this->getDbname()) . "' ";
  407. foreach ($wheres as $where) {
  408. $sqlQuery .= 'AND ' . $where . ' ';
  409. }
  410. $sqlQuery .= 'order by search_name ASC ';
  411. $resList = $this->relation->queryAsControlUser($sqlQuery);
  412. $list = [];
  413. while ($oneResult = $dbi->fetchArray($resList)) {
  414. $list[$oneResult['id']] = $oneResult['search_name'];
  415. }
  416. return $list;
  417. }
  418. }