File "widget-writer.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/elementskit-lite/modules/widget-builder/controls/widget-writer.php
File size: 15.69 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace ElementsKit_Lite\Modules\Widget_Builder\Controls;

use ElementsKit_Lite\Modules\Widget_Builder\Widget_File;

defined( 'ABSPATH' ) || exit;

class Widget_Writer {

	private $widget_name;
	private $widget_id;
	private $file_handler = '';
	private $file_name;
	private $enqueue_handler_prefix;
	private $widget_class_name;
	private $folder_name;
	private $control_prefix;
	private $name_prefix       = 'ekit_wb_';
	private $class_name_prefix = 'Ekit_Wb_';
	private $widget_obj;
	private $prepared_content = '';
	public $text_domain       = 'elementskit-lite';

	const TAB_CONTENT = 'Controls_Manager::TAB_CONTENT';
	const TAB_STYLE   = 'Controls_Manager::TAB_STYLE';
	const TAB_ADVANCE = 'Controls_Manager::TAB_ADVANCED';

	const CONTROL_GROUP_TYPE_SINGLE     = 'single';
	const CONTROL_GROUP_TYPE_RESPONSIVE = 'responsive';
	const CONTROL_GROUP_TYPE_GROUPED    = 'group';


	public function __construct( $widget, $widget_id, $txt_domain = 'elementskit-lite' ) {

		$this->widget_obj  = $widget;
		$this->file_name   = '';
		$this->text_domain = $txt_domain;
		$this->widget_id   = $widget_id;

		$this->folder_name       = $this->name_prefix . $this->widget_id;
		$this->widget_name       = $this->name_prefix . $this->widget_id;
		$this->widget_class_name = $this->class_name_prefix . $this->widget_id;
		$this->control_prefix    = $this->folder_name . '_';

		$this->enqueue_handler_prefix = 'ekit-wb-' . $this->widget_id;
	}


	public function start_backing( $wp_filesystem ) {

		$css_enqueue = $this->prepare_css_file( $this->widget_obj->css, $wp_filesystem );
		$js_enqueue  = $this->prepare_js_file( $this->widget_obj->js, $wp_filesystem );
		$include_js  = ! empty( $this->widget_obj->js_includes ) || ! empty( $this->widget_obj->css_includes );

		$content = $this->prepare_php_file();

		if ( $css_enqueue === true || $js_enqueue === true || $include_js === true ) {

			$content .= $this->write_construct_method( $css_enqueue, $js_enqueue );
		}

		$content .= $this->write_name_method();
		$content .= $this->write_title_method( $this->widget_obj->title );
		$content .= $this->write_categories_method( $this->widget_obj->categories );
		$content .= $this->write_icon_method( $this->widget_obj->icon );
		$content .= $this->write_register_control_method( $this->widget_obj->tabs );
		$content .= $this->write_render_method( $this->widget_obj->markup );
		$content .= $this->close_widget_class();

		$this->prepared_content = $content;
	}


	private function get_url_path() {

		$upload = wp_upload_dir();

		return $upload['baseurl'] . '/elementskit/custom_widgets/' . $this->folder_name;
	}


	private function get_file_path() {

		$upload     = wp_upload_dir();
		$upload_dir = $upload['basedir'];
		$upload_dir = $upload_dir . '/elementskit/custom_widgets/' . $this->folder_name;

		if ( ! is_dir( $upload_dir ) ) {
			wp_mkdir_p( $upload_dir );
		}

		return $upload_dir;
	}


	public static function delete_widget( $widget_id ) {

		$fl_sys = Widget_File::get_wp_filesystem_pointer();

		$wb = new self( array(), $widget_id );

		$dir = $wb->get_file_path();

		if ( file_exists( $dir ) ) {

			$fl_sys->delete( $dir, true );
		}

		return true; // :P
	}


