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