[ 'frontend' => self::CONTEXT_FRONTEND, 'preview' => self::CONTEXT_PREVIEW, ], 'meta_key' => [ 'frontend' => self::META_KEY_FRONTEND, 'preview' => self::META_KEY_PREVIEW, ], ]; private ?Global_Classes $cache = null; public function __construct( ?Kit $kit = null ) { if ( null !== $kit ) { $this->set_kit( $kit ); } } public static function make( ?Kit $kit = null ): Global_Classes_Repository { return new self( $kit ); } protected function on_preview_change(): void { $this->cache = null; } /** * This method may be too heavy to use * Be mindful as this call would cause the server to freeze for as much time as needed until it fetches * all global classes */ public function all( bool $force = false ): Global_Classes { if ( ! $force && null !== $this->cache ) { return $this->cache; } $this->cache = $this->all_from_posts(); return $this->cache; } public function all_labels(): array { return $this->labels()->get_ordered_labels(); } public function get_order(): array { return Global_Classes_Order::make( $this->get_kit() )->get_order(); } public function update_order_and_labels( array $order, array $new_labels ): void { Global_Classes_Order::make( $this->get_kit() )->set_order( $order ); $labels = $this->labels(); $existing_labels = $labels->get_labels(); foreach ( $new_labels as $id => $label ) { $existing_labels[ $id ] = $label; } $labels->set_labels( $existing_labels ); $this->cache = null; } private function labels(): Global_Classes_Labels { return Global_Classes_Labels::make( $this->get_kit() )->set_preview( $this->is_preview() ); } public function get( string $class_id ): ?array { $post = Global_Class_Post::find_by_class_id( $class_id, $this->is_preview() ); return $post ? $post->to_array() : null; } public function get_by_ids( array $class_ids ): array { if ( empty( $class_ids ) ) { return []; } $items = []; foreach ( $this->iterate_class_posts_for_ids( $class_ids ) as $class_post ) { $class_data = $class_post->to_array(); $items[ $class_data['id'] ] = $class_data; } return $items; } public function apply_changes( array $touched_items, array $changes, array $order ): void { $labels = $this->labels(); $before = $labels->get_labels(); $is_preview = $this->is_preview(); $to_delete = $changes['deleted'] ?? []; $to_create = $changes['added'] ?? []; $to_update = $changes['modified'] ?? []; $order_changed = isset( $changes['order'] ) && $changes['order']; $final_label_map = []; foreach ( $order as $id ) { if ( isset( $touched_items[ $id ] ) ) { $final_label_map[ $id ] = $touched_items[ $id ]['label']; } elseif ( isset( $before[ $id ] ) ) { $final_label_map[ $id ] = $before[ $id ]; } } $this->persist_class_batch_mutations( $to_delete, $to_create, $to_update, $touched_items, $is_preview ); $classes_order = Global_Classes_Order::make( $this->get_kit() ); $classes_order->set_order( $order ); $labels->set_labels( $final_label_map ); if ( ! $is_preview ) { Global_Classes_Sync_Map::make( $this->get_kit() )->apply_changes( $touched_items, $to_delete ); $this->each_class_id_batch( array_values( $to_update ), function ( string $class_id ) { $post = Global_Class_Post::find_by_class_id( $class_id, false ); if ( $post ) { delete_post_meta( $post->get_post_id(), Global_Class_Post::META_KEY_DATA_PREVIEW ); clean_post_cache( $post->get_post_id() ); } } ); } $this->cache = null; $this->flush_runtime_cache(); do_action( 'elementor/global_classes/update', $this->get_context_key( 'event' ), [ 'added' => $to_create, 'deleted' => $to_delete, 'modified' => $to_update, 'order' => $order_changed, ] ); } public function each_item( callable $cb, bool $skip_migration = false, int $batch_size = self::READ_BATCH_SIZE ): void { $order = Global_Classes_Order::make( $this->get_kit() )->get_order(); if ( empty( $order ) ) { return; } foreach ( array_chunk( $order, $batch_size ) as $chunk ) { foreach ( $this->iterate_class_posts_for_ids( $chunk ) as $class_post ) { $cb( $class_post->to_array( $skip_migration ) ); } } } public function put( array $items, array $order ) { $current_ids = Global_Classes_Order::make( $this->get_kit() )->get_order(); $new_ids = array_keys( $items ); $current_order_string = implode( ';', $current_ids ); $changes = [ 'added' => array_values( array_diff( $new_ids, $current_ids ) ), 'deleted' => array_values( array_diff( $current_ids, $new_ids ) ), 'modified' => array_values( array_intersect( $new_ids, $current_ids ) ), 'order' => implode( ';', $order ) !== $current_order_string, ]; $this->put_to_posts( $items, $order, $current_ids ); $this->cache = null; do_action( 'elementor/global_classes/update', $this->get_context_key( 'event' ), $changes ); } private function all_from_posts(): Global_Classes { $classes_order = Global_Classes_Order::make( $this->get_kit() ); $order = $classes_order->get_order(); if ( empty( $order ) ) { return Global_Classes::make( [], [] ); } $items = []; foreach ( $this->iterate_class_posts_for_ids( $order ) as $class_post ) { $class_data = $class_post->to_array(); $items[ $class_data['id'] ] = $class_data; } $order = Global_Classes_Parser::sanitize_order( $items, $order ); return Global_Classes::make( $items, $order ); } private function put_to_posts( array $items, array $order, array $current_ids ): void { $is_preview = $this->is_preview(); $new_ids = array_keys( $items ); $to_delete = array_diff( $current_ids, $new_ids ); $to_create = array_diff( $new_ids, $current_ids ); $to_update = array_intersect( $new_ids, $current_ids ); $this->persist_class_batch_mutations( $to_delete, $to_create, $to_update, $items, $is_preview ); $classes_order = Global_Classes_Order::make( $this->get_kit() ); $classes_order->set_order( $order ); $label_map = []; foreach ( $order as $id ) { if ( isset( $items[ $id ]['label'] ) ) { $label_map[ $id ] = $items[ $id ]['label']; } } $this->labels()->set_labels( $label_map ); if ( ! $is_preview ) { $touched_ids = array_merge( array_values( $to_create ), array_values( $to_update ) ); $touched_items = array_intersect_key( $items, array_flip( $touched_ids ) ); Global_Classes_Sync_Map::make( $this->get_kit() )->apply_changes( $touched_items, array_values( $to_delete ) ); $this->each_class_id_batch( array_values( $to_update ), function ( string $class_id ) { $post = Global_Class_Post::find_by_class_id( $class_id, false ); if ( $post ) { delete_post_meta( $post->get_post_id(), Global_Class_Post::META_KEY_DATA_PREVIEW ); clean_post_cache( $post->get_post_id() ); } } ); } } private function iterate_class_posts_for_ids( array $class_ids ): \Generator { foreach ( array_chunk( $class_ids, self::READ_BATCH_SIZE ) as $chunk ) { $posts = get_posts( [ 'post_type' => Global_Class_Post_Type::CPT, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => Global_Class_Post::META_KEY_ID, 'value' => $chunk, 'compare' => 'IN', ], ], ] ); $seen_class_ids = []; foreach ( $posts as $post ) { $class_id = get_post_meta( $post->ID, Global_Class_Post::META_KEY_ID, true ); if ( $class_id && isset( $seen_class_ids[ $class_id ] ) ) { // if for some reason there's already a post meta to that class - delete any other ones // otherwise we may end up updating a stale post meta wp_delete_post( $post->ID, true ); continue; } if ( $class_id ) { $seen_class_ids[ $class_id ] = true; } yield Global_Class_Post::from_post( $post, $this->is_preview() ); clean_post_cache( $post->ID ); } unset( $posts, $seen_class_ids ); $this->flush_runtime_cache(); } } private function persist_class_batch_mutations( array $to_delete, array $to_create, array $to_update, array $items_by_id, bool $is_preview ): void { $relations = new Global_Classes_Relations(); $this->each_class_id_batch( array_values( $to_delete ), function ( string $class_id ) use ( $is_preview, $relations ) { $post = Global_Class_Post::find_by_class_id( $class_id, false ); if ( $post ) { if ( $is_preview ) { $post->set_preview( true ); $post->update_data( [] ); clean_post_cache( $post->get_post_id() ); } else { $relations->clear_class_relations( $class_id ); $post->delete(); } } } ); $this->each_class_id_batch( $to_create, function ( string $class_id ) use ( $items_by_id, $is_preview ) { if ( ! isset( $items_by_id[ $class_id ] ) ) { return; } $item = $items_by_id[ $class_id ]; $data = $this->build_class_data_for_storage( $item ); if ( $is_preview ) { $post = Global_Class_Post::find_by_class_id( $class_id, true ); if ( $post ) { $post->set_preview( true ); $post->update_data( $data ); $post->update_label( $item['label'] ); clean_post_cache( $post->get_post_id() ); } else { $created = Global_Class_Post::create( $class_id, $item['label'], $data ); if ( $created ) { clean_post_cache( $created->get_post_id() ); } } } else { $created = Global_Class_Post::create( $class_id, $item['label'], $data ); if ( $created ) { clean_post_cache( $created->get_post_id() ); } } } ); $this->each_class_id_batch( $to_update, function ( string $class_id ) use ( $items_by_id, $is_preview ) { if ( ! isset( $items_by_id[ $class_id ] ) ) { return; } $item = $items_by_id[ $class_id ]; $post = Global_Class_Post::find_by_class_id( $class_id, $is_preview ); if ( ! $post ) { return; } $data = $this->build_class_data_for_storage( $item ); $post->update_data( $data ); $post->update_label( $item['label'] ); clean_post_cache( $post->get_post_id() ); } ); } public function delete_all(): void { $order = $this->get_order(); $this->each_class_id_batch( $order, function ( string $class_id ) { $post = Global_Class_Post::find_by_class_id( $class_id ); if ( $post ) { $post->delete(); } } ); Global_Classes_Order::make( $this->get_kit() )->set_order( [] ); $this->labels()->set_labels( [] ); } private function each_class_id_batch( $class_ids, callable $callback, int $batch_size = self::PERSIST_BATCH_SIZE ): void { $class_ids = is_array( $class_ids ) ? $class_ids : iterator_to_array( $class_ids, false ); foreach ( array_chunk( array_values( $class_ids ), $batch_size ) as $batch ) { foreach ( $batch as $class_id ) { $callback( $class_id ); } $this->flush_runtime_cache(); } } private function flush_runtime_cache(): void { if ( function_exists( 'wp_cache_flush_runtime' ) ) { wp_cache_flush_runtime(); } } private function build_class_data_for_storage( array $item ): array { $data = [ 'type' => $item['type'] ?? 'class', 'variants' => $item['variants'] ?? [], ]; if ( array_key_exists( 'sync_to_v3', $item ) ) { $data['sync_to_v3'] = (bool) $item['sync_to_v3']; } return $data; } }