Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/kirki/app/Constants/Form/FormActionTypes.php
+++ b/kirki/app/Constants/Form/FormActionTypes.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace KirkiAppConstantsForm;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkConcernsHasConstants;
+
+/**
+ * Supported form-submission action types.
+ *
+ * These values mirror the `type` key stored on each entry of a form's
+ * `actions` configuration.
+ */
+class FormActionTypes
+{
+ use HasConstants;
+
+ const EMAIL = 'email';
+ const WEBHOOK = 'webhooks';
+}
--- a/kirki/app/Constants/Form/FormEmailBodyPartTypes.php
+++ b/kirki/app/Constants/Form/FormEmailBodyPartTypes.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace KirkiAppConstantsForm;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkConcernsHasConstants;
+
+/**
+ * Supported part types for a composed email action body.
+ *
+ * These values mirror the `type` key stored on each entry of an email
+ * action's `body` configuration.
+ */
+class FormEmailBodyPartTypes
+{
+ use HasConstants;
+
+ const TEXT = 'text';
+ const FORM = 'form';
+}
--- a/kirki/app/Constants/Form/FormFieldTypes.php
+++ b/kirki/app/Constants/Form/FormFieldTypes.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace KirkiAppConstantsForm;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkConcernsHasConstants;
+
+/**
+ * Supported input types for a submittable form field.
+ *
+ * These values mirror the `type` key stored on each entry of a form's
+ * `fields` configuration.
+ */
+class FormFieldTypes
+{
+ use HasConstants;
+
+ const EMAIL = 'email';
+ const NUMBER = 'number';
+ const TEL = 'tel';
+ const DATE = 'date';
+ const DATETIME_LOCAL = 'datetime-local';
+ const FILE = 'file';
+}
--- a/kirki/app/Constants/Form/FormWebhookMethods.php
+++ b/kirki/app/Constants/Form/FormWebhookMethods.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace KirkiAppConstantsForm;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkConcernsHasConstants;
+
+/**
+ * Supported HTTP methods for a webhook action.
+ *
+ * These values mirror the `method` key stored on a form's `webhooks` action
+ * configuration.
+ */
+class FormWebhookMethods
+{
+ use HasConstants;
+
+ const GET = 'get';
+ const POST = 'post';
+}
--- a/kirki/app/Constants/OptionKeys.php
+++ b/kirki/app/Constants/OptionKeys.php
@@ -14,4 +14,5 @@
const GLOBAL_DATA_POST_TYPE_ID = 'KIRKI_GLOBAL_DATA_POST_TYPE_ID';
const DROIP_GLOBAL_DATA_POST_TYPE_ID = 'DROIP_GLOBAL_DATA_POST_TYPE_ID'; // @todo: will be removed if not needed
const PAGE_ON_FRONT = 'page_on_front';
+ const WP_ADMIN_COMMON_DATA = 'kirki_wp_admin_common_data';
}
--- a/kirki/app/Constants/PageMetaKeys.php
+++ b/kirki/app/Constants/PageMetaKeys.php
@@ -59,6 +59,10 @@
const UTILITY_PAGE_TYPE = 'kirki_utility_page_type';
+ const CONTENT_MANAGER_COLLECTION_ID = 'kirki_content_manager_collection_id';
+
+ const CONTENT_MANAGER_PAGE_KIND = 'kirki_content_manager_page_kind';
+
const PAGE_TEMPLATE = '_wp_page_template';
/**
@@ -80,6 +84,8 @@
static::TEMPLATE_CONDITIONS,
static::TEMPLATE_COLLECTION_TYPE,
static::UTILITY_PAGE_TYPE,
+ static::CONTENT_MANAGER_COLLECTION_ID,
+ static::CONTENT_MANAGER_PAGE_KIND,
static::BLOCKS,
static::STYLE_BLOCKS,
static::USED_FONT_LIST,
--- a/kirki/app/Constants/UserMetaKeys.php
+++ b/kirki/app/Constants/UserMetaKeys.php
@@ -11,4 +11,9 @@
use HasConstants;
const CAPABILITIES = 'wp_capabilities';
+
+ /**
+ * @see KIRKI_USER_WALKTHROUGH_SHOWN_META_KEY
+ */
+ const WALKTHROUGH_SHOWN_STATE = 'user_walkthrough_shown_state';
}
--- a/kirki/app/Contracts/FormActionHandler.php
+++ b/kirki/app/Contracts/FormActionHandler.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace KirkiAppContracts;
+
+use Exception;
+
+defined('ABSPATH') || exit;
+
+use KirkiAppDTOFormFormConfigDTO;
+
+/**
+ * A single form-submission action channel (email, webhook, mail client, ...).
+ *
+ * Each handler runs one configured action of its type. The dispatcher routes
+ * actions by type, so handlers stay fully independent — a new or changed
+ * handler can never break another.
+ */
+interface FormActionHandler
+{
+ /**
+ * Run a single configured action for the given submission.
+ *
+ * @param array $action A single configured action of this handler's type.
+ * @param array $form_data The submission data.
+ * @param FormConfigDTO $form_config The form configuration.
+ * @return bool Whether it succeeded. Fire-and-forget channels return true.
+ * @throws Exception on error
+ */
+ public function handle(array $action, array $form_data, FormConfigDTO $form_config);
+}
--- a/kirki/app/DTO/Collection/UpsertCollectionDTO.php
+++ b/kirki/app/DTO/Collection/UpsertCollectionDTO.php
@@ -15,6 +15,9 @@
/** @var string */
public $post_name;
+ /** @var string|null */
+ public $preset_type;
+
/** @var array */
public $fields;
--- a/kirki/app/DTO/Form/FormConfigDTO.php
+++ b/kirki/app/DTO/Form/FormConfigDTO.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace KirkiAppDTOForm;
+
+use KirkiFrameworkSanitizer;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkDTO;
+
+class FormConfigDTO extends DTO
+{
+ /** @var string The form element's builder id. */
+ public $id;
+
+ /** @var string Whether the form posts to this site ('default') or an external URL ('external'). Not used server-side. */
+ public $type;
+
+ /** @var string */
+ public $name;
+
+ /** @var array */
+ public $fields = [];
+
+ /** @var array */
+ public $actions = [];
+
+ /** @var array Client-side "what to show after submit" config. Not used server-side. */
+ public $onSubmit = [];
+
+ /** @var array */
+ public $maxEntry = [];
+
+ /** @var array */
+ public $responseLimit = [];
+
+ /** @var bool */
+ public $saveData = false;
+
+ public function __construct(array $data = [])
+ {
+ parent::__construct($data);
+
+ $this->id = (string) $this->id;
+ $this->type = (string) $this->type;
+ $this->name = (string) $this->name;
+ $this->fields = is_array($this->fields) ? $this->fields : [];
+ $this->actions = is_array($this->actions) ? $this->actions : [];
+ $this->onSubmit = is_array($this->onSubmit) ? $this->onSubmit : [];
+ $this->maxEntry = is_array($this->maxEntry) ? $this->maxEntry : [];
+ $this->responseLimit = is_array($this->responseLimit) ? $this->responseLimit : [];
+ $this->saveData = Sanitizer::apply_rule($this->saveData, Sanitizer::BOOL);
+ }
+}
--- a/kirki/app/DTO/Page/PageFilterDTO.php
+++ b/kirki/app/DTO/Page/PageFilterDTO.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace KirkiAppDTOPage;
+
+defined('ABSPATH') || exit;
+
+use KirkiAppConstantsPostStatus;
+use KirkiAppConstantsPostTypes;
+use KirkiFrameworkDTO;
+
+class PageFilterDTO extends DTO
+{
+ /** @var string search param */
+ public $query;
+
+ /** @var int page number */
+ public $current_page = 1;
+
+ /** @var int number of posts per page */
+ public $limit = 20;
+
+ /** @var string[] post types */
+ public $post_types = [PostTypes::WP_PAGE];
+
+ /** @var int[] exclude page ids */
+ public $exclude_page_ids = [];
+
+ /** @var string[] */
+ public $post_statuses = [
+ PostStatus::PUBLISH,
+ PostStatus::DRAFT
+ ];
+}
No newline at end of file
--- a/kirki/app/DTO/Page/PagePayloadDTO.php
+++ b/kirki/app/DTO/Page/PagePayloadDTO.php
@@ -28,4 +28,10 @@
/** @var array|null */
public $custom_template;
-}
No newline at end of file
+
+ /** @var int|null */
+ public $content_manager_collection_id;
+
+ /** @var string|null */
+ public $content_manager_page_kind;
+}
--- a/kirki/app/FormActions/Actions/EmailActionHandler.php
+++ b/kirki/app/FormActions/Actions/EmailActionHandler.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace KirkiAppFormActionsActions;
+
+defined('ABSPATH') || exit;
+
+use KirkiAppConstantsFormFormEmailBodyPartTypes;
+use KirkiAppContractsFormActionHandler;
+use KirkiAppDTOFormFormConfigDTO;
+
+/**
+ * Sends a form submission over email, for a single configured `email` action.
+ */
+class EmailActionHandler implements FormActionHandler
+{
+ public function handle(array $action, array $form_data, FormConfigDTO $form_config)
+ {
+ $this->register_shortcodes($action, $form_data); //@todo: maybe we can implement this without wp shortcodes but a different approach as current implementation might collide with other shortcodes registered
+
+ return $this->send_single_email_action($action, $form_data, $form_config->name);
+ }
+
+ /**
+ * Register shortcodes referenced by the email action's fields.
+ *
+ * @param array $action
+ * @param array $form_data
+ * @return void
+ */
+ protected function register_shortcodes($action, $form_data)
+ {
+ $this->register_shortcodes_from_field($action['emailList'] ?? '', $form_data);
+ $this->register_shortcodes_from_field($action['replyTo'] ?? '', $form_data);
+ $this->register_shortcodes_from_field($action['name'] ?? '', $form_data);
+ $this->register_shortcodes_from_field($action['subject'] ?? '', $form_data);
+
+ add_shortcode(
+ 'admin_email',
+ function () {
+ return get_option('admin_email');
+ }
+ );
+ }
+
+ /**
+ * Register shortcodes referenced in a single email action field.
+ *
+ * @param string $field_value The field value containing shortcodes.
+ * @param array $form_data The form data to use for shortcode values.
+ * @return void
+ */
+ protected function register_shortcodes_from_field($field_value, $form_data)
+ {
+ if (empty($field_value)) {
+ return;
+ }
+
+ $regex = '/[([^]]+)]/';
+ preg_match_all($regex, $field_value, $matches);
+
+ foreach (($matches[1] ?? []) as $match) {
+ if (isset($form_data[$match])) {
+ add_shortcode($match, fn() => $form_data[$match]);
+ }
+ }
+ }
+
+ /**
+ * Send a single email action.
+ *
+ * @param array $email_action Email action configuration.
+ * @param array $form_data Form data.
+ * @param string $form_name Form name.
+ * @return bool
+ */
+ protected function send_single_email_action($email_action, $form_data, $form_name)
+ {
+ $body = $this->convert_form_data_into_html_for_email($form_data);
+ $reply_to = '';
+ $name = '';
+ $subject = 'New ' . $form_name;
+ $header = [];
+
+ if (isset($email_action['body']) && is_array($email_action['body'])) {
+ $body = $this->build_email_body($email_action['body'], $form_data);
+ }
+
+ if (isset($email_action['replyTo'])) {
+ $reply_to = do_shortcode($email_action['replyTo']);
+ }
+
+ if (isset($email_action['name'])) {
+ $name = do_shortcode($email_action['name']);
+ }
+
+ if (isset($email_action['subject'])) {
+ $subject = do_shortcode($email_action['subject']);
+ }
+
+ if (strlen($reply_to) > 0 && strlen($name) > 0) {
+ $header = ['Reply-To: ' . $name . ' <' . $reply_to . '>'];
+ }
+
+ if (isset($email_action['emailList']) && !empty($email_action['emailList'])) {
+ return $this->send_email_notification(do_shortcode($email_action['emailList']), $subject, $body, $header);
+ }
+
+ return false;
+ }
+
+ /**
+ * Build the email body from a body configuration.
+ *
+ * @param array $body_config Body configuration.
+ * @param array $form_data Form data.
+ * @return string Email body HTML.
+ */
+ protected function build_email_body($body_config, $form_data)
+ {
+ $body_parts = [];
+
+ foreach ($body_config as $body_data) {
+ if (!isset($body_data['type'], $body_data['value'])) {
+ continue;
+ }
+
+ if ($body_data['type'] === FormEmailBodyPartTypes::TEXT) {
+ $body_parts[] = $body_data['value'];
+ } elseif ($body_data['type'] === FormEmailBodyPartTypes::FORM && isset($form_data[$body_data['value']])) {
+ $body_parts[] = $form_data[$body_data['value']];
+ }
+ }
+ return nl2br(implode('', $body_parts));
+ }
+
+ /**
+ * Convert form data into an HTML list for email.
+ *
+ * @param array $form_data Form data.
+ * @return string
+ */
+ protected function convert_form_data_into_html_for_email($form_data = [])
+ {
+ $html = '<ul>';
+
+ if (is_array($form_data)) {
+ foreach ($form_data as $key => $value) {
+ $html .= '<li>' . esc_html($key) . ': ' . esc_html($value) . '</li>';
+ }
+ }
+
+ $html .= '</ul>';
+ return $html;
+ }
+
+ /**
+ * Send an email notification.
+ *
+ * @param string|string[] $to Address(es) to send to.
+ * @param string $subject Email subject.
+ * @param string $message Message contents.
+ * @param array $headers Email headers.
+ * @return bool
+ */
+ protected function send_email_notification($to, $subject, $message, $headers = [])
+ {
+ $headers[] = 'Content-Type: text/html; charset=UTF-8';
+ return wp_mail($to, $subject, $message, $headers);
+ }
+}
--- a/kirki/app/FormActions/Actions/WebhookActionHandler.php
+++ b/kirki/app/FormActions/Actions/WebhookActionHandler.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace KirkiAppFormActionsActions;
+
+defined('ABSPATH') || exit;
+
+use KirkiAppConstantsFormFormWebhookMethods;
+use KirkiAppContractsFormActionHandler;
+use KirkiAppDTOFormFormConfigDTO;
+use KirkiFrameworkSupportsFacadesHttp;
+
+/**
+ * Delivers a submission to a single configured webhook endpoint.
+ *
+ * Unlike the other channels, a webhook transport failure marks the whole
+ * submission as failed, so this is the only handler whose return value can be
+ * false.
+ */
+class WebhookActionHandler implements FormActionHandler
+{
+ public function handle(array $webhook, array $form_data, FormConfigDTO $form_config)
+ {
+ if (!isset($webhook['action'], $webhook['method'])) {
+ return false;
+ }
+
+ if ($webhook['method'] === FormWebhookMethods::GET) {
+ return $this->send_get($webhook['action'], $form_data);
+ }
+
+ if ($webhook['method'] === FormWebhookMethods::POST) {
+ return $this->send_post($webhook['action'], $form_data);
+ }
+
+ return false;
+ }
+
+ /**
+ * Send a GET webhook request.
+ *
+ * @param string $url Webhook URL.
+ * @param array $form_data Form data.
+ * @return bool Success status.
+ */
+ protected function send_get($url, $form_data)
+ {
+ $query_string = http_build_query($form_data);
+ $url = rtrim($url, '/');
+ $url .= '/';
+
+ $response = Http::get($url . '?' . $query_string);
+
+ return $response->status() !== 0;
+ }
+
+ /**
+ * Send a POST webhook request.
+ *
+ * @param string $url Webhook URL.
+ * @param array $form_data Form data.
+ * @return bool Success status.
+ */
+ protected function send_post($url, $form_data)
+ {
+ $response = Http::as_form()->post($url, $form_data);
+
+ return $response->status() !== 0;
+ }
+}
--- a/kirki/app/FormActions/FormActionDispatcher.php
+++ b/kirki/app/FormActions/FormActionDispatcher.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace KirkiAppFormActions;
+
+defined('ABSPATH') || exit;
+
+use KirkiAppDTOFormFormConfigDTO;
+
+use function KirkiFrameworkapp;
+
+/**
+ * Routes each configured form action to the handler registered for its type.
+ *
+ * Only the types a submission actually configures are resolved, so a form that
+ * uses one action type never instantiates the others' handlers.
+ */
+class FormActionDispatcher
+{
+ /**
+ * @var array<string, class-string> Action type => handler class.
+ */
+ protected $handler_map;
+
+ /**
+ * @param array<string, class-string> $handler_map Action type => handler class.
+ */
+ public function __construct(array $handler_map)
+ {
+ $this->handler_map = $handler_map;
+ }
+
+ /**
+ * Dispatch every configured action to its handler.
+ *
+ * @param array $form_data The submission data.
+ * @param FormConfigDTO $form_config The form configuration.
+ * @return bool Whether every dispatched action succeeded.
+ * @throws Exception on error
+ */
+ public function dispatch(array $form_data, FormConfigDTO $form_config)
+ {
+ $success = true;
+ $handlers = [];
+
+ foreach ($form_config->actions as $action) {
+ $type = $action['type'] ?? null;
+
+ if (!$type || !isset($this->handler_map[$type])) {
+ continue;
+ }
+
+ $handler = $handlers[$type] ?? ($handlers[$type] = app($this->handler_map[$type]));
+ $success = $handler->handle($action, $form_data, $form_config) && $success;
+ }
+
+ return $success;
+ }
+}
--- a/kirki/app/Http/Controllers/Api/CollaborationController.php
+++ b/kirki/app/Http/Controllers/Api/CollaborationController.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace KirkiAppHttpControllersApi;
+
+defined('ABSPATH') || exit;
+
+use KirkiAppDTOCollaborationCreateCollaborationDTO;
+use KirkiAppHttpRequestsCollaborationRequest;
+use KirkiAppServicesCollaborationService;
+
+use function KirkiFrameworkcollection;
+use function KirkiFrameworkresponse;
+
+class CollaborationController
+{
+
+ /**
+ * @var CollaborationService
+ */
+ protected $service;
+
+ public function __construct(CollaborationService $service)
+ {
+ $this->service = $service;
+ }
+
+ public function save_actions(CollaborationRequest $request)
+ {
+ $payload = collection($request->array('data', []))->map(function ($data) use ($request) {
+ return CreateCollaborationDTO::from_array([
+ 'session_id' => $request->string('session_id'),
+ 'parent' => $data['parent'] ?? '',
+ 'parent_id' => $data['parent_id'] ?? 0,
+ 'data' => $data['action'] ?? [],
+ ]);
+ });
+
+ return response()->json([
+ 'data' => $this->service->save_actions($payload),
+ ]);
+ }
+}
No newline at end of file
--- a/kirki/app/Http/Controllers/Api/CollectionController.php
+++ b/kirki/app/Http/Controllers/Api/CollectionController.php
@@ -100,13 +100,13 @@
{
$is_valid = ContentManager::validate_slug(
$request->int('post_id', 0),
- $request->string('post_type'),
- $request->string('post_name')
+ $request->string('post_type', ''),
+ $request->string('post_name', '')
);
return response()->json([
'data' => $is_valid,
- 'message' => __('Slug validated successfully.', 'kirki'),
+ 'message' => $is_valid ? __('Slug validated successfully..', 'kirki') : __('Slug is not available.', 'kirki'),
]);
}
}
--- a/kirki/app/Http/Controllers/Api/EditorController.php
+++ b/kirki/app/Http/Controllers/Api/EditorController.php
@@ -24,7 +24,7 @@
public function back_to_kirki_editor(Request $request)
{
- $page = PageModel::find($request->int('postId'));
+ $page = PageModel::exclude_trash()->find($request->int('postId'));
if (empty($page)) {
throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
@@ -41,7 +41,7 @@
public function back_to_wordpress_editor(Request $request)
{
- $page = PageModel::find($request->int('postId'));
+ $page = PageModel::exclude_trash()->find($request->int('postId'));
if (empty($page)) {
throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
--- a/kirki/app/Http/Controllers/Api/FormController.php
+++ b/kirki/app/Http/Controllers/Api/FormController.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace KirkiAppHttpControllersApi;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkHttpRequest;
+use KirkiAppServicesFormSubmissionService;
+use function KirkiFrameworkresponse;
+
+/**
+ * Handles front-end form submissions.
+ *
+ * Route: POST /kirki/v1/frontend/form (public).
+ */
+class FormController
+{
+ /**
+ * @var FormSubmissionService
+ */
+ protected $service;
+
+ /**
+ * @param FormSubmissionService $service
+ */
+ public function __construct(FormSubmissionService $service)
+ {
+ $this->service = $service;
+ }
+
+ /**
+ * Store a form submission.
+ *
+ * @param Request $request The submission request.
+ * @return KirkiFrameworkHttpJsonResponse
+ */
+ public function store(Request $request)
+ {
+ $result = $this->service->handle($request->all(), $request->all_files());
+
+ return response()->json([
+ 'data' => $result,
+ 'message' => __('Form submitted successfully.', 'kirki'),
+ ]);
+ }
+}
--- a/kirki/app/Http/Controllers/Api/PageController.php
+++ b/kirki/app/Http/Controllers/Api/PageController.php
@@ -9,17 +9,23 @@
use KirkiAppDTOPageEditorPagePayloadDTO;
use KirkiAppDTOPageEditPageDTO;
use KirkiAppDTOPageEditPopupDTO;
+use KirkiAppDTOPagePageFilterDTO;
use KirkiAppDTOPagePagePayloadDTO;
use KirkiAppDTOPageTogglePageSymbolDTO;
+use KirkiAppHttpRequestsCollectionItemCollectionItemConditionRequest;
use KirkiAppHttpRequestsPagePageDataRequest;
use KirkiAppHttpRequestsPagePageRequest;
use KirkiAppHttpRequestsPagePageUpdateRequest;
use KirkiAppHttpRequestsPagePopupRequest;
+use KirkiAppHttpRequestsPageRenameStagingVersionRequest;
+use KirkiAppHttpRequestsPageStagingVersionRequest;
use KirkiAppHttpRequestsPageTogglePageSymbolRequest;
use KirkiAppModelsPage as PageModel;
+use KirkiAppModelsPost as PostModel;
use KirkiAppResourcesPageContentResource;
use KirkiAppResourcesPageResource;
use KirkiAppServicesPageService;
+use KirkiAppSupportsCollectionItem;
use KirkiAppSupportsFacadesPage;
use KirkiFrameworkHttpRequest;
use KirkiFrameworkHttpResponse;
@@ -50,7 +56,7 @@
public function save_page_data(PageDataRequest $request, int $page_id, string $page_content_type)
{
- $page = PageModel::find($page_id);
+ $page = PageModel::exclude_trash()->find($page_id);
if (empty($page)) {
throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
@@ -68,7 +74,7 @@
], Response::OK);
}
- public function publish_staging_version(Request $request)
+ public function get_all_staged_versions(Request $request)
{
$page = PageModel::find($request->int('page_id'));
@@ -77,14 +83,72 @@
}
return response()->json([
+ 'data' => Page::get_all_staged_versions($page->ID, false),
+ ], Response::OK);
+ }
+
+ public function publish_staging_version(Request $request)
+ {
+ $page = PageModel::exclude_trash()->find($request->int('page_id'));
+
+ if (empty($page)) {
+ throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
+ }
+
+ return response()->json([
'data' => Page::publish_stage_version($page->ID),
'message' => __('Staging version published successfully.', 'kirki'),
], Response::OK);
}
+ public function rename_staging_version(RenameStagingVersionRequest $request)
+ {
+ $page = PageModel::find($request->int('page_id'));
+
+ if (empty($page)) {
+ throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
+ }
+
+ return response()->json([
+ 'data' => Page::rename_stage_version($page->ID, $request->int('version_id'), $request->string('name')),
+ 'message' => __('Staging version renamed successfully.', 'kirki'),
+ ], Response::OK);
+ }
+
+ public function restore_staging_version(StagingVersionRequest $request)
+ {
+ $page = PageModel::find($request->int('page_id'));
+
+ if (empty($page)) {
+ throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
+ }
+
+ return response()->json([
+ 'data' => [
+ 'new_version' => Page::restore_stage_version($page->ID, $request->int('version_id')),
+ 'versions' => Page::get_all_staged_versions($page->ID, false),
+ ],
+ 'message' => __('Staging version renamed successfully.', 'kirki'),
+ ], Response::OK);
+ }
+
+ public function remove_staging_version(StagingVersionRequest $request)
+ {
+ $page = PageModel::find($request->int('page_id'));
+
+ if (empty($page)) {
+ throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
+ }
+
+ return response()->json([
+ 'data' => Page::remove_stage_version($page->ID, $request->int('version_id')),
+ 'message' => __('Staging version removed successfully.', 'kirki'),
+ ], Response::OK);
+ }
+
public function update(PageUpdateRequest $request, int $page_id)
{
- $page = PageModel::find($page_id);
+ $page = PageModel::exclude_trash()->find($page_id);
if (empty($page)) {
throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
@@ -102,7 +166,7 @@
public function update_popup(PopupRequest $request, int $popup_id)
{
- $popup = PageModel::find($popup_id);
+ $popup = PageModel::exclude_trash()->find($popup_id);
if (empty($popup) || $popup->post_type !== PostTypes::POPUP) {
throw new Exception(esc_html__('Popup not found.', 'kirki'), Response::NOT_FOUND);
@@ -120,7 +184,7 @@
public function toggle_disabled_page_symbols(TogglePageSymbolRequest $request)
{
- $page = PageModel::find($request->int('post_id'));
+ $page = PageModel::exclude_trash()->find($request->int('post_id'));
if (empty($page)) {
throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
@@ -139,7 +203,7 @@
public function duplicate(Request $request)
{
- $page = PageModel::find($request->int('page_id'));
+ $page = PageModel::exclude_trash()->find($request->int('page_id'));
if (empty($page)) {
throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
@@ -153,7 +217,7 @@
public function delete(Request $request)
{
- $page = PageModel::find($request->int('page_id'));
+ $page = PageModel::exclude_trash()->find($request->int('page_id'));
if (empty($page)) {
throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
@@ -169,6 +233,71 @@
]);
}
+ public function paginated(Request $request)
+ {
+ $filter_dto = PageFilterDTO::from_array([
+ 'query' => $request->string('query'),
+ 'current_page' => $request->int('page', 1),
+ 'limit' => $request->int('numberposts', 20),
+ 'post_types' => $request->array('post_types', []),
+ 'exclude_page_ids' => $request->array('exclude_post_ids', []),
+ ]);
+
+ return response()->json([
+ 'data' => PageResource::paginated($this->service->paginated($filter_dto)),
+ ]);
+ }
+
+ public function page_panel_pages(Request $request)
+ {
+ $filter_dto = PageFilterDTO::from_array([
+ 'query' => $request->string('query'),
+ 'current_page' => $request->int('page', 1),
+ 'limit' => $request->int('numberposts', 20),
+ 'post_types' => $request->array('post_types', [PostTypes::WP_PAGE]),
+ ]);
+
+ return response()->json([
+ 'data' => PageResource::paginated($this->service->paginated($filter_dto)),
+ ]);
+ }
+
+ public function get_wp_single_post(Request $request)
+ {
+ $post = PostModel::exclude_trash()->find($request->int('post_id'));
+
+ if (empty($post)) {
+ throw new Exception(esc_html__('Post not found.', 'kirki'), Response::NOT_FOUND);
+ }
+
+ return response()->json([
+ 'data' => $post,
+ ]);
+ }
+
+ public function get_current_page(Request $request)
+ {
+ $page = PageModel::exclude_trash()->find($request->int('page_id'));
+
+ if (empty($page)) {
+ throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
+ }
+
+ return response()->json([
+ 'data' => PageResource::make($page),
+ ]);
+ }
+
+ public function get_data_list_for_template_edit_search_flyout(CollectionItemConditionRequest $request)
+ {
+ $query = $request->string('query', '');
+ $conditions = $request->array('conditions', []);
+
+ return response()->json([
+ 'data' => CollectionItem::get_items_from_condition($conditions, $query)['data'] ?? [],
+ ]);
+ }
+
public function get_page_content(Request $request)
{
$page = PageModel::find($request->int('page_id'));
--- a/kirki/app/Http/Controllers/Api/PageSettingsController.php
+++ b/kirki/app/Http/Controllers/Api/PageSettingsController.php
@@ -27,7 +27,7 @@
public function update_page_settings(PageSettingsRequest $request, int $page_id)
{
- $page = PageModel::find($page_id);
+ $page = PageModel::exclude_trash()->find($page_id);
if (empty($page)) {
throw new Exception(esc_html__('Page not found.', 'kirki'), Response::NOT_FOUND);
--- a/kirki/app/Http/Controllers/Api/PostController.php
+++ b/kirki/app/Http/Controllers/Api/PostController.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace KirkiAppHttpControllersApi;
+
+defined('ABSPATH') || exit;
+
+use KirkiAppHttpRequestsPostPostSlugValidationRequest;
+use KirkiAppServicesPostService;
+use KirkiAppSupportsContentManager;
+use KirkiFrameworkHttpRequest;
+use KirkiFrameworkHttpResponse;
+use function KirkiFrameworkresponse;
+
+class PostController
+{
+ /**
+ * @var PostService
+ */
+ protected $service;
+
+ public function __construct(PostService $service)
+ {
+ $this->service = $service;
+ }
+
+ /**
+ * Validate WP post slug
+ *
+ * @since 6.0.14
+ *
+ * @param Request $request
+ *
+ * @return Response Returns a JSON response with the validation result.
+ */
+ public function validate_slug(PostSlugValidationRequest $request)
+ {
+ $is_valid = ContentManager::validate_slug(
+ $request->int('post_id', 0),
+ $request->string('post_type'),
+ $request->string('post_name')
+ );
+
+ return response()->json([
+ 'data' => $is_valid,
+ 'message' => $is_valid ? __('Slug validated successfully..', 'kirki') : __('Slug is not available.', 'kirki'),
+ ]);
+ }
+
+ public function get_all_posts_grouped_by_type(Request $request)
+ {
+ return response()->json([
+ 'data' => $this->service->get_all_posts_grouped_by_type($request->string('search', '')),
+ ]);
+ }
+}
--- a/kirki/app/Http/Controllers/Api/UserController.php
+++ b/kirki/app/Http/Controllers/Api/UserController.php
@@ -4,8 +4,11 @@
defined('ABSPATH') || exit;
+use KirkiAppConstantsUserMetaKeys;
use KirkiFrameworkHttpRequest;
+use KirkiFrameworkWordpressUserMeta;
+use function KirkiAppto_boolean;
use function KirkiFrameworkresponse;
use function KirkiFrameworkuser;
@@ -21,4 +24,22 @@
],
]);
}
+
+ public function set_walkthrough_state(Request $request) {
+ $state = $request->bool('walkthrough_shown_state', false);
+
+ UserMeta::update(user()->get_id(), UserMetaKeys::WALKTHROUGH_SHOWN_STATE, $state);
+
+ return response()->json([
+ 'data' => true,
+ ]);
+ }
+
+ public function get_walkthrough_state(Request $request) {
+ $state = UserMeta::get(user()->get_id(), UserMetaKeys::WALKTHROUGH_SHOWN_STATE);
+
+ return response()->json([
+ 'data' => to_boolean($state),
+ ]);
+ }
}
No newline at end of file
--- a/kirki/app/Http/Requests/Apps/AppSettingsRequest.php
+++ b/kirki/app/Http/Requests/Apps/AppSettingsRequest.php
@@ -7,8 +7,17 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class AppSettingsRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'settings' => ensure_array($this->settings),
+ ]);
+ }
+
public function rules()
{
return [
--- a/kirki/app/Http/Requests/CollaborationComment/CollaborationCommentStoreRequest.php
+++ b/kirki/app/Http/Requests/CollaborationComment/CollaborationCommentStoreRequest.php
@@ -6,8 +6,18 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class CollaborationCommentStoreRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'comment' => ensure_array($this->comment),
+ 'meta_data' => ensure_array($this->meta_data),
+ ]);
+ }
+
/**
* Validation rules.
*/
--- a/kirki/app/Http/Requests/CollaborationRequest.php
+++ b/kirki/app/Http/Requests/CollaborationRequest.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace KirkiAppHttpRequests;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkHttpRequest;
+use KirkiFrameworkSanitizer;
+
+use function KirkiAppensure_array;
+
+class CollaborationRequest extends Request
+{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'data' => ensure_array($this->data),
+ ]);
+ }
+
+ public function rules()
+ {
+ return [
+ 'data' => 'required|array',
+ 'data.*.parent' => 'nullable|string',
+ 'data.*.parent_id' => 'nullable|integer',
+ 'data.*.action' => 'nullable|array',
+ 'session_id' => 'required|string',
+ ];
+ }
+
+ public function filters()
+ {
+ return [
+ 'data' => Sanitizer::ARRAY,
+ 'data.*.parent' => Sanitizer::TEXT,
+ 'data.*.parent_id' => Sanitizer::INT,
+ 'data.*.action' => Sanitizer::ARRAY,
+ 'session_id' => Sanitizer::TEXT,
+ ];
+ }
+}
No newline at end of file
--- a/kirki/app/Http/Requests/Collection/CollectionStoreRequest.php
+++ b/kirki/app/Http/Requests/Collection/CollectionStoreRequest.php
@@ -6,6 +6,8 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class CollectionStoreRequest extends Request
{
/**
@@ -13,15 +15,14 @@
*/
protected function prepare_for_validation()
{
- $data = $this->get('data');
+ $data = ensure_array($this->get('data') ?? []);
+
+ $this->merge($data);
- if (is_string($data)) {
- $data = json_decode($data, true);
- }
-
- if (is_array($data)) {
- $this->merge($data);
- }
+ $this->merge([
+ 'fields' => ensure_array($this->fields),
+ 'basic_fields' => ensure_array($this->basic_fields),
+ ]);
}
/**
@@ -33,6 +34,7 @@
'ID' => 'nullable',
'post_title' => 'required|string',
'post_name' => 'nullable|string',
+ 'preset_type' => 'nullable|string',
'fields' => 'nullable|array',
'fields.*.id' => 'required|string',
@@ -41,6 +43,7 @@
'fields.*.title' => 'required|string',
'fields.*.help_text' => 'nullable|string',
'fields.*.required' => 'required|boolean',
+ 'fields.*.templateKey' => 'nullable|string',
'basic_fields' => 'nullable|array',
];
@@ -55,6 +58,7 @@
'ID' => Sanitizer::INT,
'post_title' => Sanitizer::TEXT,
'post_name' => Sanitizer::TEXT,
+ 'preset_type' => Sanitizer::TEXT,
'fields' => Sanitizer::ARRAY ,
'fields.*.id' => Sanitizer::TEXT,
@@ -63,6 +67,7 @@
'fields.*.title' => Sanitizer::TEXT,
'fields.*.help_text' => Sanitizer::TEXT,
'fields.*.required' => Sanitizer::BOOL,
+ 'fields.*.templateKey' => Sanitizer::TEXT,
'basic_fields' => Sanitizer::ARRAY ,
];
--- a/kirki/app/Http/Requests/CollectionItem/CollectionItemBulkActionRequest.php
+++ b/kirki/app/Http/Requests/CollectionItem/CollectionItemBulkActionRequest.php
@@ -6,8 +6,17 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class CollectionItemBulkActionRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'post_ids' => ensure_array($this->post_ids),
+ ]);
+ }
+
/**
* Validation rules.
*/
--- a/kirki/app/Http/Requests/CollectionItem/CollectionItemConditionRequest.php
+++ b/kirki/app/Http/Requests/CollectionItem/CollectionItemConditionRequest.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace KirkiAppHttpRequestsCollectionItem;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkHttpRequest;
+use KirkiFrameworkSanitizer;
+
+use function KirkiAppensure_array;
+
+class CollectionItemConditionRequest extends Request
+{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'conditions' => ensure_array($this->conditions),
+ ]);
+ }
+
+ public function rules()
+ {
+ return [
+ 'query' => 'nullable|string',
+ 'conditions' => 'nullable|array',
+ 'conditions.*.type' => 'nullable|string',
+ 'conditions.*.post_type' => 'nullable|string',
+ 'conditions.*.from' => 'nullable|string',
+ 'conditions.*.to' => 'nullable|string',
+ 'conditions.*.category' => 'nullable|string',
+ 'conditions.*.where' => 'nullable|string',
+ ];
+ }
+
+ public function filters()
+ {
+ return [
+ 'query' => Sanitizer::TEXT,
+ 'conditions' => Sanitizer::ARRAY,
+ 'conditions.*.type' => Sanitizer::TEXT,
+ 'conditions.*.post_type' => Sanitizer::TEXT,
+ 'conditions.*.from' => Sanitizer::TEXT,
+ 'conditions.*.to' => Sanitizer::TEXT,
+ 'conditions.*.category' => Sanitizer::TEXT,
+ 'conditions.*.where' => Sanitizer::TEXT,
+ ];
+ }
+}
No newline at end of file
--- a/kirki/app/Http/Requests/CollectionItem/CollectionItemStoreRequest.php
+++ b/kirki/app/Http/Requests/CollectionItem/CollectionItemStoreRequest.php
@@ -7,6 +7,8 @@
use KirkiFrameworkSanitizer;
use KirkiFrameworkSupportsArr;
+use function KirkiAppensure_array;
+
class CollectionItemStoreRequest extends Request
{
/**
@@ -27,6 +29,10 @@
if (empty($data['post_status'])) {
$this->merge(['post_status' => PostStatus::DRAFT]);
}
+
+ $this->merge([
+ 'fields' => ensure_array($this->fields),
+ ]);
}
/**
--- a/kirki/app/Http/Requests/DataRequest.php
+++ b/kirki/app/Http/Requests/DataRequest.php
@@ -7,27 +7,22 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
use function KirkiFrameworkis_valid_json;
class DataRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'data' => ensure_array($this->data),
+ ]);
+ }
+
public function rules()
{
return [
- 'data' => [
- 'required',
- function ($value) {
- if (is_array($value)) {
- return true;
- }
-
- if (is_valid_json($value)) {
- return true;
- }
-
- return __("The data must be an array or a valid JSON string.", 'kirki');
- }
- ],
+ 'data' => 'required|array'
];
}
--- a/kirki/app/Http/Requests/DownloadGoogleFontRequest.php
+++ b/kirki/app/Http/Requests/DownloadGoogleFontRequest.php
@@ -7,8 +7,17 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class DownloadGoogleFontRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'font' => ensure_array($this->font),
+ ]);
+ }
+
public function rules()
{
return [
--- a/kirki/app/Http/Requests/Media/PaginatedMediaListRequest.php
+++ b/kirki/app/Http/Requests/Media/PaginatedMediaListRequest.php
@@ -12,12 +12,12 @@
public function rules()
{
return [
- 'search' => 'string',
- 'category' => 'string',
- 'page' => 'integer|min:1',
- 'limit' => 'integer',
- 'sort_by' => 'string',
- 'sort_order' => 'string',
+ 'search' => 'nullable|string',
+ 'category' => 'nullable|string',
+ 'page' => 'nullable|integer|min:1',
+ 'limit' => 'nullable|integer',
+ 'sort_by' => 'nullable|string',
+ 'sort_order' => 'nullable|string',
];
}
--- a/kirki/app/Http/Requests/Page/GlobalStyleRequest.php
+++ b/kirki/app/Http/Requests/Page/GlobalStyleRequest.php
@@ -2,6 +2,8 @@
namespace KirkiAppHttpRequestsPage;
+use function KirkiAppensure_array;
+
defined('ABSPATH') || exit;
use KirkiFrameworkHttpRequest;
@@ -11,13 +13,20 @@
class GlobalStyleRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'styles' => ensure_array($this->styles),
+ ]);
+ }
+
public function rules()
{
return [
'session_id' => 'required|string',
- 'styles' => 'array',
- 'styles.*' => 'array',
- 'styles.*.isGlobalStyle' => ['required', fn ($value) => is_falsy($value) ? __('The isGlobalStyle field is required.', 'kirki') : true],
+ 'styles' => 'nullable|array',
+ 'styles.*' => 'nullable|array',
+ 'styles.*.isGlobalStyle' => ['required', fn($value) => is_falsy($value) ? __('The isGlobalStyle field is required.', 'kirki') : true],
];
}
@@ -25,7 +34,7 @@
{
return [
'session_id' => Sanitizer::TEXT,
- 'styles' => Sanitizer::ARRAY,
+ 'styles' => Sanitizer::ARRAY ,
];
}
}
No newline at end of file
--- a/kirki/app/Http/Requests/Page/PageDataRequest.php
+++ b/kirki/app/Http/Requests/Page/PageDataRequest.php
@@ -9,46 +9,55 @@
use KirkiFrameworkHttpResponse;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class PageDataRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'data' => ensure_array($this->data),
+ ]);
+ }
+
public function rules()
{
$rules = [
'session_id' => 'required|string',
- 'is_staging' => 'boolean',
+ 'is_staging' => 'nullable|boolean',
'data' => 'required|array',
'data.blocks' => 'prohibited',
'data.styles' => 'prohibited',
'data.usedStyles' => 'prohibited',
'data.usedStyleIdsRandom' => 'prohibited',
'data.usedFonts' => 'prohibited',
-
+
];
- switch($this->page_content_type) {
+ switch ($this->page_content_type) {
case 'blocks':
$rules = array_merge($rules, [
- 'data.blocks' => 'array',
+ 'data.blocks' => 'nullable|array',
]);
break;
case 'styles':
$rules = array_merge($rules, [
- 'data.styles' => 'array',
+ 'data.styles' => 'nullable|array',
]);
break;
case 'used-styles':
$rules = array_merge($rules, [
- 'data.usedStyles' => 'array',
+ 'data.usedStyles' => 'nullable|array',
]);
break;
case 'used-style-ids-random':
$rules = array_merge($rules, [
- 'data.usedStyleIdsRandom' => 'array',
+ 'data.usedStyleIdsRandom' => 'nullable|array',
]);
break;
case 'used-fonts':
$rules = array_merge($rules, [
- 'data.usedFonts' => 'array',
+ 'data.usedFonts' => 'nullable|array',
]);
break;
default:
@@ -61,12 +70,12 @@
public function filters()
{
return [
- 'data' => Sanitizer::ARRAY,
- 'data.blocks' => Sanitizer::ARRAY,
- 'data.styles' => Sanitizer::ARRAY,
- 'data.usedStyles' => Sanitizer::ARRAY,
- 'data.usedStyleIdsRandom' => Sanitizer::ARRAY,
- 'data.usedFonts' => Sanitizer::ARRAY,
+ 'data' => Sanitizer::ARRAY ,
+ 'data.blocks' => Sanitizer::ARRAY ,
+ 'data.styles' => Sanitizer::ARRAY ,
+ 'data.usedStyles' => Sanitizer::ARRAY ,
+ 'data.usedStyleIdsRandom' => Sanitizer::ARRAY ,
+ 'data.usedFonts' => Sanitizer::ARRAY ,
'session_id' => Sanitizer::TEXT,
'is_staging' => Sanitizer::BOOL,
];
--- a/kirki/app/Http/Requests/Page/PageRequest.php
+++ b/kirki/app/Http/Requests/Page/PageRequest.php
@@ -10,8 +10,19 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class PageRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'blocks' => ensure_array($this->blocks),
+ 'conditions' => ensure_array($this->conditions),
+ 'custom_template' => ensure_array($this->custom_template),
+ ]);
+ }
+
public function rules()
{
return [
@@ -43,6 +54,8 @@
],
'custom_template' => 'nullable|array',
'custom_template.url' => 'nullable|url',
+ 'content_manager_collection_id' => 'nullable|integer',
+ 'content_manager_page_kind' => 'nullable|string|in:index,details',
];
}
@@ -57,6 +70,8 @@
'utility_page_type' => Sanitizer::TEXT,
'custom_template' => Sanitizer::ARRAY,
'custom_template.url' => Sanitizer::URL,
+ 'content_manager_collection_id' => Sanitizer::INT,
+ 'content_manager_page_kind' => Sanitizer::TEXT,
];
}
-}
No newline at end of file
+}
--- a/kirki/app/Http/Requests/Page/PageSettingsRequest.php
+++ b/kirki/app/Http/Requests/Page/PageSettingsRequest.php
@@ -7,18 +7,28 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class PageSettingsRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'seo_settings' => ensure_array($this->seo_settings),
+ 'custom_code' => ensure_array($this->custom_code),
+ ]);
+ }
+
public function rules()
{
return [
- 'page_title' => 'string',
- 'slug' => 'string',
- 'page_description' => 'string',
- 'post_status' => 'string',
- 'featured_image' => 'url',
- 'seo_settings' => 'array',
- 'custom_code' => 'array',
+ 'page_title' => 'nullable|string',
+ 'slug' => 'nullable|string',
+ 'page_description' => 'nullable|string',
+ 'post_status' => 'nullable|string',
+ 'featured_image' => 'nullable|url',
+ 'seo_settings' => 'nullable|array',
+ 'custom_code' => 'nullable|array',
];
}
--- a/kirki/app/Http/Requests/Page/PageUpdateRequest.php
+++ b/kirki/app/Http/Requests/Page/PageUpdateRequest.php
@@ -12,9 +12,9 @@
public function rules()
{
return [
- 'post_title' => 'string',
- 'post_name' => 'string',
- 'post_status' => 'string',
+ 'post_title' => 'nullable|string',
+ 'post_name' => 'nullable|string',
+ 'post_status' => 'nullable|string',
];
}
--- a/kirki/app/Http/Requests/Page/PopupRequest.php
+++ b/kirki/app/Http/Requests/Page/PopupRequest.php
@@ -7,14 +7,25 @@
use KirkiFrameworkHttpRequest;
use KirkiFrameworkSanitizer;
+use function KirkiAppensure_array;
+
class PopupRequest extends Request
{
+ protected function prepare_for_validation()
+ {
+ $this->merge([
+ 'blocks' => ensure_array($this->blocks),
+ 'styleBlocks' => ensure_array($this->styleBlocks),
+ 'usedFonts' => ensure_array($this->usedFonts),
+ ]);
+ }
+
public function rules()
{
return [
- 'blocks' => 'array',
- 'styleBlocks' => 'array',
- 'usedFonts' => 'array',
+ 'blocks' => 'nullable|array',
+ 'styleBlocks' => 'nullable|array',
+ 'usedFonts' => 'nullable|array',
];
}
--- a/kirki/app/Http/Requests/Page/RenameStagingVersionRequest.php
+++ b/kirki/app/Http/Requests/Page/RenameStagingVersionRequest.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace KirkiAppHttpRequestsPage;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkHttpRequest;
+use KirkiFrameworkSanitizer;
+
+class RenameStagingVersionRequest extends Request
+{
+ public function rules()
+ {
+ return [
+ 'version_id' => 'required|integer',
+ 'name' => 'required|string',
+ ];
+ }
+
+ public function filters()
+ {
+ return [
+ 'version_id' => Sanitizer::INT,
+ 'name' => Sanitizer::TEXT,
+ ];
+ }
+}
No newline at end of file
--- a/kirki/app/Http/Requests/Page/StagingVersionRequest.php
+++ b/kirki/app/Http/Requests/Page/StagingVersionRequest.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace KirkiAppHttpRequestsPage;
+
+defined('ABSPATH') || exit;
+
+use KirkiFrameworkHttpRequest;
+use KirkiFrameworkSanitizer;
+
+class StagingVersionRequest extends Request
+{
+ public function rules()
+ {
+ return [
+ 'version_id' => 'required|integer',
+ ];
+ }
+
+ public function filters()
+ {
+ return [
+ 'version_id' => Sanitizer::INT,
+ ];
+ }
+}
No newline at end of file
--- a/kirki/app/Http/Requests/Post/PostSlugValidationRequest.php
+++ b/kirki/app/Http/Requests/Post/PostSlugValidationRequest.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace KirkiAppHttpRequestsPost;
+
+use KirkiFrameworkHttpRequest;
+use KirkiFrameworkSanitizer;
+
+class PostSlugValidationRequest extends Request
+{
+ /**
+ * Validation rules.
+ */
+ public function rules()
+ {
+ return [
+ 'post_id' => 'nullable|integer',
+ 'post_type' => 'required|string',
+ 'post_name' => 'required|string',
+ ];
+ }
+
+ /**
+ * Sanitization filters.
+ */
+ public function filters()
+ {
+ return [
+ 'post_id' => Sanitizer::INT,
+ 'post_type' => Sanitizer::TEXT,
+ 'post_name' => Sanitizer::TEXT,
+ ];
+ }
+}
--- a/kirki/app/Managers/PageManager.php
+++ b/kirki/app/Managers/PageManager.php
@@ -5,6 +5,7 @@
defined('ABSPATH') || exit;
use KirkiAppConstantsKirkiDateTimeFormat;
+use KirkiAppConstantsOptionKeys;
use KirkiAppConstantsPageMetaKeys;
use KirkiAppConstantsPostTypes;
use KirkiAppModelsPage as PageModel;
@@ -12,12 +13,13 @@
use KirkiAppModelsPostMeta;
use KirkiAppSupportsFacadesGlobalData;
use KirkiFrameworkCollectionsCollection;
+use KirkiFrameworkSupportsFacadesOption;
use KirkiFrameworkConstantsDateTimeFormats;
-use KirkiFrameworkSupportsArr;
use KirkiFrameworkSupportsFacadesDate;
use function KirkiAppget_editor_mode;
use function KirkiAppget_timezone;
+use function KirkiAppis_falsy;
use function KirkiAppis_truthy;
use function KirkiFrameworkcollection;
use function KirkiFrameworkuser;
@@ -170,9 +172,10 @@
* Update last edited datetime of stage version
*
* @param int $page_id
+ * @param bool $has_legacy_global_style default false
* @return array|false
*/
- public function set_last_edited_datetime_of_stage_version(int $page_id)
+ public function set_last_edited_datetime_of_stage_version(int $page_id, bool $has_legacy_global_style = false)
{
$staged_versions = $this->get_all_staged_versions($page_id);
@@ -182,10 +185,10 @@
return false;
}
- $this->staged_versions = $staged_versions->map(function ($item, $index) use ($total_versions) {
+ $this->staged_versions = $staged_versions->map(function ($item, $index) use ($total_versions, $has_legacy_global_style) {
if ($index === $total_versions - 1) {
$item['last_updated'] = Date::now(get_timezone(true))->format(DateTimeFormats::DB_DATETIME);
- $item['no_legacy_global_style'] = true;
+ $item['no_legacy_global_style'] = !$has_legacy_global_style;
}
return $item;
@@ -259,7 +262,7 @@
return $this->staged_versions = collection();
}
- $staged_versions = $this->create_first_stage_version($page_id);
+ $staged_versions = $this->create_first_stage_version_if_empty($page_id);
} else {
$staged_versions = collection($staged_versions);
}
@@ -289,9 +292,12 @@
* @param int $page_id
* @return Collection
*/
- private function create_first_stage_version(int $page_id)
+ private function create_first_stage_version_if_empty(int $page_id)
{
- $new_version = $this->add_stage_version($page_id, 1);
+ // When stage version is empty, it means there is legacy global style blocks
+ $has_legacy_global_style = true;
+
+ $new_version = $this->add_stage_version($page_id, 1, [], false, $has_legacy_global_style);
$style_blocks_old_data = PostMeta::get_meta_value($page_id, PageMetaKeys::STYLE_BLOCKS, []);
$this->save_style_blocks($page_id, $style_blocks_old_data, $new_version);
@@ -307,8 +313,8 @@
$kirki_block_data = PostMeta::get_meta_value($page_id, PageMetaKeys::BLOCKS, []);
$this->save_blocks($page_id, $kirki_block_data, $new_version);
-
- return $this->publish_stage_version($page_id);
+
+ return $this->publish_stage_version($page_id, $has_legacy_global_style);
}
/**
@@ -318,9 +324,10 @@
* @param int $version_number
* @param array $prev_versions
* @param array|false $being_restored
+ * @param bool $has_legacy_global_style default false
* @return int
*/
- private function add_stage_version(int $page_id, int $version_number, array $prev_versions = [], $being_restored = false)
+ private function add_stage_version(int $page_id, int $version_number, array $prev_versions = [], $being_restored = false, $has_legacy_global_style = false)
{
$version_name = $being_restored
? sprintf(__('[Restored] %s', 'kirki'), $being_restored['name'])
@@ -328,6 +335,10 @@
$datetime = wp_date(KirkiDateTimeFormat::DB_DATETIME); // @todo: improve later
+ if (!empty($being_restored)) {
+ $has_legacy_global_style = !($being_restored['has_legacy_global_style'] ?? false);
+ }
+
$new_version = [
'version' => $version_number,
'edited_by_id' => user()->get_id(),
@@ -336,7 +347,7 @@
'last_updated' => $datetime,
'name' => $version_name,
'publish' => false,
- 'no_legacy_global_style' => true,
+ 'no_legacy_global_style' => !$has_legacy_global_style,
];
$prev_versions[] = $new_version;
@@ -352,19 +363,20 @@
* Publish stage version
*
* @param int $page_id
+ * @param bool $has_legacy_global_style default false
* @return Collection
*/
- public function publish_stage_version(int $page_id)
+ public function publish_stage_version(int $page_id, bool $has_legacy_global_style = false)
{
$stage_must = false;
$version_id = $this->get_most_recent_stage_version($page_id, $stage_must);
$this->staged_versions = $this->get_all_staged_versions($page_id)
- ->map(function ($item) use ($version_id) {
+ ->map(function ($item) use ($version_id, $has_legacy_global_style) {
$is_published = isset($item['version']) && intval($item['version']) === intval($version_id);
$item['publish'] = $is_published;
- $item['no_legacy_global_style'] = true;
+ $item['no_legacy_global_style'] = !$has_legacy_global_style;
return $item;
});
@@ -379,6 +391,112 @@
}
/**
+ * Rename stage version
+ *
+ * @param int $page_id
+ * @param int $version_id
+ * @param string $new_name
+ * @return Collection
+ */
+ public function rename_stage_version(int $page_id, int $version_id, string $new_name)
+ {
+ $staged_versions = $this->get_all_staged_versions($page_id, false);
+
+ $this->staged_versions = $staged_versions->map(function ($item) use ($version_id, $new_name) {
+ if (is_array($item) && isset($item['version']) && intval($item['version']) === $version_id) {
+ $item['name'] = $new_name;
+ }
+
+ return $item;
+ }
+ );
+
+ PostMeta::update_meta_value($page_id, PageMetaKeys::STAGED_VERSIONS, $this->staged_versions->to_array());
+
+ return $this->staged_versions;
+ }
+
+ /**
+ * Remove stage version
+ *
+ * @param int $page_id
+ * @param int $version_id
+ * @return Collection
+ */
+ public function remove_stage_version(int $page_id, int $version_id)
+ {
+ $stage_only = true;
+ $meta_keys = [];
+
+ foreach([
+ PageMetaKeys::STYLE_BLOCKS,
+ PageMetaKeys::GLOBAL_STYLE_BLOCK_DEPRECATED,
+ PageMetaKeys::USED_GLOBAL_STYLE_BLOCK_IDS,
+ PageMetaKeys::USED_STYLE_BLOCK_IDS,
+ PageMetaKeys::USED_FONT_LIST,
+ PageMetaKeys::BLOCKS
+ ] as $key) {
+ $meta_keys[] = $this->get_staged_meta_name($key, $page_id, $version_id, $stage_only);
+ }
+
+ PostMeta::query()
+ ->where('post_id', $page_id)
+ ->where_in('meta_key', $meta_keys)
+ ->delete();
+
+ $staged_versions = $this->get_all_staged_versions($page_id, false);
+
+ $this->staged_versions = $staged_versions->filter(function ($item) use ($version_id) {
+ // Filter out the version which is matched and not published
+ return !(
+ is_array($item)
+ && isset($item['publish'], $item['version'])
+ && is_falsy($item['publish'])
+ && intval($item['version']) === $version_id
+ );
+ })->values();
+
+ PostMeta::update_meta_value($page_id, PageMetaKeys::STAGED_VERSIONS, $this->staged_versions->to_array());
+
+ return $this->staged_versions;
+ }
+
+ protected function restore_page_meta(string $meta_key,int $page_id, int $old_version_id, int $new_version_id)
+ {
+ $old_meta_key = $this->get_staged_meta_name($meta_key, $page_id, $old_version_id);
+ $old_data = PostMeta::get_m