HEX
Server: Apache/2.4.65 (Debian)
System: Linux wordpress-7cb4c6b6f6-js66c 5.15.0-131-generic #141-Ubuntu SMP Fri Jan 10 21:18:28 UTC 2025 x86_64
User: www-data (33)
PHP: 8.3.27
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/jet-engine/includes/components/listings/components/editor.php
<?php
namespace Jet_Engine\Listings\Components;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Define components editor class
 */
class Editor {

	private $settings_template_hooked = false;

	/**
	 * Constructor for the class
	 */
	public function __construct() {

		add_filter( 'jet-engine/templates/localized-settings', [ $this, 'add_button_template' ] );
		add_filter( 'jet-engine/templates/editor-views-list', [ $this, 'component_view_link' ], 10, 3 );
		add_filter( 'jet-engine/templates/edit-settings-button', [ $this, 'component_settings_button' ], 10, 2 );

		add_action( 'jet-engine/templates/before-listing-assets', [ $this, 'editor_assets' ] );
		add_action( 'wp_ajax_jet_engine_get_component_settings', [ $this, 'ajax_get_settings' ] );
		add_action( 'wp_ajax_jet_engine_set_component_settings', [ $this, 'ajax_set_settings' ] );

	}

	/**
	 * AJAX callback to set component settings
	 *
	 * @return [type] [description]
	 */
	public function ajax_set_settings() {

		$component_id = ! empty( $_REQUEST['component_id'] ) ? absint( $_REQUEST['component_id'] ) : false;

		if ( ! $component_id || ! current_user_can( 'edit_post', $component_id ) ) {
			wp_send_json_error( __( 'You can`t edit this compnent', 'jet-engine' ) );
		}

		$nonce_action = jet_engine()->listings->post_type->get_nonce_action();

		// phpcs:disable
		if ( empty( $_REQUEST['_nonce'] ) || ! wp_verify_nonce( $_REQUEST['_nonce'], $nonce_action ) ) {
			wp_send_json_error( __( 'Page is expired, please reload it and try again', 'jet-engine' ) );
		}
		// phpcs:enable

		$settings = ! empty( $_REQUEST['settings'] ) ? $_REQUEST['settings'] : []; // phpcs:ignore
		$component = jet_engine()->listings->components->get( $component_id, 'id' );

		$props  = ! empty( $settings['component_controls_list'] ) ? $settings['component_controls_list'] : [];
		$styles = ! empty( $settings['component_style_controls_list'] ) ? $settings['component_style_controls_list'] : [];

		$component->set_props( $props );
		$component->set_styles( $styles );

		$template_view = get_post_meta( $component_id, '_listing_type', true );

		do_action( 'jet-engine/listings/components/update-settings', $component );

		wp_send_json_success(
			jet_engine()->listings->post_type->admin_screen->get_edit_url( $template_view, $component_id )
		);

	}

	/**
	 * AJAX callback to get component settings
	 *
	 * @return [type] [description]
	 */
	public function ajax_get_settings() {

		$component_id = ! empty( $_REQUEST['component_id'] ) ? absint( $_REQUEST['component_id'] ) : false;

		if ( ! $component_id || ! current_user_can( 'edit_post', $component_id ) ) {
			wp_send_json_error( __( 'You can`t edit this compnent', 'jet-engine' ) );
		}

		$nonce_action = jet_engine()->listings->post_type->get_nonce_action();

		// phpcs:disable
		if ( empty( $_REQUEST['_nonce'] ) || ! wp_verify_nonce( $_REQUEST['_nonce'], $nonce_action ) ) {
			wp_send_json_error( __( 'Page is expired, please reload it and try again', 'jet-engine' ) );
		}
		// phpcs:enable

		$component = jet_engine()->listings->components->get( $component_id, 'id' );

		$settings = [
			'component_controls_list'       => $component->get_props(),
			'component_style_controls_list' => $component->get_styles(),
		];

		wp_send_json_success( $settings );

	}

	/**
	 * Change edit settings button for the component
	 *
	 * @return [type] [description]
	 */
	public function component_settings_button( $button, $post_id ) {

		if ( jet_engine()->listings->components->is_component( $post_id ) ) {

			$button = sprintf(
				'<button type="button" class="button button-small jet-engine-component-edit-settings" data-component-id="%1$s">%2$s<span class="spinner"></span></button>',
				$post_id,
				__( 'Edit Component Settings', 'jet-engine' )
			);

			if ( false === $this->settings_template_hooked ) {

				add_action( 'admin_footer', [ $this, 'render_settings_template' ] );

				$this->settings_template_hooked = true;
			}

		}

		return $button;
	}

