Commit realizado el 12:13:52 08-04-2024

This commit is contained in:
Pagina Web Monito
2024-04-08 12:13:55 -04:00
commit 0c33094de9
7815 changed files with 1365694 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
<?php
/**
* Theme Options library.
*
* Registers post types to be used in the "Theme Options" library.
*
* @package Divi
* @subpackage Cloud
* @since ??
*/
/**
* Core class used to implement "Theme Options" library.
*
* Register post types & taxonomies to be used in "Theme Options" library.
*/
class ET_Builder_Theme_Options_Library {
/**
* Instance of `ET_Builder_Theme_Options_Library`.
*
* @var ET_Builder_Theme_Options_Library
*/
private static $_instance;
/**
* Instance of`ET_Core_Data_Utils`.
*
* @var ET_Core_Data_Utils
*/
protected static $_;
/**
* List of i18n strings.
*
* @var mixed[]
*/
protected static $_i18n;
/**
* ET_Builder_Post_Taxonomy_LayoutCategory instance.
*
* Shall be used for querying `et_theme_options` taxonomy.
*
* @var ET_Builder_Post_Taxonomy_LayoutCategory
*/
public $theme_options_categories;
/**
* ET_Builder_Post_Taxonomy_LayoutTag instance.
*
* Shall be used for querying `et_theme_options` taxonomy .
*
* @var ET_Builder_Post_Taxonomy_LayoutTag
*/
public $theme_options_tags;
/**
* ET_Builder_Post_Type_TBItem instance.
*
* Shall be used for querying `et_tb_item` posts .
*
* @var ET_Builder_Post_Type_TBItem
*/
public $theme_options;
/**
* Class constructor.
*/
public function __construct() {
$this->_instance_check();
$this->_register_cpt_and_taxonomies();
}
/**
* Dies if an instance already exists.
*/
protected function _instance_check() {
if ( self::$_instance ) {
et_error( 'Multiple instances are not allowed!' );
wp_die();
}
}
/**
* Registers the Theme Options Library's custom post type and its taxonomies.
*/
protected function _register_cpt_and_taxonomies() {
$files = [
ET_THEME_OPTIONS_DIR . 'post/type/ThemeOptions.php',
];
if ( ! $files ) {
return;
}
foreach ( $files as $file ) {
require_once $file;
}
$this->theme_options = ET_Post_Type_Theme_Options::instance();
$this->theme_options_categories = ET_Builder_Post_Taxonomy_LayoutCategory::instance();
$this->theme_options_tags = ET_Builder_Post_Taxonomy_LayoutTag::instance();
// We manually call register_all() now to ensure the CPT and taxonomies are registered
// at exactly the same point during the request that they were in prior releases.
ET_Builder_Post_Type_Layout::register_all( 'builder' );
}
/**
* Returns the ET_Builder_Theme_Options_Library instance.
*
* @return ET_Builder_Theme_Options_Library
*/
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self();
}
return self::$_instance;
}
}
ET_Builder_Theme_Options_Library::instance();

View File

