PluginProbe
seQura / 3.2.0
seQura v3.2.0
4.3.4 4.3.3 4.3.2 4.3.1 trunk 2.0.0 2.0.10 2.0.11 2.0.12 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0.0 3.0.2 3.0.5 3.0.6 3.0.7 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 4.0.0 All 30 releases
sequra / src / Repositories / class-repository.php

class-repository.php in seQura 3.2.0, at src/Repositories/class-repository.php

761 lines 21.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shared repository functionality.
4 *
5 * @package SeQura/WC
6 * @subpackage SeQura/WC/Repositories
7 */
8
9 namespace SeQura\WC\Repositories;
10
11 use SeQura\Core\Infrastructure\ORM\Entity;
12 use SeQura\Core\Infrastructure\ORM\Exceptions\QueryFilterInvalidParamException;
13 use SeQura\Core\Infrastructure\ORM\Interfaces\RepositoryInterface;
14 use SeQura\Core\Infrastructure\ORM\QueryFilter\QueryCondition;
15 use SeQura\Core\Infrastructure\ORM\QueryFilter\QueryFilter;
16 use SeQura\Core\Infrastructure\ORM\Utility\IndexHelper;
17 use SeQura\Core\Infrastructure\ServiceRegister;
18 use SeQura\WC\Dto\Table_Index;
19 use wpdb;
20
21 /**
22 * Shared repository functionality.
23 */
24 abstract class Repository implements RepositoryInterface, Interface_Deletable_Repository, Interface_Table_Migration_Repository {
25
26 /**
27 * Entity class FQN.
28 *
29 * @var string
30 */
31 protected $entity_class;
32
33 /**
34 * Database session object.
35 *
36 * @var \wpdb
37 */
38 protected $db;
39
40 /**
41 * Returns unprefixed table name.
42 */
43 abstract protected function get_unprefixed_table_name(): string;
44
45 /**
46 * Returns full table name.
47 */
48 public function get_table_name(): string {
49 return $this->db->prefix . $this->get_unprefixed_table_name();
50 }
51
52 /**
53 * Get the name that is set to the original table during the migration.
54 *
55 * @return string The name of the old table.
56 */
57 public function get_legacy_table_name() {
58 return $this->get_table_name() . '_legacy';
59 }
60
61 /**
62 * Constructor.
63 *
64 * @throws \RuntimeException If database service not found.
65 */
66 public function __construct() {
67 $db = ServiceRegister::getService( \wpdb::class );
68 if ( ! $db instanceof \wpdb ) {
69 throw new \RuntimeException( 'Database service not found.' );
70 }
71 $this->db = $db;
72 }
73
74 /**
75 * Returns full class name.
76 *
77 * @return string Full class name.
78 */
79 public static function getClassName() {
80 return __CLASS__;
81 }
82
83 /**
84 * Sets repository entity
85 *
86 * @noinspection PhpDocMissingThrowsInspection
87 *
88 * @param string $entity_class Entity class.
89 * @return void
90 */
91 public function setEntityClass( $entity_class ): void {
92 $this->entity_class = $entity_class;
93 }
94
95 /**
96 * Executes select query.
97 *
98 * @param QueryFilter $filter Filter for query.
99 *
100 * @return Entity[] A list of found entities ot empty array.
101 * @throws QueryFilterInvalidParamException If filter condition is invalid.
102 */
103 public function select( QueryFilter $filter = null ) {
104 /**
105 * Entity object.
106 *
107 * @var Entity $entity
108 */
109 $entity = new $this->entity_class();
110 $type = $entity->getConfig()->getType();
111
112 $query = "SELECT * FROM {$this->get_table_name()} WHERE type = '$type' ";
113 if ( $filter ) {
114 $query .= $this->apply_query_filter( $filter, IndexHelper::mapFieldsToIndexes( $entity ) );
115 }
116
117 $raw_results = array();
118 if ( $this->table_exists() ) {
119 $raw_results = $this->db->get_results( $query, ARRAY_A );
120 if ( ! is_array( $raw_results ) ) {
121 $raw_results = array();
122 }
123 }
124 if ( $this->table_exists( true ) ) {
125 // If the legacy table exists the data may be there.
126 $query = str_replace( $this->get_table_name(), $this->get_legacy_table_name(), $query );
127 $legacy_raw_results = $this->db->get_results( $query, ARRAY_A );
128 if ( ! is_array( $legacy_raw_results ) ) {
129 $legacy_raw_results = array();
130 }
131 $raw_results = array_merge( $raw_results, $legacy_raw_results );
132 }
133
134 return $this->translateToEntities( $raw_results );
135 }
136
137 /**
138 * Executes select query and returns first result.
139 *
140 * @param QueryFilter $filter Filter for query.
141 *
142 * @return Entity|null First found entity or NULL.
143 * @throws QueryFilterInvalidParamException If filter condition is invalid.
144 */
145 public function selectOne( QueryFilter $filter = null ) {
146 if ( ! $filter ) {
147 $filter = new QueryFilter();
148 }
149
150 $filter->setLimit( 1 );
151 $results = $this->select( $filter );
152
153 return ! empty( $results ) ? $results[0] : null;
154 }
155
156 /**
157 * Executes insert query and returns ID of created entity. Entity will be updated with new ID.
158 *
159 * @param Entity $entity Entity to be saved.
160 *
161 * @return int Identifier of saved entity.
162 */
163 public function save( Entity $entity ) {
164 if ( ! $this->table_exists() ) {
165 return -1;
166 }
167
168 if ( $entity->getId() ) {
169 $this->update( $entity );
170
171 return $entity->getId();
172 }
173
174 return $this->save_entity_to_storage( $entity );
175 }
176
177 /**
178 * Executes update query and returns success flag.
179 *
180 * @param Entity $entity Entity to be updated.
181 *
182 * @return bool TRUE if operation succeeded; otherwise, FALSE.
183 */
184 public function update( Entity $entity ) {
185 if ( ! $this->table_exists() ) {
186 return false;
187 }
188 $item = $this->prepare_entity_for_storage( $entity );
189 $where = array( 'id' => $entity->getId() );
190
191 // Check if entity wasn't already migrated and migrate it including the new data.
192 if ( $this->table_exists( true ) && $this->entity_exists( $entity->getId(), true ) ) {
193 if ( 1 !== $this->db->update( $this->get_legacy_table_name(), $item, $where ) ) {
194 return false;
195 }
196 // Read from the legacy table.
197 $raw_results = $this->db->get_results( "SELECT * FROM {$this->get_legacy_table_name()} WHERE id = {$entity->getId()} LIMIT 1;", ARRAY_A );
198 if ( empty( $raw_results ) ) {
199 return false;
200 }
201 $entity = $this->translateToEntities( $raw_results )[0] ?? null;
202 if ( ! $entity ) {
203 return false;
204 }
205 // Insert into the new table.
206 $item = $this->prepare_entity_for_storage( $entity );
207 if ( false !== $this->db->insert( $this->get_table_name(), $item ) ) {
208 return false;
209 }
210 // Delete the row from the legacy table.
211 $this->db->delete( $this->get_legacy_table_name(), $where );
212 return true;
213 }
214 // Only one record should be updated.
215 return 1 === $this->db->update( $this->get_table_name(), $item, $where );
216 }
217
218 /**
219 * Executes delete query and returns success flag.
220 *
221 * @param Entity $entity Entity to be deleted.
222 *
223 * @return bool TRUE if operation succeeded; otherwise, FALSE.
224 */
225 public function delete( Entity $entity ) {
226 $where = array( 'id' => $entity->getId() );
227 $deleted = false;
228 if ( $this->table_exists() ) {
229 $result = $this->db->delete( $this->get_table_name(), $where );
230 $deleted = ! empty( $result );
231 }
232 if ( $this->table_exists( true ) ) {
233 // Delete from legacy table.
234 $result = $this->db->delete( $this->get_legacy_table_name(), $where );
235 $deleted = $deleted || ! empty( $result );
236 }
237 return $deleted;
238 }
239
240 /**
241 * Counts records that match filter criteria.
242 *
243 * @param QueryFilter $filter Filter for query.
244 *
245 * @return int Number of records that match filter criteria.
246 * @throws QueryFilterInvalidParamException If filter condition is invalid.
247 */
248 public function count( QueryFilter $filter = null ) {
249 /**
250 * Entity object.
251 *
252 * @var Entity $entity
253 */
254 $entity = new $this->entity_class();
255 $type = $entity->getConfig()->getType();
256
257 $query = "SELECT COUNT(*) as `total` FROM {$this->get_table_name()} WHERE type = '$type' ";
258 if ( $filter ) {
259 $query .= $this->apply_query_filter( $filter, IndexHelper::mapFieldsToIndexes( $entity ) );
260 }
261 $count = 0;
262 if ( $this->table_exists() ) {
263 $result = $this->db->get_results( $query, ARRAY_A );
264 $count += empty( $result[0]['total'] ) || ! is_numeric( $result[0]['total'] ) ? 0 : (int) $result[0]['total'];
265 }
266 if ( $this->table_exists( true ) ) {
267 // If the legacy table exists, count the data there too.
268 $query = str_replace( $this->get_table_name(), $this->get_legacy_table_name(), $query );
269 $result = $this->db->get_results( $query, ARRAY_A );
270 $count += empty( $result[0]['total'] ) || ! is_numeric( $result[0]['total'] ) ? 0 : (int) $result[0]['total'];
271 }
272 return $count;
273 }
274
275 /**
276 * Escapes provided value.
277 *
278 * @param mixed $value Value to be escaped.
279 *
280 * @return string Escaped value.
281 */
282 protected function escape( $value ) {
283 return addslashes( strval( $value ) );
284 }
285
286 /**
287 * Checks if value exists and escapes it if it's not.
288 *
289 * @param mixed $value Value to be escaped.
290 *
291 * @return string Escaped value.
292 */
293 protected function escape_value( $value ) {
294 return null === $value ? 'NULL' : "'" . $this->escape( $value ) . "'";
295 }
296
297 /**
298 * Builds WHERE part of select query.
299 *
300 * @param mixed[]$filter_by Filter conditions in query.
301 *
302 * @return string Where condition.
303 */
304 protected function build_condition( $filter_by ) {
305 if ( empty( $filter_by ) ) {
306 return '';
307 }
308
309 $where = array();
310 foreach ( $filter_by as $key => $value ) {
311 if ( null === $value ) {
312 $where[] = "`$key` IS NULL";
313 } else {
314 $where[] = "`$key` = '" . $this->escape( $value ) . "'";
315 }
316 }
317
318 return ' WHERE ' . implode( ' AND ', $where );
319 }
320
321 /**
322 * Converts filter value to index string representation.
323 *
324 * @param QueryCondition $condition Query condition.
325 *
326 * @return string|null Converted value.
327 */
328 protected function convert_value( QueryCondition $condition ) {
329 $value = IndexHelper::castFieldValue( $condition->getValue(), $condition->getValueType() );
330 switch ( $condition->getValueType() ) {
331 case 'string':
332 $value = $this->escape_value( $condition->getValue() );
333 break;
334 case 'array':
335 /**
336 * Values
337 *
338 * @var mixed[] $values
339 */
340 $values = $condition->getValue();
341 $escaped_values = array();
342 foreach ( $values as $value ) {
343 $escaped_values[] = is_string( $value ) ? $this->escape_value( $value ) : $value;
344 }
345
346 $value = '(' . implode( ', ', $escaped_values ) . ')';
347 break;
348 default:
349 // 'integer', 'dateTime','boolean','double'
350 $value = $this->escape_value( $value );
351 break;
352 }
353
354 return $value;
355 }
356
357 /**
358 * Builds query filter part of the query.
359 *
360 * @param QueryFilter $filter Query filter object.
361 * @param mixed[] $field_index_map Property to index number map.
362 *
363 * @return string Query filter addendum.
364 * @throws QueryFilterInvalidParamException If filter condition is invalid.
365 */
366 protected function apply_query_filter( QueryFilter $filter, array $field_index_map = array() ) {
367 $query = '';
368 $conditions = $filter->getConditions();
369 if ( ! empty( $conditions ) ) {
370 $query .= ' AND (';
371 $first = true;
372 foreach ( $conditions as $condition ) {
373 $this->validate_index_column( $condition->getColumn(), $field_index_map );
374 $chain_op = $first ? '' : $condition->getChainOperator();
375 $first = false;
376 $column = 'id' === $condition->getColumn() ? 'id' : 'index_' . $field_index_map[ $condition->getColumn() ];
377 $operator = $condition->getOperator();
378 $query .= " $chain_op $column $operator " . $this->convert_value( $condition );
379 }
380
381 $query .= ')';
382 }
383
384 if ( $filter->getOrderByColumn() ) {
385 $this->validate_index_column( $filter->getOrderByColumn(), $field_index_map );
386 $order_index = 'id' === $filter->getOrderByColumn() ? 'id' : 'index_' . $field_index_map[ $filter->getOrderByColumn() ];
387 $query .= " ORDER BY {$order_index} {$filter->getOrderDirection()}";
388 }
389
390 if ( $filter->getLimit() ) {
391 $offset = (int) $filter->getOffset();
392 $query .= " LIMIT {$offset}, {$filter->getLimit()}";
393 }
394
395 return $query;
396 }
397
398 /**
399 * Transforms raw database query rows to entities.
400 *
401 * @param mixed[]$result Raw database query result.
402 *
403 * @return Entity[] Array of transformed entities.
404 */
405 protected function translateToEntities( array $result ) {
406 /**
407 * Array of decoded entities.
408 *
409 * @var Entity[] $entities
410 */
411 $entities = array();
412 foreach ( $result as $item ) {
413 /**
414 * Raw data.
415 *
416 * @var mixed[] $item
417 */
418 if ( ! isset( $item['data'] ) || ! isset( $item['id'] ) ) {
419 continue;
420 }
421 $data = (array) json_decode( strval( $item['data'] ), true );
422 /**
423 * Entity object.
424 *
425 * @var Entity $entity
426 */
427 $entity = isset( $data['class_name'] ) ? new $data['class_name']() : new $this->entity_class();
428 $entity->inflate( $data );
429 if ( is_numeric( $item['id'] ) ) {
430 $entity->setId( (int) $item['id'] );
431 }
432
433 $entities[] = $entity;
434 }
435
436 return $entities;
437 }
438
439 /**
440 * Saves entity to system storage.
441 *
442 * @param Entity $entity Entity to be stored.
443 *
444 * @return int Inserted entity identifier.
445 */
446 protected function save_entity_to_storage( Entity $entity ) {
447 if ( ! $this->table_exists() ) {
448 return -1;
449 }
450 $storage_item = $this->prepare_entity_for_storage( $entity );
451
452 $this->db->insert( $this->get_table_name(), $storage_item );
453
454 $insert_id = (int) $this->db->insert_id;
455 $entity->setId( $insert_id );
456
457 return $insert_id;
458 }
459
460 /**
461 * Prepares entity in format for storage.
462 *
463 * @param Entity $entity Entity to be stored.
464 *
465 * @return mixed[] Item prepared for storage.
466 */
467 protected function prepare_entity_for_storage( Entity $entity ) {
468 $indexes = IndexHelper::transformFieldsToIndexes( $entity );
469 $storage_item = array(
470 'type' => $entity->getConfig()->getType(),
471 'index_1' => null,
472 'index_2' => null,
473 'index_3' => null,
474 'index_4' => null,
475 'index_5' => null,
476 'index_6' => null,
477 'index_7' => null,
478 'data' => \wp_json_encode( $entity->toArray() ),
479 );
480
481 if ( $entity->getId() ) {
482 $storage_item['id'] = $entity->getId();
483 }
484
485 foreach ( $indexes as $index => $value ) {
486 $storage_item[ 'index_' . $index ] = $value;
487 }
488
489 return $storage_item;
490 }
491
492 /**
493 * Validates if column can be filtered or sorted by.
494 *
495 * @param string $column Column name.
496 * @param mixed[] $index_map Index map.
497 *
498 * @throws QueryFilterInvalidParamException If filter condition is invalid.
499 *
500 * @return void
501 */
502 protected function validate_index_column( $column, array $index_map ) {
503 if ( 'id' !== $column && ! array_key_exists( $column, $index_map ) ) {
504 throw new QueryFilterInvalidParamException( esc_html__( 'Column is not id or index.', 'sequra' ) );
505 }
506 }
507
508 /**
509 * Delete all the entities.
510 *
511 * @param string|null $store_id Delete entities from this store. Passing null will delete all entities.
512 */
513 public function delete_all( $store_id = null ): bool {
514 $deleted = false;
515 $sql = 'DELETE FROM ' . \sanitize_text_field( $this->get_table_name() );
516 if ( $store_id ) {
517 $column = $this->get_store_id_index_column();
518 if ( ! $column ) {
519 return false;
520 }
521 $sql .= ' WHERE ' . \sanitize_text_field( $column ) . ' = ' . \sanitize_text_field( $store_id );
522 }
523 if ( $this->table_exists() ) {
524 $result = $this->db->query( $sql );
525 $deleted = ! empty( $result );
526 }
527 if ( $this->table_exists( true ) ) {
528 $result = $this->db->query( str_replace( $this->get_table_name(), $this->get_legacy_table_name(), $sql ) );
529 $deleted = $deleted || ! empty( $result );
530 }
531 return $deleted;
532 }
533
534 /**
535 * Get the index column name that stores the store ID.
536 *
537 * @return string Index column name or empty string if not applicable.
538 */
539 protected function get_store_id_index_column(): string {
540 return 'index_1';
541 }
542
543 /**
544 * Check if table exists in the database.
545 *
546 * @param boolean $legacy If true, check for legacy table.
547 */
548 public function table_exists( $legacy = false ): bool {
549 $table_name = \sanitize_text_field( ! $legacy ? $this->get_table_name() : $this->get_legacy_table_name() );
550 return $this->db->get_var( "SHOW TABLES LIKE '{$table_name}'" ) === $table_name;
551 }
552
553 /**
554 * Remove entities that are older than a certain date or that are invalid.
555 * This performs a cleanup of the repository data.
556 */
557 public function delete_old_and_invalid() {
558 // Do nothing by default. Implement in child class if needed.
559 }
560
561 /**
562 * Check if the index exists.
563 *
564 * @param Table_Index $index The index to check.
565 * @return bool True if the index exists, false otherwise.
566 */
567 public function index_exists( $index ) {
568 $index_name = \sanitize_key( $index->name );
569 return ! empty( $this->db->get_col( "SHOW INDEX FROM `{$this->get_table_name()}` WHERE Key_name = '{$index_name}'" ) );
570 }
571
572 /**
573 * Add an index to the table.
574 *
575 * @param Table_Index $index The index.
576 * @return bool True if the index was added or already exists, false otherwise.
577 */
578 public function add_index( $index ) {
579 if ( $this->index_exists( $index ) ) {
580 return true;
581 }
582 $index_name = \sanitize_key( $index->name );
583 $columns = $index->columns;
584 foreach ( $columns as &$column ) {
585 $column = '`' . \sanitize_key( $column ) . '`';
586 }
587 $columns = implode( ',', $columns );
588 return false !== $this->db->query( "ALTER TABLE `{$this->get_table_name()}` ADD INDEX `{$index_name}` ({$columns})" );
589 }
590
591 /**
592 * Execute the migration process one by one.
593 * This implementation is intended for migrations that don't change the table structure.
594 */
595 public function migrate_next_row() {
596 if ( ! $this->table_exists() || ! $this->table_exists( true ) ) {
597 return;
598 }
599 $raw_results = $this->db->get_results( "SELECT * FROM {$this->get_legacy_table_name()} LIMIT 1;", ARRAY_A );
600 if ( ! is_array( $raw_results ) ) {
601 return;
602 }
603
604 $entity = $this->translateToEntities( $raw_results )[0] ?? null;
605 if ( ! $entity ) {
606 return;
607 }
608 // Check if entity already exists in the table.
609 if ( $this->entity_exists( $entity->getId() ) ) {
610 return;
611 }
612
613 $storage_item = $this->prepare_entity_for_storage( $entity );
614 $result = $this->db->insert( $this->get_table_name(), $storage_item );
615 if ( false !== $result ) {
616 // Delete the row from the legacy table.
617 $this->db->delete( $this->get_legacy_table_name(), array( 'id' => $entity->getId() ) );
618 }
619 }
620
621 /**
622 * Check if the migration process is complete.
623 *
624 * @return bool True if the migration process is complete, false otherwise.
625 */
626 public function is_migration_complete() {
627 // Check if the legacy table exists.
628 if ( $this->table_exists( true ) ) {
629 return false;
630 }
631 // Check if the indexes exist.
632 $indexes = $this->get_required_indexes();
633 foreach ( $indexes as $index ) {
634 if ( ! $this->index_exists( $index ) ) {
635 return false;
636 }
637 }
638
639 return true;
640 }
641
642 /**
643 * Get the SQL statement to create the table without the indexes definition.
644 * Resulting string should include an additional %s placeholder for the indexes.
645 *
646 * @return string The SQL statement to create the table.
647 */
648 protected function get_create_table_sql() {
649 $charset_collate = $this->db->get_charset_collate();
650 return "CREATE TABLE {$this->get_table_name()} (
651 `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
652 `type` VARCHAR(255),
653 `index_1` VARCHAR(127),
654 `index_2` VARCHAR(127),
655 `index_3` VARCHAR(127),
656 `index_4` VARCHAR(127),
657 `index_5` VARCHAR(127),
658 `index_6` VARCHAR(127),
659 `index_7` VARCHAR(127),
660 `data` LONGTEXT,
661 PRIMARY KEY (id) %s) $charset_collate;";
662 }
663
664 /**
665 * Create the table if it doesn't exist.
666 *
667 * @return bool True if the table was created successfully, false otherwise.
668 */
669 public function create_table() {
670 $indexes = array();
671 foreach ( $this->get_required_indexes() as $index ) {
672 $index_name = $index->name;
673 $columns = $index->columns;
674 foreach ( $columns as &$column ) {
675 $column = '`' . \sanitize_key( $column ) . '`';
676 }
677 $columns = implode( ',', $columns );
678 $indexes[] = "KEY `{$index_name}` ({$columns})";
679 }
680 $indexes = implode( ', ', $indexes );
681 if ( ! empty( $indexes ) ) {
682 $indexes = ', ' . $indexes;
683 }
684
685 $sql = sprintf( $this->get_create_table_sql(), $indexes );
686 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
687 \dbDelta( $sql );
688 return $this->table_exists();
689 }
690
691 /**
692 * Make sure that the required tables for the migration are created.
693 *
694 * @return bool
695 */
696 public function prepare_tables_for_migration() {
697 if ( $this->table_exists( true ) ) {
698 // If the legacy table already exists, we don't need to do anything.
699 return true;
700 }
701 // Rename the table to legacy table.
702 if ( false === $this->db->query( "RENAME TABLE {$this->get_table_name()} TO {$this->get_legacy_table_name()};" ) ) {
703 return false;
704 }
705
706 // Create the table if not exists.
707 if ( ! $this->create_table() ) {
708 return false;
709 }
710 // Add the auto-increment next value to the new table.
711 $raw_id = $this->db->get_var( "SELECT MAX(id) FROM {$this->get_legacy_table_name()};" );
712 $auto_increment = null !== $raw_id && is_numeric( $raw_id ) ? (int) $raw_id + 1 : 1;
713 if ( false === $this->db->query( "ALTER TABLE {$this->get_table_name()} AUTO_INCREMENT = {$auto_increment};" ) ) {
714 return false;
715 }
716
717 return true;
718 }
719
720 /**
721 * Evaluates if the legacy table should be removed and if so, removes it.
722 *
723 * @return bool True if the legacy table was removed or did not exist, false otherwise.
724 */
725 public function maybe_remove_legacy_table() {
726 if ( ! $this->table_exists( true ) ) {
727 return true;
728 }
729 $raw_results = $this->db->get_results( "SELECT 1 FROM `{$this->get_legacy_table_name()}` LIMIT 1;", ARRAY_A );
730 if ( ! empty( $raw_results ) ) {
731 // Legacy table is not empty, do not remove it.
732 return false;
733 }
734 return false !== $this->db->query( "DROP TABLE IF EXISTS `{$this->get_legacy_table_name()}`;" );
735 }
736
737 /**
738 * Get a list of indexes that are required for the table.
739 *
740 * @return Table_Index[] The list of indexes.
741 */
742 public function get_required_indexes() {
743 return array(
744 new Table_Index( $this->get_table_name() . '_type', array( 'type' ) ),
745 );
746 }
747
748 /**
749 * Check if entity exists in the database.
750 *
751 * @param int $id Entity ID.
752 * @param bool $legacy If true, check for legacy table.
753 * @return bool True if entity exists, false otherwise.
754 */
755 protected function entity_exists( $id, $legacy = false ): bool {
756 $table_name = $legacy ? $this->get_legacy_table_name() : $this->get_table_name();
757 $raw_results = $this->db->get_results( "SELECT 1 FROM `$table_name` WHERE id = {$id} LIMIT 1;", ARRAY_A );
758 return ! empty( $raw_results );
759 }
760 }
761