	public function render_settings_template() {
		// phpcs:disable
		?>
		<script type="text/x-template" id="jet-engine-component-edit-settings-tmpl">
			<div class="jet-engine-component-settings">
				<div class="jet-engine-component-settings__nav">
					<a
						href="#"
						:class="{
							'jet-engine-component-settings__nav-link': true,
							'is-active': 'content' === componentControlsMode
						}"
						@click.prevent="componentControlsMode = 'content'"
					><?php _e( 'Content Controls', 'jet-engine' ); ?></a>
					|
					<a
						href="#"
						:class="{
							'jet-engine-component-settings__nav-link': true,
							'is-active': 'style' === componentControlsMode
						}"
						@click.prevent="componentControlsMode = 'style'"
					><?php _e( 'Style Controls', 'jet-engine' ); ?></a>
				</div>
				<cx-vui-repeater
					:style="{ marginBottom: '2px' }"
					button-label="<?php _e( 'New Control', 'jet-engine' ); ?>"
					button-style="accent-border"
					button-size="mini"
					v-model="settings.component_controls_list"
					v-if="'content' === componentControlsMode"
					@add-new-item="addNewControl( 'component_controls_list', {
						control_label: '',
						control_name: '',
						control_type: 'text',
						control_options: '',
						control_default: '',
						control_default_image: '',
						control_default_icon: '',
					} )"
				>
					<cx-vui-repeater-item
						v-for="( control, index ) in settings.component_controls_list"
						:title="control.control_label"
						:subtitle="control.control_name"
						:collapsed="isCollapsed( control )"
						:index="index"
						@clone-item="cloneControl( $event, 'component_controls_list' )"
						@delete-item="deleteControl( $event, 'component_controls_list' )"
						:key="control.id ? control.id : control.id = getRandomID()"
						:ref="'control' + control.id"
					>
						<cx-vui-input
							label="<?php _e( 'Control Label', 'jet-engine' ); ?>"
							description="<?php _e( 'Control label to show in the component UI in editor', 'jet-engine' ); ?>"
							:wrapper-css="[ 'vertical-fullwidth' ]"
							size="fullwidth"
							:value="control.control_label"
							@input="setControlProp( 'component_controls_list', 'control_label', $event, index )"
							@on-input-change="preSetControlName( index, 'component_controls_list' )"
						></cx-vui-input>
						<cx-vui-input
							label="<?php _e( 'Control Name', 'jet-engine' ); ?>"
							description="<?php _e( 'Control key/name to save into the DB. Please use only lowercase letters, numbers and `_`. Also please note - name must be unique for this component (for both - styles and controls)', 'jet-engine' ); ?>"
							:wrapper-css="[ 'vertical-fullwidth' ]"
							size="fullwidth"
							:value="control.control_name"
							@input="setControlProp( 'component_controls_list', 'control_name', $event, index )"
						></cx-vui-input>
						<cx-vui-select
							label="<?php _e( 'Control Type', 'jet-engine' ); ?>"
							description="<?php _e( 'Type of control for UI', 'jet-engine' ); ?>"
							:wrapper-css="[ 'vertical-fullwidth' ]"
							size="fullwidth"
							:options-list="componentControlTypes()"
							:value="control.control_type"
							@input="setControlProp( 'component_controls_list', 'control_type', $event, index )"
						></cx-vui-select>
						<cx-vui-textarea
							label="<?php _e( 'Options', 'jet-engine' ); ?>"
							description="<?php _e( 'One option per line. Split label and value with `::`, for example - red::Red', 'jet-engine' ); ?>"
							:wrapper-css="[ 'vertical-fullwidth' ]"
							size="fullwidth"
							:value="control.control_options"
							@input="setControlProp( 'component_controls_list', 'control_options', $event, index )"
							v-if="'select' === control.control_type"
						></cx-vui-textarea>
						<cx-vui-textarea
							label="<?php _e( 'Default Value', 'jet-engine' ); ?>"
							description="<?php _e( 'Default value of the given control', 'jet-engine' ); ?>"
							:wrapper-css="[ 'vertical-fullwidth' ]"
							size="fullwidth"
							:value="control.control_default"
							@input="setControlProp( 'component_controls_list', 'control_default', $event, index )"
							v-if="'media' !== control.control_type"
						></cx-vui-textarea>
						<cx-vui-component-wrapper
							label="<?php _e( 'Default Value', 'jet-engine' ); ?>"
							description="<?php _e( 'Default value of the given control', 'jet-engine' ); ?>"
							:wrapper-css="[ 'media' ]"
							v-if="'media' === control.control_type"
						>
							<div
								class="jet-engine-component-settings__media"
								@click="openMediaFrame( control, index )"
							>
								<div
									class="jet-engine-component-settings__media-preview"
									v-if="hasControlDefaultImage( control )"
								>
									<img
										:src="defaultImageSRC( control.control_default_image )"
										alt=""
									>
									<div
										class="jet-engine-component-settings__media-remove"
										@click.stop="clearMediaControl( index )"
									>
										<svg width="12" height="12" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.28564 14.192V3.42847H13.7142V14.192C13.7142 14.6685 13.5208 15.0889 13.1339 15.4533C12.747 15.8177 12.3005 15.9999 11.7946 15.9999H4.20529C3.69934 15.9999 3.25291 15.8177 2.866 15.4533C2.4791 15.0889 2.28564 14.6685 2.28564 14.192Z"></path><path d="M14.8571 1.14286V2.28571H1.14282V1.14286H4.57139L5.56085 0H10.4391L11.4285 1.14286H14.8571Z"></path></svg>
									</div>
								</div>
								<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="jet-engine-component-settings__media-add"><path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm6 13h-5v5h-2v-5h-5v-2h5v-5h2v5h5v2z"/></svg>
							</div>
						</cx-vui-component-wrapper>
					</cx-vui-repeater-item>
				</cx-vui-repeater>
				<cx-vui-repeater
					button-label="<?php _e( 'New Control', 'jet-engine' ); ?>"
					button-style="accent-border"
					button-size="mini"
					v-model="settings.component_style_controls_list"
					v-if="'style' === componentControlsMode"
					@add-new-item="addNewControl( 'component_style_controls_list', {
						control_label: '',
						control_name: '',
						control_default: '',
					} )"
				>
					<cx-vui-repeater-item
						v-for="( control, index ) in settings.component_style_controls_list"
						:title="control.control_label"
						:subtitle="control.control_name"
						:collapsed="isCollapsed( control )"
						:index="index"
						@clone-item="cloneControl( $event, 'component_style_controls_list' )"
						@delete-item="deleteControl( $event, 'component_style_controls_list' )"
						:key="control.id ? control.id : control.id = getRandomID()"
						:ref="'control' + control.id"
					>
						<cx-vui-input
							label="<?php _e( 'Control Label', 'jet-engine' ); ?>"
							description="<?php _e( 'Control label to show in the component UI in editor', 'jet-engine' ); ?>"
							:wrapper-css="[ 'vertical-fullwidth' ]"
							size="fullwidth"
							:value="control.control_label"
							@input="setControlProp( 'component_style_controls_list', 'control_label', $event, index )"
							@on-input-change="preSetControlName( index, 'component_style_controls_list' )"
						></cx-vui-input>
						<cx-vui-input
							label="<?php _e( 'Control Name', 'jet-engine' ); ?>"
							description="<?php _e( 'Control key/name to save into the DB. Please use only lowercase letters, numbers and `_`. Also please note - name must be unique for this component (for both - styles and controls)', 'jet-engine' ); ?>"
							:wrapper-css="[ 'vertical-fullwidth' ]"
							size="fullwidth"
							:value="control.control_name"
							@input="setControlProp( 'component_style_controls_list', 'control_name', $event, index )"
						></cx-vui-input>
						<cx-vui-input
							label="<?php _e( 'Default Value', 'jet-engine' ); ?>"
							description="<?php _e( 'Default value of the given control', 'jet-engine' ); ?>"
							:wrapper-css="[ 'vertical-fullwidth' ]"
							size="fullwidth"
							:value="control.control_default"
							@input="setControlProp( 'component_style_controls_list', 'control_default', $event, index )"
							v-if="'media' !== control.control_type"
						><jet-engine-timber-css-vars-helper
							button-label="<?php esc_html_e( 'Use CSS Variable', 'jet-engine' ); ?>"
							@input="setControlProp( 'component_style_controls_list', 'control_default', $event, index )"
						></jet-engine-timber-css-vars-helper></cx-vui-input>
					</cx-vui-repeater-item>
				</cx-vui-repeater>
			</div>
		</script>
		<?php
		// phpcs:enable
	}

	/**
	 * Add components link to the views tab
	 *
	 * @param  [type] $links  [description]
	 * @param  [type] $counts [description]
	 * @return [type]         [description]
	 */
	public function component_view_link( $links, $counts, $base_url ) {

		// phpcs:disable
		$links['component'] = sprintf(
			'<a href="%1$s" %3$s>%2$s <span class="count">(%4$s)</span></a>',
			add_query_arg( [ 'entry_type' => 'component' ], $base_url ),
			esc_html__( 'Components', 'jet-engine' ),
			( ! empty( $_GET['entry_type'] ) && 'component' === $_GET['entry_type'] ) ? 'class="current" aria-current="page"' : '',
			isset( $counts['component'] ) ? $counts['component']->posts_count : 0
		);
		// phpcs:enable

		return $links;
	}

	/**
	 * Editor assets
	 *
	 * @return [type] [description]
	 */
	public function editor_assets() {

		/**
		 * Chek we're currently on Listing Items list page.
		 * Because 'jet-engine/templates/before-listing-assets' hook fires also in Elementor editor
		 * @see https://github.com/Crocoblock/issues-tracker/issues/13481
		 */
		if ( ! jet_engine()->listings->post_type->is_listings_edit_page() ) {
			return;
		}

		$module_data = jet_engine()->framework->get_included_module_data( 'cherry-x-vue-ui.php' );
		$ui          = new \CX_Vue_UI( $module_data );

		$ui->enqueue_assets();

		wp_enqueue_script(
			'jet-engine-listing-components',
			jet_engine()->listings->components->url( 'assets/js/components-editor.js' ),
			array( 'jquery', 'jet-plugins' ),
			jet_engine()->get_version(),
			true
		);

		$component_control_types = \Jet_Engine_Tools::prepare_list_for_js(
			jet_engine()->listings->components->get_supported_control_types(), ARRAY_A
		);

		wp_localize_script( 'jet-engine-listing-components', 'JetEngineComponentsData', [
			'component_control_types' => $component_control_types,
		] );
	}

	/**
	 * Add New Component button template to localized Listing data
	 */
	public function add_button_template( $data ) {

		/**
		 * Chek we're currently on Listing Items list page.
		 * Because 'jet-engine/templates/before-listing-assets' hook fires also in Elementor editor
		 * @see https://github.com/Crocoblock/issues-tracker/issues/13481
		 */
		if ( ! jet_engine()->listings->post_type->is_listings_edit_page() ) {
			return $data;
		}

		$data['addNewComponent'] = sprintf(
			'<a href="#" class="page-title-action is-new-component">%1$s</a>',
			__( 'Add New Component', 'jet-engine' )
		);

		add_action( 'admin_footer', [ $this, 'component_popup_template' ], 1000 );

		return $data;

	}

	/**
	 * Render new component popup template in footer
	 *
	 * @return [type] [description]
	 */
	public function component_popup_template() {

		$action = jet_engine()->listings->post_type->admin_screen->get_listing_popup_action();
		$views  = jet_engine()->listings->post_type->get_listing_views();

		?>
		<div class="jet-listings-popup jet-listings-popup--new is-component-popup" style="display: none;">
			<div class="jet-listings-popup__overlay"></div>
			<div class="jet-listings-popup__content">
				<div class="jet-listings-popup__close">
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g><path d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"/></g></svg>
				</div>
				<h3 class="jet-listings-popup__heading"><?php
					esc_html_e( 'Setup Component', 'jet-engine' );
				?></h3>
				<form class="jet-listings-popup__form is-component-popup" id="jet_engine_new_component" method="POST" action="<?php echo esc_attr( $action ); ?>" >
					<input type="hidden" name="template_entry_type" value="component">
					<input type="hidden" name="listing_source" value="post">
					<div class="jet-listings-popup__form-row">
						<label for="component_template_name"><?php esc_html_e( 'Component name:', 'jet-engine' ); ?></label>
						<input type="text" id="component_template_name" name="template_name" placeholder="<?php esc_html_e( 'Set component name. Will be used as generated widget/block name.', 'jet-engine' ); ?>" value="" class="jet-listings-popup__control">
					</div>
					<div class="jet-listings-popup__form-row">
						<label for="component_view_type"><?php esc_html_e( 'Component view:', 'jet-engine' ); ?></label>
						<select id="component_view_type" name="listing_view_type" class="jet-listings-popup__control"><?php
							foreach ( $views as $view_key => $view_label ) {
								printf(
									'<option value="%1$s">%2$s</option>',
									esc_attr( $view_key ),
									esc_html( $view_label )
								);
							}
						?></select>
					</div>
					<div class="jet-listings-popup__form-actions">
						<button type="submit" class="button button-primary button-hero"><?php
							esc_html_e( 'Create Component', 'jet-engine' );
						?></button>
					</div>
				</form>
			</div>
		</div>
		<div class="jet-listings-popup jet-listings-popup--edit-settings is-component-popup jet-listings-popup--keep-alive" id="jet_engine_component_settings_popup" style="display: none;">
			<div class="jet-listings-popup__overlay"></div>
			<div class="jet-listings-popup__content">
				<div class="jet-listings-popup__close">
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g><path d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"/></g></svg>
				</div>
				<div class="jet-engine-component-settings__header">
					<h3 class="jet-listings-popup__heading"><?php
						esc_html_e( 'Setup Component', 'jet-engine' );
					?></h3>
					<div class="jet-engine-component-settings__actions">
						<button type="button" class="button button-primary jet-engine-component-save"><?php
							esc_html_e( 'Save', 'jet-engine' );
						?></button>
						<button type="button" class="button button-secondary jet-engine-component-save open-editor"><?php
							esc_html_e( 'Save & Go To Editor', 'jet-engine' );
						?></button>
					</div>
				</div>
				<div id="jet_engine_component_settings_content"></div>
			</div>
		</div>
		<?php
	}

}