	private function prepare_css_file( $content, $file_system ) {

		if ( empty( $content ) ) {
			return false;
		}

		$trimmed = trim( $content );

		if ( empty( $trimmed ) ) {
			return false;
		}

		$path = $this->get_file_path();

		return $file_system->put_contents( $path . '/style.css', $trimmed );
	}


	private function prepare_js_file( $content, $file_system ) {

		if ( empty( $content ) ) {
			return false;
		}

		$trimmed = trim( $content );

		if ( empty( $trimmed ) ) {
			return false;
		}

		$path = $this->get_file_path();

		return $file_system->put_contents( $path . '/script.js', $trimmed );
	}


	public function finish_backing( $file_system ) {

		$path = $this->get_file_path();

		return $file_system->put_contents( $path . '/widget.php', $this->prepared_content );
	}


	private function prepare_php_file() {

		$ret  = '<?php' . PHP_EOL . PHP_EOL;
		$ret .= 'namespace Elementor;' . PHP_EOL . PHP_EOL;
		$ret .= 'defined(\'ABSPATH\') || exit;' . PHP_EOL . PHP_EOL;
		$ret .= 'class ' . $this->widget_class_name . ' extends Widget_Base {' . PHP_EOL . PHP_EOL;

		return $ret;
	}


	private function write_construct_method( $css = false, $js = false ) {

		$nm       = $this->enqueue_handler_prefix;
		$url_path = $this->get_url_path();

		$ret  = "\t" . 'public function __construct($data = [], $args = null) {' . PHP_EOL;
		$ret .= "\t\t" . 'parent::__construct($data, $args);' . PHP_EOL . PHP_EOL;

		if ( $css === true ) {
			$ret .= "\t\t" . 'wp_register_style( \'' . $nm . '-style-handle\', \'' . $url_path . '/style.css\');' . PHP_EOL;
		}

		if ( $js === true ) {
			$ret .= "\t\t" . 'wp_register_script( \'' . $nm . '-script-handle\', \'' . $url_path . '/script.js\', [ \'elementor-frontend\' ], \'1.0.0\', true );' . PHP_EOL;
		}

		if ( ! empty( $this->widget_obj->css_includes ) ) {

			$ret .= PHP_EOL;

			foreach ( $this->widget_obj->css_includes as $idx => $cssInclude ) {

				$ret .= "\t\t" . 'wp_enqueue_style( \'' . $nm . '-' . $idx . '-style-handle\', \'' . $cssInclude . '\');' . PHP_EOL;
			}
		}

		if ( ! empty( $this->widget_obj->js_includes ) ) {

			$ret .= PHP_EOL;

			foreach ( $this->widget_obj->js_includes as $idx => $jsInclude ) {

				$ret .= "\t\t" . 'wp_enqueue_script( \'' . $nm . '-' . $idx . '-script-handle\', \'' . $jsInclude . '\', [ \'elementor-frontend\' ], \'1.0.0\', true );' . PHP_EOL;
			}
		}

		$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;

		if ( $css === true ) {
			$ret .= "\n\t" . 'public function get_style_depends() {' . PHP_EOL;
			$ret .= "\t\t" . 'return [ \'' . $nm . '-style-handle\' ];' . PHP_EOL;
			$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;
		}

		if ( $js === true ) {
			$ret .= "\n\t" . 'public function get_script_depends() {' . PHP_EOL;
			$ret .= "\t\t" . 'return [ \'' . $nm . '-script-handle\' ];' . PHP_EOL;
			$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;
		}

		return $ret;
	}


	private function write_name_method() {

		$ret  = "\t" . 'public function get_name() {' . PHP_EOL;
		$ret .= "\t\t" . 'return \'' . $this->widget_name . '\';' . PHP_EOL;
		$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;

		return $ret;
	}


	private function write_title_method( $title = 'empty_title' ) {

		$ret  = "\n\t" . 'public function get_title() {' . PHP_EOL;
		$ret .= "\t\t" . 'return esc_html__( \'' . esc_html($title) . '\', \'' . $this->text_domain . '\' );' . PHP_EOL;
		$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;

		return $ret;
	}