@@ -0,0 +1,294 @@
<?php
/**
* Theme Options Library API.
*
* @package Divi
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Retrieves Theme Options Library items.
*
* @return void
*/
function et_theme_options_library_get_items() {
et_core_security_check( et_core_portability_cap( 'epanel' ), 'et_theme_options_library_get_items', 'nonce' );
$context = isset( $_POST['context'] )
? sanitize_text_field( $_POST['context'] )
: '';
if ( '' === $context ) {
wp_send_json_error( 'missing_context' );
}
$item_library_local = et_pb_theme_options_library_local();
$data = $item_library_local->get_library_items( $context );
wp_send_json_success( $data );
}
add_action( 'wp_ajax_et_theme_options_library_get_items', 'et_theme_options_library_get_items' );
/**
* Update Terms.
*
* @return void
*/
function et_theme_options_library_update_terms() {
et_core_security_check( 'manage_categories', 'et_theme_options_library_update_terms', 'nonce' );
$payload = isset( $_POST['payload'] ) ? (array) $_POST['payload'] : array(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['payload'] is an array, it's value sanitization is done at the time of accessing value.
if ( empty( $payload ) ) {
wp_send_json_error( 'Payload is empty.' );
}
$item_library_local = et_pb_theme_options_library_local();
$response = $item_library_local->perform_terms_update( $payload );
wp_send_json_success( $response );
}
add_action( 'wp_ajax_et_theme_options_library_update_terms', 'et_theme_options_library_update_terms' );
/**
* Export the Theme Options library item.
* This function only retrieves the data.
* All the permissions checks should be performed at the top level function which calls this one.
*
* @since 4.19.0
*
* @param int $id Item ID.
* @param array $cloud_content Optional cloud content.
*
* @return array
*/
function et_theme_options_library_export_item_data( $id, $cloud_content ) {
if ( empty( $cloud_content ) ) {
if ( empty( $id ) ) {
return false;
}
$id = absint( $id );
$post = get_post( $id );
$export_content = $post->post_content;
$export_content = json_decode( $export_content );
} else {
$export_content = $cloud_content;
}
if ( empty( $export_content ) ) {
return;
}
$transient = 'et_theme_options_export_' . get_current_user_id() . '_' . $id;
set_transient( $transient, $export_content, 60 * 60 * 24 );
return $export_content;
}
/**
* Export Theme options Library item.
*
* @return void
*/
function et_theme_options_library_export_item() {
et_core_security_check( et_core_portability_cap( 'epanel' ), 'et_theme_options_library_export_item', 'nonce' );
$post_id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;
$cloud_content = isset( $_POST['cloudContent'] ) ? $_POST['cloudContent'] : ''; // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['cloudContent'] is an array, it's value sanitization is done at the time of accessing value.
$post_type = get_post_type( $post_id );
// When exporting cloud content this check doesn't make sense as we already have data.
if ( empty( $cloud_content ) && ( ! current_user_can( 'edit_post', $post_id ) || ET_THEME_OPTIONS_POST_TYPE !== $post_type ) ) {
wp_send_json_error( 'You do not have permission.' );
}
$response = et_theme_options_library_export_item_data( $post_id, $cloud_content );
if ( ! $response ) {
wp_send_json_error( 'Error: Wrong data provided.' );
}
wp_send_json_success( $response );
}
add_action( 'wp_ajax_et_theme_options_library_export_item', 'et_theme_options_library_export_item' );
/**
* Download exported Theme options Library item.
*
* @return void
*/
function et_theme_options_library_export_item_download() {
et_core_security_check( et_core_portability_cap( 'epanel' ), 'et_theme_options_library_export_item', 'nonce', '_GET' );
$id = ! empty( $_GET['id'] ) ? absint( $_GET['id'] ) : 0;
$file_name = empty( $_GET['fileName'] ) ? 'Theme Options' : sanitize_file_name( $_GET['fileName'] );
header( 'Content-Description: File Transfer' );
header( 'Content-Disposition: attachment; filename="' . $file_name . '.json"' );
header( 'Content-Type: application/json' );
header( 'Pragma: no-cache' );
$transient = 'et_theme_options_export_' . get_current_user_id() . '_' . $id;
$export_content = get_transient( $transient );
delete_transient( $transient );
echo wp_json_encode( $export_content );
wp_die();
}
add_action( 'wp_ajax_et_theme_options_library_export_item_download', 'et_theme_options_library_export_item_download' );
/**
* Update theme options Library item.
*
* @return void
*/
function et_theme_options_library_update_item() {
et_core_security_check( et_core_portability_cap( 'epanel' ), 'et_theme_options_library_update_item', 'nonce' );
$payload = isset( $_POST['payload'] ) ? $_POST['payload'] : array(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['payload'] is an array, it's value sanitization is done at the time of accessing value.
if ( empty( $payload ) ) {
wp_send_json_error( 'Payload is empty.' );
}
$item_library_local = et_pb_theme_options_library_local();
$response = $item_library_local->perform_item_update( $payload );
if ( ! $response ) {
wp_send_json_error( 'Error: Wrong data provided.' );
}
wp_send_json_success( $response );
}
add_action( 'wp_ajax_et_theme_options_library_update_item', 'et_theme_options_library_update_item' );
/**
* Get cloud access token.
*
* @return void
*/
function et_theme_options_library_get_token() {
et_core_security_check( et_core_portability_cap( 'epanel' ), 'et_theme_options_library_get_token', 'nonce' );
wp_send_json_success(
array( 'accessToken' => get_transient( 'et_cloud_access_token' ) )
);
}
add_action( 'wp_ajax_et_theme_options_library_get_token', 'et_theme_options_library_get_token' );
/**
* Get Theme options Library item.
*
* @since ??
* Retrieves theme options library item content.
*
* @return void
*/
function et_theme_options_library_get_item_content() {
et_core_security_check( et_core_portability_cap( 'epanel' ), 'et_theme_options_library_get_item_content', 'nonce' );
$id = isset( $_POST['et_theme_option_id'] ) ? (int) sanitize_text_field( $_POST['et_theme_option_id'] ) : 0;
if ( empty( $id ) ) {
wp_send_json_error();
}
$result = array();
$post = get_post( $id );
$post_type = ET_THEME_OPTIONS_POST_TYPE;
if ( $post_type !== $post->post_type ) {
wp_die();
}
$result = [];
$result['exported'] = json_decode( $post->post_content );
$response = wp_json_encode(
array(
'success' => true,
'data' => $result,
)
);
// Charset has to be explicitly mentioned when it is other than UTF-8.
header( 'Content-Type: application/json; charset=' . esc_attr( get_option( 'blog_charset' ) ) );
die( et_core_intentionally_unescaped( $response, 'html' ) );
}
add_action( 'wp_ajax_et_theme_options_library_get_item_content', 'et_theme_options_library_get_item_content' );
/**
* AJAX Callback: Remove the Library layout after it was moved to the Cloud.
*
* @since ??
*
* @global $_POST['payload'] Array with the layout data to remove.
*
* @return void|string JSON encoded in case of empty payload
*/
function et_theme_options_toggle_cloud_status() {
et_core_security_check( et_core_portability_cap( 'epanel' ), 'et_theme_options_library_toggle_item_location', 'nonce' );
$post_id = isset( $_POST['et_theme_option_id'] ) ? (int) sanitize_text_field( $_POST['et_theme_option_id'] ) : 0;
$post_type = get_post_type( $post_id );
if ( empty( $post_id ) ) {
wp_send_json_error( 'No post ID' );
}
$post_type = get_post_type( $post_id );
if ( ! current_user_can( 'edit_post', $post_id ) || ET_THEME_OPTIONS_POST_TYPE !== $post_type ) {
wp_send_json_error( 'You do not have permission.' );
}
wp_delete_post( $post_id, true );
$item_library_local = et_pb_theme_options_library_local();
wp_send_json_success(
array(
'localLibraryTerms' => [
'layout_category' => $item_library_local->get_formatted_library_terms(),
'layout_tag' => $item_library_local->get_formatted_library_terms( 'layout_tag' ),
],
)
);
}
add_action( 'wp_ajax_et_theme_options_toggle_cloud_status', 'et_theme_options_toggle_cloud_status' );
/**
* Delete temporary options library
*/
function et_theme_options_delete_temp_options() {
et_core_security_check( et_core_portability_cap( 'epanel' ), 'et_theme_options_delete_temp_options' );
$deleted = delete_option( 'et_divi_' . get_current_user_id() );
if ( $deleted ) {
return wp_send_json_success();
}
return wp_send_json_error();
}
add_action( 'wp_ajax_et_theme_options_delete_temp_options', 'et_theme_options_delete_temp_options' );

View File

@@ -0,0 +1,76 @@
// External Dependencies
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import $ from 'jquery';
import App from 'cerebral';
import Devtools from 'cerebral/devtools';
import { Container } from '@cerebral/react';
// Internal Dependencies
import store from './store/index';
import ThemeOptionsApp from './components/App';
const initialState = {
content: '',
context: 'theme-options',
items: [],
showLibrary: false,
showPortability: false,
showSave: false,
};
const unMountCommonLibraryApp = () => {
const container = document.getElementById('et-theme-options-container');
if (container) {
unmountComponentAtNode(container);
container.remove();
}
}
// Note: Hyphen is used to stay consistent w/ the Cloud context.
$(window).on(`et_theme-options_container_ready`, (event, preferences) => {
let devtools = null;
if (process.env.NODE_ENV === 'development') {
devtools = Devtools({
host: '127.0.0.1:22722',
reconnect: false,
bigComponentsWarning: 15,
});
}
const modalType = preferences?.modalType || '';
const state = {
...initialState,
modalType
};
state.sidebarLabel = preferences?.sidebarLabel || '';
state.builtFor = preferences?.builtFor ?? 'Divi';
const app = App(store(state), {
devtools,
returnSequencePromise: true,
});
const {
containerId = 'et-theme-options-container',
containerClass = 'et-theme-options-container'
} = preferences;
$(document.body).first().append(`<div id=${containerId} class=${containerClass}></div>`);
render(
<Container app={app}>
<ThemeOptionsApp />
</Container>,
document.getElementById(containerId)
);
});
$(window).on('et_theme-options_container_close', () => {
unMountCommonLibraryApp();
});

View File

@@ -0,0 +1 @@
export default window.et_theme_options_data;

View File

@@ -0,0 +1,4 @@
// Portability states.
export const PORTABILITY_STATE_DEFAULT = 'default';
export const PORTABILITY_STATE_EXPORT_THEME_OPTIONS = 'export';
export const PORTABILITY_STATE_IMPORT_THEME_OPTIONS = 'import';

View File

@@ -0,0 +1,23 @@
// External dependencies.
import { get } from 'lodash';
// Internal dependencies.
import config from './config';
const i18n = (context, key, ...args) => {
const value = get(context, key, '');
if ('production' !== process.env.NODE_ENV && '' === value) {
console.error('Failed to find i18n string:', key);
}
if (args.length > 0) {
const sprintf = get(window, 'wp.i18n.sprintf');
return sprintf(value, ...args);
}
return value;
};
export default (path, key, ...args) => i18n(config.i18n, [path, key], ...args);

View File

@@ -0,0 +1,26 @@
// External dependencies.
import $ from 'jquery';
// Internal dependencies.
import config from './config';
export const request = (method, data, options = {}) => {
const deferred = $.ajax({
type: method,
url: config.api,
dataType: 'json',
data,
...options,
});
return Promise.resolve(deferred.promise())
.then(response => {
if (false === response.success) {
return Promise.reject(response.data || {});
}
return Promise.resolve(response.data);
});
};
export const post = (data, options = {}) => request('POST', data, options);

View File

@@ -0,0 +1,12 @@
// Set global variable to detect new library item creation.
window.themeOptionsLibraryItemsLoaded = {};
export const setThemeOptionsLibraryItemsLoaded = (context, flag) => {
window.themeOptionsLibraryItemsLoaded = {
[context] : flag,
};
};
export const setThemeOptionsLibraryToken = (token) => {
window.globalCloudToken = token;
};

View File

@@ -0,0 +1,81 @@
import {
noop,
trim,
set,
} from 'lodash';
import config from '@common-ui/lib/config';
import { post } from '@common-ui/lib/request';
import { saveToCloudPure } from '@cloud/app/lib/api';
/* eslint-disable import/prefer-default-export */
const saveThemeOptionsToLocal = (item, content) => {
const {
item_name,
selected_cats,
new_category_name,
new_tag_name,
builtFor,
} = item;
const {
nonces,
post_types,
} = config;
return post({
action: 'et_library_save_item',
et_library_save_item_nonce: nonces.et_library_save_item,
post_type: post_types.et_theme_options,
item_name,
selected_cats,
new_category_name,
new_tag_name,
content,
builtFor,
});
};
// eslint-disable-next-line arrow-parens
const sanitizeCommaSeparatedTaxNames = (taxName) => {
const categoryName = 'string' === typeof taxName && taxName ? taxName : '';
return categoryName.split(',').map(newCategory => trim(newCategory));
};
const saveThemeOptionsToCloud = async (obj, content) => {
const {
builtFor,
item_name,
new_category_name,
new_tag_name,
providedBaseUrl,
selected_cats,
selected_tags,
} = obj;
const newCategories = sanitizeCommaSeparatedTaxNames(new_category_name);
const newTags = sanitizeCommaSeparatedTaxNames(new_tag_name);
const termsData = {
tags: [...selected_tags, ...newTags],
categories: [...selected_cats, ...newCategories],
};
const newCloudItem = {
title: item_name,
content,
status: 'publish',
};
if (builtFor) {
set(newCloudItem, 'meta._built_for', builtFor);
}
return saveToCloudPure('theme-options', newCloudItem, termsData, noop, 0, providedBaseUrl);
};
/* eslint-enable */
export {
saveThemeOptionsToCloud,
saveThemeOptionsToLocal,
};

View File

@@ -0,0 +1,5 @@
// Internal dependencies.
import themeOptionsLibrary from './theme-options-library/module';
export default themeOptionsLibrary;

View File

@@ -0,0 +1,12 @@
async function importThemeOptions({ themeOptionsLibApi, props: { item } }) {
const exportedContent = item;
exportedContent.context = 'epanel';
const file = new File([JSON.stringify(exportedContent)], 'theme_option.json', { type: 'application/json' });
await themeOptionsLibApi.importContent(file);
window.location = window.location.href.replace(/reset\=true\&|\&reset\=true/, '');
};
export default {
importThemeOptions,
};

View File

@@ -0,0 +1,24 @@
import { state } from 'cerebral';
import * as sequences from './sequences';
import themeOptionsLibApi from './providers';
import { PORTABILITY_STATE_DEFAULT } from '../../lib/constants';
export default initialState => (
{
state: {
...initialState,
showSaveModal: get => 'save' === get(state`modalType`),
showLibraryModal: get => 'add' === get(state`modalType`),
itemsLoadedAndCached: false,
portability: {
state: PORTABILITY_STATE_DEFAULT,
export: {},
},
},
providers: {
themeOptionsLibApi,
},
sequences,
}
);

View File

@@ -0,0 +1,225 @@
// Internal dependencies.
import { noop } from 'lodash';
// Internal dependencies.
import { post } from '../../lib/request';
import config from '../../lib/config';
import { saveToCloudPure } from '@cloud/app/lib/api';
export default {
/**
* Gets the Theme Options library items.
*
* @param {string} context Context (a.k.a Item type).
*
* @returns {Array} Resolved value from promise. Array of objects.
*/
getItems(context) {
/* eslint-disable key-spacing */
return post({
action: 'et_theme_options_library_get_items',
context,
nonce: config.nonces.et_theme_options_library_get_items,
});
/* eslint-enable key-spacing */
},
getItemsContent(item) {
return post({
action: 'et_theme_options_library_get_item_content',
et_theme_option_id: item,
nonce: config.nonces.et_theme_options_library_get_item_content,
});
},
importContent(file) {
const formData = new FormData();
formData.append('action', 'et_core_portability_import');
formData.append('file', file, 'theme-options.json');
formData.append('context', 'epanel');
formData.append('nonce', config.nonces.et_core_portability_import);
return post(formData, {
contentType: false,
processData: false,
});
},
exportItem(id, cloudContent) {
return post({
action: 'et_theme_options_library_export_item',
nonce: config.nonces.et_theme_options_library_export_item,
id,
cloudContent,
});
},
downloadExportFile(id, fileName) {
const args = {
action: 'et_theme_options_library_export_item_download',
nonce: config.nonces.et_theme_options_library_export_item,
fileName,
id,
};
return `${config.api}?${jQuery.param(args)}`;
},
/**
* Update Local Tags/Categories and return updated list.
*
* @param {object} payload Payload.
* @returns {Array} Response.
*/
updateFilters(payload) {
return post({
action: 'et_theme_options_library_update_terms',
nonce: config.nonces.et_theme_options_library_update_terms,
payload,
});
},
/**
* Update the theme options library item.
*
* @param {object} payload Updated item details.
* @returns {Array} Resolved value from promise. Array of objects.
*/
updateItem(payload) {
return post({
action : 'et_theme_options_library_update_item',
nonce : config.nonces.et_theme_options_library_update_item,
payload,
});
},
/*
* Gets the Theme Options library item content.
*
* @returns {Array} Resolved value from promise. Array of objects.
*/
getItemContent(id) {
return post({
action: 'et_theme_options_library_get_item_content',
nonce: config.nonces.et_theme_options_library_get_item_content,
et_theme_option_id: id,
});
},
/**
* Retrieve Cloud Token.
*
* @returns {Array} Response with cloud token.
*/
getCloudToken() {
return post({
action: 'et_theme_options_library_get_token',
nonce : config.nonces.et_theme_options_library_get_token,
});
},
/**
* Remove local item.
*
* @param {int} id
* @returns {Array} Response with cloud token.
*/
export() {
return post({
action: 'et_core_portability_export',
nonce: config.nonces.et_core_portability_export,
context: 'epanel_temp',
content: false,
selection: false,
timestamp: 0,
page: 1,
});
},
saveTempOptions() {
let opsForm = jQuery('#main_options_form').formSerialize();
const nonce = `&_ajax_nonce=${config.nonces.et_core_save_theme_options}`;
opsForm += `${nonce}&action=save_epanel_temp`;
return post(opsForm);
},
download(timestamp) {
let downloadURL = config.epanel_save_url;
const query = {
timestamp,
name: '',
};
Object.entries(query).forEach(([key, value]) => {
if (value) {
downloadURL = `${downloadURL}&${key}=${value}`;
}
});
return fetch(downloadURL);
},
saveThemeOptionsToLocal(item, content) {
const {
item_name,
selected_cats,
selected_tags,
new_category_name,
new_tag_name,
} = item;
return post({
action: 'et_library_save_item',
et_library_save_item_nonce: config.nonces.et_library_save_item,
post_type: config.post_types.et_theme_options,
item_name,
selected_cats,
selected_tags,
new_category_name,
new_tag_name,
content,
});
},
saveThemeOptionsToCloud(item, content) {
const {
new_category_name,
new_tag_name,
selected_tags,
selected_cats,
item_name,
providedBaseUrl,
} = item;
const newCategories = new_category_name.split(',').map(newCategory => newCategory.trim());
const newTags = new_tag_name.split(',').map(newTag => newTag.trim());
const termsData = {
tags: [...selected_tags, ...newTags],
categories: [...selected_cats, ...newCategories],
};
const newCloudItem = {
title: item_name,
content,
status: 'publish',
};
return saveToCloudPure('theme-options', newCloudItem, termsData, noop, 0, providedBaseUrl);
},
deleteTempOptions() {
return post({
action: 'et_theme_options_delete_temp_options',
et_theme_options_delete_temp_options_nonce: config.nonces.et_theme_options_delete_temp_options,
});
},
removeLocalItem(id) {
/* eslint-disable key-spacing */
return post({
action : 'et_theme_options_toggle_cloud_status',
nonce : config.nonces.et_theme_options_library_toggle_item_location,
et_theme_option_id : id,
});
/* eslint-enable */
},
};

View File

@@ -0,0 +1,246 @@
// External dependencies.
import { props, sequence, state } from 'cerebral';
import { set, when } from 'cerebral/factories';
import { get as lodashGet } from 'lodash';
// Internal dependencies.
import { setThemeOptionsLibraryItemsLoaded, setThemeOptionsLibraryToken } from '../../lib/theme-options-library';
import actions from './actions';
import {
PORTABILITY_STATE_EXPORT_THEME_OPTIONS,
PORTABILITY_STATE_IMPORT_THEME_OPTIONS,
} from '../../lib/constants';
const closePortability = sequence('Close Theme Options portability modal', [
set(state`showPortability`, false),
set(state`importError`, false),
]);
const closeThemeOptionApp = sequence('Close theme options library app', [
set(state`modalType`, null),
]);
const loadItems = sequence('Load theme options library items', [
/* eslint-disable arrow-body-style, arrow-parens */
({ get, themeOptionsLibApi, path }) => {
const context = get(state`context`);
return themeOptionsLibApi
.getItems(context)
.then(response => path.success({
items: response,
}))
.catch(() => path.error());
},
{
success: [
set(state`items`, props`items`),
set(state`itemsLoadedAndCached`, true),
({ get }) => {
setThemeOptionsLibraryItemsLoaded(get(state`context`), true);
},
],
error: [],
},
/* eslint-enable */
]);
const updateLocalFilters = sequence('Update Local Filters', [
({ themeOptionsLibApi, path, props: { payload } }) => themeOptionsLibApi
.updateFilters(payload)
.then((response => path.success(response)))
.catch(() => path.error()),
{
success: [Promise.resolve(props`response`)],
error: [],
},
]);
const getExportedItem = sequence('Get the exported theme option content', [
({ themeOptionsLibApi, path, props: { id } }) => themeOptionsLibApi
.getItemContent(id)
.then(response => path.success(response))
.catch(() => path.error()),
{
success: [Promise.resolve(props`response`)],
error: [],
},
]);
const updateItem = sequence('Update theme options library item', [
({ themeOptionsLibApi, get, path }) => {
const payload = get(props`payload`);
return themeOptionsLibApi.updateItem(payload)
.then(response => path.success({
updatedItem: {
success: true,
data: response,
},
}))
.catch(() => path.error());
},
{
success: [Promise.resolve(props`updatedItem`)],
error: [],
},
]);
const setCloudToken = sequence('Set cloudToken', [
({ get }) => {
setThemeOptionsLibraryToken(get(props`cloudToken`));
},
]);
const cacheCloudToken = sequence('Retrieve saved Cloud Access token and save to state', [
({ themeOptionsLibApi, path }) => {
return themeOptionsLibApi.getCloudToken()
.then(cloudTokenData => {
return path.success({cloudToken: cloudTokenData.accessToken});
})
.catch(() => path.error());
},
{
success: [
setCloudToken,
],
error: [],
},
]);
const setLibraryContext = sequence('Set Theme Options library context', [
set(state`context`, props`context`),
]);
const useThemeOptions = sequence('Insert theme options into a field', [
when(props`item`, item => isNaN(parseInt(item))),
{
true: [
({ props: contextProps }) => ({ item: JSON.parse(contextProps.item) }),
actions.importThemeOptions,
],
false: [
async ({ props: { item }, themeOptionsLibApi }) => {
const data = await themeOptionsLibApi.getItemsContent(item);
const { exported } = data;
return { item: exported };
},
actions.importThemeOptions,
],
},
]);
const openPortablity = sequence('Open theme options library modal', [
({ store, props: { data } }) => {
const portabilityState = lodashGet(data, 'action');
if (PORTABILITY_STATE_EXPORT_THEME_OPTIONS === portabilityState) {
const itemLocation = lodashGet(data, 'item.item_location');
const exportItemId = lodashGet(data, 'item.id');
store.set(state`portability.export.id`, exportItemId);
if ('cloud' === itemLocation) {
const exportItemContent = lodashGet(data, 'content');
store.set(state`portability.export.content`, exportItemContent);
store.set(state`portability.export.item_location`, itemLocation);
}
}
if ([PORTABILITY_STATE_IMPORT_THEME_OPTIONS, PORTABILITY_STATE_EXPORT_THEME_OPTIONS].includes(portabilityState)) {
store.set(state`portability.state`, portabilityState);
} else {
store.set(state`portability.state`, PORTABILITY_STATE_IMPORT_THEME_OPTIONS);
}
},
set(state`showPortability`, true),
]);
const setShowLibrary = sequence('Set theme options library', [
set(state`showLibrary`, props`toggle`),
]);
const exportThemeOptions = sequence('Export theme option', [
({ themeOptionsLibApi, path, props: { id, cloudContent } }) => themeOptionsLibApi.exportItem(id, cloudContent)
.then(() => path.success())
.catch(() => path.error()),
{
success: [
({ themeOptionsLibApi, props: { id, fileName } }) => {
const downloadURI = themeOptionsLibApi.downloadExportFile(id, fileName);
window.location.assign(downloadURI);
window.ETCloudApp.emitSignal({
signal: 'finishDownload',
data: {},
});
},
closePortability,
],
error: [],
},
]);
const saveThemeOptions = sequence('Save theme options', [
async ({ themeOptionsLibApi }) => {
await themeOptionsLibApi.saveTempOptions();
const exportRestData = await themeOptionsLibApi.export();
return { timestamp: exportRestData.timestamp };
},
async ({ themeOptionsLibApi, props: contextProps }) => {
const response = await themeOptionsLibApi.download(contextProps.timestamp);
const exportedContent = await response.json();
return { content: JSON.stringify(exportedContent) };
},
when(props`item.cloud`, cloud => 'on' === cloud),
{
true: [
({ themeOptionsLibApi, props: contextProps }) => {
const { item, content } = contextProps;
return themeOptionsLibApi.saveThemeOptionsToCloud(item, content);
},
],
false: [
({ themeOptionsLibApi, props: contextProps }) => {
const { item, content } = contextProps;
return themeOptionsLibApi.saveThemeOptionsToLocal(item, content);
},
],
},
({ themeOptionsLibApi }) => themeOptionsLibApi.deleteTempOptions(),
]);
const toggleLibraryItemLocation = sequence('Remove local item from WPDB', [
/* eslint-disable-next-line arrow-body-style */
({ themeOptionsLibApi, path, props: { id } }) => {
return themeOptionsLibApi.removeLocalItem(id)
.then((response => path.success(response)))
.catch(() => path.error());
},
{
success: [Promise.resolve(props`response`)],
error: [],
},
/* eslint-enable arrow-body-style */
]);
export {
closePortability,
closeThemeOptionApp,
exportThemeOptions,
cacheCloudToken,
getExportedItem,
loadItems,
openPortablity,
setCloudToken,
setLibraryContext,
setShowLibrary,
toggleLibraryItemLocation,
updateItem,
updateLocalFilters,
useThemeOptions,
saveThemeOptions,
};

View File

@@ -0,0 +1,16 @@
<?php
/**
* "Theme Options Library" quick feature constants file.
*
* Divi Cloud Theme Options Library constants.
*
* @link https://elegantthemes.slack.com/archives/C03073D7S04/p1676610967464779?thread_ts=1676602536.658269&cid=C03073D7S04
*
* @package Divi
* @subpackage Cloud
* @since ??
*/
if ( ! defined( 'ET_THEME_OPTIONS_POST_TYPE' ) ) {
define( 'ET_THEME_OPTIONS_POST_TYPE', 'et_theme_options' );
}

View File

@@ -0,0 +1,108 @@
<?php
/**
* Register ET_THEME_OPTIONS_POST_TYPE.
*
* @since ??
*
* @package Divi
* @subpackage Cloud
* @since ??
*/
/**
* Class to handle `et_theme_options` post type.
*
* Registers TO Item.
*/
class ET_Post_Type_Theme_Options extends ET_Core_Post_Type {
/**
* {@inheritDoc}
*
* @var string
*/
protected $_owner = 'builder';
/**
* {@inheritDoc}
*
* @var string
*/
public $name = ET_THEME_OPTIONS_POST_TYPE;
/**
* {@inheritDoc}
*/
protected function _get_args() {
return array(
'can_export' => true,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => false,
'map_meta_cap' => true,
'public' => false,
'publicly_queryable' => false,
'query_var' => false,
'show_in_menu' => false,
'show_ui' => false,
'supports' => array(
'editor',
'excerpt',
'revisions',
'thumbnail',
'title',
),
'taxonomies' => array(
'layout_category',
'layout_tag',
),
);
}
/**
* {@inheritDoc}
*/
protected function _get_labels() {
return array(
'add_new' => esc_html_x( 'Add New', 'Layout', 'et_builder' ),
'add_new_item' => esc_html__( 'Add New Theme Options', 'et_builder' ),
'all_items' => esc_html__( 'All Theme Options', 'et_builder' ),
'edit_item' => esc_html__( 'Edit Theme Options', 'et_builder' ),
'name' => esc_html__( 'Theme Options', 'et_builder' ),
'new_item' => esc_html__( 'New Theme Options', 'et_builder' ),
'not_found' => esc_html__( 'Nothing found', 'et_builder' ),
'not_found_in_trash' => esc_html__( 'Nothing found in Trash', 'et_builder' ),
'parent_item_colon' => '',
'search_items' => esc_html__( 'Search Theme Options', 'et_builder' ),
'singular_name' => esc_html__( 'Theme Options', 'et_builder' ),
'view_item' => esc_html__( 'View Theme Options', 'et_builder' ),
);
}
/**
* Get the class instance.
*
* @param string $type See {@see self::$wp_type} for accepted values. Default is 'cpt'.
* @param string $name The name/slug of the post object. Default is {@see self::$name}.
*
* @return self|null
*/
public static function instance( $type = 'cpt', $name = ET_THEME_OPTIONS_POST_TYPE ) {
$instance = parent::instance( $type, $name );
if ( ! $instance ) {
$instance = new self();
}
return $instance;
}
/**
* Returns TRUE when a layout is Favorite.
*
* @param string $post_id Post ID.
*
* @return bool
*/
public function is_favorite( $post_id ) {
return 'favorite' === get_post_meta( $post_id, 'favorite_status', true );
}
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* Theme Options Library App helpers.
*
* @since ??
*
* @package Divi
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* ET_Theme_Options_Library_App class.
*
* Nonces and i18n strings needed for Theme Options Library Cloud app.
*
* @package Divi
*/
class ET_Theme_Options_Library_App {
/**
* Class instance.
*
* @var ET_Theme_Options_Library_App
*/
private static $_instance;
/**
* Get the class instance.
*
* @return ET_Theme_Options_Library_App
*/
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Get Cloud Helpers.
*
* @return array Helpers.
*/
public static function get_cloud_helpers() {
$role_capabilities = et_pb_get_role_settings();
$user_role = et_pb_get_current_user_role();
$args = array(
'et_core_portability' => true,
'context' => 'epanel',
'name' => 'save',
'nonce' => wp_create_nonce( 'et_core_portability_export' ),
);
$epanel_save_url = add_query_arg( $args, admin_url() );
// phpcs:disable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned, WordPress.Arrays.MultipleStatementAlignment.LongIndexSpaceBeforeDoubleArrow -- Aligned manually.
return [
'i18n' => [
'library' => require ET_CORE_PATH . '/i18n/library.php',
'epanel' => require ET_EPANEL_DIR . '/i18n/epanel.php',
],
'api' => admin_url( 'admin-ajax.php' ),
'capabilities' => isset( $role_capabilities[ $user_role ] ) ? $role_capabilities[ $user_role ] : array(),
'epanel_save_url' => $epanel_save_url,
'post_types' => [
'et_theme_options' => ET_THEME_OPTIONS_POST_TYPE,
],
'nonces' => [
'et_theme_options_library_get_items' => wp_create_nonce( 'et_theme_options_library_get_items' ),
'et_theme_options_library_update_terms' => wp_create_nonce( 'et_theme_options_library_update_terms' ),
'et_theme_options_library_get_item_content' => wp_create_nonce( 'et_theme_options_library_get_item_content' ),
'et_theme_options_library_import_item_content' => wp_create_nonce( 'et_theme_options_library_import_item_content' ),
'et_core_portability_import' => wp_create_nonce( 'et_core_portability_import' ),
'et_theme_options_library_update_item' => wp_create_nonce( 'et_theme_options_library_update_item' ),
'et_theme_options_library_export_item' => wp_create_nonce( 'et_theme_options_library_export_item' ),
'et_theme_options_library_get_token' => wp_create_nonce( 'et_theme_options_library_get_token' ),
'et_core_save_theme_options' => wp_create_nonce( 'et_core_save_theme_options' ),
'et_core_portability_export' => wp_create_nonce( 'et_core_portability_export' ),
'et_library_save_item' => wp_create_nonce( 'et_library_save_item' ),
'et_theme_options_delete_temp_options' => wp_create_nonce( 'et_theme_options_delete_temp_options' ),
'et_theme_options_library_toggle_item_location' => wp_create_nonce( 'et_theme_options_library_toggle_item_location' ),
],
];
// phpcs:enable
}
/**
* Load the Cloud App scripts.
*
* @param string $enqueue_prod_scripts Flag to force Production scripts.
* @param bool $skip_react_loading Flag to skip react loading.
*
* @return void
*/
public static function load_js( $enqueue_prod_scripts = true, $skip_react_loading = false ) {
// phpcs:disable ET.Sniffs.ValidVariableName.VariableNotSnakeCase -- Following the pattern found in /cloud.
$EPANEL_VERSION = et_get_theme_version();
$ET_DEBUG = defined( 'ET_DEBUG' ) && ET_DEBUG;
$DEBUG = $ET_DEBUG;
$home_url = wp_parse_url( get_site_url() );
$build_dir_uri = ET_EPANEL_URI . '/build';
$cache_buster = $DEBUG ? mt_rand() / mt_getrandmax() : $EPANEL_VERSION; // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- mt_rand() should do for cache busting.
$asset_path = ET_EPANEL_DIR . '/build/et-theme-options-library-app.bundle.js';
if ( file_exists( $asset_path ) ) {
wp_enqueue_style( 'et-theme-options-library-styles', "{$build_dir_uri}/et-theme-options-library-app.bundle.css", [], (string) $cache_buster );
}
$BUNDLE_DEPS = [
'jquery',
'react',
'react-dom',
'es6-promise',
];
if ( $DEBUG || $enqueue_prod_scripts || file_exists( $asset_path ) ) {
$BUNDLE_URI = ! file_exists( $asset_path ) ? "{$home_url['scheme']}://{$home_url['host']}:31599/et-theme-options-library-app.bundle.js" : "{$build_dir_uri}/et-theme-options-library-app.bundle.js";
// Skip the React loading if we already have React ( Gutenberg editor for example ) to avoid conflicts.
if ( ! $skip_react_loading ) {
if ( function_exists( 'et_fb_enqueue_react' ) ) {
et_fb_enqueue_react();
}
}
wp_enqueue_script(
'et-theme-options-library-app',
$BUNDLE_URI,
$BUNDLE_DEPS,
(string) $cache_buster,
true
);
wp_localize_script(
'et-theme-options-library-app',
'et_theme_options_data',
self::get_cloud_helpers()
);
}
// phpcs:enable
}
}
ET_Theme_Options_Library_App::instance();

View File

@@ -0,0 +1,222 @@
<?php
/**
* Local Library API.
*
* @since ??
*
* @package Divi
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* ET_Theme_Options_Library_Local utility class.
*
* Item can be a layout, a template, a theme option, a code snippet, etc.
*
* @since ??
*
* @return void
*/
class ET_Theme_Options_Library_Local extends ET_Item_Library_Local {
/**
* Gets the class instance.
*
* @since ??
*
* @return ET_Item_Library_Local
*/
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Constructor.
*/
public function __construct() {
$this->post_type = ET_THEME_OPTIONS_POST_TYPE;
$this->exceptional_processes = array(
'duplicate',
'duplicate_and_delete',
);
}
/**
* Gets the library items.
*
* @param string $item_type Item type.
* @return array
*/
public function get_library_items( $item_type ) {
$_ = ET_Core_Data_Utils::instance();
$theme_options_items = ET_Post_Type_Theme_Options::instance();
$theme_options_tags = ET_Builder_Post_Taxonomy_LayoutTag::instance();
$theme_options_categories = ET_Builder_Post_Taxonomy_LayoutCategory::instance();
$item_categories = [];
$item_tags = [];
$items = [];
$index = 0;
$query_posts = $theme_options_items
->query()
->run(
array(
'post_status' => array( 'publish', 'trash' ),
'orderby' => 'name',
'fields' => 'ids',
)
);
$post_ids = is_array( $query_posts ) ? $query_posts : array( $query_posts );
foreach ( $post_ids as $post_id ) {
$item = new stdClass();
$post = get_post( $post_id );
$item->id = $post->ID;
$item->index = $index;
$item->date = $post->post_date;
$title = html_entity_decode( $post->post_title );
// check if current user can edit library item.
$can_edit_post = current_user_can( 'edit_post', $item->id );
if ( $title ) {
// Remove periods since we use dot notation to retrieve translation.
$title = str_replace( '.', '', $title );
$item->name = et_core_intentionally_unescaped( $title, 'react_jsx' );
}
$built_for = get_post_meta( $item->id, '_built_for', true );
$item->slug = $post->post_name;
$item->url = esc_url( wp_make_link_relative( get_permalink( $post ) ) );
$item->short_name = '';
$item->builtFor = $built_for && '' !== $built_for ? $built_for : 'Divi'; // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- This is valid format for the property in the Cloud App.
$item->description = '';
$item->is_favorite = $theme_options_items->is_favorite( $item->id );
$item->isTrash = 'trash' === $post->post_status; // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- This is valid format for the property in the Cloud App.
$item->isReadOnly = ! $can_edit_post; // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- This is valid format for the property in the Cloud App.
$item->categories = array();
$item->category_ids = array();
$item->tags = array();
$item->tag_ids = array();
$this->process_item_taxonomy(
$post,
$item,
$index,
$item_categories,
$theme_options_categories->name,
'category'
);
$this->process_item_taxonomy(
$post,
$item,
$index,
$item_tags,
$theme_options_tags->name,
'tag'
);
$items[] = $item;
$index++;
}
return [
'categories' => $this->get_processed_terms( $theme_options_categories->name ),
'tags' => $this->get_processed_terms( $theme_options_tags->name ),
'items' => $items,
];
}
/**
* Performs item exceptional updates.
*
* @param array $payload Payload.
* @param array $updated_data Updated data.
*
* @since ??
*
* @return array
*/
private function _perform_item_exceptional_updates( $payload, $updated_data ) {
if ( empty( $payload['item_id'] ) || empty( $payload['update_details'] ) ) {
return false;
}
$update_details = $payload['update_details'];
if ( empty( $update_details['updateType'] ) ) {
return false;
}
$item_id = absint( $payload['item_id'] );
$update_type = sanitize_text_field( $update_details['updateType'] );
$item_name = isset( $update_details['itemName'] ) ? sanitize_text_field( $update_details['itemName'] ) : '';
$et_builder_categories = ET_Builder_Post_Taxonomy_LayoutCategory::instance();
$et_builder_tags = ET_Builder_Post_Taxonomy_LayoutTag::instance();
switch ( $update_type ) {
case 'duplicate':
case 'duplicate_and_delete':
if ( isset( $update_details['content'] ) ) {
$content = $update_details['content'];
} else {
$content = get_the_content( null, false, $item_id );
}
if ( is_array( $content ) ) {
$content = wp_json_encode( $content );
}
$new_item = array(
'post_title' => $item_name,
'post_content' => $content,
'post_status' => 'publish',
'post_type' => $this->post_type,
'tax_input' => array(
$et_builder_categories->name => $updated_data['categories'],
$et_builder_tags->name => $updated_data['tags'],
),
);
$updated_data['newItem'] = wp_insert_post( $new_item );
break;
}
$updated_data['updateType'] = $update_type;
return $updated_data;
}
/**
* Updates the library item.
*
* @param array $payload Payload.
*
* @return array
*/
public function perform_item_update( $payload ) {
$updated_data = $this->_perform_item_common_updates( $payload );
if ( ! empty( $this->exceptional_processes ) ) {
$updated_data = $this->_perform_item_exceptional_updates( $payload, $updated_data );
}
return $updated_data;
}
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* Theme options quick feature entry file.
*
* Divi Cloud Theme Options Library.
*
* @package Divi
* @subpackage Epanel
* @since ??
*/
if ( ! defined( 'ET_THEME_OPTIONS_DIR' ) ) {
define( 'ET_THEME_OPTIONS_DIR', get_template_directory() . '/epanel/theme-options-library/' );
}
require_once trailingslashit( ET_THEME_OPTIONS_DIR ) . 'constants.php';
require_once trailingslashit( ET_THEME_OPTIONS_DIR ) . 'api.php';
if ( ! function_exists( 'et_init_theme_options_library' ) ) :
/**
* Init Theme Options Library.
*
* @return void
*/
function et_init_theme_options_library() {
require_once trailingslashit( ET_THEME_OPTIONS_DIR ) . 'ThemeOptionsLibrary.php';
}
endif;
add_action( 'init', 'et_init_theme_options_library' );