From 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 25 Aug 2026 22:57:22 +0000
Subject: [PATCH] =It's been a minute. I went through the Routes and associated Managers and ensured they played together well. Reworked the Cache.php, and I think I caught all the references. Moving away from JVBase() root class, to individuall classes that are Instanced. Double checked for the ABSPATH check in every file, and added index.php to every directory that didn't already have it. Also still working on the MenuBlock to work with WooCommerce for now for Northeh
---
inc/managers/queue/executors/ContentExecutor.php | 1003 +++++++++++++++++++++++++++++-----------------------------
1 files changed, 500 insertions(+), 503 deletions(-)
diff --git a/inc/managers/queue/executors/ContentExecutor.php b/inc/managers/queue/executors/ContentExecutor.php
index dd4c80c..2755eb9 100644
--- a/inc/managers/queue/executors/ContentExecutor.php
+++ b/inc/managers/queue/executors/ContentExecutor.php
@@ -1,9 +1,9 @@
<?php
namespace JVBase\managers\queue\executors;
-use JVBase\managers\queue\{Executor, Operation, Progress, Result};
-use JVBase\meta\MetaManager;
-use JVBase\utility\Features;
+use JVBase\managers\queue\{Executor, Operation, Progress, Result, Storage};
+use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
use Exception;
if (!defined('ABSPATH')) {
@@ -18,34 +18,30 @@
{
private const HANDLED_TYPES = [
'content_update',
- 'batch_creation',
+// 'batch_creation',
];
private int $userId;
private string $postType;
private array $fields = [];
- private array $timelineSharedFields = [];
- private array $timelineUniqueFields = [];
public function execute(Operation $operation, Progress $progress): Result
{
+ $this->userId = $operation->userId;
+
if (!in_array($operation->type, self::HANDLED_TYPES)) {
throw new Exception("ContentExecutor cannot handle type: {$operation->type}");
}
-
- $this->userId = $operation->userId;
+ error_log('Executing ContentExecutor.php');
try {
$data = $operation->requestData;
- $result = match($operation->type) {
+ return match($operation->type) {
'content_update' => $this->processContentUpdate($operation, $data, $progress),
- 'batch_creation' => $this->processBatchCreation($operation, $data, $progress),
default => throw new Exception("Unknown type: {$operation->type}")
};
- return $result;
-
} catch (Exception $e) {
JVB()->error()->log(
'[ContentExecutor]:execute',
@@ -71,135 +67,71 @@
private function processContentUpdate(Operation $operation, array $data, Progress $progress): Result
{
$posts = $data['posts'] ?? [];
-
+ error_log('Processing Content Update: '.print_r($posts, true));
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 = [];
- $updateTimelineOrder = [];
-
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)
- ];
-
- if (Features::forContent($content)->has('is_timeline')) {
- $this->updateTimelineLatestDate($newId);
- }
-
- $progress->advance(1);
- 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;
- }
- // Check if this is a timeline post
- $isTimeline = Features::forContent($content)->has('is_timeline');
-
- if ($isTimeline) {
- $post = get_post((int)$id);
- $parentId = $post->post_parent;
- $isParent = ($parentId === 0);
-
- // Track timeline reordering only if date changed
- if (array_key_exists('post_date', $postData)) {
- $timelineRoot = $isParent ? (int)$id : $parentId;
- if (!in_array($timelineRoot, $updateTimelineOrder)) {
- $updateTimelineOrder[] = $timelineRoot;
- }
- }
-
- // Update shared fields if this is the parent
- if ($isParent) {
- $this->initTimelineFields($content);
- $sharedFieldsUpdated = array_filter($postData, function($key) {
- return in_array($key, $this->timelineSharedFields);
- }, ARRAY_FILTER_USE_KEY);
-
- if (!empty($sharedFieldsUpdated)) {
- $this->updateSharedFields((int)$id, $sharedFieldsUpdated);
- }
- }
- }
- $this->processPostUpdate((int)$id, $postData);
-
- if (Features::forContent($content)->has('is_timeline')
- && array_key_exists('post_date', $postData)) {
- $post = get_post((int)$id);
- $parentId = $post->post_parent === 0 ? (int)$id : $post->post_parent;
- $this->updateTimelineLatestDate($parentId);
- }
-
- $results[$id] = [
- 'success' => true,
- 'processed_fields' => array_keys($postData)
- ];
- $progress->advance(1);
-
} catch (Exception $e) {
$progress->failItem($id, $e->getMessage());
- $errors[$id] = $e->getMessage();
- $results[$id] = [
- 'success' => false,
- 'error' => $e->getMessage()
- ];
+ $results['errors'][$id] = $e->getMessage();
}
}
- if (!empty($updateTimelineOrder)) {
- $processedParents = []; // Track to avoid duplicate processing
+ error_log('Final Results: '.print_r($results, true));
- foreach ($updateTimelineOrder as $oldParentID) {
- if (in_array($oldParentID, $processedParents)) continue;
-
- $actualParentId = $this->reorderTimelineByDate($oldParentID);
- $processedParents[] = $actualParentId;
-
- // If parent changed, mark the new parent as processed too
- if ($actualParentId !== $oldParentID) {
- $processedParents[] = $oldParentID;
- }
+ try {
+ if (!empty($results['timelineSharedFields'])) {
+ $this->checkSharedFields($results['timelineSharedFields']);
}
+ if (!empty($results['timelineStatus'])) {
+ $this->handleTimelineStatusChange($results['timelineStatus']);
+ }
+ if (!empty($results['timelineParents'])) {
+ $this->maybeReorderTimelines($results['timelineParents']);
+ }
+ } catch (Exception $e) {
+ $results['errors'][] = $e->getMessage();
}
+
// Send notification
- if (jvbSiteHasNotifications()) {
- JVB()->notification()->addNotification(
- $this->userId,
- 'content_update_complete',
- null,
- 'Content updates completed!'
- );
- }
+// if (jvbSiteHasNotifications()) {
+// NotificationManager::getInstance()->addNotification(
+// $this->userId,
+// 'content_update_complete',
+// null,
+// 'Content updates completed!'
+// );
+// }
$outcome = 'success';
if (!empty($errors)) {
@@ -208,431 +140,496 @@
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 processPostUpdate(int $postId, array $postData): void
- {
- $content = $postData['content'] ?? '';
-
- // Handle status changes
- if (isset($postData['post_status'])) {
- switch ($postData['post_status']) {
- case 'publish':
- if (user_can($this->userId, 'manage_options') || user_can($this->userId, 'skip_moderation')) {
- wp_update_post(['ID' => $postId, 'post_status' => 'publish']);
- }
- unset($postData['post_status']);
- break;
- case 'draft':
- wp_update_post(['ID' => $postId, 'post_status' => 'draft']);
- unset($postData['post_status']);
- break;
- case 'trash':
- wp_trash_post($postId);
- return;
- case 'delete':
- wp_delete_post($postId, true);
- return;
- }
- }
-
- // Save all fields via MetaManager (handles post fields too)
- $this->savePostFields($postId, $postData);
- }
-
- private function updateSharedFields(int $parentId, array $sharedFields): void
- {
- // Get all posts in timeline
- $children = get_posts([
- 'post_type' => get_post_type($parentId),
- 'post_parent' => $parentId,
- 'post_status' => ['publish', 'draft'],
- 'posts_per_page' => -1,
- 'fields' => 'ids'
- ]);
-
- $allPostIds = array_merge([$parentId], $children);
-
- // Apply shared fields to all posts
- foreach ($allPostIds as $timelinePostId) {
- $meta = new MetaManager($timelinePostId, 'post');
- $meta->setAll($sharedFields);
- }
- }
-
- private function reorderTimelineByDate(int $parentId): int
- {
- $parent = get_post($parentId);
- if (!$parent) return $parentId;
-
- // Get all posts in this timeline (parent + children)
- $children = get_posts([
- 'post_type' => get_post_type($parentId),
- 'post_parent' => $parentId,
- 'post_status' => ['publish', 'draft'],
- 'posts_per_page' => -1,
- 'orderby' => 'date',
- 'order' => 'ASC'
- ]);
-
- // Combine and sort by post_date
- $allPosts = array_merge([$parent], $children);
- usort($allPosts, function($a, $b) {
- return strtotime($a->post_date) <=> strtotime($b->post_date);
- });
-
- $newParent = $allPosts[0];
- $actualParentId = $newParent->ID; // Track the actual parent
-
- // If parent changed, restructure
- if ($newParent->ID !== $parentId) {
- wp_update_post([
- 'ID' => $newParent->ID,
- 'post_parent' => 0,
- 'menu_order' => 0
- ]);
-
- wp_update_post([
- 'ID' => $parentId,
- 'post_parent' => $newParent->ID
- ]);
-
- foreach ($allPosts as $index => $post) {
- if ($index === 0) continue;
-
- wp_update_post([
- 'ID' => $post->ID,
- 'post_parent' => $newParent->ID,
- 'menu_order' => $index
- ]);
-
- $this->getOrCreateTerm($post->ID, (string)$index, 'number');
- }
- } else {
- // Just update menu_order
- foreach ($allPosts as $index => $post) {
- if ($index === 0) continue;
-
- wp_update_post([
- 'ID' => $post->ID,
- 'menu_order' => $index
- ]);
-
- $this->getOrCreateTerm($post->ID, (string)$index, 'number');
- }
- }
-
- // Calculate and set timeline taxonomy (time since previous post)
- $previousPost = null;
- foreach ($allPosts as $index => $post) {
- if ($index === 0) {
- // Parent post - no timeline term (it's the baseline)
- wp_set_object_terms($post->ID, [], BASE . 'timeline', false);
- $previousPost = $post;
- continue;
- }
-
- $timelineTerm = $this->calculateTimelineTerm($previousPost, $post);
- if ($timelineTerm) {
- $this->getOrCreateTerm($post->ID, $timelineTerm, 'timeline');
- }
-
- $previousPost = $post;
- }
-
- // Update latest_date AFTER reordering with the actual parent
- $this->updateTimelineLatestDate($actualParentId);
-
- return $actualParentId; // Return the actual parent ID
- }
-
- private function updateTimelineLatestDate(int $parentId): void
- {
- $parent = get_post($parentId);
- if (!$parent) return;
-
- // Get all posts in timeline
- $children = get_posts([
- 'post_type' => get_post_type($parentId),
- 'post_parent' => $parentId,
- 'post_status' => ['publish', 'draft'],
- 'posts_per_page' => -1,
- 'orderby' => 'date',
- 'order' => 'DESC', // Get newest first
- 'fields' => 'ids'
- ]);
-
- // Count: parent + children
- $number = count($children) + 1;
-
- $allPostIds = array_merge([$parentId], $children);
-
- // Get all timestamps
- $timestamps = array_map(function($id) {
- $post = get_post($id);
- return $post ? strtotime($post->post_date) : 0;
- }, $allPostIds);
-
- $latestTimestamp = max($timestamps);
-
- // Update both meta fields
- update_post_meta($parentId, BASE . 'number', $number);
- update_post_meta($parentId, BASE . 'latest_date', $latestTimestamp);
- }
-
- private function calculateTimelineTerm(\WP_Post $previousPost, \WP_Post $currentPost): ?string
- {
- $previousDate = strtotime($previousPost->post_date);
- $currentDate = strtotime($currentPost->post_date);
-
- if (!$previousDate || !$currentDate || $currentDate <= $previousDate) {
- return null;
- }
-
- // Calculate difference in days
- $daysDiff = floor(($currentDate - $previousDate) / (60 * 60 * 24));
-
- // Convert to weeks
- $weeks = floor($daysDiff / 7);
-
- // If less than 16 weeks, use weeks
- if ($weeks < 16) {
- if ($weeks === 0) {
- return null; // Same week, no term
- }
- return $weeks === 1 ? '1 Week' : $weeks . ' Weeks';
- }
-
- // 16+ weeks, calculate months
- // Using actual month calculation rather than weeks/4
- $previousDateTime = new \DateTime($previousPost->post_date);
- $currentDateTime = new \DateTime($currentPost->post_date);
- $interval = $previousDateTime->diff($currentDateTime);
-
- $months = ($interval->y * 12) + $interval->m;
-
- if ($months === 0) {
- // Edge case: technically less than a full month but 16+ weeks
- return $weeks . ' Weeks';
- }
-
- return $months === 1 ? '1 Month' : $months . ' Months';
- }
-
- private function getOrCreateTerm(int $postID, string $termName, string $taxonomy): void
- {
- $taxonomy = jvbCheckBase($taxonomy);
- $term = get_term_by('name', $termName, $taxonomy);
-
- if (!$term) {
- $result = wp_insert_term($termName, $taxonomy);
- if (is_wp_error($result)) {
- return;
- }
- $termID = $result['term_id'];
- } else {
- $termID = $term->term_id;
- }
-
- if ($termID) {
- wp_set_object_terms($postID, [$termID], $taxonomy, false);
- }
- }
-
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 = new MetaManager($postId, 'post');
- return $meta->setAll($allowedFields);
+ return Meta::forPost($postId)
+ ->setAll($allowedFields);
}
-
- // ─────────────────────────────────────────────────────────────
- // Batch Creation
- // ─────────────────────────────────────────────────────────────
-
- private function processBatchCreation(Operation $operation, array $data, Progress $progress): Result
+ private function saveTermFields(int $termId, array $data): bool
{
- $this->postType = BASE . $data['content'];
+ $content = $data['content'] ?? '';
+ error_log('Saving term fields: '.print_r($data, true));
+ $fields = Registrar::getFieldsFor($content);
- // Get upload results from dependency
- $uploadOpId = $operation->id . '_upload';
- $images = JVB()->queue()->get($uploadOpId)?->result ?? null;
+ $allowedFields = array_filter($data, function ($key) use ($fields) {
+ return array_key_exists($key, $fields);
+ }, ARRAY_FILTER_USE_KEY);
- if (!$images) {
- return new Result(
- outcome: 'failed',
- result: ['message' => 'No upload results found']
- );
+
+ //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;
}
- $results = [];
+ error_log('Allowed fields: '.print_r($allowedFields, true));
- if ($data['mode'] === 'selection') {
- $results = $this->createFromSelection($operation, $data, $images, $progress);
- } else {
- $results = $this->createFromDirect($operation, $data, $images, $progress);
- }
-
- return new Result(
- outcome: !empty($results) ? 'success' : 'failed',
- result: $results
- );
+ return Meta::forTerm($termId)
+ ->setAll($allowedFields);
}
-
- private function createFromSelection(Operation $operation, array $data, array $images, Progress $progress): array
+ private function saveUserFields(int $userId, array $data): bool
{
- $results = [];
+ $content = $data['content'] ?? '';
+ $fields = Registrar::getFieldsFor($content);
- foreach ($images as $group => $files) {
- $settings = json_decode($data['files_data'][$group] ?? '{}');
+ $allowedFields = array_filter($data, function ($key) use ($fields) {
+ return array_key_exists($key, $fields);
+ }, ARRAY_FILTER_USE_KEY);
- if (($settings->type ?? '') === 'group') {
- $postId = $this->createGroupPost($operation, $data, $files, $settings);
- } else {
- $postId = $this->createIndividualPosts($operation, $data, $files);
- }
+ //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 ($postId) {
- $results = array_merge($results, (array)$postId);
- }
-
- $progress->advance(1);
+ if (empty($allowedFields)) {
+ return true;
}
- return $results;
+ return Meta::forUser($userId)
+ ->setAll($allowedFields);
}
- private function createFromDirect(Operation $operation, array $data, array $images, Progress $progress): array
- {
- $results = [];
-
- foreach ($images as $img) {
- $postId = wp_insert_post([
- 'post_type' => $this->postType,
- 'post_title' => $this->generatePostTitle($data['content']),
- 'post_status' => 'draft',
- 'post_author' => $operation->userId,
- ]);
-
- if ($postId && !is_wp_error($postId)) {
- set_post_thumbnail($postId, $img['attachment_id']);
- $results[] = $postId;
- }
-
- $progress->advance(1);
- }
-
- return $results;
- }
-
- private function createGroupPost(Operation $operation, array $data, array $files, object $settings): ?int
- {
- $featuredIndex = $settings->metadata->featuredFile ?? 0;
- $title = $settings->metadata->title ?? $this->generatePostTitle($data['content']);
-
- $postId = wp_insert_post([
- 'post_type' => $this->postType,
- 'post_title' => $title,
- 'post_status' => 'draft',
- 'post_author' => $operation->userId,
- ]);
-
- if (!$postId || is_wp_error($postId)) {
- return null;
- }
-
- // Set featured image
- set_post_thumbnail($postId, $files[$featuredIndex]['attachment_id']);
-
- // Remaining files go to gallery
- unset($files[$featuredIndex]);
- if (!empty($files)) {
- $meta = new MetaManager($postId, 'post');
- $ids = array_column($files, 'attachment_id');
- $meta->updateValue('gallery', implode(',', $ids));
- }
-
- return $postId;
- }
-
- private function createIndividualPosts(Operation $operation, array $data, array $files): array
- {
- $results = [];
-
- foreach ($files as $img) {
- $title = $this->generatePostTitle($data['content']);
- $postId = wp_insert_post([
- 'post_type' => $this->postType,
- 'post_title' => $title,
- 'post_slug' => sanitize_title($title),
- 'post_status' => 'draft',
- 'post_author' => $operation->userId,
- ]);
-
- if ($postId && !is_wp_error($postId)) {
- set_post_thumbnail($postId, $img['attachment_id']);
- $results[] = $postId;
- }
- }
-
- return $results;
- }
// ─────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────
- private function initTimelineFields(string $content): void
- {
- $content = jvbNoBase($content);
- if (!Features::forContent($content)->has('is_timeline')) {
- return;
- }
-
- $config = Features::getConfig($content);
- $this->fields = $config['fields'] ?? [];
-
- // Shared fields (apply to all posts)
- $this->timelineSharedFields = array_keys(array_filter($this->fields, function ($field) {
- return !isset($field['for_all']) || $field['for_all'] === false;
- }));
- array_unshift($this->timelineSharedFields, 'post_thumbnail', 'post_title', 'post_status');
-
- // Unique fields (per-entry)
- $this->timelineUniqueFields = array_keys(array_filter($this->fields, function ($field) {
- return isset($field['for_all']) && $field['for_all'] === true;
- }));
- }
-
private function verifyOwnership(int $postId): bool
{
$post = get_post($postId);
return $post && (int)$post->post_author === $this->userId;
}
- private function generatePostTitle(string $content): string
+ /*************************************************************
+ * TIMELINE HELPERS
+ *************************************************************/
+ protected function maybeReorderTimelines(array $parentIDs):void {
+ foreach ($parentIDs as $parentId) {
+ try {
+ $this->maybeReorderTimeline((int)$parentId);
+ } catch (Exception $e) {
+ error_log("Timeline reorder failed for parent {$parentId}: " . $e->getMessage());
+ }
+ }
+ }
+ protected function getTimelinePosts(int $parentID):array
{
- $username = get_user_meta($this->userId, 'first_name', true);
- $link = get_user_meta($this->userId, BASE . 'link', true);
- $city = function_exists('jvbArtistCity') ? jvbArtistCity($link) : '';
+ $parent = get_post($parentID);
+ if (!$parent) {
+ return [];
+ }
- return ucfirst($content) . ' by ' . ($city ? "$city artist " : '') . $username;
+ $children = jvbTimelinePoints($parentID, $parent->post_type, ['publish', 'draft']);
+
+ if (count($children) === 0) {
+ return [];
+ }
+
+ $allPosts = array_merge([$parent], $children);
+
+ // Sort by post_date
+ usort($allPosts, function($a, $b) {
+ 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 ($allPosts as $index => $post) {
+ if ($index === 0 && $post->ID !== $parent->ID) {
+ $needsReorder = true;
+ break;
+ }
+ if ($index > 0 && (int)$post->menu_order !== $index) {
+ $needsReorder = true;
+ break;
+ }
+ }
+
+ if (!$needsReorder) {
+ // Just recalculate timelines without reordering
+ $this->recalculateTimelines($allPosts);
+ return;
+ }
+
+ // Handle parent swap if needed
+ $newParent = $allPosts[0];
+ if ($newParent->ID !== $parent->ID) {
+ $this->swapTimelineParent($parent, $newParent, $allPosts);
+ } else {
+ // Just update menu orders and timelines
+ foreach ($allPosts as $index => $post) {
+ if ($index === 0) continue; // Skip parent
+
+ $success = jvb_update_post([
+ 'ID' => $post->ID,
+ 'post_parent' => $newParent->ID,
+ 'menu_order' => $index
+ ]);
+ }
+
+ $this->recalculateTimelines($allPosts);
+ }
+ }
+
+ private function recalculateTimelines(array $posts): void
+ {
+ $previousPost = null;
+ $latestTimestamp = 0;
+
+
+ $lastKey = array_key_last($posts);
+ foreach ($posts as $index => $post) {
+ $meta = Meta::forPost($post->ID);
+ if ($index === 0) {
+ $meta->set('timeline', '');
+ $previousPost = $post;
+ continue; // Parent has no timeline
+ }
+
+ // Calculate timeline from previous post
+ if ($previousPost) {
+ $timeline = $this->calculateTimeline($previousPost, $post);
+ if ($timeline) {
+ $termId = $this->getOrCreateTerm($timeline, 'timeline');
+ if ($termId) {
+ $success = $meta->set('timeline', $termId);
+ }
+ }
+ }
+ $isUpdate = $meta->get('is_update');
+ if (!(bool) $isUpdate) {
+ $meta->set('number', $index);
+ }
+
+ if ($lastKey === $index) {
+ $latestTimestamp = strtotime($post->post_date);
+ }
+ $previousPost = $post;
+ }
+
+ // Update parent's latest_date
+ if ($latestTimestamp > 0) {
+ $success = update_post_meta($posts[0]->ID, BASE . 'latest_date', $latestTimestamp);
+ }
+ }
+
+ private function calculateTimeline(\WP_POST $previous, \WP_POST $current): ?string
+ {
+ $previousDate = strtotime($previous->post_date);
+ $currentDate = strtotime($current->post_date);
+
+ if (!$previousDate || !$currentDate || $currentDate <= $previousDate) {
+ return null;
+ }
+
+ $daysDiff = floor(($currentDate - $previousDate) / (60*60*24));
+ $weeks = floor($daysDiff / 7);
+ if ($weeks === 0) {
+ return 'Less than 1 Week';
+ }
+ if ($weeks < 16) {
+ return $weeks === 1 ? '1 Week' : $weeks . ' Weeks';
+ }
+
+ $previousDateTime = new \DateTime($previous->post_date);
+ $currentDateTime = new \DateTime($current->post_date);
+
+ $interval = $previousDateTime->diff($currentDateTime);
+ $months = ($interval->y * 12) + $interval->m;
+
+ if ($months === 0) {
+ return $weeks . ' Weeks';
+ }
+
+ return ($months === 1) ? '1 Month' : $months . ' Months';
+ }
+
+ private function swapTimelineParent(\WP_Post $oldParent, \WP_Post $newParent, array $allPosts): void
+ {
+ // Swap titles and content
+ $originalTitle = $oldParent->post_title;
+ $originalSlug = $oldParent->post_name;
+ $originalContent = $oldParent->post_content;
+
+ $updateParent = jvb_update_post([
+ 'ID' => $oldParent->ID,
+ 'post_title' => 'Treatment',
+ 'post_name' => sanitize_title('Treatment ' . $newParent->ID),
+ 'post_content' => '',
+ ]);
+
+ $updateNewParent = jvb_update_post([
+ 'ID' => $newParent->ID,
+ 'post_title' => $originalTitle,
+ 'post_name' => $originalSlug,
+ 'post_content' => $originalContent,
+ 'post_parent' => 0,
+ 'menu_order' => 0
+ ]);
+
+ // Clear timeline taxonomy from new parent
+ wp_set_object_terms($newParent->ID, [], BASE . 'timeline', false);
+
+ // Update all other posts to new parent
+ foreach ($allPosts as $index => $post) {
+ if ($index === 0) continue; // Skip new parent
+
+ $title = $post->post_title;
+ if (str_starts_with($title, 'Treatment #')) {
+ $title = 'Treatment #' . $index;
+ }
+
+ $childUpdate = jvb_update_post([
+ 'ID' => $post->ID,
+ 'post_title' => $title,
+ 'post_parent' => $newParent->ID,
+ 'menu_order' => $index,
+ ]);
+ }
+
+ // Recalculate timelines for all posts
+ $this->recalculateTimelines($allPosts);
+ }
+
+ private function getOrCreateTerm(string $termName, string $taxonomy): ?int
+ {
+ $taxonomy = jvbCheckBase($taxonomy);
+ $term = get_term_by('name', $termName, $taxonomy);
+
+ if (!$term) {
+ $result = wp_insert_term($termName, $taxonomy);
+ if (is_wp_error($result)) {
+ return null;
+ }
+ return $result['term_id'];
+ }
+
+ return $term->term_id;
+ }
+
+ protected function checkSharedFields(array $fields): void
+ {
+ foreach ($fields as $parentID => $shared) {
+ $meta = Meta::forPost($parentID);
+ $values = $meta->getAll($shared);
+
+ $children = jvbTimelinePoints($parentID, get_post_type($parentID), ['any']);
+
+ if (empty($children)) {
+ continue;
+ }
+
+ foreach ($children as $child) {
+ Meta::forPost($child)->setAll($values);
+ }
+ }
+ }
+
+ protected function handleTimelineStatusChange(array $updates):void
+ {
+ $updates = array_filter($updates, function ($status) {
+ return in_array($status, ['trash', 'delete', 'publish', 'draft']);
+ });
+
+ foreach ($updates as $parentID => $status) {
+ $children = jvbTimelinePoints($parentID, get_post_type($parentID), ['any']);
+
+ if (!empty($children)) {
+ foreach($children as $child) {
+ if ($status === 'trash') {
+ wp_trash_post($child);
+ } elseif ($status === 'delete') {
+ wp_delete_post($child, true);
+ }else {
+ jvb_update_post([
+ 'ID' => $child,
+ 'post_status' => $status
+ ]);
+ }
+
+ }
+ }
+ }
+ }
+
+ 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') && (!Roles::getInstance()()->isOwner($this->userId, $ID) && !Roles::getInstance()()->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;
}
}
--
Gitblit v1.10.0