	private function write_categories_method( $cat = array( 'basic' ) ) {

		$cat = empty( $cat ) ? array( 'basic' ) : $cat;

		$joined  = '\'';
		$joined .= implode( '\', \'', $cat );
		$joined .= '\'';

		$ret  = "\n\t" . 'public function get_categories() {' . PHP_EOL;
		$ret .= "\t\t" . 'return [' . $joined . '];' . PHP_EOL;
		$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;

		return $ret;
	}


	private function write_icon_method( $icon = 'eicon-cog' ) {

		$ret  = "\n\t" . 'public function get_icon() {' . PHP_EOL;
		$ret .= "\t\t" . 'return \'' . $icon . '\';' . PHP_EOL;
		$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;

		return $ret;
	}


	private function write_register_control_method( $conf = array() ) {

		$ret = "\n\t" . 'protected function register_controls() {' . PHP_EOL;

		if ( ! empty( $conf->content ) ) {

			foreach ( $conf->content as $indx => $section ) {

				$ret .= $this->write_section( $section->title, self::TAB_CONTENT, 'content', $indx );

				if ( ! empty( $section->controls ) ) {

					$ret .= $this->write_add_control( $section->controls );
				}

				$ret .= $this->close_section();
			}
		}

		if ( ! empty( $conf->style ) ) {

			foreach ( $conf->style as $indx => $section ) {

				$ret .= $this->write_section( $section->title, self::TAB_STYLE, 'style', $indx );

				if ( ! empty( $section->controls ) ) {

					$ret .= $this->write_add_control( $section->controls );
				}

				$ret .= $this->close_section();
			}
		}

		if ( ! empty( $conf->advanced ) ) {

			foreach ( $conf->advanced as $indx => $section ) {

				$ret .= $this->write_section( $section->title, self::TAB_ADVANCE, 'advance', $indx );

				if ( ! empty( $section->controls ) ) {

					$ret .= $this->write_add_control( $section->controls );
				}

				$ret .= $this->close_section();
			}
		}

		$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;

		return $ret;
	}


	private function write_section( $label, $tab = 'Controls_Manager::TAB_CONTENT', $tab_name = 'content', $indx = '' ) {

		$key = $tab_name . '_section_' . $this->widget_id . '_' . $indx;

		$ret  = "\n\t\t" . '$this->start_controls_section(' . PHP_EOL;
		$ret .= "\t\t\t" . '\'' . $key . '\',' . PHP_EOL;
		$ret .= "\t\t\t" . 'array(' . PHP_EOL;

		$ret .= "\t\t\t\t" . '\'label\' => esc_html__( \'' . esc_html($label) . '\', \'' . $this->text_domain . '\' ),' . PHP_EOL;
		$ret .= "\t\t\t\t" . '\'tab\'   => ' . $tab . ',' . PHP_EOL;

		$ret .= "\t\t\t" . ')' . PHP_EOL;
		$ret .= "\t\t" . ');' . PHP_EOL;

		return $ret;
	}


	private function write_add_control( $controls = array() ) {

		$ret = '';

		foreach ( $controls as $controlObj ) {

			if ( $controlObj->control_group === self::CONTROL_GROUP_TYPE_SINGLE ) {

				$ret .= $this->prepare_add_control( $controlObj );

			} elseif ( $controlObj->control_group === self::CONTROL_GROUP_TYPE_RESPONSIVE ) {

				$ret .= $this->prepare_responsive_control( $controlObj );

			} elseif ( $controlObj->control_group === self::CONTROL_GROUP_TYPE_GROUPED ) {

				$ret .= $this->prepare_group_control( $controlObj );
			}
		}

		return $ret;
	}


