4
0

searchController.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * Controller to handle advanced search actions.
  5. */
  6. class FreshRSS_search_Controller extends FreshRSS_ActionController {
  7. #[\Override]
  8. public function firstAction(): void {
  9. if (!FreshRSS_Auth::hasAccess()) {
  10. Minz_Error::error(403);
  11. }
  12. }
  13. /**
  14. * Display the advanced search form.
  15. */
  16. public function indexAction(): void {
  17. FreshRSS_View::prependTitle(_t('gen.menu.advanced_search') . ' · ');
  18. // Get categories and feeds for dropdowns
  19. $catDAO = FreshRSS_Factory::createCategoryDao();
  20. $this->view->categories = $catDAO->listCategories(true, true);
  21. $feedDAO = FreshRSS_Factory::createFeedDao();
  22. $this->view->feeds = $feedDAO->listFeeds();
  23. // Get labels
  24. $tagDAO = FreshRSS_Factory::createTagDao();
  25. $this->view->labels = $tagDAO->listTags(true);
  26. // Get user queries
  27. $this->view->queries = [];
  28. foreach (FreshRSS_Context::userConf()->queries as $key => $query) {
  29. $this->view->queries[intval($key)] = new FreshRSS_UserQuery($query, FreshRSS_Context::categories(), FreshRSS_Context::labels());
  30. }
  31. }
  32. /**
  33. * Build an OR-separated clause from newline delimited values.
  34. */
  35. private static function buildOrClause(string $rawValue, string $prefix = ''): string {
  36. $lines = preg_split('/[\r\n]+/', $rawValue);
  37. if ($lines === false) {
  38. $lines = [$rawValue];
  39. }
  40. $terms = [];
  41. foreach ($lines as $line) {
  42. $line = trim($line, " \n\r\t\v\0\"'"); // Also trim existing quotes
  43. if ($line === '') {
  44. continue;
  45. }
  46. $quoted = str_contains($line, ' ') && !str_starts_with($line, '/') ? "'$line'" : $line;
  47. $terms[] = $prefix . $quoted;
  48. }
  49. if (empty($terms)) {
  50. return '';
  51. }
  52. if (count($terms) === 1) {
  53. return $terms[0];
  54. }
  55. return '(' . implode(' OR ', $terms) . ')';
  56. }
  57. /**
  58. * Process the advanced search form submission.
  59. */
  60. public function submitAction(): void {
  61. if (!Minz_Request::isPost()) {
  62. Minz_Request::forward(['c' => 'search', 'a' => 'index'], true);
  63. return;
  64. }
  65. // Build the search query from form parameters
  66. $searchTerms = [];
  67. $freeTextClause = self::buildOrClause(Minz_Request::paramString('free_text'));
  68. if ($freeTextClause !== '') {
  69. $searchTerms[] = $freeTextClause;
  70. }
  71. $titleClause = self::buildOrClause(Minz_Request::paramString('title'), 'intitle:');
  72. if ($titleClause !== '') {
  73. $searchTerms[] = $titleClause;
  74. }
  75. $contentClause = self::buildOrClause(Minz_Request::paramString('content'), 'intext:');
  76. if ($contentClause !== '') {
  77. $searchTerms[] = $contentClause;
  78. }
  79. $urlClause = self::buildOrClause(Minz_Request::paramString('url'), 'inurl:');
  80. if ($urlClause !== '') {
  81. $searchTerms[] = $urlClause;
  82. }
  83. $authorClause = self::buildOrClause(Minz_Request::paramString('authors'), 'author:');
  84. if ($authorClause !== '') {
  85. $searchTerms[] = $authorClause;
  86. }
  87. $tagsClause = self::buildOrClause(Minz_Request::paramString('tags'), '#');
  88. if ($tagsClause !== '') {
  89. $searchTerms[] = $tagsClause;
  90. }
  91. // Received date
  92. $dateFrom = trim(Minz_Request::paramString('date_from'));
  93. $dateTo = trim(Minz_Request::paramString('date_to'));
  94. $dateNumber = Minz_Request::paramInt('date_number');
  95. $dateUnit = trim(Minz_Request::paramString('date_unit'));
  96. if ($dateNumber > 0 && $dateUnit !== '') {
  97. // Convert to ISO 8601 duration format: P1D, P1W, P1M, PT1H, etc.
  98. // Time units (H, M, S) require a T separator
  99. $prefix = ($dateUnit === 'H' || $dateUnit === 'M' || $dateUnit === 'S') ? 'PT' : 'P';
  100. $searchTerms[] = "date:{$prefix}{$dateNumber}{$dateUnit}";
  101. } elseif ($dateFrom !== '' || $dateTo !== '') {
  102. if ($dateFrom !== '' && $dateTo !== '') {
  103. $searchTerms[] = "date:$dateFrom/$dateTo";
  104. } elseif ($dateFrom !== '') {
  105. $searchTerms[] = "date:$dateFrom/";
  106. } elseif ($dateTo !== '') {
  107. $searchTerms[] = "date:/$dateTo";
  108. }
  109. }
  110. // Publication date
  111. $pubDateFrom = trim(Minz_Request::paramString('pubdate_from'));
  112. $pubDateTo = trim(Minz_Request::paramString('pubdate_to'));
  113. $pubDateNumber = Minz_Request::paramInt('pubdate_number');
  114. $pubDateUnit = trim(Minz_Request::paramString('pubdate_unit'));
  115. if ($pubDateNumber > 0 && $pubDateUnit !== '') {
  116. // Convert to ISO 8601 duration format: P1D, P1W, P1M, PT1H, etc.
  117. // Time units (H, M, S) require a T separator
  118. $prefix = ($pubDateUnit === 'H' || $pubDateUnit === 'M' || $pubDateUnit === 'S') ? 'PT' : 'P';
  119. $searchTerms[] = "pubdate:{$prefix}{$pubDateNumber}{$pubDateUnit}";
  120. } elseif ($pubDateFrom !== '' || $pubDateTo !== '') {
  121. if ($pubDateFrom !== '' && $pubDateTo !== '') {
  122. $searchTerms[] = "pubdate:$pubDateFrom/$pubDateTo";
  123. } elseif ($pubDateFrom !== '') {
  124. $searchTerms[] = "pubdate:$pubDateFrom/";
  125. } elseif ($pubDateTo !== '') {
  126. $searchTerms[] = "pubdate:/$pubDateTo";
  127. }
  128. }
  129. // User modification date
  130. $userDateFrom = trim(Minz_Request::paramString('userdate_from'));
  131. $userDateTo = trim(Minz_Request::paramString('userdate_to'));
  132. $userDateNumber = Minz_Request::paramInt('userdate_number');
  133. $userDateUnit = trim(Minz_Request::paramString('userdate_unit'));
  134. if ($userDateNumber > 0 && $userDateUnit !== '') {
  135. // Convert to ISO 8601 duration format: P1D, P1W, P1M, PT1H, etc.
  136. // Time units (H, M, S) require a T separator
  137. $prefix = ($userDateUnit === 'H' || $userDateUnit === 'M' || $userDateUnit === 'S') ? 'PT' : 'P';
  138. $searchTerms[] = "userdate:{$prefix}{$userDateNumber}{$userDateUnit}";
  139. } elseif ($userDateFrom !== '' || $userDateTo !== '') {
  140. if ($userDateFrom !== '' && $userDateTo !== '') {
  141. $searchTerms[] = "userdate:$userDateFrom/$userDateTo";
  142. } elseif ($userDateFrom !== '') {
  143. $searchTerms[] = "userdate:$userDateFrom/";
  144. } elseif ($userDateTo !== '') {
  145. $searchTerms[] = "userdate:/$userDateTo";
  146. }
  147. }
  148. $feedIds = Minz_Request::paramArrayInt('feed_ids');
  149. if (!empty($feedIds)) {
  150. $searchTerms[] = 'f:' . implode(',', $feedIds);
  151. }
  152. $categoryIds = Minz_Request::paramArrayInt('category_ids');
  153. if (!empty($categoryIds)) {
  154. $searchTerms[] = 'c:' . implode(',', $categoryIds);
  155. }
  156. $labelIds = Minz_Request::paramArrayInt('label_ids');
  157. if (!empty($labelIds)) {
  158. $searchTerms[] = 'L:' . implode(',', $labelIds);
  159. }
  160. $userQueryIds = Minz_Request::paramArrayInt('user_query_ids');
  161. if (!empty($userQueryIds)) {
  162. $searchTerms[] = 'S:' . implode(',', $userQueryIds);
  163. }
  164. // Combine all search terms
  165. $searchQuery = implode(' ', $searchTerms);
  166. // Redirect to the main view with the search query
  167. Minz_Request::forward([
  168. 'c' => 'index',
  169. 'a' => 'index',
  170. 'params' => [
  171. 'search' => $searchQuery,
  172. ],
  173. ], redirect: true);
  174. }
  175. }