PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / frontend / PublishedPostsProvider.php

PublishedPostsProvider.php in 404 Solution trunk, at includes/frontend/PublishedPostsProvider.php

173 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 /** Return some of the published pages when requested. */
9 class ABJ_404_Solution_PublishedPostsProvider {
10
11 /** Track which rows to get from the database using limit.
12 * @var integer */
13 private $currentLowRowNumber = 0;
14
15 /** When not null then use this data instead of querying the database.
16 * @var array<int, mixed>|null
17 */
18 private $dataToUse = null;
19
20 /** Tracks whether we're using user supplied data or not.
21 * @var bool */
22 private $useDataMode = false;
23
24 /** When set, only return posts with these IDs (whitelist mode).
25 * @var array<int, int>|null */
26 private $restrictedIds = null;
27
28 /** @var self|null */
29 private static $instance = null;
30 /**
31 * Test seam: install or clear the cached singleton instance without
32 * private-field reflection. Pass null to reset between tests; pass a
33 * configured instance (or double) to install it. Mirrors the setInstance()
34 * contract on DataAccess / PluginLogic (M105 singleton-reset seam).
35 *
36 * @param self|null $instance
37 * @return void
38 */
39 public static function setInstance($instance) {
40 self::$instance = $instance;
41 }
42
43
44 /** @var ABJ_404_Solution_ContentRepository */
45 private $contentRepository;
46
47 /**
48 * @param ABJ_404_Solution_ContentRepository|null $contentRepository Content repository
49 */
50 public function __construct($contentRepository = null) {
51 $this->contentRepository = $contentRepository !== null ? $contentRepository : abj_service('content_repository');
52 }
53
54 public static function resetForTests(?self $replacement = null): void {
55 self::$instance = $replacement;
56 }
57
58 /** @return self */
59 public static function getInstance() {
60 if (self::$instance == null) {
61 self::$instance = new ABJ_404_Solution_PublishedPostsProvider();
62 }
63
64 return self::$instance;
65 }
66
67 /** Use this data instead of querying the database.
68 * @param array<int, mixed> $data
69 * @return void
70 */
71 function useThisData(array $data): void {
72 $this->dataToUse = $data;
73 $this->useDataMode = true;
74 }
75
76 /**
77 * Restrict results to only posts with the given IDs (whitelist mode).
78 * This enables N-gram prefiltering to limit the dataset before expensive processing.
79 *
80 * @param array<int, int|string> $ids Array of post IDs to include
81 * @return void
82 */
83 function restrictToIds(array $ids): void {
84 if (!empty($ids) && is_array($ids)) {
85 $this->restrictedIds = array_map('intval', $ids);
86 }
87 }
88
89 /**
90 * Check if we're in restricted ID mode.
91 * @return bool
92 */
93 function hasRestrictedIds() {
94 return !empty($this->restrictedIds);
95 }
96
97 /**
98 * @param int $permalinkLength Order by prioritizing things with a permalink
99 * close to this length.
100 * @param int $batchSize The number of results to return. e.g. 100.
101 * @param int|null $maxAcceptableDistance
102 * @return array<int, mixed>
103 */
104 function getNextBatch(int $permalinkLength, int $batchSize = 1000, $maxAcceptableDistance = null): array {
105 if ($this->useDataMode) {
106 return $this->getNextBatchFromLocalData($permalinkLength, $batchSize, $maxAcceptableDistance);
107 }
108
109 return $this->getNextBatchFromTheDatabase($permalinkLength, $batchSize, $maxAcceptableDistance);
110 }
111
112 /**
113 * @param int $permalinkLength
114 * @param int $batchSize
115 * @param int|null $maxAcceptableDistance
116 * @return array<int, mixed>
117 */
118 private function getNextBatchFromLocalData(int $permalinkLength, int $batchSize, $maxAcceptableDistance): array {
119 $data = $this->dataToUse ?? array();
120 // get the rows to return.
121 $rows = array_slice($data, 0, $batchSize);
122
123 // remove the rows we'll return.
124 $this->dataToUse = array_slice($data, $batchSize);
125
126 return $rows;
127 }
128
129 /**
130 * @param int $permalinkLength
131 * @param int $batchSize
132 * @param int|null $maxAcceptableDistance
133 * @return array<int, mixed>
134 */
135 private function getNextBatchFromTheDatabase(int $permalinkLength, int $batchSize, $maxAcceptableDistance): array {
136 $orderBy = "abs(plc.url_length - " . $permalinkLength . "), wp_posts.id";
137 $limit = $this->currentLowRowNumber . ", " . $batchSize;
138 $extraWhereClause = '';
139
140 if ($maxAcceptableDistance != null) {
141 $extraWhereClause = "and abs(plc.url_length - " . $permalinkLength .
142 ") <= " . $maxAcceptableDistance;
143 }
144
145 // N-gram prefiltering: restrict to specific IDs if whitelist is set
146 if (!empty($this->restrictedIds)) {
147 $idList = implode(',', $this->restrictedIds);
148 $extraWhereClause .= " and wp_posts.id IN (" . $idList . ")";
149 }
150
151 $rows = $this->contentRepository->getPublishedPagesAndPostsIDs(array(
152 'limit_results' => $limit,
153 'order_results' => $orderBy,
154 'extra_where_clause' => $extraWhereClause,
155 ));
156
157 $this->currentLowRowNumber += $batchSize;
158
159 return $rows;
160 }
161
162 /** Start over at 0 when getting the next batch.
163 * @return void
164 */
165 function resetBatch(): void {
166 $this->currentLowRowNumber = 0;
167 $this->dataToUse = null;
168 $this->useDataMode = false;
169 $this->restrictedIds = null;
170 }
171
172 }
173