	private function prepare_add_control( $controlObj ) {

		$factory = new CT_Factory();
		$cnt_obj = $factory->make( $controlObj->controlType, $this->text_domain );

		$ret  = "\n\t\t" . '$this->add_control(' . PHP_EOL;
		$ret .= "\t\t\t" . '\'' . $this->control_prefix . $controlObj->key . '\',' . PHP_EOL;
		$ret .= "\t\t\t" . 'array(' . PHP_EOL;

		$ret .= "\t\t\t\t" . '\'label\' => esc_html__( \'' . esc_html($controlObj->label) . '\', \'' . $this->text_domain . '\' ),' . PHP_EOL;
		$ret .= "\t\t\t\t" . '\'type\'  => ' . $controlObj->control_type . ',' . PHP_EOL;

		$ret .= $cnt_obj->start_writing_conf( $this->file_handler, $controlObj );

		$ret .= "\t\t\t" . ')' . PHP_EOL;
		$ret .= "\t\t" . ');' . PHP_EOL;

		return $ret;
	}


	private function prepare_responsive_control( $controlObj ) {

		$factory = new CT_Factory();
		$cnt_obj = $factory->make( $controlObj->controlType, $this->text_domain, 'responsive' );

		$ret  = "\n\t\t" . '$this->add_responsive_control(' . PHP_EOL;
		$ret .= "\t\t\t" . '\'' . $this->control_prefix . $controlObj->key . '\',' . PHP_EOL;
		$ret .= "\t\t\t" . 'array(' . PHP_EOL;

		$ret .= "\t\t\t\t" . '\'label\' => esc_html__( \'' . esc_html($controlObj->label) . '\', \'' . $this->text_domain . '\' ),' . PHP_EOL;
		$ret .= "\t\t\t\t" . '\'type\'  => ' . $controlObj->control_type . ',' . PHP_EOL;

		//$cnt_obj->start_writing_conf($this->file_handler, $controlObj);

		$ret .= "\t\t\t" . ')' . PHP_EOL;
		$ret .= "\t\t" . ');' . PHP_EOL;

		return $ret;
	}


	private function prepare_group_control( $controlObj ) {

		$factory = new CT_Factory();
		$cnt_obj = $factory->make( $controlObj->controlType, $this->text_domain, 'group' );

		$ret  = "\n\t\t" . '$this->add_group_control(' . PHP_EOL;
		$ret .= "\t\t\t" . '' . $controlObj->control_type . ',' . PHP_EOL;
		$ret .= "\t\t\t" . 'array(' . PHP_EOL;

		$ret .= "\t\t\t\t" . '\'name\' => \'' . $this->control_prefix . $controlObj->key . '\',' . PHP_EOL;

		$ret .= $cnt_obj->start_writing_conf( $this->file_handler, $controlObj );

		$ret .= "\t\t\t" . ')' . PHP_EOL;
		$ret .= "\t\t" . ');' . PHP_EOL;

		return $ret;
	}


	private function close_section() {

		return "\n\t\t" . '$this->end_controls_section();' . PHP_EOL . PHP_EOL;
	}

