Files
masimmo.pl/modules/creativeelements/core/documents-manager.php
2024-11-20 09:09:44 +01:00

641 lines
18 KiB
PHP

<?php
/**
* Creative Elements - live Theme & Page Builder
*
* @author WebshopWorks, Elementor
* @copyright 2019-2023 WebshopWorks.com & Elementor.com
* @license https://www.gnu.org/licenses/gpl-3.0.html
*/
namespace CE;
defined('_PS_VERSION_') or exit;
use CE\CoreXCommonXModulesXAjaxXModule as Ajax;
use CE\CoreXDocumentTypesXContent as Content;
use CE\CoreXDocumentTypesXPost as Post;
use CE\ModulesXCatalogXDocumentsXProduct as Product;
use CE\ModulesXCatalogXDocumentsXProductMiniature as ProductMiniature;
use CE\ModulesXCatalogXDocumentsXProductQuickView as ProductQuickView;
use CE\ModulesXThemeXDocumentsXFooter as Footer;
use CE\ModulesXThemeXDocumentsXHeader as Header;
use CE\ModulesXThemeXDocumentsXPageContact as PageContact;
use CE\ModulesXThemeXDocumentsXPageIndex as PageIndex;
use CE\ModulesXThemeXDocumentsXPageNotFound as PageNotFound;
use CE\TemplateLibraryXSourceLocal as SourceLocal;
/**
* Elementor documents manager.
*
* Elementor documents manager handler class is responsible for registering and
* managing Elementor documents.
*
* @since 2.0.0
*/
class CoreXDocumentsManager
{
/**
* Registered types.
*
* Holds the list of all the registered types.
*
* @since 2.0.0
*
* @var Document[]
*/
protected $types = [];
/**
* Registered documents.
*
* Holds the list of all the registered documents.
*
* @since 2.0.0
*
* @var Document[]
*/
protected $documents = [];
/**
* Current document.
*
* Holds the current document.
*
* @since 2.0.0
*
* @var Document
*/
protected $current_doc;
/**
* Switched data.
*
* Holds the current document when changing to the requested post.
*
* @since 2.0.0
*
* @var array
*/
protected $switched_data = [];
protected $cpt = [];
/**
* Documents manager constructor.
*
* Initializing the Elementor documents manager.
*
* @since 2.0.0
*/
public function __construct()
{
add_action('elementor/documents/register', [$this, 'registerDefaultTypes'], 0);
add_action('elementor/ajax/register_actions', [$this, 'registerAjaxActions']);
// add_filter('post_row_actions', [$this, 'filterPostRowActions'], 11, 2);
// add_filter('page_row_actions', [$this, 'filterPostRowActions'], 11, 2);
// add_filter('user_has_cap', [$this, 'removeUserEditCap'], 10, 3);
add_filter('elementor/editor/localize_settings', [$this, 'localizeSettings']);
}
/**
* Register ajax actions.
*
* Process ajax action handles when saving data and discarding changes.
*
* Fired by `elementor/ajax/register_actions` action.
*
* @since 2.0.0
*
* @param Ajax $ajax_manager An instance of the ajax manager
*/
public function registerAjaxActions($ajax_manager)
{
$ajax_manager->registerAjaxAction('save_builder', [$this, 'ajaxSave']);
$ajax_manager->registerAjaxAction('discard_changes', [$this, 'ajaxDiscardChanges']);
$ajax_manager->registerAjaxAction('get_document_config', [$this, 'ajaxGetDocumentConfig']);
}
/**
* Register default types.
*
* Registers the default document types.
*
* @since 2.0.0
*/
public function registerDefaultTypes()
{
$default_types = [
'post' => Post::getClassFullName(),
'content' => Content::getClassFullName(),
'header' => Header::getClassFullName(),
'footer' => Footer::getClassFullName(),
'product' => Product::getClassFullName(),
'product-quick-view' => ProductQuickView::getClassFullName(),
'product-miniature' => ProductMiniature::getClassFullName(),
'page-index' => PageIndex::getClassFullName(),
'page-contact' => PageContact::getClassFullName(),
'page-not-found' => PageNotFound::getClassFullName(),
];
foreach ($default_types as $type => $class) {
$this->registerDocumentType($type, $class);
}
}
/**
* Register document type.
*
* Registers a single document.
*
* @since 2.0.0
*
* @param string $type Document type name
* @param string $class the name of the class that registers the document type
* Full name with the namespace
*
* @return DocumentsManager The updated document manager instance
*/
public function registerDocumentType($type, $class)
{
$this->types[$type] = $class;
$cpt = $class::getProperty('cpt');
if ($cpt) {
foreach ($cpt as $post_type) {
$this->cpt[$post_type] = $type;
}
}
if ($class::getProperty('register_type')) {
SourceLocal::addTemplateType($type);
}
return $this;
}
/**
* Get document.
*
* Retrieve the document data based on a post ID.
*
* @since 2.0.0
*
* @param int $post_id Post ID
* @param bool $from_cache Optional. Whether to retrieve cached data. Default is true
*
* @return false|Document Document data or false if post ID was not entered
*/
public function get($post_id, $from_cache = true)
{
$this->registerTypes();
$post_id = absint($post_id);
if (!$post_id || !get_post($post_id)) {
return false;
}
$post_id = apply_filters('elementor/documents/get/post_id', "$post_id");
if (!$from_cache || !isset($this->documents[$post_id])) {
if ($parent = wp_is_post_autosave($post_id)) {
$post_type = get_post_type($parent);
} else {
$post_type = get_post_type($post_id);
}
$doc_type = 'post';
if (isset($this->cpt[$post_type])) {
$doc_type = $this->cpt[$post_type];
}
// $meta_type = get_post_meta($post_id, Document::TYPE_META_KEY, true);
$uid = UId::parse($post_id);
$meta_type = get_post($uid)->template_type;
if ($meta_type && isset($this->types[$meta_type])) {
$doc_type = $meta_type;
}
$doc_type_class = $this->getDocumentType($doc_type);
$this->documents[$post_id] = new $doc_type_class([
'post_id' => $post_id,
]);
}
return $this->documents[$post_id];
}
/**
* Get document or autosave.
*
* Retrieve either the document or the autosave.
*
* @since 2.0.0
*
* @param int $id Post ID
* @param int $user_id User ID. Default is `0`
*
* @return false|Document The document if it exist, False otherwise
*/
public function getDocOrAutoSave($id, $user_id = 0)
{
$document = $this->get($id);
if ($document && $document->getAutosaveId($user_id)) {
$document = $document->getAutosave($user_id);
}
return $document;
}
/**
* Get document for frontend.
*
* Retrieve the document for frontend use.
*
* @since 2.0.0
*
* @param int $post_id Optional. Post ID. Default is `0`
*
* @return false|Document The document if it exist, False otherwise
*/
public function getDocForFrontend($post_id)
{
if (\CreativeElements::getPreviewUId(false)) {
$document = $this->getDocOrAutoSave($post_id, get_current_user_id());
} else {
$document = $this->get($post_id);
}
return $document;
}
/**
* Get document type.
*
* Retrieve the type of any given document.
*
* @since 2.0.0
*
* @param string $type
* @param string $fallback
*
* @return Document|bool The type of the document
*/
public function getDocumentType($type, $fallback = 'post')
{
$types = $this->getDocumentTypes();
if (isset($types[$type])) {
return $types[$type];
}
if (isset($types[$fallback])) {
return $types[$fallback];
}
return false;
}
/**
* Get document types.
*
* Retrieve the all the registered document types.
*
* @since 2.0.0
*
* @param array $args Optional. An array of key => value arguments to match against
* the properties. Default is empty array.
* @param string $operator Optional. The logical operation to perform. 'or' means only one
* element from the array needs to match; 'and' means all elements
* must match; 'not' means no elements may match. Default 'and'.
*
* @return Document[] All the registered document types
*/
public function getDocumentTypes($args = [], $operator = 'and')
{
$this->registerTypes();
if (!empty($args)) {
// $types_properties = $this->getTypesProperties();
// $filtered = wp_filter_object_list($types_properties, $args, $operator);
// return array_intersect_key($this->types, $filtered);
throw new \RuntimeException('TODO');
}
return $this->types;
}
/**
* Get document types with their properties.
*
* @return array A list of properties arrays indexed by the type
*/
public function getTypesProperties()
{
$types_properties = [];
foreach ($this->getDocumentTypes() as $type => $class) {
$types_properties[$type] = $class::getProperties();
}
return $types_properties;
}
/**
* Create a document.
*
* Create a new document using any given parameters.
*
* @since 2.0.0
*
* @param string $type Document type
* @param array $post_data An array containing the post data
* @param array $meta_data An array containing the post meta data
*
* @return Document The type of the document
*/
public function create($type, $post_data = [], $meta_data = [])
{
$class = $this->getDocumentType($type, false);
if (!$class) {
return new WPError(500, sprintf('Type %s does not exist.', $type));
}
// if (empty($post_data['post_title'])) {
// $post_data['post_title'] = __('Elementor');
// if ('post' !== $type) {
// $post_data['post_title'] = sprintf(
// __('Elementor %s'),
// call_user_func([$class, 'get_title'])
// );
// }
// $update_title = true;
// }
// $meta_data['_elementor_edit_mode'] = 'builder';
// $meta_data[Document::TYPE_META_KEY] = $type;
$post_data['template_type'] = $type;
// $post_data['meta_input'] = $meta_data;
$post_id = wp_insert_post($post_data);
// if (!empty($update_title)) {
// $post_data['ID'] = $post_id;
// $post_data['post_title'] .= ' #' . $post_id;
// // The meta doesn't need update.
// unset($post_data['meta_input']);
// wp_update_post($post_data);
// }
/* @var Document $document */
$document = new $class([
'post_id' => $post_id,
]);
// Let the $document to re-save the template type by his way + version.
$document->save([]);
return $document;
}
// public function removeUserEditCap($allcaps, $caps, $args)
// public function filterPostRowActions($actions, $post)
/**
* Save document data using ajax.
*
* Save the document on the builder using ajax, when saving the changes, and refresh the editor.
*
* @since 2.0.0
*
* @param $request Post ID
*
* @return array The document data after saving
*
* @throws \Exception if current user don't have permissions to edit the post or the post is not using Elementor
*/
public function ajaxSave($request)
{
$document = $this->get($request['editor_post_id']);
// if (!$document->isBuiltWithElementor() || !$document->isEditableByCurrentUser()) {
if (!$document->isEditableByCurrentUser()) {
throw new \Exception('Access denied.');
}
$this->switchToDocument($document);
$post = $document->getPost();
// Set the post as global post.
Plugin::$instance->db->switchToPost($post->ID);
$status = DB::STATUS_DRAFT;
if (isset($request['status']) && in_array($request['status'], [DB::STATUS_PUBLISH, DB::STATUS_PRIVATE, DB::STATUS_AUTOSAVE], true)) {
$status = $request['status'];
}
if (DB::STATUS_AUTOSAVE === $status) {
// If the post is a draft - save the `autosave` to the original draft.
// Allow a revision only if the original post is already published.
if (in_array($post->post_status, [DB::STATUS_PUBLISH, DB::STATUS_PRIVATE], true)) {
$document = $document->getAutosave(0, true);
}
}
// Set default page template because the footer-saver doesn't send default values,
// But if the template was changed from canvas to default - it needed to save.
if (Utils::isCptCustomTemplatesSupported($post) && !isset($request['settings']['template'])) {
$request['settings']['template'] = 'default';
}
$data = [
'elements' => $request['elements'],
'settings' => $request['settings'],
];
$document->save($data);
// Refresh after save.
$document = $this->get($post->ID, false);
$return_data = [
'status' => $document->getPost()->post_status,
'config' => [
'document' => [
'last_edited' => $document->getLastEdited(),
'urls' => [
'wp_preview' => $document->getWpPreviewUrl(),
],
],
],
];
/*
* Returned documents ajax saved data.
*
* Filters the ajax data returned when saving the post on the builder.
*
* @since 2.0.0
*
* @param array $return_data The returned data
* @param Document $document The document instance
*/
$return_data = apply_filters('elementor/documents/ajax_save/return_data', $return_data, $document);
return $return_data;
}
/**
* Ajax discard changes.
*
* Load the document data from an autosave, deleting unsaved changes.
*
* @since 2.0.0
*
* @param $request
*
* @return bool True if changes discarded, False otherwise
*/
public function ajaxDiscardChanges($request)
{
$document = $this->get($request['editor_post_id']);
$autosave = $document->getAutosave();
if ($autosave) {
$success = $autosave->delete();
} else {
$success = true;
}
return $success;
}
public function ajaxGetDocumentConfig($request)
{
$post_id = absint($request['id']);
Plugin::$instance->editor->setPostId($post_id);
$document = $this->getDocOrAutoSave($post_id);
if (!$document) {
throw new \Exception('Not Found.');
}
if (!$document->isEditableByCurrentUser()) {
throw new \Exception('Access denied.');
}
// Set the global data like $post, $authordata and etc
Plugin::$instance->db->switchToPost($post_id);
$this->switchToDocument($document);
// Change mode to Builder
// Plugin::$instance->db->setIsElementorPage($post_id);
$doc_config = $document->getConfig();
return $doc_config;
}
/**
* Switch to document.
*
* Change the document to any new given document type.
*
* @since 2.0.0
*
* @param Document $document The document to switch to
*/
public function switchToDocument($document)
{
// If is already switched, or is the same post, return.
if ($this->current_doc === $document) {
$this->switched_data[] = false;
return;
}
$this->switched_data[] = [
'switched_doc' => $document,
'original_doc' => $this->current_doc, // Note, it can be null if the global isn't set
];
$this->current_doc = $document;
}
/**
* Restore document.
*
* Rollback to the original document.
*
* @since 2.0.0
*/
public function restoreDocument()
{
$data = array_pop($this->switched_data);
// If not switched, return.
if (!$data) {
return;
}
$this->current_doc = $data['original_doc'];
}
/**
* Get current document.
*
* Retrieve the current document.
*
* @since 2.0.0
*
* @return Document The current document
*/
public function getCurrent()
{
return $this->current_doc;
}
public function localizeSettings($settings)
{
$translations = [];
foreach ($this->getDocumentTypes() as $type => $class) {
$translations[$type] = $class::getTitle();
}
return array_replace_recursive($settings, [
'i18n' => $translations,
]);
}
private function registerTypes()
{
if (!did_action('elementor/documents/register')) {
/*
* Register Elementor documents.
*
* @since 2.0.0
*
* @param DocumentsManager $this The document manager instance
*/
do_action('elementor/documents/register', $this);
}
}
}