PluginProbe
The Innovative Form Builder – IvyForms / 1.5.1
The Innovative Form Builder – IvyForms v1.5.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
← All changes | backend/src/Repository/AbstractRepository.php +153 -53 0.2.1 → 1.5.1 View file →
@@ -1,19 +1,18 @@
1 1 <?php
2 2
3 3 /**
4 - * @copyright © TMS-Plugins. All rights reserved.
4 + * @copyright © Melograno Venture Studio. All rights reserved.
5 5 * @licence See LICENCE.md for license details.
6 6 */
7 7
8 8 namespace IvyForms\Repository;
9 9
10 -// phpcs:disable PSR1.Files.SideEffects
11 -if (!defined('ABSPATH')) {
12 - exit; // Exit if accessed directly
13 -}
14 -
15 10 use IvyForms\Common\Exceptions\InvalidArgumentException;
11 +use IvyForms\Common\Exceptions\NotFoundException;
12 +use IvyForms\Common\Exceptions\QueryExecutionException;
13 +use IvyForms\Services\Translations\BackendStrings;
14 +use IvyForms\Repository\ColumnRepositoryTrait;
16 15
17 16 /**
18 17 * Class AbstractRepository
19 18 *
@@ -20,8 +19,10 @@
20 19 * @package IvyForms\Repository
21 20 */
22 21 class AbstractRepository
23 22 {
23 + use ColumnRepositoryTrait;
24 +
24 25 public const FACTORY = '';
25 26
26 27 /** @var string */
27 28 protected string $table;
@@ -64,9 +65,9 @@
64 65 */
65 66 public function getAll(): array
66 67 {
67 68 $rows = $this->wpdb->get_results(
68 - $this->wpdb->prepare($this->selectQuery()),
69 + $this->selectQuery(),
69 70 ARRAY_A
70 71 );
71 72
72 73 $result = [];
@@ -77,19 +78,133 @@
77 78
78 79 return $result;
79 80 }
80 81
82 + /**
83 + * @param int $id
84 + * @return int
85 + * @throws QueryExecutionException
86 + */
81 87 public function delete(int $id): int
82 88 {
83 - return $this->wpdb->query(
89 + $result = $this->wpdb->query(
84 90 $this->wpdb->prepare(
85 91 "DELETE FROM $this->table WHERE id = %d",
86 92 $id
87 93 )
88 94 );
95 +
96 + if ($result === false) {
97 + throw new QueryExecutionException(
98 + sprintf(BackendStrings::getExceptionStrings()['failed_delete_by_id'], $id, $this->table)
99 + );
100 + }
101 +
102 + return $result;
89 103 }
90 104
91 105 /**
106 + * Delete multiple entities by their IDs in bulk.
107 + *
108 + * @param array<int> $ids
109 + * @return int Number of deleted entities
110 + * @throws QueryExecutionException
111 + */
112 + public function deleteMany(array $ids): int
113 + {
114 + if (empty($ids)) {
115 + return 0;
116 + }
117 + $placeholders = implode(',', array_fill(0, count($ids), '%d'));
118 + $query = "DELETE FROM $this->table WHERE id IN ($placeholders)";
119 + $result = $this->wpdb->query($this->wpdb->prepare($query, ...$ids));
120 +
121 + if ($result === false) {
122 + throw new QueryExecutionException(
123 + sprintf(
124 + BackendStrings::getExceptionStrings()['failed_delete_by_ids'],
125 + implode(', ', $ids),
126 + $this->table
127 + )
128 + );
129 + }
130 +
131 + return $result;
132 + }
133 +
134 + /**
135 + * Delete multiple entities by foreign key values (e.g., form IDs).
136 + *
137 + * @param array<int> $foreignIds Array of foreign key values (e.g., form IDs)
138 + * @param string $foreignColumn Foreign key column name (default: 'formId')
139 + * @return int Number of deleted entities
140 + * @throws InvalidArgumentException If no foreign IDs are provided
141 + * @throws QueryExecutionException
142 + */
143 + public function deleteManyByForeignKeyValues(array $foreignIds, string $foreignColumn = 'formId'): int
144 + {
145 + if (empty($foreignIds)) {
146 + throw new InvalidArgumentException(
147 + BackendStrings::getExceptionStrings()['no_form_ids_provided']
148 + );
149 + }
150 +
151 + // Validate foreign column to prevent SQL injection
152 + if (!in_array($foreignColumn, $this->getAllowedForeignColumns(), true)) {
153 + throw new InvalidArgumentException(
154 + BackendStrings::getExceptionStrings()['invalid_foreign_column']
155 + );
156 + }
157 +
158 + $foreignIds = array_map('intval', $foreignIds);
159 + $placeholders = implode(',', array_fill(0, count($foreignIds), '%d'));
160 + $query = "DELETE FROM {$this->table} WHERE {$foreignColumn} IN ($placeholders)";
161 + $result = $this->wpdb->query($this->wpdb->prepare($query, ...$foreignIds));
162 +
163 + if ($result === false) {
164 + throw new QueryExecutionException(
165 + sprintf(
166 + BackendStrings::getExceptionStrings()['failed_delete_by_foreign_keys'],
167 + implode(', ', $foreignIds),
168 + $this->table
169 + )
170 + );
171 + }
172 +
173 + return $result;
174 + }
175 +
176 + /**
177 + * Delete entities by a single foreign key value (e.g., form ID).
178 + *
179 + * @param int $foreignId Foreign key value (e.g., form ID)
180 + * @param string $foreignColumn Foreign key column name (default: 'formId')
181 + * @return int Number of deleted entities
182 + * @throws InvalidArgumentException If invalid foreign column is provided
183 + * @throws QueryExecutionException
184 + */
185 + public function deleteOneByForeignKeyValue(int $foreignId, string $foreignColumn = 'formId'): int
186 + {
187 + // Validate foreign column to prevent SQL injection
188 + if (!in_array($foreignColumn, $this->getAllowedForeignColumns(), true)) {
189 + throw new InvalidArgumentException(
190 + BackendStrings::getExceptionStrings()['invalid_foreign_column']
191 + );
192 + }
193 +
194 + $query = "DELETE FROM {$this->table} WHERE {$foreignColumn} = %d";
195 + $result = $this->wpdb->query($this->wpdb->prepare($query, $foreignId));
196 +
197 + if ($result === false) {
198 + throw new QueryExecutionException(
199 + sprintf(BackendStrings::getExceptionStrings()['failed_delete_by_foreign_key'], $foreignId, $this->table)
200 + );
201 + }
202 +
203 + return $result;
204 + }
205 +
206 + /**
92 207 * @param array<string, mixed>|null $params
93 208 * @return array<string, mixed>
94 209 */
95 210 public function search(?array $params): array
@@ -116,9 +231,9 @@
116 231 * @param array<string, mixed>|null $params
117 232 * @param array<int|string, mixed> $queryParams
118 233 * @return string
119 234 */
120 - private function buildWhereClauses(?array $params, array &$queryParams): string
235 + protected function buildWhereClauses(?array $params, array &$queryParams): string
121 236 {
122 237 $whereClauses = ['1=1'];
123 238
124 239 $this->addSearchClauses($params, $whereClauses, $queryParams);
@@ -132,9 +247,9 @@
132 247 * @param array<string, mixed>|null $params
133 248 * @param array<int, string> $whereClauses
134 249 * @param array<int|string, mixed> $queryParams
135 250 */
136 - private function addSearchClauses(?array $params, array &$whereClauses, array &$queryParams): void
251 + protected function addSearchClauses(?array $params, array &$whereClauses, array &$queryParams): void
137 252 {
138 253 if (!empty($params['search'])) {
139 254 $searchEscaped = '%' . $this->wpdb->esc_like($params['search']) . '%';
140 255 $searchableColumns = $this->getSearchableColumns();
@@ -150,9 +265,9 @@
150 265 * @param array<string, mixed>|null $params
151 266 * @param array<int, string> $whereClauses
152 267 * @param array<int|string, mixed> $queryParams
153 268 */
154 - private function addFilterClauses(?array $params, array &$whereClauses, array &$queryParams): void
269 + protected function addFilterClauses(?array $params, array &$whereClauses, array &$queryParams): void
155 270 {
156 271 if (!empty($params['filters'])) {
157 272 $filterableColumns = $this->getFilterableColumns();
158 273 foreach ($params['filters'] as $key => $value) {
@@ -168,21 +283,38 @@
168 283 * @param array<int, string> $dateRange
169 284 * @param array<int, string> $whereClauses
170 285 * @param array<int|string, mixed> $queryParams
171 286 */
172 - private function addDateRangeClauses(array $dateRange, array &$whereClauses, array &$queryParams): void
287 + protected function addDateRangeClauses(array $dateRange, array &$whereClauses, array &$queryParams): void
173 288 {
289 +
290 + if (empty($dateRange) || empty($dateRange[0])) {
291 + return;
292 + }
293 +
174 294 $dateColumn = $this->getDateColumn();
175 295
176 - if (!empty($dateRange[0])) {
177 - $whereClauses[] = "$dateColumn >= %s";
178 - $queryParams[] = gmdate('Y-m-d H:i:s', strtotime($dateRange[0]));
296 + if (empty($dateRange[1])) {
297 + $dateRange[1] = $dateRange[0];
179 298 }
180 299
181 - if (!empty($dateRange[1])) {
182 - $whereClauses[] = "$dateColumn <= %s";
183 - $queryParams[] = gmdate('Y-m-d H:i:s', strtotime($dateRange[1]));
184 - }
300 + $normalizeStartOfDay = function ($isoDate) {
301 + $day = substr($isoDate, 0, 10);
302 + return $day . ' 00:00:00';
303 + };
304 + $normalizeEndOfDay = function ($isoDate) {
305 + $day = substr($isoDate, 0, 10);
306 + return $day . ' 23:59:59';
307 + };
308 +
309 + $start = $normalizeStartOfDay($dateRange[0]);
310 + $end = $normalizeEndOfDay($dateRange[1]);
311 +
312 + $whereClauses[] = "$dateColumn >= %s";
313 + $queryParams[] = $start;
314 +
315 + $whereClauses[] = "$dateColumn <= %s";
316 + $queryParams[] = $end;
185 317 }
186 318
187 319 /**
188 320 * @param string $whereClauses
@@ -189,9 +321,9 @@
189 321 * @param array<string, mixed>|null $params
190 322 * @param array<int|string, mixed> $queryParams
191 323 * @return string
192 324 */
193 - private function buildSearchQuery(string $whereClauses, ?array $params, array &$queryParams): string
325 + protected function buildSearchQuery(string $whereClauses, ?array $params, array &$queryParams): string
194 326 {
195 327 $sortableColumns = $this->getSortableColumns();
196 328 $sortBy = in_array($params['orderBy'] ?? 'id', $sortableColumns, true) ? $params['orderBy'] : 'id';
197 329 $order = strtolower($params['order'] ?? 'asc') === 'asc' ? 'ASC' : 'DESC';
@@ -209,9 +341,9 @@
209 341 * @param string $whereClauses
210 342 * @param array<int|string, mixed> $queryParams
211 343 * @return int
212 344 */
213 - private function getTotalCount(string $whereClauses, array $queryParams): int
345 + protected function getTotalCount(string $whereClauses, array $queryParams): int
214 346 {
215 347 // Exclude only LIMIT and OFFSET, keeping filter parameters intact
216 348 $countParams = $queryParams;
217 349 if (isset($queryParams[count($queryParams) - 2]) && isset($queryParams[count($queryParams) - 1])) {
@@ -254,38 +386,6 @@
254 386 */
255 387 public function rollback(): bool
256 388 {
257 389 return (bool) $this->wpdb->query('ROLLBACK');
258 - }
259 -
260 - /**
261 - * @return string[]
262 - */
263 - protected function getSearchableColumns(): array
264 - {
265 - return ['id'];
266 - }
267 -
268 - /**
269 - * @return array<string, string>
270 - */
271 - protected function getFilterableColumns(): array
272 - {
273 - return [];
274 - }
275 -
276 - /**
277 - * @return string[]
278 - */
279 - protected function getSortableColumns(): array
280 - {
281 - return ['id'];
282 - }
283 -
284 - /**
285 - * @return string
286 - */
287 - protected function getDateColumn(): string
288 - {
289 - return 'dateCreated';
290 390 }
291 391 }