types
3 months ago
class-group-ad-relation.php
5 days ago
class-group-factory.php
2 days ago
class-group-ordered.php
1 year ago
class-group-repository.php
2 days ago
class-group-slider.php
5 months ago
class-group-standard.php
1 year ago
class-group-types.php
2 days ago
class-groups.php
2 days ago
index.php
1 year ago
class-group-repository.php
504 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Group Repository. |
| 4 | * |
| 5 | * @package AdvancedAds |
| 6 | * @author Advanced Ads <info@wpadvancedads.com> |
| 7 | * @since 1.48.0 |
| 8 | */ |
| 9 | |
| 10 | namespace AdvancedAds\Groups; |
| 11 | |
| 12 | use AdvancedAds\Abstracts\Group; |
| 13 | use AdvancedAds\Constants; |
| 14 | use AdvancedAds\Framework\Utilities\Formatting; |
| 15 | use AdvancedAds\Cache_Invalidator; |
| 16 | use AdvancedAds\Traits\Repository_Helpers; |
| 17 | use AdvancedAds\Utilities\Cache; |
| 18 | use Exception; |
| 19 | |
| 20 | defined( 'ABSPATH' ) || exit; |
| 21 | |
| 22 | /** |
| 23 | * Group Repository. |
| 24 | */ |
| 25 | class Group_Repository { |
| 26 | use Repository_Helpers; |
| 27 | |
| 28 | /** |
| 29 | * Group options metakey |
| 30 | * |
| 31 | * @var string |
| 32 | */ |
| 33 | const OPTION_METAKEY = 'advanced_ads_group_options'; |
| 34 | |
| 35 | /** |
| 36 | * Group type metakey |
| 37 | * |
| 38 | * @var string |
| 39 | */ |
| 40 | const TYPE_METAKEY = '_advads_group_type'; |
| 41 | |
| 42 | /* CRUD Methods ------------------- */ |
| 43 | |
| 44 | /** |
| 45 | * Create a new group in the database. |
| 46 | * |
| 47 | * @param Group $group Group object. |
| 48 | * |
| 49 | * @return Group |
| 50 | */ |
| 51 | public function create( &$group ): Group { |
| 52 | apply_filters( 'advanced-ads-group-pre-save', $group ); |
| 53 | |
| 54 | $ids = wp_insert_term( |
| 55 | $group->get_title(), |
| 56 | Constants::TAXONOMY_GROUP, |
| 57 | [ |
| 58 | 'description' => $group->get_content(), |
| 59 | 'slug' => $group->get_slug(), |
| 60 | ] |
| 61 | ); |
| 62 | |
| 63 | if ( $ids && ! is_wp_error( $ids ) ) { |
| 64 | $group->set_id( $ids['term_id'] ); |
| 65 | $this->update_term_meta( $group ); |
| 66 | $group->apply_changes(); |
| 67 | } |
| 68 | |
| 69 | return $group; |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Read an group from the database. |
| 74 | * |
| 75 | * @param Group $group Group object. |
| 76 | * @throws Exception If invalid group. |
| 77 | * |
| 78 | * @return void |
| 79 | */ |
| 80 | public function read( &$group ): void { |
| 81 | $group->set_defaults(); |
| 82 | $term_object = get_term( $group->get_id(), Constants::TAXONOMY_GROUP ); |
| 83 | |
| 84 | if ( null === $term_object || is_wp_error( $term_object ) ) { |
| 85 | throw new Exception( esc_html__( 'Invalid group.', 'advanced-ads' ) ); |
| 86 | } |
| 87 | |
| 88 | $group->set_title( $term_object->name ); |
| 89 | $group->set_slug( $term_object->slug ); |
| 90 | $group->set_content( $term_object->description ); |
| 91 | |
| 92 | $this->read_group_data( $group ); |
| 93 | $group->set_object_read( true ); |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * Update an existing group in the database. |
| 98 | * |
| 99 | * @param Group $group Group object. |
| 100 | * |
| 101 | * @return void |
| 102 | */ |
| 103 | public function update( &$group ): void { |
| 104 | apply_filters( 'advanced-ads-group-pre-save', $group ); |
| 105 | |
| 106 | $changed = array_keys( $group->get_changes() ); |
| 107 | |
| 108 | $term_args = []; |
| 109 | |
| 110 | if ( in_array( 'title', $changed, true ) ) { |
| 111 | $term_args['name'] = $group->get_title( 'edit' ); |
| 112 | } |
| 113 | |
| 114 | if ( in_array( 'slug', $changed, true ) ) { |
| 115 | $term_args['slug'] = $group->get_slug( 'edit' ); |
| 116 | } |
| 117 | |
| 118 | if ( in_array( 'content', $changed, true ) ) { |
| 119 | $term_args['description'] = $group->get_content( 'edit' ); |
| 120 | } |
| 121 | |
| 122 | if ( ! empty( $term_args ) ) { |
| 123 | wp_update_term( $group->get_id(), Constants::TAXONOMY_GROUP, $term_args ); |
| 124 | } |
| 125 | |
| 126 | // Only update weights when there is a change. |
| 127 | if ( in_array( 'ad_weights', $changed, true ) ) { |
| 128 | ( new Group_Ad_Relation() )->relate( $group ); |
| 129 | } |
| 130 | |
| 131 | $this->update_term_meta( $group ); |
| 132 | $group->apply_changes(); |
| 133 | |
| 134 | if ( empty( $term_args ) ) { |
| 135 | Cache_Invalidator::invalidate_groups(); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | /** |
| 140 | * Delete an group from the database. |
| 141 | * |
| 142 | * @param Group $group Group object or Id. |
| 143 | * @param bool $force_delete Unused; taxonomy terms are always deleted permanently. |
| 144 | * |
| 145 | * @return void |
| 146 | */ |
| 147 | public function delete( &$group, $force_delete = false ): void { |
| 148 | // Early bail!! |
| 149 | if ( ! $group || ! $group->get_id() ) { |
| 150 | return; |
| 151 | } |
| 152 | |
| 153 | wp_delete_term( $group->get_id(), Constants::TAXONOMY_GROUP ); |
| 154 | |
| 155 | $group->set_id( 0 ); |
| 156 | $group->set_status( 'trash' ); |
| 157 | } |
| 158 | |
| 159 | /* Finder Methods ------------------- */ |
| 160 | |
| 161 | /** |
| 162 | * Get all groups object. |
| 163 | * |
| 164 | * @deprecated 2.0.24 Use get_group_summaries() or get_groups_by_ids() instead. |
| 165 | * |
| 166 | * @return Group[] |
| 167 | */ |
| 168 | public function get_all_groups(): array { |
| 169 | return $this->hydrate_groups( array_keys( $this->get_group_summaries() ) ); |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * Get lightweight group summaries for list UIs (cached cross-request). |
| 174 | * |
| 175 | * @return array<int, array{id: int, title: string, slug: string, type: string, ad_weights: array<int, int>, publish_date: string, modified_date: string}> |
| 176 | */ |
| 177 | public function get_group_summaries(): array { |
| 178 | $summaries = Cache::get( Cache::PREFIX_GROUPS, Cache::KEY_SUMMARIES ); |
| 179 | |
| 180 | if ( null === $summaries ) { |
| 181 | $summaries = $this->query_group_summaries(); |
| 182 | Cache::set( Cache::PREFIX_GROUPS, Cache::KEY_SUMMARIES, $summaries ); |
| 183 | } |
| 184 | |
| 185 | return $summaries; |
| 186 | } |
| 187 | |
| 188 | /** |
| 189 | * Get all group as dropdown (ID => title), derived from cached summaries. |
| 190 | * |
| 191 | * @return array<int, string> |
| 192 | */ |
| 193 | public function get_groups_dropdown(): array { |
| 194 | return $this->summaries_to_dropdown( $this->get_group_summaries() ); |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Get ads belonging to a group in the requested shape. |
| 199 | * |
| 200 | * @param int $group_id Group term ID. |
| 201 | * @param string $output OBJECT for hydrated ads, 'ids' for ad IDs, 'summaries' for cached ad rows. |
| 202 | * |
| 203 | * @return Ad[]|int[]|array<int, array{id: int, title: string, type: string, status: string, author_id: int, expiry_date: int}> |
| 204 | */ |
| 205 | public function get_ads_by_group_id( int $group_id, $output = OBJECT ): array { |
| 206 | $ad_weights = $this->get_ad_weights_by_group_id( $group_id ); |
| 207 | |
| 208 | if ( empty( $ad_weights ) ) { |
| 209 | return []; |
| 210 | } |
| 211 | |
| 212 | $ad_ids = array_map( 'absint', array_keys( $ad_weights ) ); |
| 213 | |
| 214 | if ( 'ids' === $output ) { |
| 215 | return $ad_ids; |
| 216 | } |
| 217 | |
| 218 | if ( 'summaries' === $output ) { |
| 219 | return array_intersect_key( wp_advads_get_ad_summaries(), array_flip( $ad_ids ) ); |
| 220 | } |
| 221 | |
| 222 | $ads = wp_advads_get_ads_by_ids( $ad_ids ); |
| 223 | |
| 224 | foreach ( $ads as $ad_id => $ad ) { |
| 225 | $ad->set_prop( 'weight', $ad_weights[ $ad_id ] ); |
| 226 | } |
| 227 | |
| 228 | return $ads; |
| 229 | } |
| 230 | |
| 231 | /** |
| 232 | * Get ad IDs assigned to a group from cached summaries. |
| 233 | * |
| 234 | * @param int $group_id Group term ID. |
| 235 | * |
| 236 | * @return int[] |
| 237 | */ |
| 238 | private function get_ad_ids_by_group_id( int $group_id ): array { |
| 239 | return array_map( 'absint', array_keys( $this->get_ad_weights_by_group_id( $group_id ) ) ); |
| 240 | } |
| 241 | |
| 242 | /** |
| 243 | * Get ad weights for a group from cached summaries. |
| 244 | * |
| 245 | * @param int $group_id Group term ID. |
| 246 | * |
| 247 | * @return array<int, int> |
| 248 | */ |
| 249 | private function get_ad_weights_by_group_id( int $group_id ): array { |
| 250 | $summaries = $this->get_group_summaries(); |
| 251 | |
| 252 | if ( ! isset( $summaries[ $group_id ] ) ) { |
| 253 | return []; |
| 254 | } |
| 255 | |
| 256 | return $summaries[ $group_id ]['ad_weights'] ?? []; |
| 257 | } |
| 258 | |
| 259 | /** |
| 260 | * Query lightweight group summaries without hydrating Group objects. |
| 261 | * |
| 262 | * @return array<int, array{id: int, title: string, slug: string, type: string, ad_weights: array<int, int>, publish_date: string, modified_date: string}> |
| 263 | */ |
| 264 | private function query_group_summaries(): array { |
| 265 | $terms = get_terms( |
| 266 | [ |
| 267 | 'taxonomy' => Constants::TAXONOMY_GROUP, |
| 268 | 'hide_empty' => false, |
| 269 | 'number' => 0, |
| 270 | 'orderby' => 'name', |
| 271 | 'update_term_meta_cache' => false, |
| 272 | 'suppress_filters' => defined( 'ICL_SITEPRESS_VERSION' ) ? true : false, // Suppress filters if WPML is present. |
| 273 | ] |
| 274 | ); |
| 275 | |
| 276 | if ( empty( $terms ) || is_wp_error( $terms ) ) { |
| 277 | return []; |
| 278 | } |
| 279 | |
| 280 | $term_ids = wp_list_pluck( $terms, 'term_id' ); |
| 281 | update_termmeta_cache( $term_ids ); |
| 282 | |
| 283 | $summaries = []; |
| 284 | foreach ( $terms as $term ) { |
| 285 | $type = get_term_meta( $term->term_id, self::TYPE_METAKEY, true ); |
| 286 | $meta_values = get_term_meta( $term->term_id, self::OPTION_METAKEY, true ); |
| 287 | |
| 288 | if ( ! $type ) { |
| 289 | $type = is_array( $meta_values ) ? ( $meta_values['type'] ?? 'default' ) : 'default'; |
| 290 | } |
| 291 | |
| 292 | $type = $this->normalize_group_type( $type ); |
| 293 | |
| 294 | $summaries[ (int) $term->term_id ] = [ |
| 295 | 'id' => (int) $term->term_id, |
| 296 | 'title' => $term->name, |
| 297 | 'slug' => $term->slug, |
| 298 | 'type' => $type, |
| 299 | 'ad_weights' => $this->extract_ad_weights_from_meta( $meta_values, $type ), |
| 300 | 'publish_date' => (string) get_term_meta( $term->term_id, 'publish_date', true ), |
| 301 | 'modified_date' => (string) get_term_meta( $term->term_id, 'modified_date', true ), |
| 302 | ]; |
| 303 | } |
| 304 | |
| 305 | return $summaries; |
| 306 | } |
| 307 | |
| 308 | /** |
| 309 | * Extract ad weights from stored group term meta. |
| 310 | * |
| 311 | * @param mixed $meta_values Group option meta values. |
| 312 | * @param string $type Resolved group type. |
| 313 | * |
| 314 | * @return array<int, int> |
| 315 | */ |
| 316 | private function extract_ad_weights_from_meta( $meta_values, $type ): array { |
| 317 | if ( ! is_array( $meta_values ) ) { |
| 318 | return []; |
| 319 | } |
| 320 | |
| 321 | $meta_values = $this->merge_group_options_meta( $meta_values, $type ); |
| 322 | |
| 323 | $ad_weights = $meta_values['ad_weights'] ?? []; |
| 324 | |
| 325 | return is_array( $ad_weights ) ? $ad_weights : []; |
| 326 | } |
| 327 | |
| 328 | /** |
| 329 | * Hydrate group objects for the given term IDs. |
| 330 | * |
| 331 | * @param int[] $term_ids Group term IDs. |
| 332 | * |
| 333 | * @return Group[] |
| 334 | */ |
| 335 | private function hydrate_groups( array $term_ids ): array { |
| 336 | $term_ids = $this->normalize_entity_ids( $term_ids ); |
| 337 | |
| 338 | if ( ! empty( $term_ids ) ) { |
| 339 | if ( function_exists( 'wp_prime_term_caches' ) ) { |
| 340 | wp_prime_term_caches( $term_ids, Constants::TAXONOMY_GROUP ); |
| 341 | } else { |
| 342 | foreach ( $term_ids as $term_id ) { |
| 343 | get_term( $term_id, Constants::TAXONOMY_GROUP ); |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | update_termmeta_cache( $term_ids ); |
| 348 | } |
| 349 | |
| 350 | $groups = []; |
| 351 | foreach ( $term_ids as $term_id ) { |
| 352 | $group = wp_advads_get_group( $term_id ); |
| 353 | |
| 354 | if ( $group ) { |
| 355 | $groups[ $term_id ] = $group; |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | return $groups; |
| 360 | } |
| 361 | |
| 362 | /** |
| 363 | * Get groups associated with a given ad id. |
| 364 | * |
| 365 | * @param int $ad_id The ID of the ad. |
| 366 | * |
| 367 | * @return Group[] Groups array |
| 368 | */ |
| 369 | public function get_groups_by_ad_id( $ad_id ) { |
| 370 | $ad_id = absint( $ad_id ); |
| 371 | |
| 372 | if ( ! $ad_id ) { |
| 373 | return []; |
| 374 | } |
| 375 | |
| 376 | $group_ids = get_post_meta( $ad_id, Constants::AD_META_GROUP_IDS, true ); |
| 377 | |
| 378 | if ( ! is_array( $group_ids ) || empty( $group_ids ) ) { |
| 379 | return []; |
| 380 | } |
| 381 | |
| 382 | return $this->hydrate_groups( $group_ids ); |
| 383 | } |
| 384 | |
| 385 | /** |
| 386 | * Hydrate group objects for the given term IDs. |
| 387 | * |
| 388 | * @param int[] $term_ids Group term IDs. |
| 389 | * |
| 390 | * @return array<int, Group> |
| 391 | */ |
| 392 | public function get_groups_by_ids( array $term_ids ): array { |
| 393 | return $this->hydrate_groups( $term_ids ); |
| 394 | } |
| 395 | |
| 396 | /* Additional Methods ------------------- */ |
| 397 | |
| 398 | /** |
| 399 | * Read group data. Can be overridden by child classes to load other props. |
| 400 | * |
| 401 | * @param Group $group Group object. |
| 402 | * |
| 403 | * @return void |
| 404 | */ |
| 405 | private function read_group_data( &$group ): void { |
| 406 | $type = get_term_meta( $group->get_id(), self::TYPE_METAKEY, true ); |
| 407 | $meta_values = get_term_meta( $group->get_id(), self::OPTION_METAKEY, true ); |
| 408 | $publish_date = get_term_meta( $group->get_id(), 'publish_date', true ); |
| 409 | $modified_date = get_term_meta( $group->get_id(), 'modified_date', true ); |
| 410 | |
| 411 | if ( ! is_array( $meta_values ) ) { |
| 412 | $meta_values = []; |
| 413 | } |
| 414 | |
| 415 | $type = $this->normalize_group_type( $type ?: ( $meta_values['type'] ?? 'default' ) ); |
| 416 | |
| 417 | $meta_values = $this->merge_group_options_meta( $meta_values, $type ); |
| 418 | $meta_values['type'] = $type; |
| 419 | |
| 420 | $meta_values['publish_date'] = $publish_date ?? ''; |
| 421 | $meta_values['modified_date'] = $modified_date ?? ''; |
| 422 | |
| 423 | $group->set_props( $meta_values ); |
| 424 | |
| 425 | foreach ( [ 'random', 'enabled' ] as $prop ) { |
| 426 | if ( array_key_exists( $prop, $meta_values ) ) { |
| 427 | $value = $meta_values[ $prop ]; |
| 428 | $value = Formatting::string_to_bool( $value ); |
| 429 | $group->set_prop( $prop, $value ); |
| 430 | } |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | /** |
| 435 | * Update group data. Can be overridden by child classes to load other props. |
| 436 | * |
| 437 | * @param Group $group Group object. |
| 438 | * |
| 439 | * @return void |
| 440 | */ |
| 441 | private function update_term_meta( &$group ): void { |
| 442 | $current_date = current_time( 'mysql', true ); |
| 443 | $meta_values = [ |
| 444 | 'type' => $group->get_type(), |
| 445 | 'ad_count' => $group->get_ad_count(), |
| 446 | 'options' => $group->get_options(), |
| 447 | 'ad_weights' => $group->get_ad_weights(), |
| 448 | ]; |
| 449 | |
| 450 | update_term_meta( $group->get_id(), self::TYPE_METAKEY, $group->get_type() ); |
| 451 | update_term_meta( $group->get_id(), self::OPTION_METAKEY, $meta_values ); |
| 452 | |
| 453 | update_term_meta( $group->get_id(), 'modified_date', $current_date ); |
| 454 | if ( empty( $group->get_publish_date() ) ) { |
| 455 | update_term_meta( $group->get_id(), 'publish_date', $current_date ); |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | /** |
| 460 | * Normalize stored group type to a registered type slug. |
| 461 | * |
| 462 | * @param string $type Raw type from meta or legacy storage. |
| 463 | * |
| 464 | * @return string |
| 465 | */ |
| 466 | private function normalize_group_type( $type ): string { |
| 467 | if ( empty( $type ) ) { |
| 468 | return 'default'; |
| 469 | } |
| 470 | |
| 471 | if ( 'refresh' === $type ) { |
| 472 | return 'default'; |
| 473 | } |
| 474 | |
| 475 | return $type; |
| 476 | } |
| 477 | |
| 478 | /** |
| 479 | * Merge per-type options from stored group meta. |
| 480 | * |
| 481 | * @param array $meta_values Group option meta values. |
| 482 | * @param string $type Normalized group type. |
| 483 | * |
| 484 | * @return array |
| 485 | */ |
| 486 | private function merge_group_options_meta( array $meta_values, string $type ): array { |
| 487 | if ( ! isset( $meta_values['options'] ) || ! is_array( $meta_values['options'] ) ) { |
| 488 | return $meta_values; |
| 489 | } |
| 490 | |
| 491 | $bucket = $type; |
| 492 | |
| 493 | if ( ! isset( $meta_values['options'][ $bucket ] ) && 'default' === $type && isset( $meta_values['options']['refresh'] ) ) { |
| 494 | $bucket = 'refresh'; |
| 495 | } |
| 496 | |
| 497 | if ( isset( $meta_values['options'][ $bucket ] ) && is_array( $meta_values['options'][ $bucket ] ) ) { |
| 498 | return array_merge( $meta_values['options'][ $bucket ], $meta_values ); |
| 499 | } |
| 500 | |
| 501 | return $meta_values; |
| 502 | } |
| 503 | } |
| 504 |