	/**
	 * Apply proper security escaping to widget markup
	 * 
	 * @param string $markup The widget markup template
	 * @return string The processed markup with proper escaping
	 */
	private function apply_escaping($markup) {
		// Array of regex patterns and their replacements
		$patterns = [
			// Pattern 1: URL attributes in href, src, action with array access ["url"]
			'/(href|src|action)=["\']\s*<\?php\s+echo\s+isset\s*\(\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*\[\s*["\']url["\']\s*\]\s*\)\s*\?\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*\[\s*["\']url["\']\s*\]\s*:\s*["\'][^"\']*["\']\s*;\s*\?>/i' 
				=> '$1="<?php echo isset($settings["$2"]["url"]) ? esc_url($settings["$3"]["url"]) : ""; ?>"',
			
			// Pattern 2: URL attributes in href, src, action (standard, not arrays)
			'/(href|src|action)=["\']\s*<\?php\s+echo\s+isset\s*\(\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*\)\s*\?\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*:\s*["\'][^"\']*["\']\s*;\s*\?>/i'
				=> '$1="<?php echo isset($settings["$2"]) ? esc_url($settings["$3"]) : ""; ?>"',
			
			// Pattern 3: Non-URL attributes (class, data-*, etc.) with array access
			'/(?<!href|src|action)=["\']\s*<\?php\s+echo\s+isset\s*\(\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*\[\s*["\'](?!url)([^"\']+)["\']\s*\]\s*\)\s*\?\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*\[\s*["\']([^"\']+)["\']\s*\]\s*:\s*["\'][^"\']*["\']\s*;\s*\?>/i'
				=> '="<?php echo isset($settings["$1"]["$2"]) ? esc_attr($settings["$3"]["$4"]) : ""; ?>"',
			
			// Pattern 4: Non-URL attributes (class, data-*, etc.) standard access
			'/(?<!href|src|action)=["\']\s*<\?php\s+echo\s+isset\s*\(\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*\)\s*\?\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*:\s*["\'][^"\']*["\']\s*;\s*\?>/i'
				=> '="<?php echo isset($settings["$1"]) ? esc_attr($settings["$2"]) : ""; ?>"',
			
			// Pattern 5: Text content (not in attributes) - use wp_kses_post()
			'/>\s*<\?php\s+echo\s+isset\s*\(\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*\)\s*\?\s*\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*:\s*["\'][^"\']*["\']\s*;\s*\?>\s*</i'
				=> '><?php echo isset($settings["$1"]) ? wp_kses_post($settings["$2"]) : ""; ?><',
		];
		
		// Apply patterns in order
		foreach ($patterns as $pattern => $replacement) {
			$markup = preg_replace($pattern, $replacement, $markup);
		}
		
		// Additional safety check for any remaining URL array references
		$markup = preg_replace_callback(
			'/<\?php.*?\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]\s*\[\s*["\']url["\']\s*\].*?\?>/i',
			function($matches) {
				$fullMatch = $matches[0];
				
				// Only modify if it doesn't already have proper array checking
				if (strpos($fullMatch, 'isset') === false || strpos($fullMatch, 'esc_url') === false) {
					// Extract setting key
					preg_match('/\$settings\s*\[\s*["\']([^"\']+)["\']\s*\]/', $fullMatch, $keyMatches);
					$key = isset($keyMatches[1]) ? $keyMatches[1] : '';
					
					if (!empty($key)) {
						return '<?php echo isset($settings["' . $key . '"]["url"]) ? esc_url($settings["' . $key . '"]["url"]) : ""; ?>';
					}
				}
				
				return $fullMatch;
			},
			$markup
		);
		
		return $markup;
	}

	private function write_render_method( $markup = '' ) {

		$markup = \ElementsKit_Lite\Libs\Template\Loader::instance()->replace_tags( $markup, $this->control_prefix );

		// Apply security escaping
		$markup = $this->apply_escaping($markup);

		$ret = "\n\t" . 'protected function render() {' . PHP_EOL;

		if ( ! empty( $markup ) ) {

			$ret .= "\t\t" . '$settings = $this->get_settings_for_display();' . PHP_EOL . PHP_EOL;

			$ret .= "\t\t" . '?>' . PHP_EOL;
			$ret .= $markup . PHP_EOL;
			$ret .= "\t\t" . '<?php' . PHP_EOL;
		}

		$ret .= "\t" . '}' . PHP_EOL . PHP_EOL;

		return $ret;
	}


	private function close_widget_class() {

		return PHP_EOL . '}' . PHP_EOL;
	}


	private function close_php_writer() {

		fclose( $this->file_handler ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose

		return true;
	}


	/**
	 * @return string
	 */
	public function get_widget_class_name() {
		return $this->widget_class_name;
	}
}