| | |
| | | |
| | | use JVBase\managers\queue\{Executor, Operation, Progress, Result, Storage}; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\utility\Features; |
| | | use JVBase\registrar\Registrar; |
| | | use Exception; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | if (!in_array($operation->type, self::HANDLED_TYPES)) { |
| | | throw new Exception("ContentExecutor cannot handle type: {$operation->type}"); |
| | | } |
| | | error_log('Executing ContentExecutor.php'); |
| | | |
| | | try { |
| | | $data = $operation->requestData; |
| | |
| | | |
| | | if (empty($posts)) { |
| | | return new Result( |
| | | outcome: 'failed', |
| | | outcome: 'success', |
| | | result: ['message' => 'No posts to update'] |
| | | ); |
| | | } |
| | | |
| | | $results = []; |
| | | $results = [ |
| | | 'errors' => [], |
| | | 'success' => [], |
| | | 'newPosts' => [], |
| | | 'timelineParents' => [], |
| | | 'timelineStatus' => [], |
| | | 'timelineSharedFields' => [], |
| | | ]; |
| | | $errors = []; |
| | | $timelineParents = []; |
| | | $timelineStatus = []; |
| | | $timelineSharedFields = []; |
| | | |
| | | foreach ($posts as $id => $postData) { |
| | | try { |
| | | $content = $postData['content'] ?? ''; |
| | | |
| | | // New post creation |
| | | if (str_starts_with((string)$id, 'new')) { |
| | | $newId = wp_insert_post([ |
| | | 'post_author' => $this->userId, |
| | | 'post_type' => jvbCheckBase($content), |
| | | 'post_title' => $postData['post_title'] ?? '', |
| | | 'post_status' => $postData['status'] ?? 'draft', |
| | | ]); |
| | | |
| | | if (!$newId || is_wp_error($newId)) { |
| | | $progress->failItem($id, 'Could not create post'); |
| | | continue; |
| | | } |
| | | |
| | | $this->savePostFields($newId, $postData); |
| | | $results[$id] = [ |
| | | 'success' => true, |
| | | 'new_id' => $newId, |
| | | 'processed_fields' => array_keys($postData) |
| | | ]; |
| | | $progress->advance(); |
| | | continue; |
| | | $content = $postData['content'] ?? false; |
| | | if (!$content) continue; |
| | | $registrar = Registrar::getInstance($content); |
| | | switch ($registrar->getType()) { |
| | | case 'post': |
| | | $results = $this->handlePost($id, $postData, $registrar, $results, $progress); |
| | | break; |
| | | case 'term': |
| | | $results = $this->handleTerm($id, $postData, $registrar, $results, $progress); |
| | | break; |
| | | case 'user': |
| | | $results = $this->handleUser($id, $postData, $registrar, $results, $progress); |
| | | break; |
| | | } |
| | | |
| | | // Existing post update |
| | | if (!$this->verifyOwnership((int)$id)) { |
| | | $progress->failItem($id, 'No permission to modify this post'); |
| | | $errors[$id] = 'No permission'; |
| | | continue; |
| | | } |
| | | |
| | | $this->savePostFields((int)$id, $postData); |
| | | |
| | | |
| | | if (Features::forContent($content)->has('is_timeline')) { |
| | | $post = get_post((int)$id); |
| | | $parentId = $post->post_parent > 0 ? $post->post_parent : $post->ID; |
| | | $sharedFields = array_keys(array_filter(JVB_CONTENT[$content]['fields'], function ($field) { |
| | | return !array_key_exists('for_all', $field) || !$field['for_all']; |
| | | })); |
| | | |
| | | if (array_key_exists('post_date', $postData) && !in_array($parentId, $timelineParents)) { |
| | | $timelineParents[] = $parentId; |
| | | } |
| | | if ($parentId === $id) { |
| | | if (array_key_exists('post_status', $postData) && !array_key_exists($parentId, $timelineStatus)) { |
| | | $timelineStatus[$parentId] = $postData['post_status']; |
| | | } |
| | | |
| | | if (count(array_intersect($sharedFields, array_keys($postData))) > 0) { |
| | | if (!array_key_exists($parentId, $timelineSharedFields)) { |
| | | $timelineSharedFields[$parentId] = []; |
| | | } |
| | | $temp = array_intersect($sharedFields, array_keys($postData)); |
| | | $timelineSharedFields[$parentId] = array_unique(array_merge($timelineSharedFields[$parentId], $temp)); |
| | | } |
| | | } |
| | | } |
| | | $results[$id] = [ |
| | | 'success' => true, |
| | | 'processed_fields' => array_keys($postData) |
| | | ]; |
| | | $progress->advance(); |
| | | |
| | | } catch (Exception $e) { |
| | | $progress->failItem($id, $e->getMessage()); |
| | | $errors[$id] = $e->getMessage(); |
| | | $results[$id] = [ |
| | | 'success' => false, |
| | | 'error' => $e->getMessage() |
| | | ]; |
| | | $results['errors'][$id] = $e->getMessage(); |
| | | } |
| | | } |
| | | error_log('Final Results: '.print_r($results, true)); |
| | | |
| | | try { |
| | | if (!empty($timelineSharedFields)) { |
| | | $this->checkSharedFields($timelineSharedFields); |
| | | if (!empty($results['timelineSharedFields'])) { |
| | | $this->checkSharedFields($results['timelineSharedFields']); |
| | | } |
| | | if (!empty($timelineStatus)) { |
| | | $this->handleTimelineStatusChange($timelineStatus); |
| | | if (!empty($results['timelineStatus'])) { |
| | | $this->handleTimelineStatusChange($results['timelineStatus']); |
| | | } |
| | | if (!empty($timelineParents)) { |
| | | $this->maybeReorderTimelines($timelineParents); |
| | | if (!empty($results['timelineParents'])) { |
| | | $this->maybeReorderTimelines($results['timelineParents']); |
| | | } |
| | | } catch (Exception $e) { |
| | | $errors[] = $e->getMessage(); |
| | | $results['errors'][] = $e->getMessage(); |
| | | } |
| | | |
| | | |
| | |
| | | $outcome = count($errors) === count($posts) ? 'failed' : 'partial'; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | return new Result( |
| | | outcome: $outcome, |
| | | result: [ |
| | | 'posts' => $results, |
| | | 'errors' => $errors, |
| | | 'updated_count' => count(array_filter($results, fn($r) => $r['success'] ?? false)), |
| | | 'failed_count' => count($errors) |
| | | ] |
| | | result: $results, |
| | | ); |
| | | } |
| | | |
| | | private function savePostFields(int $postId, array $postData): bool |
| | | { |
| | | $content = $postData['content'] ?? ''; |
| | | $fields = jvbGetFields($content); |
| | | $fields = Registrar::getFieldsFor($content); |
| | | |
| | | $allowedFields = array_filter($postData, function ($key) use ($fields) { |
| | | return array_key_exists($key, $fields); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | //Remove values that are already saved |
| | | $check = Meta::forPost($postId)->getAll(array_keys($allowedFields)); |
| | | error_log('Stored values: '.print_r($check, true)); |
| | | $allowedFields = array_filter($allowedFields, function ($key) use ($allowedFields, $check) { |
| | | return $allowedFields[$key] !== $check[$key]; |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | if (empty($allowedFields)) { |
| | | return true; |
| | | } |
| | | |
| | | $meta = Meta::forPost($postId); |
| | | $meta->setAll($allowedFields); |
| | | return true; |
| | | return Meta::forPost($postId) |
| | | ->setAll($allowedFields); |
| | | } |
| | | private function saveTermFields(int $termId, array $data): bool |
| | | { |
| | | $content = $data['content'] ?? ''; |
| | | error_log('Saving term fields: '.print_r($data, true)); |
| | | $fields = Registrar::getFieldsFor($content); |
| | | |
| | | $allowedFields = array_filter($data, function ($key) use ($fields) { |
| | | return array_key_exists($key, $fields); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | |
| | | //Remove values that are already saved |
| | | $check = Meta::forTerm($termId)->getAll(array_keys($allowedFields)); |
| | | error_log('Stored values: '.print_r($check, true)); |
| | | $allowedFields = array_filter($allowedFields, function ($value, $key) use ($check) { |
| | | error_log('Sent value: '.print_r($value, true)); |
| | | error_log('Stored Value: '.print_r($check[$key], true)); |
| | | return $value !== $check[$key]; |
| | | }, ARRAY_FILTER_USE_BOTH); |
| | | |
| | | if (empty($allowedFields)) { |
| | | return true; |
| | | } |
| | | |
| | | error_log('Allowed fields: '.print_r($allowedFields, true)); |
| | | |
| | | return Meta::forTerm($termId) |
| | | ->setAll($allowedFields); |
| | | } |
| | | private function saveUserFields(int $userId, array $data): bool |
| | | { |
| | | $content = $data['content'] ?? ''; |
| | | $fields = Registrar::getFieldsFor($content); |
| | | |
| | | $allowedFields = array_filter($data, function ($key) use ($fields) { |
| | | return array_key_exists($key, $fields); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | //Remove values that are already saved |
| | | $check = Meta::forUser($userId)->getAll(array_keys($allowedFields)); |
| | | $allowedFields = array_filter($allowedFields, function ($key) use ($allowedFields, $check) { |
| | | return $allowedFields[$key] !== $check[$key]; |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | if (empty($allowedFields)) { |
| | | return true; |
| | | } |
| | | |
| | | return Meta::forUser($userId) |
| | | ->setAll($allowedFields); |
| | | } |
| | | |
| | | |
| | | // ───────────────────────────────────────────────────────────── |
| | | // Helpers |
| | |
| | | } |
| | | } |
| | | } |
| | | protected function maybeReorderTimeline(int $parentID):void |
| | | protected function getTimelinePosts(int $parentID):array |
| | | { |
| | | // clean_post_cache($parentID); |
| | | $parent = get_post($parentID); |
| | | if (!$parent) { |
| | | return; |
| | | return []; |
| | | } |
| | | |
| | | $children = get_children([ |
| | |
| | | |
| | | |
| | | if (count($children) === 0) { |
| | | return; |
| | | return []; |
| | | } |
| | | |
| | | $allPosts = array_merge([$parent], $children); |
| | |
| | | return strtotime($a->post_date) <=> strtotime($b->post_date); |
| | | }); |
| | | |
| | | return $allPosts; |
| | | } |
| | | protected function maybeReorderTimeline(int $parentID):void |
| | | { |
| | | // clean_post_cache($parentID); |
| | | $parent = get_post($parentID); |
| | | if (!$parent) { |
| | | return; |
| | | } |
| | | |
| | | $allPosts = $this->getTimelinePosts($parentID); |
| | | if (empty($allPosts)) { |
| | | return; |
| | | } |
| | | |
| | | |
| | | |
| | | // Check if order changed |
| | | $needsReorder = false; |
| | |
| | | foreach ($posts as $index => $post) { |
| | | $meta = Meta::forPost($post->ID); |
| | | if ($index === 0) { |
| | | $meta->set('timeline', '', false); |
| | | $meta->set('timeline', ''); |
| | | $previousPost = $post; |
| | | continue; // Parent has no timeline |
| | | } |
| | |
| | | if ($timeline) { |
| | | $termId = $this->getOrCreateTerm($timeline, 'timeline'); |
| | | if ($termId) { |
| | | $success = $meta->set('timeline', $termId, false); |
| | | $success = $meta->set('timeline', $termId); |
| | | } |
| | | } |
| | | } |
| | |
| | | if ($lastKey === $index) { |
| | | $latestTimestamp = strtotime($post->post_date); |
| | | } |
| | | |
| | | $previousPost = $post; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | foreach ($children as $child) { |
| | | $childMeta = Meta::forPost($child); |
| | | $result = $childMeta->setAll($values, false); |
| | | Meta::forPost($child)->setAll($values); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | protected function handlePost(string|int $ID, array $data, Registrar $registrar, array $results, Progress $progress):array |
| | | { |
| | | // New post creation |
| | | if (str_starts_with((string)$ID, 'new')) { |
| | | |
| | | $newId = wp_insert_post([ |
| | | 'post_author' => $this->userId, |
| | | 'post_type' => $registrar->getBased(), |
| | | 'post_title' => $data['post_title'] ?? apply_filters('jvbDefaultTitle', '', $registrar->getSlug()), |
| | | 'post_status' => $data['status'] ?? 'draft', |
| | | ]); |
| | | error_log('Created new post: '.print_r($newId, true)); |
| | | |
| | | if (!$newId || is_wp_error($newId)) { |
| | | $results['errors'][$ID] = 'Could not create post'; |
| | | $progress->failItem($ID, 'Could not create post'); |
| | | return $results; |
| | | } |
| | | |
| | | $results['newPosts'][$ID] = $newId; |
| | | $this->savePostFields($newId, $data); |
| | | unset($data['content']); |
| | | $results['success'][$newId] = $data; |
| | | $progress->advance(); |
| | | return $results; |
| | | } |
| | | |
| | | //Existing post update |
| | | if (!$this->verifyOwnership((int)$ID)) { |
| | | $progress->failItem($ID, 'No permission to modify this post'); |
| | | $results['errors'][$ID] = 'No permission'; |
| | | return $results; |
| | | } |
| | | |
| | | $result = $this->savePostFields((int)$ID, $data); |
| | | unset($data['content']); |
| | | if ($result) { |
| | | $results['success'][$ID] = $data; |
| | | } else { |
| | | $results['errors'][$ID] = 'Could not update post data'; |
| | | } |
| | | if ($registrar && $registrar->hasFeature('is_timeline')) { |
| | | $post = get_post((int)$ID); |
| | | $parentId = $post->post_parent > 0 ? $post->post_parent : $post->ID; |
| | | $fields = $registrar->getFields(); |
| | | $sharedFields = array_keys(array_filter($fields, function ($field) { |
| | | return !array_key_exists('for_all', $field) || !$field['for_all']; |
| | | })); |
| | | |
| | | |
| | | if (array_key_exists('timeline_gallery', $data)) { |
| | | //This should only happen if we delete an image from the gallery |
| | | $changes = explode(',', $data['timeline_gallery']); |
| | | $timelinePosts = $this->getTimelinePosts($parentId); |
| | | if (!empty($timelinePosts)) { |
| | | $posts = array_map(function($item) { return $item->ID; }, $timelinePosts); |
| | | $changed = false; |
| | | foreach ($posts as $tID) { |
| | | if (!in_array($tID, $changes)) { |
| | | $changed = true; |
| | | wp_delete_post($tID, true); |
| | | } |
| | | } |
| | | if ($changed) { |
| | | $results['timelineParents'][] = $parentId; |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | if (array_key_exists('post_date', $data) && !in_array($parentId, $results['timelineParents'])) { |
| | | $results['timelineParents'][] = $parentId; |
| | | } |
| | | if ($parentId === $ID) { |
| | | if (array_key_exists('post_status', $data) && !array_key_exists($parentId, $results['timelineStatus'])) { |
| | | $results['timelineStatus'][$parentId] = $data['post_status']; |
| | | } |
| | | |
| | | if (count(array_intersect($sharedFields, array_keys($data))) > 0) { |
| | | if (!array_key_exists($parentId, $results['timelineSharedFields'])) { |
| | | $results['timelineSharedFields'][$parentId] = []; |
| | | } |
| | | $temp = array_intersect($sharedFields, array_keys($data)); |
| | | $results['timelineSharedFields'][$parentId] = array_unique(array_merge($results['timelineSharedFields'][$parentId], $temp)); |
| | | } |
| | | } |
| | | } |
| | | $progress->advance(); |
| | | return $results; |
| | | } |
| | | |
| | | |
| | | protected function handleTerm(int $ID, array $data, Registrar $registrar, array $results, Progress $progress):array |
| | | { |
| | | error_log('Handling term '.$ID.' with data: '.print_r($data, true)); |
| | | //Existing term update |
| | | if ($registrar->hasFeature('is_ownable') && (!JVB()->roles()->isOwner($this->userId, $ID) && !JVB()->roles()->isManager($this->userId, $ID))) { |
| | | error_log('Term is ownable. User does not own this term.'); |
| | | $progress->failItem($ID, 'No permission to modify this term'); |
| | | $results['errors'][$ID] = 'No permission'; |
| | | return $results; |
| | | } |
| | | |
| | | $result = $this->saveTermFields($ID, $data); |
| | | unset($data['content']); |
| | | if ($result) { |
| | | $results['success'][$ID] = $data; |
| | | } else { |
| | | $results['errors'][$ID] = 'Could not update term data'; |
| | | } |
| | | $progress->advance(); |
| | | return $results; |
| | | } |
| | | protected function handleUser(int $ID, array $data, Registrar $registrar, array $results, Progress $progress):array |
| | | { |
| | | //Existing term update |
| | | if ($ID !== $this->userId || !user_can($this->userId, 'manage_options')) { |
| | | $progress->failItem($ID, 'No permission to modify this term'); |
| | | $results['errors'][$ID] = 'No permission'; |
| | | return $results; |
| | | } |
| | | |
| | | $result = $this->saveUserFields($ID, $data); |
| | | unset($data['content']); |
| | | if ($result) { |
| | | $results['success'][$ID] = $data; |
| | | } else { |
| | | $results['errors'][$ID] = 'Could not update post data'; |
| | | } |
| | | $progress->advance(); |
| | | return $results; |
| | | } |
| | | } |