From 42fa8304ddb811b0f725f245130f70c0f5e86a6c Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 04 Nov 2025 06:12:02 +0000
Subject: [PATCH] =Refactored LoginManager to be more extensible and configurable, as well as an AjaxRateLimiter
---
inc/rest/routes/ContentRoutes.php | 197 ++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 170 insertions(+), 27 deletions(-)
diff --git a/inc/rest/routes/ContentRoutes.php b/inc/rest/routes/ContentRoutes.php
index 6916093..ff056a9 100644
--- a/inc/rest/routes/ContentRoutes.php
+++ b/inc/rest/routes/ContentRoutes.php
@@ -5,6 +5,8 @@
use JVBase\rest\RestRouteManager;
use JVBase\managers\CacheManager;
use JVBase\meta\MetaManager;
+use JVBase\utility\Features;
+use WP_Post;
use WP_Query;
use WP_Error;
use WP_REST_Request;
@@ -185,7 +187,7 @@
}
$post_type = str_replace('-', '_',jvbCheckBase($params['content']));
- $config = (array_key_exists($params['content'], JVB_CONTENT) && !empty(JVB_CONTENT[$params['content']])) ? JVB_CONTENT[$params['content']] : [];
+ $config = Features::getConfig($params['content']);
@@ -199,9 +201,13 @@
'author' => $user_id,
'post_status' => $post_status
];
+ //Only top level posts for timeline types
+ if (Features::forContent($post_type)->has('is_timeline')) {
+ $args['post_parent'] = 0;
+ }
//Calendar filters
- if (jvbCheck('is_calendar', $config)) {
+ if (Features::forContent($post_type)->has('is_calendar')) {
$args = $this->applyCalendarFilters($args, $params);
}
if (array_key_exists('taxonomies', $params)) {
@@ -218,27 +224,26 @@
$args['s'] = sanitize_text_field($params['search']);
}
+
+
error_log('Content Routes final args: '.print_r($args, true));
$key = $this->cache->generateKey($args);
- $lastModified = $this->cache->getTimestamp($key);
- if ($lastModified !== false) {
- $headerCheck = $this->ifModifiedSince($lastModified, $args, $request);
- if (!is_null($headerCheck)) {
- return $headerCheck;
- }
- } else {
- // No timestamp yet, but we can still set ETag
- $etag = '"' . md5(serialize($args)) . '"';
- header('ETag: ' . $etag);
- header('Cache-Control: private, max-age=30');
+ // 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,
+ ]);
+ if ($cache_check) {
+ return $cache_check;
}
$cache = $this->cache->get($key);
$cache = false;
if ($cache) {
- return new WP_REST_Response($cache);
+ $response = new WP_REST_Response($cache);
+ return $this->addCacheHeaders($response);
}
// Run query
@@ -260,7 +265,8 @@
$this->cache->set($key, $data);
- return new WP_REST_Response($data);
+ $response = new WP_REST_Response($data);
+ return $this->addCacheHeaders($response);
}
/**
@@ -306,6 +312,10 @@
$results = [];
foreach ($posts as $ID => $post_data) {
+ if (Features::forContent($post_data['content'])->has('is_timeline')) {
+ $results[$ID] =$this->processTimelinePost($ID, $post_data);
+ continue;
+ }
if (str_starts_with($ID, 'new')) {
error_log('New post detected. Creating... with: '.print_r([
@@ -411,6 +421,97 @@
];
}
+ /**
+ * Extracts the postdata for timeline post child posts from the pseudo-repeater element
+ * @param int $parent_id
+ * @param array $post_data
+ * @return array|true[]
+ */
+ protected function processTimelinePost(int $parent_id, array $post_data):array
+ {
+ if (!$this->verifyOwnership($parent_id)) {
+ return ['success' => false, 'message' => 'No permission'];
+ }
+
+ $rows = $post_data['fields'] ?? [];
+ if (empty($rows)) {
+ return ['success' => false, 'message' => 'No data'];
+ }
+
+ $fields = jvbGetFields($post_data['content']);
+
+ // First row = parent post
+ $parent_row = array_shift($rows);
+ if (($parent_row['id'] ?? null) != $parent_id) {
+ return ['success' => false, 'message' => 'Parent ID mismatch'];
+ }
+
+ $allowedFields = array_filter($parent_row, function($key) use ($fields) {
+ return array_key_exists($key, $fields);
+ }, ARRAY_FILTER_USE_KEY);
+
+ $parentMeta = new MetaManager($parent_id, 'post');
+ $parentMeta->setAll($allowedFields);
+
+ // Get existing children to track deletions
+ $existing_children = get_children([
+ 'post_parent' => $parent_id,
+ 'post_type' => jvbCheckBase($post_data['content']),
+ 'fields' => 'ids'
+ ]);
+
+ $processed_ids = [];
+
+ // Process remaining rows as children
+ foreach ($rows as $index => $row_data) {
+ $row_id = $row_data['id'] ?? null;
+
+ // New child post
+ if (!$row_id || str_starts_with($row_id, 'new')) {
+ $child_id = wp_insert_post([
+ 'post_type' => jvbCheckBase($post_data['content']),
+ 'post_parent' => $parent_id,
+ 'post_author' => $this->user_id,
+ 'post_status' => $post_data['status'] ?? 'draft',
+ 'menu_order' => $index
+ ]);
+ }
+ // Existing child post
+ else {
+ $child_id = (int) $row_id;
+
+ // Verify ownership via parent
+ if (!in_array($child_id, $existing_children)) {
+ continue; // Skip if not actually a child of this parent
+ }
+
+ // Update menu_order (position may have changed)
+ wp_update_post([
+ 'ID' => $child_id,
+ 'menu_order' => $index
+ ]);
+ }
+
+ // Update child meta
+ $allowedChildFields = array_filter($row_data, function($key) use ($fields) {
+ return array_key_exists($key, $fields) && $key !== 'id' && $key !== 'draggable';
+ }, ARRAY_FILTER_USE_KEY);
+
+ $childMeta = new MetaManager($child_id, 'post');
+ $childMeta->setAll($allowedChildFields);
+
+ $processed_ids[] = $child_id;
+ }
+
+ // Delete removed children
+ $deleted_ids = array_diff($existing_children, $processed_ids);
+ foreach ($deleted_ids as $delete_id) {
+ wp_delete_post($delete_id, true);
+ }
+
+ return ['success' => true, 'processed' => $processed_ids];
+ }
+
/**
* Handle batch content creation from uploads
* @param WP_REST_Request $request
@@ -485,12 +586,15 @@
}
/**
- * @param object $post the wordpress post object
+ * @param WP_Post $post the wordpress post object
*
* @return array
*/
- protected function prepareItem(object $post):array
+ protected function prepareItem(WP_Post $post, $skip = false):array
{
+ if (!$skip && Features::forContent($post->post_type)->has('is_timeline')) {
+ return $this->formatTimeline($post);
+ }
$this->meta = new MetaManager($post->ID, 'post');
$data = [
'id' => $post->ID,
@@ -520,15 +624,25 @@
];
}
+ $images = $this->extractImages();
- //Extract images
+
+ if (!empty($images)) {
+ $data['images'] = $images;
+ }
+
+ return $data;
+ }
+ protected function extractImages():array
+ {
+ //Extract images
$images = [];
$get = [];
- foreach ($this->fields as $field => $config) {
- if ($config['type'] === 'gallery' || $config['type'] === 'image' || $field === 'post_thumbnail') {
+ foreach ($this->fields as $field => $config) {
+ if ($config['type'] === 'gallery' || $config['type'] === 'image' || $field === 'post_thumbnail') {
$get[] = $field;
- }
- }
+ }
+ }
if (!empty($get)) {
$allImages = $this->meta->getAll($get);
@@ -541,13 +655,42 @@
}
}
}
+ return $images;
+ }
- if (!empty($images)) {
- $data['images'] = $images;
- }
+ protected function formatTimeline(WP_Post $post):array
+ {
+ $data = $this->prepareItem($post, true);
+ $firstRow = $data['fields'];
+ $firstRow['id'] = $post->ID;
+ $firstRow['draggable'] = false;
+ $fields = [$firstRow];
- return $data;
- }
+ $children = get_children(['post_parent' => $post->ID, 'orderby' => 'menu_order']);
+ $allImages = [];
+
+ foreach ($children as $child) {
+ $this->meta = new MetaManager($child->ID, 'post');
+ $row = $this->meta->getAll(); // Store in variable first
+ $row['id'] = $child->ID; // Add ID to the row
+ $row['draggable'] = true; // Mark as draggable
+ $fields[] = $row; // Then append to fields
+
+ $images = $this->extractImages();
+ if (!empty($images)) {
+ $allImages = $allImages + $images;
+ }
+ }
+
+ if (!empty($allImages)) {
+ if (!array_key_exists('images', $data)) {
+ $data['images'] = [];
+ }
+ $data['images'] = $data['images'] + $allImages;
+ }
+ $data['fields']['timeline'] = $fields;
+ return $data;
+ }
/**
* Builds the taxonomy query
--
Gitblit v1.10.0