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,165 @@
<?php
/**
* Code Snippets library.
*
* Registers post types to be used in the "Code Snippets" library.
*
* @link https://github.com/elegantthemes/Divi/issues/26232
*
* @package Divi
* @subpackage Builder
* @since 4.19.0
*/
/**
* Core class used to implement "Code Snippets" library.
*
* Register post types & taxonomies to be used in "Code Snippets" library.
*/
class ET_Builder_Code_Snippets_Library {
/**
* Instance of `ET_Builder_Code_Snippets_Library`.
*
* @var ET_Builder_Code_Snippets_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_code_snippet` taxonomy.
*
* @var ET_Builder_Post_Taxonomy_LayoutCategory
*/
public $code_snippet_categories;
/**
* ET_Builder_Post_Taxonomy_LayoutTag instance.
*
* Shall be used for querying `et_code_snippet` taxonomy .
*
* @var ET_Builder_Post_Taxonomy_LayoutTag
*/
public $code_snippet_tags;
/**
* ET_Builder_Post_Taxonomy_CodeSnippetType instance.
*
* Shall be used for querying `et_code_snippet` taxonomy .
*
* @var ET_Builder_Post_Taxonomy_CodeSnippetType
*/
public $code_snippet_types;
/**
* ET_Builder_Post_Type_TBItem instance.
*
* Shall be used for querying `et_tb_item` posts .
*
* @var ET_Builder_Post_Type_TBItem
*/
public $code_snippets;
/**
* ET_Builder_Post_Taxonomy_LayoutCategory instance.
*
* Shall be used for querying `et_tb_layout_category` taxonomy .
*
* @var ET_Builder_Post_Taxonomy_LayoutCategory
*/
public $code_snippets_categories;
/**
* ET_Builder_Post_Taxonomy_LayoutTag instance.
*
* Shall be used for querying `et_tb_layout_tag` taxonomy .
*
* @var ET_Builder_Post_Taxonomy_LayoutTag
*/
public $code_snippets_tags;
/**
* ET_Builder_Post_Taxonomy_CodeSnippetType instance.
*
* Shall be used for querying `et_tb_layout_type` taxonomy .
*
* @var ET_Builder_Post_Taxonomy_CodeSnippetType
*/
public $code_snippets_type;
/**
* 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 Builder Library's custom post type and its taxonomies.
*/
protected function _register_cpt_and_taxonomies() {
$files = [
ET_CODE_SNIPPETS_DIR . 'post/type/CodeSnippet.php',
ET_CODE_SNIPPETS_DIR . 'post/taxonomy/CodeSnippetType.php',
];
if ( ! $files ) {
return;
}
foreach ( $files as $file ) {
require_once $file;
}
$this->code_snippets = ET_Builder_Post_Type_Code_Snippet::instance();
$this->code_snippets_categories = ET_Builder_Post_Taxonomy_LayoutCategory::instance();
$this->code_snippets_tags = ET_Builder_Post_Taxonomy_LayoutTag::instance();
$this->code_snippets_type = ET_Builder_Post_Taxonomy_CodeSnippetType::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_TBItem::register_all( 'builder' );
}
/**
* Returns the ET_Builder_TBItem_Library instance.
*
* @return ET_Builder_TBItem_Library
*/
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self();
}
return self::$_instance;
}
}
ET_Builder_Code_Snippets_Library::instance();

View File

