FilterActionsTrait.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * Logic to apply filter actions (for feeds, categories, user configuration...).
  5. */
  6. trait FreshRSS_FilterActionsTrait {
  7. /** @var list<FreshRSS_FilterAction>|null $filterActions */
  8. private ?array $filterActions = null;
  9. /**
  10. * @return list<FreshRSS_FilterAction>
  11. */
  12. private function filterActions(): array {
  13. if (empty($this->filterActions)) {
  14. $this->filterActions = [];
  15. $filters = $this->attributeArray('filters') ?? [];
  16. foreach ($filters as $filter) {
  17. $filterAction = FreshRSS_FilterAction::fromJSON($filter);
  18. if ($filterAction != null) {
  19. $this->filterActions[] = $filterAction;
  20. }
  21. }
  22. }
  23. return $this->filterActions;
  24. }
  25. /**
  26. * @param array<FreshRSS_FilterAction>|null $filterActions
  27. */
  28. private function _filterActions(?array $filterActions): void {
  29. $this->filterActions = is_array($filterActions) ? array_values($filterActions) : null;
  30. if ($this->filterActions !== null && !empty($this->filterActions)) {
  31. $this->_attribute('filters', array_map(
  32. static fn(?FreshRSS_FilterAction $af) => $af == null ? null : $af->toJSON(),
  33. $this->filterActions));
  34. } else {
  35. $this->_attribute('filters', null);
  36. }
  37. }
  38. /** @return list<FreshRSS_BooleanSearch> */
  39. public function filtersAction(string $action): array {
  40. $action = trim($action);
  41. if ($action == '') {
  42. return [];
  43. }
  44. $filters = [];
  45. $filterActions = $this->filterActions();
  46. for ($i = count($filterActions) - 1; $i >= 0; $i--) {
  47. $filterAction = $filterActions[$i];
  48. if (in_array($action, $filterAction->actions(), true)) {
  49. $filters[] = $filterAction->booleanSearch();
  50. }
  51. }
  52. return $filters;
  53. }
  54. /**
  55. * @param array<string> $filters
  56. */
  57. public function _filtersAction(string $action, array $filters): void {
  58. $action = trim($action);
  59. if ($action === '') {
  60. return;
  61. }
  62. $filters = array_unique(array_map('trim', $filters), SORT_STRING);
  63. $filterActions = $this->filterActions();
  64. //Check existing filters
  65. for ($i = count($filterActions) - 1; $i >= 0; $i--) {
  66. $filterAction = $filterActions[$i];
  67. if ($filterAction == null || !is_array($filterAction->actions()) ||
  68. $filterAction->booleanSearch() == null || trim($filterAction->booleanSearch()->getRawInput()) == '') {
  69. array_splice($filterActions, $i, 1);
  70. continue;
  71. }
  72. $actions = $filterAction->actions();
  73. //Remove existing rules with same action
  74. for ($j = count($actions) - 1; $j >= 0; $j--) {
  75. if ($actions[$j] === $action) {
  76. array_splice($actions, $j, 1);
  77. }
  78. }
  79. //Update existing filter with new action
  80. for ($k = count($filters) - 1; $k >= 0; $k--) {
  81. $filter = $filters[$k];
  82. if ($filter === $filterAction->booleanSearch()->getRawInput()) {
  83. $actions[] = $action;
  84. array_splice($filters, $k, 1);
  85. }
  86. }
  87. //Save result
  88. if (empty($actions)) {
  89. array_splice($filterActions, $i, 1);
  90. } else {
  91. $filterAction->_actions($actions);
  92. }
  93. }
  94. //Add new filters
  95. for ($k = count($filters) - 1; $k >= 0; $k--) {
  96. $filter = $filters[$k];
  97. if ($filter != '') {
  98. $filterAction = FreshRSS_FilterAction::fromJSON([
  99. 'search' => $filter,
  100. 'actions' => [$action],
  101. ]);
  102. if ($filterAction != null) {
  103. $filterActions[] = $filterAction;
  104. }
  105. }
  106. }
  107. if (empty($filterActions)) {
  108. $filterActions = null;
  109. }
  110. $this->_filterActions($filterActions);
  111. }
  112. /**
  113. * @param bool $applyLabel Parameter by reference, which will be set to true if the callers needs to apply a label to the article entry.
  114. * @param-out bool $applyLabel
  115. */
  116. public function applyFilterActions(FreshRSS_Entry $entry, ?bool &$applyLabel = null): void {
  117. $applyLabel = false;
  118. foreach ($this->filterActions() as $filterAction) {
  119. if ($entry->matches($filterAction->booleanSearch())) {
  120. foreach ($filterAction->actions() as $action) {
  121. switch ($action) {
  122. case 'read':
  123. if (!$entry->isRead()) {
  124. $entry->_isRead(true);
  125. Minz_ExtensionManager::callHook(Minz_HookType::EntryAutoRead, $entry, 'filter');
  126. }
  127. break;
  128. case 'star':
  129. if (!$entry->isUpdated()) {
  130. // Do not apply to updated articles, to avoid overruling a user manual action
  131. $entry->_isFavorite(true);
  132. }
  133. break;
  134. case 'label':
  135. if (!$entry->isUpdated()) {
  136. // Do not apply to updated articles, to avoid overruling a user manual action
  137. $applyLabel = true;
  138. }
  139. break;
  140. }
  141. }
  142. }
  143. }
  144. }
  145. }