| | |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\JVB; |
| | | use JVBase\managers\queue\executors\ContentExecutor; |
| | | use JVBase\managers\queue\TypeConfig; |
| | | use JVBase\rest\RestRouteManager; |
| | | use JVBase\managers\CacheManager; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\meta\MetaManager; |
| | | use JVBase\utility\Features; |
| | | use WP_Post; |
| | |
| | | protected array $timelineSharedFields = []; |
| | | protected array $timelineUniqueFields = []; |
| | | |
| | | //TODO: Ensure we are handling the bulk operations for all processes |
| | | //TODO: Also invalidate feed caches on updates!! |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->cache_name = 'user_content_' . get_current_user_id(); |
| | | parent::__construct(); |
| | | $this->cache->clear(); |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | $this->action = 'dash-'; |
| | | $this->operation_type = 'content_update'; |
| | | add_filter(BASE . 'handle_bulk_operation', [$this, 'processOperation'], 10, 3); |
| | | add_action('init', [$this, 'registerContentExecutors'], 5); |
| | | } |
| | | |
| | | /** |
| | | * Register content operation types with the queue's TypeRegistry |
| | | */ |
| | | public function registerContentExecutors(): void |
| | | { |
| | | $registry = JVB()->queue()->registry(); |
| | | $executor = new ContentExecutor(); |
| | | |
| | | // Content updates - chunked at 10 posts |
| | | $registry->register('content_update', new TypeConfig( |
| | | executor: $executor, |
| | | chunkKey: 'posts', |
| | | chunkSize: 10 |
| | | )); |
| | | |
| | | // Batch creation (from uploads) |
| | | $registry->register('batch_creation', new TypeConfig( |
| | | executor: $executor |
| | | )); |
| | | } |
| | | |
| | | /** |
| | |
| | | public function handleContentUpdate(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $data = $request->get_params(); |
| | | error_log('Received data: ' . print_r($data, true)); |
| | | $user_id = $data['user']; |
| | | |
| | | if (!$this->userCheck($user_id)) { |
| | |
| | | { |
| | | $params = $request->get_params(); |
| | | $user_id = $params['user']; |
| | | |
| | | if (!$this->userCheck($user_id)) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | |
| | | $args['s'] = sanitize_text_field($params['search']); |
| | | } |
| | | |
| | | |
| | | $key = $this->cache->generateKey($args); |
| | | // Check HTTP cache headers with the specific content type |
| | | $content_type = $params['content'] ?? $params['type']; |
| | | $cache_check = $this->checkHeaders($request, $content_type, [ |
| | | 'filter_hash' => $key, |
| | | ]); |
| | | $cache_check = $this->checkHeaders($request, $key); |
| | | if ($cache_check) { |
| | | return $cache_check; |
| | | } |
| | |
| | | $this->taxonomies = $this->getTaxonomies($this->post_type); |
| | | $posts = array_map([$this, 'prepareItem'], $query->posts); |
| | | |
| | | |
| | | $data = [ |
| | | 'items' => $posts, |
| | | 'total' => $query->found_posts, |
| | |
| | | ]; |
| | | |
| | | } |
| | | |
| | | CacheManager::for($post_data['content'])->clear(); |
| | | if (jvbSiteUsesFeedBlock()) { |
| | | CacheManager::for('feed')->clear(); |
| | | } |
| | | } |
| | | |
| | | if (jvbSiteHasNotifications()) { |
| | |
| | | return ['success' => false, 'message' => 'No permission']; |
| | | } |
| | | |
| | | error_log('[Processing Timeline Post...'); |
| | | |
| | | $ignore = ['content', 'user']; |
| | | $this->fields = jvbGetFields($post_data['content']); |
| | | $this->initTimelineFields($post_data['content']); |
| | |
| | | ]); |
| | | |
| | | $prevDate = null; |
| | | |
| | | $latest_date = null; |
| | | $earliest_date = null; |
| | | foreach ($post_data['timeline'] as $order => $timeline) { |
| | | // Get unique fields for this specific timeline entry |
| | | $allowedFields = array_filter($timeline, function ($key) use ($ignore) { |
| | |
| | | $meta = new MetaManager($timeline['id'], 'post'); |
| | | $oldValues = $meta->getAll(array_keys($allowedFields)); |
| | | |
| | | // Set number taxonomy to menu_order (always update for reordering) |
| | | if (!$is_parent) { |
| | | $number_value = $order; |
| | | $term = get_term_by('name', (string)$number_value, BASE . 'number'); |
| | | if (!$term) { |
| | | $result = wp_insert_term((string)$number_value, BASE . 'number'); |
| | | if ($result && !is_wp_error($result)) { |
| | | $term = $result['term_id']; |
| | | } |
| | | } else { |
| | | $term = $term->term_id; |
| | | } |
| | | $allowedFields['number'] = $term; |
| | | } |
| | | // // Set number taxonomy to menu_order (always update for reordering) |
| | | // if (!$is_parent) { |
| | | // $number_value = $order; |
| | | // $term = get_term_by('name', (string)$number_value, BASE . 'number'); |
| | | // if (!$term) { |
| | | // $result = wp_insert_term((string)$number_value, BASE . 'number'); |
| | | // if ($result && !is_wp_error($result)) { |
| | | // $term = $result['term_id']; |
| | | // } |
| | | // } else { |
| | | // $term = $term->term_id; |
| | | // } |
| | | // $allowedFields['number'] = $term; |
| | | // } |
| | | |
| | | // Auto-timeline logic |
| | | if ($prevDate) { |
| | |
| | | $updateValues = array_filter($allowedFields, function ($value, $key) use ($oldValues) { |
| | | return (!array_key_exists($key, $oldValues) || $value !== $oldValues[$key]); |
| | | }, ARRAY_FILTER_USE_BOTH); |
| | | error_log('Setting values for ' . $timeline['id'] . ': ' . print_r($updateValues, true)); |
| | | |
| | | |
| | | $meta->setAll($updateValues); |
| | | $timeline['id'] = (int)$timeline['id']; |
| | |
| | | } |
| | | |
| | | if ($clearParent) { |
| | | $this->cache->clear(); |
| | | CacheManager::onPostSave($parent_id, $parent_post); |
| | | $this->cache->flush(); |
| | | Cache::onPostChange($parent_id, $parent_post); |
| | | } |
| | | |
| | | |
| | |
| | | } |
| | | $item['fields']['timeline'] = $subFields; |
| | | $item['images'] = $item['images'] + $images; |
| | | |
| | | $item['number'] = $mainMeta->getValue('number'); |
| | | |
| | | return $item; |
| | | } |
| | |
| | | // $queue->updateOperationProgress($operation->id, $key + 1, $total); |
| | | } |
| | | } |
| | | |
| | | //Clear cache |
| | | CacheManager::for($data['content'])->clear(); |
| | | CacheManager::for('feed')->clear(); |
| | | } |
| | | |
| | | return [ |
| | |
| | | |
| | | return $result; |
| | | } |
| | | // Add to ContentRoutes.php |
| | | |
| | | /** |
| | | * One-time migration: Set latest_date meta for all timeline posts |
| | | * Call this once via WP-CLI or a temporary admin page |
| | | * |
| | | * Usage: add_action('admin_init', function() { |
| | | * if (current_user_can('manage_options')) { |
| | | * JVB()->routes('content')->migrateTimelineLatestDates(); |
| | | * } |
| | | * }); |
| | | */ |
| | | public function migrateTimelineLatestDates(): array |
| | | { |
| | | global $wpdb; |
| | | |
| | | $results = [ |
| | | 'processed' => 0, |
| | | 'updated' => 0, |
| | | 'skipped' => 0, |
| | | 'errors' => [] |
| | | ]; |
| | | |
| | | // Get all timeline post types |
| | | $timeline_types = []; |
| | | foreach (JVB_CONTENT as $type => $config) { |
| | | if (Features::forContent($type)->has('is_timeline')) { |
| | | $timeline_types[] = BASE . $type; |
| | | } |
| | | } |
| | | |
| | | if (empty($timeline_types)) { |
| | | return $results; |
| | | } |
| | | |
| | | // Get all parent timeline posts |
| | | $args = [ |
| | | 'post_type' => $timeline_types, |
| | | 'post_status' => ['publish', 'draft'], |
| | | 'post_parent' => 0, |
| | | 'posts_per_page' => -1, |
| | | 'fields' => 'ids' |
| | | ]; |
| | | |
| | | $parent_ids = get_posts($args); |
| | | |
| | | foreach ($parent_ids as $parent_id) { |
| | | $results['processed']++; |
| | | |
| | | try { |
| | | // Get all children including the parent |
| | | $children = get_children([ |
| | | 'post_parent' => $parent_id, |
| | | 'post_status' => ['publish', 'draft'], |
| | | 'orderby' => 'menu_order', |
| | | 'order' => 'ASC', |
| | | 'fields' => 'ids' |
| | | ]); |
| | | |
| | | // Add parent to the list |
| | | array_unshift($children, $parent_id); |
| | | |
| | | // Find latest date among all posts |
| | | $latest_timestamp = 0; |
| | | |
| | | foreach ($children as $post_id) { |
| | | $date = get_post_meta($post_id, BASE . 'date', true); |
| | | |
| | | if ($date) { |
| | | $timestamp = strtotime($date); |
| | | if ($timestamp > $latest_timestamp) { |
| | | $latest_timestamp = $timestamp; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Update parent with latest date |
| | | if ($latest_timestamp > 0) { |
| | | update_post_meta($parent_id, BASE . 'latest_date', $latest_timestamp); |
| | | $results['updated']++; |
| | | error_log("Updated post {$parent_id} with latest_date: {$latest_timestamp}"); |
| | | } else { |
| | | // Fallback to parent post's post_date |
| | | $parent_post = get_post($parent_id); |
| | | $fallback_timestamp = strtotime($parent_post->post_date); |
| | | |
| | | if ($fallback_timestamp > 0) { |
| | | update_post_meta($parent_id, BASE . 'latest_date', $fallback_timestamp); |
| | | $results['updated']++; |
| | | error_log("Updated post {$parent_id} with fallback latest_date: {$fallback_timestamp} (from post_date)"); |
| | | } else { |
| | | $results['skipped']++; |
| | | error_log("No dates found for post {$parent_id}"); |
| | | } |
| | | } |
| | | |
| | | } catch (Exception $e) { |
| | | $results['errors'][] = [ |
| | | 'post_id' => $parent_id, |
| | | 'error' => $e->getMessage() |
| | | ]; |
| | | } |
| | | } |
| | | |
| | | error_log('Timeline migration complete: ' . print_r($results, true)); |
| | | return $results; |
| | | } |
| | | } |
| | | |
| | | |
| | | //add_action('init', function() { |
| | | //// delete_option('jvb_timeline_migrated'); |
| | | // if (get_option('jvb_timeline_migrated')) { |
| | | // return; |
| | | // } |
| | | // JVB()->routes('content')->migrateTimelineLatestDates(); |
| | | // update_option('jvb_timeline_migrated', true); |
| | | //}); |