@@ -0,0 +1,333 @@
<?php
/**
* Code Snippets Library API.
*
* @package Divi
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Retrieves Code Snippets Library items.
*
* @return void
*/
function et_code_snippets_library_get_items() {
et_core_security_check( 'edit_posts', 'et_code_snippets_library_get_items', 'nonce' );
$code_snippet_type = '';
if ( isset( $_POST['et_code_snippet_type'] ) && is_string( $_POST['et_code_snippet_type'] ) ) {
$code_snippet_type = sanitize_text_field( $_POST['et_code_snippet_type'] );
}
$is_code_snippet_type_valid = in_array(
$code_snippet_type,
[
'et_code_snippet_css',
'et_code_snippet_css_no_selector',
'et_code_snippet_html_js',
],
true
);
if ( ! $code_snippet_type || ! $is_code_snippet_type_valid ) {
wp_send_json_error( 'Error: Wrong item type provided.' );
}
$item_library_local = et_pb_code_snippets_library_local();
$data = $item_library_local->get_library_items( $code_snippet_type );
wp_send_json_success( $data );
}
add_action( 'wp_ajax_et_code_snippets_library_get_items', 'et_code_snippets_library_get_items' );
/**
* Retrieves Code Snippets Library item content.
*
* @return void
*/
function et_code_snippets_library_get_item_content() {
et_core_security_check( 'edit_posts', 'et_code_snippets_library_get_item_content', 'nonce' );
$post_id = isset( $_POST['et_code_snippet_id'] ) ? (int) sanitize_text_field( $_POST['et_code_snippet_id'] ) : 0;
$return_format = isset( $_POST['et_code_snippet_format'] ) ? (string) sanitize_text_field( $_POST['et_code_snippet_format'] ) : 'raw';
$snippet_type = isset( $_POST['et_code_snippet_type'] ) ? (string) sanitize_text_field( $_POST['et_code_snippet_type'] ) : 'et_code_snippet_html_js';
$post_type = get_post_type( $post_id );
if ( ! current_user_can( 'edit_post', $post_id ) || ET_CODE_SNIPPET_POST_TYPE !== $post_type ) {
wp_send_json_error( 'You do not have permission.' );
}
$post = get_post( $post_id );
$post_content = $post->post_content;
$exported = array();
if ( 'exported' === $return_format ) {
$exported = array(
'context' => 'et_code_snippet',
'data' => $post_content,
'snippet_type' => $snippet_type,
);
}
wp_send_json_success(
array(
'snippet' => $post_content,
'exported' => $exported,
)
);
}
add_action( 'wp_ajax_et_code_snippets_library_get_item_content', 'et_code_snippets_library_get_item_content' );
/**
* Save Code Snippets Library item content.
*
* @return void
*/
function et_code_snippets_library_save_item_content() {
et_core_security_check( 'edit_posts', 'et_code_snippets_library_save_item_content', 'nonce' );
$post_id = isset( $_POST['et_code_snippet_id'] ) ? absint( $_POST['et_code_snippet_id'] ) : 0;
$post_type = get_post_type( $post_id );
if ( ! current_user_can( 'edit_post', $post_id ) || ET_CODE_SNIPPET_POST_TYPE !== $post_type ) {
wp_send_json_error( 'You do not have permission.' );
}
// phpcs:disable ET.Sniffs.ValidatedSanitizedInput -- $_POST is an array, it's value sanitization is done at the time of saving by wp_update_post.
$result = wp_update_post(
[
'ID' => $post_id,
'post_content' => $_POST['et_code_snippet_content'],
]
);
// phpcs:enable
wp_send_json_success(
array(
'result' => $result,
)
);
}
add_action( 'wp_ajax_et_code_snippets_library_save_item_content', 'et_code_snippets_library_save_item_content' );
/**
* Update Code Snippets Library item.
*
* @return void
*/
function et_code_snippets_library_update_item() {
et_core_security_check( 'edit_posts', 'et_code_snippets_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.
$item_library_local = et_pb_code_snippets_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_code_snippets_library_update_item', 'et_code_snippets_library_update_item' );
/**
* Prepare Library Categories or Tags List.
*
* @param string $taxonomy Name of the taxonomy.
*
* @return array Clean Categories/Tags array.
**/
function et_get_clean_library_terms( $taxonomy = 'layout_category' ) {
$raw_terms_array = apply_filters( 'et_pb_new_layout_cats_array', get_terms( $taxonomy, array( 'hide_empty' => false ) ) );
$clean_terms_array = array();
if ( is_array( $raw_terms_array ) && ! empty( $raw_terms_array ) ) {
foreach ( $raw_terms_array as $term ) {
$clean_terms_array[] = array(
'name' => et_core_intentionally_unescaped( html_entity_decode( $term->name ), 'react_jsx' ),
'id' => $term->term_id,
'slug' => $term->slug,
'count' => $term->count,
);
}
}
return $clean_terms_array;
}
/**
* AJAX Callback: Remove the Library layout after it was moved to the Cloud.
*
* @since 4.17.0
*
* @global $_POST['payload'] Array with the layout data to remove.
*
* @return void|string JSON encoded in case of empty payload
*/
function et_code_snippets_toggle_cloud_status() {
et_core_security_check( 'edit_posts', 'et_code_snippets_library_toggle_item_location', 'nonce' );
$post_id = isset( $_POST['et_code_snippet_id'] ) ? (int) sanitize_text_field( $_POST['et_code_snippet_id'] ) : 0;
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_CODE_SNIPPET_POST_TYPE !== $post_type ) {
wp_send_json_error( 'You do not have permission.' );
}
wp_delete_post( $post_id, true );
wp_send_json_success(
array(
'localLibraryTerms' => [
'layout_category' => et_get_clean_library_terms(),
'layout_tag' => et_get_clean_library_terms( 'layout_tag' ),
],
)
);
}
add_action( 'wp_ajax_et_code_snippets_toggle_cloud_status', 'et_code_snippets_toggle_cloud_status' );
/**
* Export Code Snippets Library item.
*
* @return void
*/
function et_code_snippets_library_export_item() {
et_core_security_check( et_core_portability_cap( 'et_code_snippets' ), 'et_code_snippets_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.
$direct_export = isset( $_POST['directExport'] ) ? $_POST['directExport'] : ''; // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['directExport'] is an array, it's value sanitization is done at the time of accessing value.
$response = et_code_snippets_library_export_item_data( $post_id, $cloud_content, $direct_export );
if ( ! $response ) {
wp_send_json_error( 'Error: Wrong data provided.' );
}
wp_send_json_success( $response );
}
add_action( 'wp_ajax_et_code_snippets_library_export_item', 'et_code_snippets_library_export_item' );
/**
* Download exported Code Snippets Library item.
*
* @return void
*/
function et_code_snippets_library_export_item_download() {
et_core_security_check( et_core_portability_cap( 'et_code_snippets' ), 'et_code_snippets_library_export_item', 'nonce', '_GET' );
$id = ! empty( $_GET['id'] ) ? absint( $_GET['id'] ) : 0;
$file_name = empty( $_GET['fileName'] ) ? 'code-snippet' : 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_code_snippet_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_code_snippets_library_export_item_download', 'et_code_snippets_library_export_item_download' );
/**
* Import Code Snippets Library item.
*
* @return void
*/
function et_code_snippets_library_import_item() {
et_core_security_check( et_core_portability_cap( 'et_code_snippets' ), 'et_code_snippets_library_import_item', 'nonce' );
$response = et_code_snippets_library_import_item_data();
if ( ! $response ) {
wp_send_json_error( 'Not a valid file.' );
}
wp_send_json_success( $response );
}
add_action( 'wp_ajax_et_code_snippets_library_import_item', 'et_code_snippets_library_import_item' );
/**
* Update Local Library Tags and Categories.
*
* @return void
*/
function et_code_snippets_library_update_terms() {
et_core_security_check( 'edit_posts', 'et_code_snippets_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_code_snippets_library_local();
$response = $item_library_local->perform_terms_update( $payload );
wp_send_json_success( $response );
}
add_action( 'wp_ajax_et_code_snippets_library_update_terms', 'et_code_snippets_library_update_terms' );
/**
* Ajax :: Save code snippets to the local library.
*/
function et_code_snippets_library_save() {
et_core_security_check( 'edit_posts', 'et_code_snippets_save_to_local_library' );
$post_id = et_save_item_to_local_library( $_POST );
if ( is_wp_error( $post_id ) ) {
wp_send_json_error();
}
wp_send_json_success();
}
add_action( 'wp_ajax_et_code_snippets_library_save', 'et_code_snippets_library_save' );
/**
* Ajax :: Get Cloud token.
*/
function et_code_snippets_library_get_token() {
et_core_security_check( 'edit_posts', 'et_code_snippets_library_get_token', 'nonce' );
$access_token = get_transient( 'et_cloud_access_token' );
wp_send_json_success(
array(
'accessToken' => $access_token,
)
);
}
add_action( 'wp_ajax_et_code_snippets_library_get_token', 'et_code_snippets_library_get_token' );

View File

@@ -0,0 +1,103 @@
// 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';
import {
assign,
clone,
set,
get,
} from 'lodash';
// Internal Dependencies
import store from './store/';
import CodeSnippetsApp from './components/App';
import { STATE_IDLE } from '@code-snippets/lib/constants';
const initialState = {
content: '',
context: 'code_css',
importError: false,
items: [],
showLibrary: false,
showPortability: false,
importState: STATE_IDLE,
};
$(window).on('et_code_snippets_container_ready', (event, preferences, container = document) => {
let devtools = null;
if (process.env.NODE_ENV === 'development') {
devtools = Devtools({
host: '127.0.0.1:4045',
reconnect: false,
bigComponentsWarning: 15,
});
}
const context = preferences.context;
const state = assign(clone(initialState), {});
const modalType = preferences.modalType;
if ('' !== context) {
set(state, 'context', context);
}
const content = get(preferences, 'content', '');
if ('' !== content) {
set(state, 'content', content);
}
const selectedContent = get(preferences, 'selectedContent', '');
set(state, 'selectedContent', selectedContent);
const sidebarLabel = get(preferences, 'sidebarLabel', '');
set(state, 'sidebarLabel', sidebarLabel);
const app = App(store(state), {
devtools,
returnSequencePromise: true,
});
const containerId = preferences.containerId;
const domNode = container.getElementById(containerId);
if ('' === modalType) {
unmountComponentAtNode(domNode);
return;
}
render(
<Container app={app}>
<CodeSnippetsApp
modalType={modalType}
codeMirrorId={preferences.codeMirrorId}
insertCodeCallback={preferences.insertCodeCallback}
container={container}
/>
</Container>,
domNode
);
// Disable main body scrolling.
$(container).find('body.et-admin-page').addClass('et-code-snippets-open');
// Properly unmount app on close.
$(window).on('et_code_snippets_library_close', () => {
const appContainer = container.getElementById(containerId);
setTimeout(() => {
if (appContainer) {
unmountComponentAtNode(appContainer);
appContainer.remove();
}
$('body.et-admin-page').removeClass('et-code-snippets-open');
});
});
});

View File

@@ -0,0 +1,27 @@
// External dependencies.
import {
every,
get,
isArray,
isEmpty,
} from 'lodash';
// Internal dependencies.
import config from './config';
const isAllowedActionPure = (capabilities, action, restrictByDefault = false) => {
if (isEmpty(action)) {
return true;
}
const defaultValue = restrictByDefault ? 'off' : 'on';
if (isArray(action)) {
return every(action, action => 'on' === get(capabilities, action, defaultValue));
}
return 'on' === get(capabilities, action, defaultValue);
};
export const isAllowedAction = (...args) => isAllowedActionPure(config.capabilities, ...args);

View File

@@ -0,0 +1,32 @@
// Set global variable to detect new library item creation.
window.CodeSnippetItemsLoaded = {};
export const setCodeSnippetItemsLoaded = (context, flag) => {
window.CodeSnippetItemsLoaded = {
...CodeSnippetItemsLoaded,
[context] : flag,
};
};
/* eslint-disable import/prefer-default-export */
export const getItemTypeByContext = context => {
let type;
switch (context) {
case 'code_html':
type = 'et_code_snippet_html_js';
break;
case 'code_css_no_selector':
type = 'et_code_snippet_css_no_selector';
break;
default:
type = 'et_code_snippet_css';
break;
}
return type;
};
export const setCodeSnippetsLibraryToken = (token) => {
window.globalCloudToken = token;
};

View File

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

View File

@@ -0,0 +1,10 @@
export const MODAL_TYPE_SAVE_ITEM = 'save_item';
export const MODAL_TYPE_EDIT_ITEM = 'edit_item';
export const STATE_LOADING = 'loading';
export const STATE_ERROR = 'error';
export const STATE_SUCCESS = 'success';
export const STATE_IDLE = 'idle';
// Capabilities.
export const CAP_PORTABILITY = 'portability';
export const CAP_SNIPPET_PORTABILITY = 'et_code_snippets_portability';

View File

@@ -0,0 +1,20 @@
// External dependencies.
import { get } from 'lodash';
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(window.et_code_snippets_data.i18n, [path, key], ...args);

View File

@@ -0,0 +1,41 @@
// 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);
export const download = url => {
const deferred = $.ajax({
type: 'GET',
url,
});
return Promise.resolve(deferred.promise())
.then(response => {
if (false === response.success) {
return Promise.reject(response || {});
}
return Promise.resolve(response);
});
};

View File

@@ -0,0 +1,20 @@
function insertCodeIntoField({ get, props: { snippet, codeMirrorId, isAppend, skipInsert } }) {
if (skipInsert) {
return Promise.resolve();
}
const codeMirrorInstance = jQuery(`#${codeMirrorId}`).next('.CodeMirror')[0].CodeMirror;
// Insert code into specified codeMirror field.
// Append or replace depending on user preferences.
if (isAppend) {
snippet = codeMirrorInstance.getValue() ? '\n' + snippet : snippet;
return Promise.resolve(codeMirrorInstance.replaceRange(snippet, {line: codeMirrorInstance.lastLine()}));
} else {
return Promise.resolve(codeMirrorInstance.setValue(snippet));
}
}
export {
insertCodeIntoField,
};

View File

@@ -0,0 +1,15 @@
// External dependencies.
import { state } from 'cerebral';
// Internal dependencies.
import { MODAL_TYPE_EDIT_ITEM } from '@code-snippets/lib/constants';
const isEditItemModalOpen = get => {
const openModal = get(state`openModal`);
return openModal === MODAL_TYPE_EDIT_ITEM;
};
export {
isEditItemModalOpen,
};

View File

@@ -0,0 +1,11 @@
import { state } from 'cerebral';
const openModal = type => ({ store }) => store.set(state`openModal`, type);
const closeModal = () => ({ store }) => store.set(state`openModal`, null);
export {
openModal,
closeModal,
};

View File

@@ -0,0 +1,23 @@
import edit from '@code-snippets/store/edit/module';
import * as providers from './providers';
import * as sequences from './sequences';
import * as computed from './computed';
export default initialState => (
{
state: {
...initialState,
openModal: null,
itemsLoadedAndCached: false,
computed: {
...computed,
},
cloudToken: null,
},
providers,
sequences,
modules: {
edit,
},
}
);

View File

@@ -0,0 +1,155 @@
/* eslint-disable import/prefer-default-export */
import { post } from '../../lib/request';
import config from '../../lib/config';
export const codeSnippetsLibApi = {
/**
* Gets the Code Snippet library items.
*
* @param {string} type One of `et_code_snippet` types.
* @returns {Array} Resolved value from promise. Array of objects.
*/
getItems(type) {
/* eslint-disable key-spacing */
return post({
action : 'et_code_snippets_library_get_items',
nonce : config.nonces.et_code_snippets_library_get_items,
et_code_snippet_type: type,
});
/* eslint-enable */
},
getItemContent(id, snippetType, contentFormat = 'raw') {
/* eslint-disable key-spacing */
return post({
action : 'et_code_snippets_library_get_item_content',
nonce : config.nonces.et_code_snippets_library_get_item_content,
et_code_snippet_id : id,
et_code_snippet_type : snippetType,
et_code_snippet_format : contentFormat,
});
/* eslint-enable */
},
saveItemContent(id, snippetContent) {
/* eslint-disable key-spacing */
return post({
action : 'et_code_snippets_library_save_item_content',
nonce : config.nonces.et_code_snippets_library_save_item_content,
et_code_snippet_id : id,
et_code_snippet_content: snippetContent,
});
/* eslint-enable */
},
removeLocalItem(id) {
/* eslint-disable key-spacing */
return post({
action : 'et_code_snippets_toggle_cloud_status',
nonce : config.nonces.et_code_snippets_library_toggle_item_location,
et_code_snippet_id : id,
});
/* eslint-enable */
},
/**
* Update the Code Snippet library item.
*
* @param {object} payload Updated item details.
* @returns {Array} Resolved value from promise. Array of objects.
*/
updateItem(payload) {
/* eslint-disable key-spacing */
return post({
action : 'et_code_snippets_library_update_item',
nonce : config.nonces.et_code_snippets_library_update_item,
payload,
});
/* eslint-enable */
},
/**
* Export Code Snippet library item.
*
* @param {int} id Item ID.
* @param {array} cloudContent Cloud content.
* @param {obj} directExport Snippet type and Content.
* @returns {Array} Resolved value from promise. Array of objects.
*/
exportItem(id, cloudContent, directExport) {
/* eslint-disable key-spacing */
return post({
action : 'et_code_snippets_library_export_item',
nonce : config.nonces.et_code_snippets_library_export_item,
id,
cloudContent,
directExport,
});
/* eslint-enable */
},
/**
* Download Code Snippet library item.
*
* @param {int} id Item ID.
* @param {string} fileName Item name.
* @returns {string} URI string.
*/
downloadExportFile(id, fileName) {
/* eslint-disable key-spacing */
const args = {
action : 'et_code_snippets_library_export_item_download',
nonce : config.nonces.et_code_snippets_library_export_item,
fileName,
id,
};
return `${config.api}?${jQuery.param(args)}`;
/* eslint-enable */
},
/**
* Import Code Snippet library item.
*
* @param {Blob} file File.
* @returns {Array} Response.
*/
importItem(fileData) {
const fileContent = JSON.parse(fileData.content);
return post({
action : 'et_code_snippets_library_import_item',
nonce : config.nonces.et_code_snippets_library_import_item,
fileContent: JSON.stringify(fileContent.data),
fileData,
});
},
/**
* Update Local Tags/Categories and return updated list.
*
* @param {Blob} file File.
* @returns {Array} Response.
*/
updateFilters(payload) {
return post({
action : 'et_code_snippets_library_update_terms',
nonce : config.nonces.et_code_snippets_library_update_terms,
payload,
});
},
/**
* Retrieve Cloud Token.
*
* @returns {Array} Response with cloud token.
*/
getCloudToken() {
return post({
action : 'et_code_snippets_library_get_token',
nonce : config.nonces.et_code_snippets_library_get_token,
});
}
};
/* eslint-enable */

View File

@@ -0,0 +1,361 @@
// External dependencies.
import {
props,
sequence,
state,
} from 'cerebral';
import { set } from 'cerebral/factories';
import { isEmpty } from 'lodash';
// Internal dependencies.
import { getItemTypeByContext, getContextByItemType } from '@common-ui/lib/local-library';
import { updateTokens, doApiRequest } from '@cloud/app/lib/api';
import { handleCloudError } from '@cloud/app/components/app/actions/shared-actions';
import { insertCodeIntoField } from './actions';
import { setCodeSnippetItemsLoaded, setCodeSnippetsLibraryToken } from '../../lib/code-snippets-library';
import { STATE_IDLE, STATE_LOADING, STATE_SUCCESS } from '@code-snippets/lib/constants';
/* eslint-disable import/prefer-default-export */
const setLibraryContext = sequence('Set Code Snippets library context', [
set(state`context`, props`context`),
set(state`itemsLoadedAndCached`, false),
]);
const setCloudToken = sequence('Set cloudToken', [
({ get }) => {
setCodeSnippetsLibraryToken(get(props`cloudToken`));
},
]);
const cacheCloudToken = sequence('Retrieve saved Cloud Access token and save to state', [
({ codeSnippetsLibApi, path }) => {
return codeSnippetsLibApi.getCloudToken()
.then(cloudTokenData => {
return path.success({cloudToken: cloudTokenData.accessToken});
})
.catch(() => path.error());
},
{
success: [
setCloudToken
],
error: [],
},
]);
const loadItems = sequence('Load Code Snippets library items', [
({ codeSnippetsLibApi, get, path }) => {
const context = get(state`context`);
const type = getItemTypeByContext(context);
// Exit if no context provided.
if ('' === context) {
return path.error();
}
return codeSnippetsLibApi.getItems(type)
.then(response => path.success({
items: response,
}))
.catch(() => path.error());
},
{
success: [
set(state`items`, props`items`),
set(state`itemsLoadedAndCached`, true),
({get}) => { setCodeSnippetItemsLoaded(get(state`context`), true); },
],
error: [],
},
]);
const insertSnippet = sequence('Insert Code Snippet into a field', [
({ codeSnippetsLibApi, get, path }) => {
const id = get(props`snippetId`);
const snippetContent = get(props`snippetContent`);
const context = get(state`context`);
const type = getItemTypeByContext(context);
if (false !== snippetContent) {
return path.success({ snippet: snippetContent });
}
return codeSnippetsLibApi.getItemContent(id, type)
.then(response => path.success({ snippet: response.snippet }))
.catch(() => path.error());
},
{
success: [
set(state`snippetCode`, props`snippet`),
set(state`snippetCodeAppend`, props`isAppend`),
insertCodeIntoField,
set(state`showLibrary`, false),
set(state`context`, ''),
() => { jQuery(window).trigger('et_code_snippets_library_close'); },
],
error: [],
},
]);
const getExportedItem = sequence('Get the exported Code Snippet content', [
({ codeSnippetsLibApi, path, props: { id, itemType } }) => {
return codeSnippetsLibApi.getItemContent(id, itemType, 'exported')
.then((response => path.success(response)))
.catch(() => path.error());
},
{
success: [Promise.resolve(props`response`)],
error: [],
},
]);
const toggleLibraryItemLocation = sequence('Remove local item from WPDB', [
({ codeSnippetsLibApi, path, props: { id } }) => {
return codeSnippetsLibApi.removeLocalItem(id)
.then((response => path.success(response)))
.catch(() => path.error());
},
{
success: [Promise.resolve(props`response`)],
error: [],
},
]);
const updateLocalFilters = sequence('Update Local Filters', [
({ codeSnippetsLibApi, path, props: { payload } }) => {
return codeSnippetsLibApi.updateFilters(payload)
.then((response => path.success(response)))
.catch(() => path.error());
},
{
success: [Promise.resolve(props`response`)],
error: [],
},
]);
const updateItem = sequence('Update Code Snippets library item', [
({ codeSnippetsLibApi, get, path }) => {
const payload = get(props`payload`);
return codeSnippetsLibApi.updateItem(payload)
.then(response => path.success({
updatedItem: {
success: true,
data: response,
},
}))
.catch(() => path.error());
},
{
success: [Promise.resolve(props`updatedItem`)],
error: []
},
]);
const setShowLibrary = sequence('Set Code Snippets show library', [
set(state`showLibrary`, props`toggle`),
]);
const resetSnippetCode = sequence('Reset the saved code value', [
set(state`snippetCode`, ''),
]);
const setShowSaveModal = sequence('Toggle Code Snippets save modal', [
set(state`showSave`, props`toggle`),
]);
const openPortablity = sequence('Open Code Snippets portability modal', [
set(state`showPortability`, true),
]);
const closePortability = sequence('Close Code Snippets portability modal', [
set(state`showPortability`, false),
set(state`importError`, false),
]);
const exportSnippet = sequence('Export code snippet', [
({ codeSnippetsLibApi, path, props: { id, cloudContent, directExport } }) => {
return codeSnippetsLibApi.exportItem(id, cloudContent, directExport)
.then(() => path.success())
.catch(() => path.error());
},
{
success: [
({ codeSnippetsLibApi, props: { id, fileName } }) => {
const downloadURI = codeSnippetsLibApi.downloadExportFile(id, fileName);
window.location.assign(downloadURI);
window.ETCloudApp.emitSignal({
signal: 'finishDownload',
data: {},
});
},
({ props: { directExport } }) => {
if (!isEmpty(directExport)) {
jQuery(window).trigger('et_code_snippets_library_close');
}
},
closePortability,
],
error: []
},
]);
const importSnippetToCloud = sequence('Import code snippet to cloud', [
({ get, store, props: { importFile } }) => updateTokens().then(refreshResponse => {
const accessToken = refreshResponse['accessToken'];
const callback = (newItem) => {
if (newItem.error) {
handleCloudError(newItem, get, store, null);
return;
}
store.set(state`showPortability`, false);
store.set(state`importError`, false),
store.set(state`importState`, STATE_SUCCESS),
// Trigger Cloud Items Refresh in the cloud snippet library modal.
window.ETCloudApp.emitSignal({
signal: 'refreshCloudItems',
data: {},
});
};
if (!isEmpty(accessToken)) {
store.set(state`cloudToken`, accessToken);
const newCloudItem = {
title : importFile.title,
content: importFile.content,
status : 'publish',
meta : {},
};
const resource = getContextByItemType(importFile.type);
const providedBaseUrl = window.ETCloudApp.getActiveFolderEndpoint();
doApiRequest({ type: 'post', resource, accessToken, providedBaseUrl }, newCloudItem).then(callback);
} else {
set(state`importState`, STATE_IDLE),
store.set(state`cloudStatus`, { error: 'auth_error' });
}
}),
]);
const importSnippetToLocal = sequence('Import code snippet to local', [
({ codeSnippetsLibApi, path, props: { importFile } }) => {
return codeSnippetsLibApi.importItem(importFile)
.then(() => path.success())
.catch(() => path.error());
},
{
success: [
closePortability,
set(state`importState`, STATE_SUCCESS),
() => { jQuery(window).trigger('et_cloud_refresh_local_items'); },
],
error: [
set(state`importState`, STATE_IDLE),
set(state`importError`, true),
]
},
]);
const decideSnippetImport = sequence('Decide import code snippet', [
set(state`importState`, STATE_LOADING),
({ path, props: { importToCloud } }) => {
if (importToCloud) {
return path.cloud();
} else {
return path.local();
}
},
{
cloud: [
importSnippetToCloud,
],
local: [
importSnippetToLocal,
],
}
]);
const importSnippet = sequence('Import code snippet', [
({ path, props: { importFile } }) => {
if (importFile) return path.yes();
},
{
yes: [
decideSnippetImport,
],
}
]);
const downloadSnippetContent = sequence('Download Snippet Content', [
() => window.ETCloudApp.setCodeSnippetPreviewState({ codeSnippetPreviewState: STATE_LOADING }),
// eslint-disable-next-line no-shadow
({ codeSnippetsLibApi, get, path, props: { snippetId, snippetContent, needImageRefresh, item_location = '' } }) => {
// When a local item is downloaded, snippetId shall be available.
if (! snippetContent && 'cloud' !== item_location ) {
const context = get(state`context`);
const type = getItemTypeByContext(context);
return codeSnippetsLibApi.getItemContent(snippetId, type)
.then(response => path.success({ snippet: response.snippet, itemId: snippetId }))
.catch(() => path.error());
}
// When a Cloud item is downloaded, snippetContent shall be available.
const snippet = snippetContent;
return path.success({ snippet, itemId: snippetId, needImageRefresh });
},
{
success: [
({ props: { snippet, itemId, needImageRefresh } }) => {
window.ETCloudApp.emitSignal({
signal: 'renderCodeSnippetPreview',
data: { snippet, itemId, needImageRefresh },
});
},
],
error: [
],
},
() => window.ETCloudApp.setCodeSnippetPreviewState({ codeSnippetPreviewState: '' }),
]);
const closeModal = sequence('Close open modal', [
set(state`openModal`, null),
]);
export {
closePortability,
downloadSnippetContent,
exportSnippet,
getExportedItem,
importSnippet,
insertSnippet,
loadItems,
openPortablity,
resetSnippetCode,
setLibraryContext,
setShowLibrary,
setShowSaveModal,
toggleLibraryItemLocation,
updateItem,
updateLocalFilters,
closeModal,
setCloudToken,
cacheCloudToken,
};
/* eslint-enable */

View File

@@ -0,0 +1,37 @@
// External dependencies.
import {
state,
} from 'cerebral';
import { noop } from 'lodash';
// Internal dependencies.
import { saveToCloudPure } from '@cloud/app/lib/api';
const saveToCloud = ({ get, props, path }) => {
const itemId = get(state`edit.item.id`);
const context = get(state`context`);
let snippetContent = get(state`edit.content`);
snippetContent = {
...snippetContent,
data: props.content,
};
return saveToCloudPure(context, { content: JSON.stringify(snippetContent) }, [], noop, itemId)
.then(() => path.success())
.catch(() => path.error());
};
const saveToLocal = ({ codeSnippetsLibApi, path, props: { content }, get }) => {
const itemId = get(state`edit.item.id`);
return codeSnippetsLibApi.saveItemContent(itemId, content)
.then(response => path.success({ snippet: response.snippet }))
.catch(() => path.error());
};
export {
saveToCloud,
saveToLocal,
};

View File

@@ -0,0 +1,15 @@
// Internal dependencies.
import * as sequences from './sequences';
export default {
state: {
item: null,
snippet: '',
saveState: null,
progress: 0,
content: {},
context: '',
},
sequences,
};

View File

@@ -0,0 +1,133 @@
// External dependencies.
import {
props,
sequence,
state,
} from 'cerebral';
import {
set,
wait,
when,
} from 'cerebral/factories';
// Internal dependencies.
import {
MODAL_TYPE_EDIT_ITEM,
STATE_ERROR,
STATE_LOADING,
STATE_SUCCESS,
} from '@code-snippets/lib/constants';
import {
closeModal,
openModal,
} from '@code-snippets/store/code-snippets-library/factories';
import { downloadSnippetContent } from '@code-snippets/store/code-snippets-library/sequences';
import {
saveToCloud,
saveToLocal,
} from './actions';
const openLocalItemEditor = sequence('Open local item editor', [
set(state`edit.progress`, 10),
({ codeSnippetsLibApi, path, props: { item } }) => codeSnippetsLibApi.getItemContent(item.id, item.type)
.then(response => path.success({ snippet: response.snippet }))
.catch(() => path.error()),
{
success: [
set(state`edit.progress`, 90),
wait(500),
set(state`edit.progress`, 100),
wait(200),
({ store, props: { snippet } }) => {
store.set(state`edit.snippet`, snippet);
},
set(state`edit.progress`, 0),
],
error: [
set(state`edit.progress`, 0),
],
},
]);
const openCloudItemEditor = sequence('Open cloud item editor', [
set(state`edit.snippet`, props`content.data`),
set(state`edit.content`, props`content`),
]);
const openEditModal = sequence('Open modal to edit code snippet', [
openModal(MODAL_TYPE_EDIT_ITEM),
set(state`edit.item`, props`item`),
set(state`edit.context`, props`context`),
when(props`item.item_location`, item_location => 'cloud' === item_location),
{
true: [
openCloudItemEditor,
],
false: [
openLocalItemEditor,
],
},
]);
const closeEditModal = sequence('Close snippet editor modal', [
closeModal(),
set(state`edit.saveState`, null),
set(state`edit.item`, null),
set(state`edit.snippet`, ''),
]);
const saveEditedItemSuccess = sequence('Edited item Saved', [
() => ({needImageRefresh: true}),
downloadSnippetContent,
set(state`edit.saveState`, STATE_SUCCESS),
wait(500),
closeEditModal,
]);
const saveEditedItemError = sequence('Error while saving edited item', [
set(state`edit.saveState`, STATE_ERROR),
wait(500),
closeEditModal,
]);
const saveEditedContent = sequence('Save editted content', [
set(state`edit.saveState`, STATE_LOADING),
when(state`edit.item.item_location`, item_location => 'cloud' === item_location),
{
true: [
saveToCloud,
{
success: [
({ get, props: { content } }) => ({ snippetId: get(state`edit.item.id`), snippetContent: content }),
({ get }) => ({needImageRefresh: true, item_location: get(state`edit.item.item_location`)}),
saveEditedItemSuccess,
],
error: [
saveEditedItemError,
],
},
],
false: [
saveToLocal,
{
success: [
({ get }) => ({ snippetId: get(state`edit.item.id`) }),
saveEditedItemSuccess,
],
error: [
saveEditedItemError,
],
},
],
},
]);
export {
openEditModal,
closeEditModal,
saveEditedContent,
};

View File

@@ -0,0 +1,5 @@
// Internal dependencies.
import codeSnippetsLibrary from './code-snippets-library/module';
export default codeSnippetsLibrary;

View File

@@ -0,0 +1,138 @@
<?php
/**
* Code Snippets App helpers.
*
* @since 4.19.0
*
* @package Divi
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* ET_Code_Snippets_App class.
*
* Nonces and i18n strings needed for Code Snippets Library Cloud app.
*
* @package Divi
*/
class ET_Code_Snippets_App {
/**
* Class instance.
*
* @var ET_Code_Snippets_App
*/
private static $_instance;
/**
* Get the class instance.
*
* @since 4.19.0
*
* @return ET_Code_Snippets_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();
// phpcs:disable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned, WordPress.Arrays.MultipleStatementAlignment.LongIndexSpaceBeforeDoubleArrow -- Aligned manually.
return [
'i18n' => [ 'library' => require ET_CORE_PATH . '/i18n/library.php' ],
'api' => admin_url( 'admin-ajax.php' ),
'capabilities' => isset( $role_capabilities[ $user_role ] ) ? $role_capabilities[ $user_role ] : array(),
'nonces' => [
'et_code_snippets_library_get_items' => wp_create_nonce( 'et_code_snippets_library_get_items' ),
'et_code_snippets_library_get_item_content' => wp_create_nonce( 'et_code_snippets_library_get_item_content' ),
'et_code_snippets_library_update_item' => wp_create_nonce( 'et_code_snippets_library_update_item' ),
'et_code_snippets_library_toggle_item_location' => wp_create_nonce( 'et_code_snippets_library_toggle_item_location' ),
'et_code_snippets_library_export_item' => wp_create_nonce( 'et_code_snippets_library_export_item' ),
'et_code_snippets_library_import_item' => wp_create_nonce( 'et_code_snippets_library_import_item' ),
'et_code_snippets_library_save_item_content' => wp_create_nonce( 'et_code_snippets_library_save_item_content' ),
'et_code_snippets_library_update_terms' => wp_create_nonce( 'et_code_snippets_library_update_terms' ),
'et_code_snippets_library_get_token' => wp_create_nonce( 'et_code_snippets_library_get_token' ),
'et_code_snippets_save_to_local_library' => wp_create_nonce( 'et_code_snippets_save_to_local_library' ),
],
];
// 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.
*
* @since 4.19.0
*
* @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.
$CORE_VERSION = defined( 'ET_CORE_VERSION' ) ? ET_CORE_VERSION : '';
$ET_DEBUG = defined( 'ET_DEBUG' ) && ET_DEBUG;
$DEBUG = $ET_DEBUG;
$home_url = wp_parse_url( get_site_url() );
$build_dir_uri = ET_CORE_URL . 'build';
$common_scripts = ET_COMMON_URL . '/scripts';
$cache_buster = $DEBUG ? mt_rand() / mt_getrandmax() : $CORE_VERSION; // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- mt_rand() should do for cache busting.
$asset_path = ET_CORE_PATH . 'build/et-core-app.bundle.js';
if ( file_exists( $asset_path ) ) {
wp_enqueue_style( 'et-code-snippets-styles', "{$build_dir_uri}/et-core-app.bundle.css", [], (string) $cache_buster );
}
wp_enqueue_script( 'es6-promise', "{$common_scripts}/es6-promise.auto.min.js", [], '4.2.2', true );
$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']}:31499/et-core-app.bundle.js" : "{$build_dir_uri}/et-core-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-code-snippets-app',
$BUNDLE_URI,
$BUNDLE_DEPS,
(string) $cache_buster,
true
);
wp_localize_script(
'et-code-snippets-app',
'et_code_snippets_data',
self::get_cloud_helpers()
);
}
}
}
ET_Code_Snippets_App::instance();
// phpcs:enable

View File

@@ -0,0 +1,230 @@
<?php
/**
* Local Library API.
*
* @since 4.21.0
*
* @package Divi
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* ET_Code_Snippets_Library_Local utility class.
*
* Item can be a layout, a template, a theme option, a code snippet, etc.
*
* @since 4.21.0
*
* @return void
*/
class ET_Code_Snippets_Library_Local extends ET_Item_Library_Local {
/**
* Gets the class instance.
*
* @since 4.21.0
*
* @return ET_Code_Snippets_Library_Local
*/
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Constructor.
*/
public function __construct() {
$this->post_type = ET_CODE_SNIPPET_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();
$code_snippet_items = ET_Builder_Post_Type_Code_Snippet::instance();
$code_snippet_tags = ET_Builder_Post_Taxonomy_LayoutTag::instance();
$code_snippet_categories = ET_Builder_Post_Taxonomy_LayoutCategory::instance();
$code_snippet_types = ET_Builder_Post_Taxonomy_CodeSnippetType::instance();
$item_categories = [];
$item_tags = [];
$items = [];
$index = 0;
$query_posts = $code_snippet_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;
$types = wp_get_post_terms( $item->id, $code_snippet_types->name );
if ( ! $types ) {
continue;
}
$item->type = $types[0]->slug;
if ( $item_type !== $item->type ) {
continue;
}
$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' );
}
$item->slug = $post->post_name;
$item->url = esc_url( wp_make_link_relative( get_permalink( $post ) ) );
$item->short_name = '';
$item->is_default = get_post_meta( $item->id, '_et_default', true );
$item->description = '';
$item->is_favorite = $code_snippet_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,
$code_snippet_categories->name,
'category'
);
$this->process_item_taxonomy(
$post,
$item,
$index,
$item_tags,
$code_snippet_tags->name,
'tag'
);
$items[] = $item;
$index++;
}
return [
'categories' => $this->get_processed_terms( $code_snippet_categories->name ),
'tags' => $this->get_processed_terms( $code_snippet_tags->name ),
'items' => $items,
];
}
/**
* Performs item exceptional updates.
*
* @param array $payload Payload.
* @param array $updated_data Updated data.
*
* @since 4.21.0
*
* @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();
$code_snippets_type = ET_Builder_Post_Taxonomy_CodeSnippetType::instance();
if ( in_array( $update_type, [ 'duplicate', 'duplicate_and_delete' ], true ) ) {
if ( isset( $update_details['content'] ) ) {
$content = isset( $update_details['content']['data'] ) ? $update_details['content']['data'] : '';
$snippet_type = isset( $update_details['content']['snippet_type'] ) ? sanitize_text_field( $update_details['content']['snippet_type'] ) : 'et_code_snippet_html_js';
} else {
$content = get_the_content( null, false, $item_id );
$snippet_type = wp_get_post_terms( $item_id, $code_snippets_type->name, array( 'fields' => 'names' ) );
$snippet_type = is_wp_error( $snippet_type ) || '' === $snippet_type ? 'et_code_snippet_html_js' : sanitize_text_field( $snippet_type[0] );
}
$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'],
$code_snippets_type->name => $snippet_type,
),
);
$updated_data['newItem'] = wp_insert_post( $new_item );
}
$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,319 @@
<?php
/**
* Code Snippets Library API.
*
* @package Divi
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Gets the terms list and processes it into desired format.
*
* @since 4.19.0
*
* @param string $tax_name Term Name.
*
* @return array $terms_by_id
*/
function et_code_snippets_library_get_processed_terms( $tax_name ) {
$terms = get_terms( $tax_name, [ 'hide_empty' => false ] );
$terms_by_id = [];
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return [];
}
foreach ( $terms as $term ) {
$term_id = $term->term_id;
$terms_by_id[ $term_id ]['id'] = $term_id;
$terms_by_id[ $term_id ]['name'] = $term->name;
$terms_by_id[ $term_id ]['slug'] = $term->slug;
$terms_by_id[ $term_id ]['count'] = $term->count;
}
return $terms_by_id;
}
/**
* Processes item taxonomies for inclusion in the theme builder library UI items data.
*
* @since 4.19.0
*
* @param WP_POST $post Unprocessed item.
* @param object $item Currently processing item.
* @param int $index The item's index position.
* @param array[] $item_terms Processed items.
* @param string $taxonomy_name Item name.
* @param string $type Item type.
*
* @return void
*/
function et_code_snippets_library_process_item_taxonomy( $post, $item, $index, &$item_terms, $taxonomy_name, $type ) {
$terms = wp_get_post_terms( $post->ID, $taxonomy_name );
if ( ! $terms ) {
if ( 'category' === $type ) {
$item->category_slug = 'uncategorized';
}
return;
}
foreach ( $terms as $term ) {
$term_name = et_core_intentionally_unescaped( $term->name, 'react_jsx' );
if ( ! isset( $item_terms[ $term->term_id ] ) ) {
$item_terms[ $term->term_id ] = array(
'id' => $term->term_id,
'name' => $term_name,
'slug' => $term->slug,
'items' => array(),
);
}
$item_terms[ $term->term_id ]['items'][] = $index;
if ( 'category' === $type ) {
$item->categories[] = $term_name;
} else {
$item->tags[] = $term_name;
}
$item->{$type . '_ids'}[] = $term->term_id;
if ( ! isset( $item->{$type . '_slug'} ) ) {
$item->{$type . '_slug'} = $term->slug;
}
$id = get_post_meta( $post->ID, "_primary_{$taxonomy_name}", true );
if ( $id ) {
// $id is a string, $term->term_id is an int.
if ( $id === $term->term_id ) {
// This is the primary term (used in the item URL).
$item->{$type . '_slug'} = $term->slug;
}
}
}
}
/**
* Sanitize txonomies.
*
* @since 4.19.0
*
* @param array $taxonomies Array of id for categories and tags.
*
* @return array Sanitized value.
*/
function et_code_snippets_library_sanitize_taxonomies( $taxonomies ) {
if ( empty( $taxonomies ) ) {
return array();
}
return array_unique(
array_map( 'intval', $taxonomies )
);
}
/**
* Get all terms of an item and merge any newly passed IDs with the list.
*
* @since 4.19.0
*
* @param string $new_terms_list List of new terms.
* @param array $taxonomies Taxonomies.
* @param string $taxonomy_name Taxonomy name.
*
* @return array
*/
function et_code_snippets_library_create_and_get_all_item_terms( $new_terms_list, $taxonomies, $taxonomy_name ) {
$new_names_array = explode( ',', $new_terms_list );
foreach ( $new_names_array as $new_name ) {
if ( '' !== $new_name ) {
$new_term = wp_insert_term( $new_name, $taxonomy_name );
if ( ! is_wp_error( $new_term ) ) {
$taxonomies[] = $new_term['term_id'];
} elseif (
! empty( $new_term->error_data ) &&
! empty( $new_term->error_data['term_exists'] )
) {
$taxonomies[] = $new_term->error_data['term_exists'];
}
}
}
return $taxonomies;
}
/**
* Export the Code Snippets library item from cloud.
*
* @since 4.19.0
*
* @param array $cloud_content Optional cloud content.
*
* @return array
*/
function et_code_snippets_library_export_cloud_item( $cloud_content ) {
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die();
}
$content = array( 'context' => 'et_code_snippet' );
$content['snippet_type'] = $cloud_content['snippet_type'];
$content['data'] = $cloud_content['data'];
return $content;
}
/**
* Export the Code Snippets library item local item.
*
* @since 4.19.0
*
* @param int $post_id Item ID.
*
* @return array
*/
function et_code_snippets_library_export_local_item( $post_id ) {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
wp_die();
}
$snippet = get_post( $post_id );
$snippet_type = current( wp_get_post_terms( $post_id, 'et_code_snippet_type' ) );
$content = array( 'context' => 'et_code_snippet' );
$content['snippet_type'] = $snippet_type->slug;
$content['data'] = $snippet->post_content;
return $content;
}
/**
* Export the Code Snippets library item directly.
*
* @since 4.19.0
* @param array $direct_export Contain snippet-type and content.
*
* @return array
*/
function et_code_snippets_library_export_directly( $direct_export ) {
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die();
}
if ( ! trim( $direct_export['content'] ) ) {
return false;
}
$content = array( 'context' => 'et_code_snippet' );
$content['snippet_type'] = $direct_export['snippet_type'];
$content['data'] = stripslashes_deep( $direct_export['content'] );
return $content;
}
/**
* Export the Code Snippets library item.
*
* @since 4.19.0
*
* @param int $id Item ID.
* @param array $cloud_content Optional cloud content.
* @param array $direct_export Contain snippet-type and content.
*
* @return array
*/
function et_code_snippets_library_export_item_data( $id, $cloud_content, $direct_export ) {
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die();
}
if ( empty( $id ) ) {
return false;
}
$id = absint( $id );
if ( empty( $direct_export ) ) {
$export_content = empty( $cloud_content ) ?
et_code_snippets_library_export_local_item( $id )
:
et_code_snippets_library_export_cloud_item( $cloud_content );
} else {
$export_content = et_code_snippets_library_export_directly( $direct_export );
}
if ( empty( $export_content ) ) {
return;
}
$transient = 'et_code_snippet_export_' . get_current_user_id() . '_' . $id;
set_transient( $transient, $export_content, 60 * 60 * 24 );
return $export_content;
}
/**
* Import the Code Snippets library item.
*
* @since 4.19.0
*
* @return array
*/
function et_code_snippets_library_import_item_data() {
if ( ! current_user_can( 'edit_posts' ) || ! isset( $_POST['fileData'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in `et_builder_security_check` before calling this function.
return false;
}
// phpcs:disable ET.Sniffs.ValidatedSanitizedInput.InputNotSanitized -- wp_insert_post function does sanitization.
$file_data = $_POST['fileData']; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in `et_builder_security_check` before calling this function.
$file_name = sanitize_text_field( $file_data['title'] );
$file_content = isset( $_POST['fileContent'] ) ? json_decode( stripslashes( $_POST['fileContent'] ), true ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in `et_builder_security_check` before calling this function.
// phpcs:enable
if ( ! isset( $file_data['type'] ) ) {
return false;
}
$snippet = array(
'item_name' => $file_name,
'item_type' => $file_data['type'],
'content' => $file_content,
);
return et_code_snippets_save_to_local_library( $snippet );
}
/**
* Save a code snippet to local library.
*
* @param array $item Item data.
*/
function et_code_snippets_save_to_local_library( $item ) {
$_ = et_();
$item_name = sanitize_text_field( $_->array_get( $item, 'item_name', '' ) );
$content = $_->array_get( $item, 'content', '' );
$post_id = wp_insert_post(
array(
'post_type' => ET_CODE_SNIPPET_POST_TYPE,
'post_status' => 'publish',
'post_title' => $item_name,
'post_content' => $content,
)
);
et_local_library_set_item_taxonomy( $post_id, $item );
return $post_id;
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Code snippets quick feature entry file.
*
* Divi Cloud Code Snippets
*
* @link https://github.com/elegantthemes/Divi/issues/26232
*
* @package Divi
* @subpackage Core
* @since 4.19.0
*/
if ( ! defined( 'ET_CODE_SNIPPETS_DIR' ) ) {
define( 'ET_CODE_SNIPPETS_DIR', ET_CORE_PATH . 'code-snippets/' );
}
require_once trailingslashit( ET_CODE_SNIPPETS_DIR ) . 'constants.php';
require_once trailingslashit( ET_CODE_SNIPPETS_DIR ) . 'code-snippets-library.php';
require_once trailingslashit( ET_CODE_SNIPPETS_DIR ) . 'api.php';
if ( ! function_exists( 'et_init_code_snippets_library' ) ) :
/**
* Init Code Snippets Library.
*
* Class `ET_Builder_Post_Taxonomy_LayoutCategory` must be initalized
* before `ET_Builder_Code_Snippets_Library` because of the internal dependency.
*
* Since `ET_Builder_Post_Taxonomy_LayoutCategory is initialized using
* `add_action( 'init', 'et_setup_builder', 0 );`,
*
* We initialize `ET_Builder_Code_Snippets_Library` using
* `add_action( 'init', 'et_init_code_snippets_library', 10 );`
*
* @return void
*/
function et_init_code_snippets_library() {
require_once trailingslashit( ET_CODE_SNIPPETS_DIR ) . 'CodeSnippetsLibrary.php';
}
endif;
add_action( 'init', 'et_init_code_snippets_library' );

View File

@@ -0,0 +1,20 @@
<?php
/**
* "Code Snippets" quick feature constants file.
*
* Divi Cloud Code Snippet constants.
*
* @link https://github.com/elegantthemes/Divi/issues/26232
*
* @package Divi
* @subpackage Cloud
* @since 4.19.0
*/
if ( ! defined( 'ET_CODE_SNIPPET_POST_TYPE' ) ) {
define( 'ET_CODE_SNIPPET_POST_TYPE', 'et_code_snippet' );
}
if ( ! defined( 'ET_CODE_SNIPPET_TAXONOMY_TYPE' ) ) {
define( 'ET_CODE_SNIPPET_TAXONOMY_TYPE', 'et_code_snippet_type' );
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* Init ET_CODE_SNIPPET_TAXONOMY_TYPE taxonomy.
*
* @since 4.19.0
*
* @package Divi
* @subpackage Builder
* @since 4.19.0
*/
/**
* Register ET_CODE_SNIPPET_TAXONOMY_TYPE taxonomy.
*/
class ET_Builder_Post_Taxonomy_CodeSnippetType extends ET_Core_Post_Taxonomy {
/**
* {@inheritDoc}
*
* @var string
*/
protected $_owner = 'builder';
/**
* {@inheritDoc}
*
* @var string
*/
public $name = 'et_code_snippet_type';
/**
* {@inheritDoc}
*/
protected function _get_args() {
return array(
'hierarchical' => false,
'show_ui' => false,
'show_admin_column' => true,
'query_var' => true,
'show_in_nav_menus' => false,
'publicly_queryable' => true,
);
}
/**
* {@inheritDoc}
*/
protected function _get_labels() {
return array(
'name' => esc_html__( 'Type', 'et_builder' ),
);
}
/**
* Get the class instance.
*
* @param string $type See {@see self::$wp_type} for accepted values. Default is 'taxonomy'.
* @param string $name The name/slug of the post object. Default is {@see self::$name}.
*
* @return ET_Builder_Post_Taxonomy_CodeSnippet_Type
*/
public static function instance( $type = 'taxonomy', $name = 'et_code_snippet_type' ) {
$instance = parent::instance( $type, $name );
if ( ! $instance ) {
$instance = new self();
}
return $instance;
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* Register ET_CODE_SNIPPET_POST_TYPE taxonomy.
*
* @since 4.19.0
*
* @package Divi
* @subpackage Cloud
* @since 4.19.0
*/
/**
* Class to handle `et_tb_item` post type.
*
* Registers TB Item.
*/
class ET_Builder_Post_Type_Code_Snippet extends ET_Core_Post_Type {
/**
* {@inheritDoc}
*
* @var string
*/
protected $_owner = 'builder';
/**
* {@inheritDoc}
*
* @var string
*/
public $name = ET_CODE_SNIPPET_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',
'et_code_snippet_type',
),
);
}
/**
* {@inheritDoc}
*/
protected function _get_labels() {
return array(
'add_new' => esc_html_x( 'Add New', 'Layout', 'et_builder' ),
'add_new_item' => esc_html__( 'Add New Code Snippet', 'et_builder' ),
'all_items' => esc_html__( 'All Code Snippets', 'et_builder' ),
'edit_item' => esc_html__( 'Edit Code Snippet', 'et_builder' ),
'name' => esc_html__( 'Code Snippets', 'et_builder' ),
'new_item' => esc_html__( 'New Code Snippet', '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 Code Snippets', 'et_builder' ),
'singular_name' => esc_html__( 'Code Snippet', 'et_builder' ),
'view_item' => esc_html__( 'View Code Snippet', '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_CODE_SNIPPET_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 );
}
}