first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
<?php
namespace Elementor\Core\Files\Assets;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
abstract class Files_Upload_Handler {
const OPTION_KEY = 'elementor_unfiltered_files_upload';
public function __construct() {
add_filter( 'upload_mimes', [ $this, 'support_unfiltered_files_upload' ] );
add_filter( 'wp_handle_upload_prefilter', [ $this, 'handle_upload_prefilter' ] );
add_filter( 'wp_check_filetype_and_ext', [ $this, 'check_filetype_and_ext' ], 10, 4 );
}
abstract public function get_mime_type();
abstract public function get_file_type();
/**
* is_elementor_media_upload
* @return bool
*/
private function is_elementor_media_upload() {
return isset( $_POST['uploadTypeCaller'] ) && 'elementor-editor-upload' === $_POST['uploadTypeCaller']; // phpcs:ignore
}
/**
* @return bool
*/
final public static function is_enabled() {
$enabled = ! ! get_option( self::OPTION_KEY ) && self::file_sanitizer_can_run();
/**
* @deprecated 3.0.0 Use `elementor/document/urls/edit` filter instead.
*/
$enabled = apply_filters( 'elementor/files/svg/enabled', $enabled );
/**
* Allow Unfiltered Files Upload.
*
* Determines whether to enable unfiltered file uploads.
*
* @since 3.0.0
*
* @param bool $enabled Whether upload is enabled or not.
*/
$enabled = apply_filters( 'elementor/files/allow_unfiltered_upload', $enabled );
return $enabled;
}
final public function support_unfiltered_files_upload( $existing_mimes ) {
if ( $this->is_elementor_media_upload() && $this->is_enabled() ) {
$existing_mimes[ $this->get_file_type() ] = $this->get_mime_type();
}
return $existing_mimes;
}
/**
* handle_upload_prefilter
* @param $file
*
* @return mixed
*/
public function handle_upload_prefilter( $file ) {
if ( ! $this->is_file_should_handled( $file ) ) {
return $file;
}
$ext = pathinfo( $file['name'], PATHINFO_EXTENSION );
$file_type = $this->get_file_type();
$display_type = strtoupper( $file_type );
if ( $file_type !== $ext ) {
$file['error'] = sprintf( __( 'The uploaded %1$s file is not supported. Please upload a valid %2$s file', 'elementor' ), $ext, $display_type );
return $file;
}
if ( ! self::is_enabled() ) {
$file['error'] = sprintf( __( '%1$s file is not allowed for security reasons', 'elementor' ), $display_type );
return $file;
}
return $file;
}
protected function is_file_should_handled( $file ) {
$ext = pathinfo( $file['name'], PATHINFO_EXTENSION );
return $this->is_elementor_media_upload() && $this->get_file_type() === $ext;
}
/**
* file_sanitizer_can_run
* @return bool
*/
public static function file_sanitizer_can_run() {
return class_exists( 'DOMDocument' ) && class_exists( 'SimpleXMLElement' );
}
/**
* Check filetype and ext
*
* A workaround for upload validation which relies on a PHP extension (fileinfo)
* with inconsistent reporting behaviour.
* ref: https://core.trac.wordpress.org/ticket/39550
* ref: https://core.trac.wordpress.org/ticket/40175
*
* @param $data
* @param $file
* @param $filename
* @param $mimes
*
* @return mixed
*/
public function check_filetype_and_ext( $data, $file, $filename, $mimes ) {
if ( ! empty( $data['ext'] ) && ! empty( $data['type'] ) ) {
return $data;
}
$wp_file_type = wp_check_filetype( $filename, $mimes );
$file_type = strtolower( $this->get_file_type() );
if ( $file_type === $wp_file_type['ext'] ) {
$data['ext'] = $file_type;
$data['type'] = $this->get_mime_type();
}
return $data;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Elementor\Core\Files\Assets\Json;
use Elementor\Core\Files\Assets\Files_Upload_Handler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Json_Handler extends Files_Upload_Handler {
public static function get_name() {
return 'json-handler';
}
public function get_mime_type() {
return 'application/json';
}
public function get_file_type() {
return 'json';
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Elementor\Core\Files\Assets;
use Elementor\Core\Files\Assets\Svg\Svg_Handler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor files manager.
*
* Elementor files manager handler class is responsible for creating files.
*
* @since 2.6.0
*/
class Manager {
/**
* Holds registered asset types
* @var array
*/
protected $asset_types = [];
/**
* Assets manager constructor.
*
* Initializing the Elementor assets manager.
*
* @access public
*/
public function __construct() {
$this->register_asset_types();
/**
* Elementor files assets registered.
*
* Fires after Elementor registers assets types
*
* @since 2.6.0
*/
do_action( 'elementor/core/files/assets/assets_registered', $this );
}
public function get_asset( $name ) {
return isset( $this->asset_types[ $name ] ) ? $this->asset_types[ $name ] : false;
}
/**
* Add Asset
* @param $instance
*/
public function add_asset( $instance ) {
$this->asset_types[ $instance::get_name() ] = $instance;
}
/**
* Register Asset Types
*
* Registers Elementor Asset Types
*/
private function register_asset_types() {
$this->add_asset( new Svg_Handler() );
}
}

View File

@@ -0,0 +1,709 @@
<?php
namespace Elementor\Core\Files\Assets\Svg;
use Elementor\Core\Files\Assets\Files_Upload_Handler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Svg_Handler extends Files_Upload_Handler {
/**
* Inline svg attachment meta key
*/
const META_KEY = '_elementor_inline_svg';
const SCRIPT_REGEX = '/(?:\w+script|data):/xi';
/**
* @var \DOMDocument
*/
private $svg_dom = null;
/**
* Attachment ID.
*
* Holds the current attachment ID.
*
* @var int
*/
private $attachment_id;
public static function get_name() {
return 'svg-handler';
}
/**
* get_meta
* @return mixed
*/
protected function get_meta() {
return get_post_meta( $this->attachment_id, self::META_KEY, true );
}
/**
* update_meta
* @param $meta
*/
protected function update_meta( $meta ) {
update_post_meta( $this->attachment_id, self::META_KEY, $meta );
}
/**
* delete_meta
*/
protected function delete_meta() {
delete_post_meta( $this->attachment_id, self::META_KEY );
}
public function get_mime_type() {
return 'image/svg+xml';
}
public function get_file_type() {
return 'svg';
}
/**
* delete_meta_cache
*/
public function delete_meta_cache() {
delete_post_meta_by_key( self::META_KEY );
}
/**
* read_from_file
* @return bool|string
*/
public function read_from_file() {
return file_get_contents( get_attached_file( $this->attachment_id ) );
}
/**
* get_inline_svg
* @param $attachment_id
*
* @return bool|mixed|string
*/
public static function get_inline_svg( $attachment_id ) {
$svg = get_post_meta( $attachment_id, self::META_KEY, true );
if ( ! empty( $svg ) ) {
return $svg;
}
$attachment_file = get_attached_file( $attachment_id );
if ( ! $attachment_file ) {
return '';
}
$svg = file_get_contents( $attachment_file );
if ( ! empty( $svg ) ) {
update_post_meta( $attachment_id, self::META_KEY, $svg );
}
return $svg;
}
/**
* decode_svg
* @param $content
*
* @return string
*/
private function decode_svg( $content ) {
return gzdecode( $content );
}
/**
* encode_svg
* @param $content
*
* @return string
*/
private function encode_svg( $content ) {
return gzencode( $content );
}
/**
* sanitize_svg
* @param $filename
*
* @return bool
*/
public function sanitize_svg( $filename ) {
$original_content = file_get_contents( $filename );
$is_encoded = $this->is_encoded( $original_content );
if ( $is_encoded ) {
$decoded = $this->decode_svg( $original_content );
if ( false === $decoded ) {
return false;
}
$original_content = $decoded;
}
$valid_svg = $this->sanitizer( $original_content );
if ( false === $valid_svg ) {
return false;
}
// If we were gzipped, we need to re-zip
if ( $is_encoded ) {
$valid_svg = $this->encode_svg( $valid_svg );
}
file_put_contents( $filename, $valid_svg );
return true;
}
/**
* Check if the contents are gzipped
* @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
*
* @param $contents
* @return bool
*/
private function is_encoded( $contents ) {
$needle = "\x1f\x8b\x08";
if ( function_exists( 'mb_strpos' ) ) {
return 0 === mb_strpos( $contents, $needle );
} else {
return 0 === strpos( $contents, $needle );
}
}
/**
* is_allowed_tag
* @param $element
*
* @return bool
*/
private function is_allowed_tag( $element ) {
static $allowed_tags = false;
if ( false === $allowed_tags ) {
$allowed_tags = $this->get_allowed_elements();
}
$tag_name = $element->tagName; // phpcs:ignore -- php DomDocument
if ( ! in_array( strtolower( $tag_name ), $allowed_tags ) ) {
$this->remove_element( $element );
return false;
}
return true;
}
private function remove_element( $element ) {
$element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomDocument
}
/**
* is_a_attribute
* @param $name
* @param $check
*
* @return bool
*/
private function is_a_attribute( $name, $check ) {
return 0 === strpos( $name, $check . '-' );
}
/**
* is_remote_value
* @param $value
*
* @return string
*/
private function is_remote_value( $value ) {
$value = trim( preg_replace( '/[^ -~]/xu', '', $value ) );
$wrapped_in_url = preg_match( '~^url\(\s*[\'"]\s*(.*)\s*[\'"]\s*\)$~xi', $value, $match );
if ( ! $wrapped_in_url ) {
return false;
}
$value = trim( $match[1], '\'"' );
return preg_match( '~^((https?|ftp|file):)?//~xi', $value );
}
/**
* has_js_value
* @param $value
*
* @return false|int
*/
private function has_js_value( $value ) {
return preg_match( '/base64|data|(?:java)?script|alert\(|window\.|document/i', $value );
}
/**
* get_allowed_attributes
* @return array
*/
private function get_allowed_attributes() {
$allowed_attributes = [
'class',
'clip-path',
'clip-rule',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'id',
'mask',
'opacity',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'style',
'systemlanguage',
'transform',
'href',
'xlink:href',
'xlink:title',
'cx',
'cy',
'r',
'requiredfeatures',
'clippathunits',
'type',
'rx',
'ry',
'color-interpolation-filters',
'stddeviation',
'filterres',
'filterunits',
'height',
'primitiveunits',
'width',
'x',
'y',
'font-size',
'display',
'font-family',
'font-style',
'font-weight',
'text-anchor',
'marker-end',
'marker-mid',
'marker-start',
'x1',
'x2',
'y1',
'y2',
'gradienttransform',
'gradientunits',
'spreadmethod',
'markerheight',
'markerunits',
'markerwidth',
'orient',
'preserveaspectratio',
'refx',
'refy',
'viewbox',
'maskcontentunits',
'maskunits',
'd',
'patterncontentunits',
'patterntransform',
'patternunits',
'points',
'fx',
'fy',
'offset',
'stop-color',
'stop-opacity',
'xmlns',
'xmlns:se',
'xmlns:xlink',
'xml:space',
'method',
'spacing',
'startoffset',
'dx',
'dy',
'rotate',
'textlength',
];
return apply_filters( 'elementor/files/svg/allowed_attributes', $allowed_attributes );
}
/**
* get_allowed_elements
* @return array
*/
private function get_allowed_elements() {
$allowed_elements = [
'a',
'circle',
'clippath',
'defs',
'style',
'desc',
'ellipse',
'fegaussianblur',
'filter',
'foreignobject',
'g',
'image',
'line',
'lineargradient',
'marker',
'mask',
'metadata',
'path',
'pattern',
'polygon',
'polyline',
'radialgradient',
'rect',
'stop',
'svg',
'switch',
'symbol',
'text',
'textpath',
'title',
'tspan',
'use',
];
return apply_filters( 'elementor/files/svg/allowed_elements', $allowed_elements );
}
/**
* validate_allowed_attributes
* @param \DOMElement $element
*/
private function validate_allowed_attributes( $element ) {
static $allowed_attributes = false;
if ( false === $allowed_attributes ) {
$allowed_attributes = $this->get_allowed_attributes();
}
for ( $index = $element->attributes->length - 1; $index >= 0; $index-- ) {
// get attribute name
$attr_name = $element->attributes->item( $index )->name;
$attr_name_lowercase = strtolower( $attr_name );
// Remove attribute if not in whitelist
if ( ! in_array( $attr_name_lowercase, $allowed_attributes ) && ! $this->is_a_attribute( $attr_name_lowercase, 'aria' ) && ! $this->is_a_attribute( $attr_name_lowercase, 'data' ) ) {
$element->removeAttribute( $attr_name );
continue;
}
$attr_value = $element->attributes->item( $index )->value;
// Remove attribute if it has a remote reference or js or data-URI/base64
if ( ! empty( $attr_value ) && ( $this->is_remote_value( $attr_value ) || $this->has_js_value( $attr_value ) ) ) {
$element->removeAttribute( $attr_name );
continue;
}
}
}
/**
* strip_xlinks
* @param \DOMElement $element
*/
private function strip_xlinks( $element ) {
$xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
if ( ! $xlinks ) {
return;
}
$allowed_links = [
'data:image/png', // PNG
'data:image/gif', // GIF
'data:image/jpg', // JPG
'data:image/jpe', // JPEG
'data:image/pjp', // PJPEG
];
if ( 1 === preg_match( self::SCRIPT_REGEX, $xlinks ) ) {
if ( ! in_array( substr( $xlinks, 0, 14 ), $allowed_links ) ) {
$element->removeAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
}
}
}
/**
* validate_use_tag
* @param $element
*/
private function validate_use_tag( $element ) {
$xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
if ( $xlinks && '#' !== substr( $xlinks, 0, 1 ) ) {
$element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomNode
}
}
/**
* strip_docktype
*/
private function strip_doctype() {
foreach ( $this->svg_dom->childNodes as $child ) {
if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType ) { // phpcs:ignore -- php DomDocument
$child->parentNode->removeChild( $child ); // phpcs:ignore -- php DomDocument
}
}
}
/**
* sanitize_elements
*/
private function sanitize_elements() {
$elements = $this->svg_dom->getElementsByTagName( '*' );
// loop through all elements
// we do this backwards so we don't skip anything if we delete a node
// see comments at: http://php.net/manual/en/class.domnamednodemap.php
for ( $index = $elements->length - 1; $index >= 0; $index-- ) {
/**
* @var \DOMElement $current_element
*/
$current_element = $elements->item( $index );
// If the tag isn't in the whitelist, remove it and continue with next iteration
if ( ! $this->is_allowed_tag( $current_element ) ) {
continue;
}
//validate element attributes
$this->validate_allowed_attributes( $current_element );
$this->strip_xlinks( $current_element );
if ( 'use' === strtolower( $current_element->tagName ) ) { // phpcs:ignore -- php DomDocument
$this->validate_use_tag( $current_element );
}
}
}
/**
* sanitizer
* @param $content
*
* @return bool|string
*/
public function sanitizer( $content ) {
// Strip php tags
$content = $this->strip_comments( $content );
$content = $this->strip_php_tags( $content );
// Find the start and end tags so we can cut out miscellaneous garbage.
$start = strpos( $content, '<svg' );
$end = strrpos( $content, '</svg>' );
if ( false === $start || false === $end ) {
return false;
}
$content = substr( $content, $start, ( $end - $start + 6 ) );
// If the server's PHP version is 8 or up, make sure to Disable the ability to load external entities
$php_version_under_eight = version_compare( PHP_VERSION, '8.0.0', '<' );
if ( $php_version_under_eight ) {
$libxml_disable_entity_loader = libxml_disable_entity_loader( true ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated
}
// Suppress the errors
$libxml_use_internal_errors = libxml_use_internal_errors( true );
// Create DomDocument instance
$this->svg_dom = new \DOMDocument();
$this->svg_dom->formatOutput = false;
$this->svg_dom->preserveWhiteSpace = false;
$this->svg_dom->strictErrorChecking = false;
$open_svg = $this->svg_dom->loadXML( $content );
if ( ! $open_svg ) {
return false;
}
$this->strip_doctype();
$this->sanitize_elements();
// Export sanitized svg to string
// Using documentElement to strip out <?xml version="1.0" encoding="UTF-8"...
$sanitized = $this->svg_dom->saveXML( $this->svg_dom->documentElement, LIBXML_NOEMPTYTAG );
// Restore defaults
if ( $php_version_under_eight ) {
libxml_disable_entity_loader( $libxml_disable_entity_loader ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated
}
libxml_use_internal_errors( $libxml_use_internal_errors );
return $sanitized;
}
/**
* strip_php_tags
* @param $string
*
* @return string
*/
private function strip_php_tags( $string ) {
$string = preg_replace( '/<\?(=|php)(.+?)\?>/i', '', $string );
// Remove XML, ASP, etc.
$string = preg_replace( '/<\?(.*)\?>/Us', '', $string );
$string = preg_replace( '/<\%(.*)\%>/Us', '', $string );
if ( ( false !== strpos( $string, '<?' ) ) || ( false !== strpos( $string, '<%' ) ) ) {
return '';
}
return $string;
}
/**
* strip_comments
* @param $string
*
* @return string
*/
private function strip_comments( $string ) {
// Remove comments.
$string = preg_replace( '/<!--(.*)-->/Us', '', $string );
$string = preg_replace( '/\/\*(.*)\*\//Us', '', $string );
if ( ( false !== strpos( $string, '<!--' ) ) || ( false !== strpos( $string, '/*' ) ) ) {
return '';
}
return $string;
}
/**
* wp_prepare_attachment_for_js
* @param $attachment_data
* @param $attachment
* @param $meta
*
* @return mixed
*/
public function wp_prepare_attachment_for_js( $attachment_data, $attachment, $meta ) {
if ( 'image' !== $attachment_data['type'] || 'svg+xml' !== $attachment_data['subtype'] || ! class_exists( 'SimpleXMLElement' ) ) {
return $attachment_data;
}
$svg = self::get_inline_svg( $attachment->ID );
if ( ! $svg ) {
return $attachment_data;
}
try {
$svg = new \SimpleXMLElement( $svg );
} catch ( \Exception $e ) {
return $attachment_data;
}
$src = $attachment_data['url'];
$width = (int) $svg['width'];
$height = (int) $svg['height'];
// Media Gallery
$attachment_data['image'] = compact( 'src', 'width', 'height' );
$attachment_data['thumb'] = compact( 'src', 'width', 'height' );
// Single Details of Image
$attachment_data['sizes']['full'] = [
'height' => $height,
'width' => $width,
'url' => $src,
'orientation' => $height > $width ? 'portrait' : 'landscape',
];
return $attachment_data;
}
/**
* set_attachment_id
* @param $attachment_id
*
* @return int
*/
public function set_attachment_id( $attachment_id ) {
$this->attachment_id = $attachment_id;
return $this->attachment_id;
}
/**
* get_attachment_id
* @return int
*/
public function get_attachment_id() {
return $this->attachment_id;
}
/**
* handle_upload_prefilter
* @param $file
*
* @return mixed
*/
public function handle_upload_prefilter( $file ) {
if ( ! $this->is_file_should_handled( $file ) ) {
return $file;
}
$file = parent::handle_upload_prefilter( $file );
if ( ! $file['error'] && self::file_sanitizer_can_run() && ! $this->sanitize_svg( $file['tmp_name'] ) ) {
$display_type = strtoupper( $this->get_file_type() );
$file['error'] = sprintf( __( 'Invalid %1$s Format, file not uploaded for security reasons', 'elementor' ), $display_type );
}
return $file;
}
/**
* @since 3.0.0
* @deprecated 3.0.0 Use Files_Upload_Handler::file_sanitizer_can_run() instead.
*/
public function svg_sanitizer_can_run() {
_deprecated_function( __METHOD__, '3.0.0', 'Files_Upload_Handler::file_sanitizer_can_run()' );
return Files_Upload_Handler::file_sanitizer_can_run();
}
/**
* @since 3.0.0
* @deprecated 3.0.0
*/
public function upload_mimes() {
_deprecated_function( __METHOD__, '3.0.0' );
}
/**
* @since 3.0.0
* @deprecated 3.0.0
*/
public function wp_handle_upload_prefilter() {
_deprecated_function( __METHOD__, '3.0.0' );
}
/**
* @since 3.0.0
* @deprecated 3.0.0 Use Files_Upload_Handler::is_enabled() instead.
* @see is_enabled()
*/
public function is_svg_uploads_enabled() {
_deprecated_function( __METHOD__, '3.0.0', 'Files_Upload_Handler::is_enabled()' );
return Files_Upload_Handler::is_enabled();
}
/**
* Svg_Handler constructor.
*/
public function __construct() {
parent::__construct();
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ], 10, 3 );
add_action( 'elementor/core/files/clear_cache', [ $this, 'delete_meta_cache' ] );
}
}

View File

@@ -0,0 +1,304 @@
<?php
namespace Elementor\Core\Files;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Base {
const UPLOADS_DIR = 'elementor/';
const DEFAULT_FILES_DIR = 'css/';
const META_KEY = '';
private static $wp_uploads_dir = [];
private $files_dir;
private $file_name;
/**
* File path.
*
* Holds the file path.
*
* @access private
*
* @var string
*/
private $path;
/**
* Content.
*
* Holds the file content.
*
* @access private
*
* @var string
*/
private $content;
/**
* @since 2.1.0
* @access public
* @static
*/
public static function get_base_uploads_dir() {
$wp_upload_dir = self::get_wp_uploads_dir();
return $wp_upload_dir['basedir'] . '/' . self::UPLOADS_DIR;
}
/**
* @since 2.1.0
* @access public
* @static
*/
public static function get_base_uploads_url() {
$wp_upload_dir = self::get_wp_uploads_dir();
return $wp_upload_dir['baseurl'] . '/' . self::UPLOADS_DIR;
}
/**
* Use a create function for PhpDoc (@return static).
*
* @return static
*/
public static function create() {
return Plugin::$instance->files_manager->get( get_called_class(), func_get_args() );
}
/**
* @since 2.1.0
* @access public
*/
public function __construct( $file_name ) {
/**
* Elementor File Name
*
* Filters the File name
*
* @since 2.3.0
*
* @param string $file_name
* @param object $this The file instance, which inherits Elementor\Core\Files
*/
$file_name = apply_filters( 'elementor/files/file_name', $file_name, $this );
$this->set_file_name( $file_name );
$this->set_files_dir( static::DEFAULT_FILES_DIR );
$this->set_path();
}
/**
* @since 2.1.0
* @access public
*/
public function set_files_dir( $files_dir ) {
$this->files_dir = $files_dir;
}
/**
* @since 2.1.0
* @access public
*/
public function set_file_name( $file_name ) {
$this->file_name = $file_name;
}
/**
* @since 2.1.0
* @access public
*/
public function get_file_name() {
return $this->file_name;
}
/**
* @since 2.1.0
* @access public
*/
public function get_url() {
$url = set_url_scheme( self::get_base_uploads_url() . $this->files_dir . $this->file_name );
return add_query_arg( [ 'ver' => $this->get_meta( 'time' ) ], $url );
}
/**
* @since 2.1.0
* @access public
*/
public function get_content() {
if ( ! $this->content ) {
$this->content = $this->parse_content();
}
return $this->content;
}
/**
* @since 2.1.0
* @access public
*/
public function update() {
$this->update_file();
$meta = $this->get_meta();
$meta['time'] = time();
$this->update_meta( $meta );
}
/**
* @since 2.1.0
* @access public
*/
public function update_file() {
$this->content = $this->parse_content();
if ( $this->content ) {
$this->write();
} else {
$this->delete();
}
}
/**
* @since 2.1.0
* @access public
*/
public function write() {
return file_put_contents( $this->path, $this->content );
}
/**
* @since 2.1.0
* @access public
*/
public function delete() {
if ( file_exists( $this->path ) ) {
unlink( $this->path );
}
$this->delete_meta();
}
/**
* Get meta data.
*
* Retrieve the CSS file meta data. Returns an array of all the data, or if
* custom property is given it will return the property value, or `null` if
* the property does not exist.
*
* @since 2.1.0
* @access public
*
* @param string $property Optional. Custom meta data property. Default is
* null.
*
* @return array|null An array of all the data, or if custom property is
* given it will return the property value, or `null` if
* the property does not exist.
*/
public function get_meta( $property = null ) {
$meta = array_merge( $this->get_default_meta(), (array) $this->load_meta() );
if ( $property ) {
return isset( $meta[ $property ] ) ? $meta[ $property ] : null;
}
return $meta;
}
/**
* @since 2.1.0
* @access protected
* @abstract
*/
abstract protected function parse_content();
/**
* Load meta.
*
* Retrieve the file meta data.
*
* @since 2.1.0
* @access protected
*/
protected function load_meta() {
return get_option( static::META_KEY );
}
/**
* Update meta.
*
* Update the file meta data.
*
* @since 2.1.0
* @access protected
*
* @param array $meta New meta data.
*/
protected function update_meta( $meta ) {
update_option( static::META_KEY, $meta );
}
/**
* Delete meta.
*
* Delete the file meta data.
*
* @since 2.1.0
* @access protected
*/
protected function delete_meta() {
delete_option( static::META_KEY );
}
/**
* @since 2.1.0
* @access protected
*/
protected function get_default_meta() {
return [
'time' => 0,
];
}
/**
* @since 2.1.0
* @access private
* @static
*/
private static function get_wp_uploads_dir() {
global $blog_id;
if ( empty( self::$wp_uploads_dir[ $blog_id ] ) ) {
self::$wp_uploads_dir[ $blog_id ] = wp_upload_dir( null, false );
}
return self::$wp_uploads_dir[ $blog_id ];
}
/**
* @since 2.1.0
* @access private
*/
private function set_path() {
$dir_path = self::get_base_uploads_dir() . $this->files_dir;
if ( ! is_dir( $dir_path ) ) {
wp_mkdir_p( $dir_path );
}
$this->path = $dir_path . $this->file_name;
}
}

View File

@@ -0,0 +1,908 @@
<?php
namespace Elementor\Core\Files\CSS;
use Elementor\Base_Data_Control;
use Elementor\Control_Repeater;
use Elementor\Controls_Manager;
use Elementor\Controls_Stack;
use Elementor\Core\Files\Base as Base_File;
use Elementor\Core\DynamicTags\Manager;
use Elementor\Core\DynamicTags\Tag;
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
use Elementor\Element_Base;
use Elementor\Plugin;
use Elementor\Stylesheet;
use Elementor\Icons_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor CSS file.
*
* Elementor CSS file handler class is responsible for generating CSS files.
*
* @since 1.2.0
* @abstract
*/
abstract class Base extends Base_File {
/**
* Elementor CSS file generated status.
*
* The parsing result after generating CSS file.
*/
const CSS_STATUS_FILE = 'file';
/**
* Elementor inline CSS status.
*
* The parsing result after generating inline CSS.
*/
const CSS_STATUS_INLINE = 'inline';
/**
* Elementor CSS empty status.
*
* The parsing result when an empty CSS returned.
*/
const CSS_STATUS_EMPTY = 'empty';
/**
* Fonts.
*
* Holds the list of fonts.
*
* @access private
*
* @var array
*/
private $fonts = [];
private $icons_fonts = [];
private $dynamic_elements_ids = [];
/**
* Stylesheet object.
*
* Holds the CSS file stylesheet instance.
*
* @access protected
*
* @var Stylesheet
*/
protected $stylesheet_obj;
/**
* Printed.
*
* Holds the list of printed files.
*
* @access protected
*
* @var array
*/
private static $printed = [];
/**
* Get CSS file name.
*
* Retrieve the CSS file name.
*
* @since 1.6.0
* @access public
* @abstract
*/
abstract public function get_name();
protected function is_global_parsing_supported() {
return false;
}
/**
* Use external file.
*
* Whether to use external CSS file of not. When there are new schemes or settings
* updates.
*
* @since 1.9.0
* @access protected
*
* @return bool True if the CSS requires an update, False otherwise.
*/
protected function use_external_file() {
return 'internal' !== get_option( 'elementor_css_print_method' );
}
/**
* Update the CSS file.
*
* Delete old CSS, parse the CSS, save the new file and update the database.
*
* This method also sets the CSS status to be used later on in the render posses.
*
* @since 1.2.0
* @access public
*/
public function update() {
$this->update_file();
$meta = $this->get_meta();
$meta['time'] = time();
$content = $this->get_content();
if ( empty( $content ) ) {
$meta['status'] = self::CSS_STATUS_EMPTY;
$meta['css'] = '';
} else {
$use_external_file = $this->use_external_file();
if ( $use_external_file ) {
$meta['status'] = self::CSS_STATUS_FILE;
} else {
$meta['status'] = self::CSS_STATUS_INLINE;
$meta['css'] = $content;
}
}
$meta['dynamic_elements_ids'] = $this->dynamic_elements_ids;
$this->update_meta( $meta );
}
/**
* @since 2.1.0
* @access public
*/
public function write() {
if ( $this->use_external_file() ) {
parent::write();
}
}
/**
* @since 3.0.0
* @access public
*/
public function delete() {
if ( $this->use_external_file() ) {
parent::delete();
} else {
$this->delete_meta();
}
}
/**
* Enqueue CSS.
*
* Either enqueue the CSS file in Elementor or add inline style.
*
* This method is also responsible for loading the fonts.
*
* @since 1.2.0
* @access public
*/
public function enqueue() {
$handle_id = $this->get_file_handle_id();
if ( isset( self::$printed[ $handle_id ] ) ) {
return;
}
self::$printed[ $handle_id ] = true;
$meta = $this->get_meta();
if ( self::CSS_STATUS_EMPTY === $meta['status'] ) {
return;
}
// First time after clear cache and etc.
if ( '' === $meta['status'] || $this->is_update_required() ) {
$this->update();
$meta = $this->get_meta();
}
if ( self::CSS_STATUS_INLINE === $meta['status'] ) {
$dep = $this->get_inline_dependency();
// If the dependency has already been printed ( like a template in footer )
if ( wp_styles()->query( $dep, 'done' ) ) {
printf( '<style id="%1$s">%2$s</style>', $this->get_file_handle_id(), $meta['css'] ); // XSS ok.
} else {
wp_add_inline_style( $dep, $meta['css'] );
}
} elseif ( self::CSS_STATUS_FILE === $meta['status'] ) { // Re-check if it's not empty after CSS update.
wp_enqueue_style( $this->get_file_handle_id(), $this->get_url(), $this->get_enqueue_dependencies(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
}
// Handle fonts.
if ( ! empty( $meta['fonts'] ) ) {
foreach ( $meta['fonts'] as $font ) {
Plugin::$instance->frontend->enqueue_font( $font );
}
}
if ( ! empty( $meta['icons'] ) ) {
$icons_types = Icons_Manager::get_icon_manager_tabs();
foreach ( $meta['icons'] as $icon_font ) {
if ( ! isset( $icons_types[ $icon_font ] ) ) {
continue;
}
Plugin::$instance->frontend->enqueue_font( $icon_font );
}
}
$name = $this->get_name();
/**
* Enqueue CSS file.
*
* Fires when CSS file is enqueued on Elementor.
*
* The dynamic portion of the hook name, `$name`, refers to the CSS file name.
*
* @since 2.0.0
*
* @param Base $this The current CSS file.
*/
do_action( "elementor/css-file/{$name}/enqueue", $this );
}
/**
* Print CSS.
*
* Output the final CSS inside the `<style>` tags and all the frontend fonts in
* use.
*
* @since 1.9.4
* @access public
*/
public function print_css() {
echo '<style>' . $this->get_content() . '</style>'; // XSS ok.
Plugin::$instance->frontend->print_fonts_links();
}
/**
* Add control rules.
*
* Parse the CSS for all the elements inside any given control.
*
* This method recursively renders the CSS for all the selectors in the control.
*
* @since 1.2.0
* @access public
*
* @param array $control The controls.
* @param array $controls_stack The controls stack.
* @param callable $value_callback Callback function for the value.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
* @param array $values Global Values.
*/
public function add_control_rules( array $control, array $controls_stack, callable $value_callback, array $placeholders, array $replacements, array $values = [] ) {
if ( empty( $control['selectors'] ) ) {
return;
}
$control_global_key = $control['name'];
if ( ! empty( $control['groupType'] ) ) {
$control_global_key = $control['groupPrefix'] . $control['groupType'];
}
$global_values = [];
$global_key = '';
if ( ! empty( $values['__globals__'] ) ) {
$global_values = $values['__globals__'];
}
if ( ! empty( $global_values[ $control_global_key ] ) ) {
$global_key = $global_values[ $control_global_key ];
}
if ( ! $global_key ) {
$value = call_user_func( $value_callback, $control );
if ( null === $value ) {
return;
}
}
$stylesheet = $this->get_stylesheet();
foreach ( $control['selectors'] as $selector => $css_property ) {
$output_css_property = '';
if ( $global_key ) {
$selector_global_value = $this->get_selector_global_value( $control, $global_key );
if ( $selector_global_value ) {
$output_css_property = preg_replace( '/(:)[^;]+(;?)/', '$1' . $selector_global_value . '$2', $css_property );
}
} else {
try {
$output_css_property = preg_replace_callback( '/{{(?:([^.}]+)\.)?([^}| ]*)(?: *\|\| *(?:([^.}]+)\.)?([^}| ]*) *)*}}/', function( $matches ) use ( $control, $value_callback, $controls_stack, $value, $css_property ) {
$external_control_missing = $matches[1] && ! isset( $controls_stack[ $matches[1] ] );
$parsed_value = '';
if ( ! $external_control_missing ) {
$parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[2], $matches[1] );
}
if ( '' === $parsed_value ) {
if ( isset( $matches[4] ) ) {
$parsed_value = $matches[4];
$is_string_value = preg_match( '/^([\'"])(.*)\1$/', $parsed_value, $string_matches );
if ( $is_string_value ) {
$parsed_value = $string_matches[2];
} elseif ( ! is_numeric( $parsed_value ) ) {
if ( $matches[3] && ! isset( $controls_stack[ $matches[3] ] ) ) {
return '';
}
$parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[4], $matches[3] );
}
}
if ( '' === $parsed_value ) {
if ( $external_control_missing ) {
return '';
}
throw new \Exception();
}
}
return $parsed_value;
}, $css_property );
} catch ( \Exception $e ) {
return;
}
}
if ( ! $output_css_property ) {
continue;
}
$device_pattern = '/^(?:\([^\)]+\)){1,2}/';
preg_match( $device_pattern, $selector, $device_rules );
$query = [];
if ( $device_rules ) {
$selector = preg_replace( $device_pattern, '', $selector );
preg_match_all( '/\(([^)]+)\)/', $device_rules[0], $pure_device_rules );
$pure_device_rules = $pure_device_rules[1];
foreach ( $pure_device_rules as $device_rule ) {
if ( Element_Base::RESPONSIVE_DESKTOP === $device_rule ) {
continue;
}
$device = preg_replace( '/\+$/', '', $device_rule );
$endpoint = $device === $device_rule ? 'max' : 'min';
$query[ $endpoint ] = $device;
}
}
$parsed_selector = str_replace( $placeholders, $replacements, $selector );
if ( ! $query && ! empty( $control['responsive'] ) ) {
$query = array_intersect_key( $control['responsive'], array_flip( [ 'min', 'max' ] ) );
if ( ! empty( $query['max'] ) && Element_Base::RESPONSIVE_DESKTOP === $query['max'] ) {
unset( $query['max'] );
}
}
$stylesheet->add_rules( $parsed_selector, $output_css_property, $query );
}
}
/**
* @param array $control
* @param mixed $value
* @param array $controls_stack
* @param callable $value_callback
* @param string $placeholder
* @param string $parser_control_name
*
* @return string
*/
public function parse_property_placeholder( array $control, $value, array $controls_stack, $value_callback, $placeholder, $parser_control_name = null ) {
if ( $parser_control_name ) {
$control = $controls_stack[ $parser_control_name ];
$value = call_user_func( $value_callback, $control );
}
if ( Controls_Manager::FONT === $control['type'] ) {
$this->fonts[] = $value;
}
/** @var Base_Data_Control $control_obj */
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
return (string) $control_obj->get_style_value( $placeholder, $value, $control );
}
/**
* Get the fonts.
*
* Retrieve the list of fonts.
*
* @since 1.9.0
* @access public
*
* @return array Fonts.
*/
public function get_fonts() {
return $this->fonts;
}
/**
* Get stylesheet.
*
* Retrieve the CSS file stylesheet instance.
*
* @since 1.2.0
* @access public
*
* @return Stylesheet The stylesheet object.
*/
public function get_stylesheet() {
if ( ! $this->stylesheet_obj ) {
$this->init_stylesheet();
}
return $this->stylesheet_obj;
}
/**
* Add controls stack style rules.
*
* Parse the CSS for all the elements inside any given controls stack.
*
* This method recursively renders the CSS for all the child elements in the stack.
*
* @since 1.6.0
* @access public
*
* @param Controls_Stack $controls_stack The controls stack.
* @param array $controls Controls array.
* @param array $values Values array.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
* @param array $all_controls All controls.
*/
public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) {
if ( ! $all_controls ) {
$all_controls = $controls_stack->get_controls();
}
$parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls );
foreach ( $controls as $control ) {
if ( ! empty( $control['style_fields'] ) ) {
$this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements );
}
if ( ! empty( $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
$this->add_dynamic_control_style_rules( $control, $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] );
}
if ( Controls_Manager::ICONS === $control['type'] ) {
$this->icons_fonts[] = $values[ $control['name'] ]['library'];
}
if ( ! empty( $parsed_dynamic_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
// Dynamic CSS should not be added to the CSS files.
// Instead it's handled by \Elementor\Core\DynamicTags\Dynamic_CSS
// and printed in a style tag.
unset( $parsed_dynamic_settings[ $control['name'] ] );
$this->dynamic_elements_ids[] = $controls_stack->get_id();
continue;
}
if ( empty( $control['selectors'] ) ) {
continue;
}
$this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements );
}
}
/**
* Get file handle ID.
*
* Retrieve the file handle ID.
*
* @since 1.2.0
* @access protected
* @abstract
*
* @return string CSS file handle ID.
*/
abstract protected function get_file_handle_id();
/**
* Render CSS.
*
* Parse the CSS.
*
* @since 1.2.0
* @access protected
* @abstract
*/
abstract protected function render_css();
protected function get_default_meta() {
return array_merge( parent::get_default_meta(), [
'fonts' => array_unique( $this->fonts ),
'icons' => array_unique( $this->icons_fonts ),
'dynamic_elements_ids' => [],
'status' => '',
] );
}
/**
* Get enqueue dependencies.
*
* Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
*
* @since 1.2.0
* @access protected
*
* @return array Name of the stylesheet.
*/
protected function get_enqueue_dependencies() {
return [];
}
/**
* Get inline dependency.
*
* Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
*
* @since 1.2.0
* @access protected
*
* @return string Name of the stylesheet.
*/
protected function get_inline_dependency() {
return '';
}
/**
* Is update required.
*
* Whether the CSS requires an update. When there are new schemes or settings
* updates.
*
* @since 1.2.0
* @access protected
*
* @return bool True if the CSS requires an update, False otherwise.
*/
protected function is_update_required() {
return false;
}
/**
* Parse CSS.
*
* Parsing the CSS file.
*
* @since 1.2.0
* @access protected
*/
protected function parse_content() {
$this->render_css();
$name = $this->get_name();
/**
* Parse CSS file.
*
* Fires when CSS file is parsed on Elementor.
*
* The dynamic portion of the hook name, `$name`, refers to the CSS file name.
*
* @since 2.0.0
*
* @param Base $this The current CSS file.
*/
do_action( "elementor/css-file/{$name}/parse", $this );
return $this->get_stylesheet()->__toString();
}
/**
* Add control style rules.
*
* Register new style rules for the control.
*
* @since 1.6.0
* @access private
*
* @param array $control The control.
* @param array $values Values array.
* @param array $controls The controls stack.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
*/
protected function add_control_style_rules( array $control, array $values, array $controls, array $placeholders, array $replacements ) {
$this->add_control_rules(
$control, $controls, function( $control ) use ( $values ) {
return $this->get_style_control_value( $control, $values );
}, $placeholders, $replacements, $values
);
}
/**
* Get style control value.
*
* Retrieve the value of the style control for any give control and values.
*
* It will retrieve the control name and return the style value.
*
* @since 1.6.0
* @access private
*
* @param array $control The control.
* @param array $values Values array.
*
* @return mixed Style control value.
*/
private function get_style_control_value( array $control, array $values ) {
if ( ! empty( $values['__globals__'][ $control['name'] ] ) ) {
// When the control itself has no global value, but it refers to another control global value
return $this->get_selector_global_value( $control, $values['__globals__'][ $control['name'] ] );
}
$value = $values[ $control['name'] ];
if ( isset( $control['selectors_dictionary'][ $value ] ) ) {
$value = $control['selectors_dictionary'][ $value ];
}
if ( ! is_numeric( $value ) && ! is_float( $value ) && empty( $value ) ) {
return null;
}
return $value;
}
/**
* Init stylesheet.
*
* Initialize CSS file stylesheet by creating a new `Stylesheet` object and register new
* breakpoints for the stylesheet.
*
* @since 1.2.0
* @access private
*/
private function init_stylesheet() {
$this->stylesheet_obj = new Stylesheet();
$active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints();
foreach ( $active_breakpoints as $breakpoint_name => $breakpoint ) {
$this->stylesheet_obj->add_device( $breakpoint_name, $breakpoint->get_value() );
}
}
/**
* Add repeater control style rules.
*
* Register new style rules for the repeater control.
*
* @since 2.0.0
* @access private
*
* @param Controls_Stack $controls_stack The control stack.
* @param array $repeater_control The repeater control.
* @param array $repeater_values Repeater values array.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
*/
protected function add_repeater_control_style_rules( Controls_Stack $controls_stack, array $repeater_control, array $repeater_values, array $placeholders, array $replacements ) {
$placeholders = array_merge( $placeholders, [ '{{CURRENT_ITEM}}' ] );
foreach ( $repeater_control['style_fields'] as $index => $item ) {
$this->add_controls_stack_style_rules(
$controls_stack,
$item,
$repeater_values[ $index ],
$placeholders,
array_merge( $replacements, [ '.elementor-repeater-item-' . $repeater_values[ $index ]['_id'] ] ),
$repeater_control['fields']
);
}
}
/**
* Add dynamic control style rules.
*
* Register new style rules for the dynamic control.
*
* @since 2.0.0
* @access private
*
* @param array $control The control.
* @param string $value The value.
*/
protected function add_dynamic_control_style_rules( array $control, $value ) {
Plugin::$instance->dynamic_tags->parse_tags_text( $value, $control, function( $id, $name, $settings ) {
$tag = Plugin::$instance->dynamic_tags->create_tag( $id, $name, $settings );
if ( ! $tag instanceof Tag ) {
return;
}
$this->add_controls_stack_style_rules( $tag, $this->get_style_controls( $tag ), $tag->get_active_settings(), [ '{{WRAPPER}}' ], [ '#elementor-tag-' . $id ] );
} );
}
private function get_selector_global_value( $control, $global_key ) {
$data = Plugin::$instance->data_manager->run( $global_key );
if ( empty( $data['value'] ) ) {
return null;
}
$global_args = explode( '?id=', $global_key );
$id = $global_args[1];
if ( ! empty( $control['groupType'] ) ) {
$property_name = str_replace( [ $control['groupPrefix'], '_tablet', '_mobile' ], '', $control['name'] );
// TODO: This check won't retrieve the proper answer for array values (multiple controls).
if ( empty( $data['value'][ Global_Typography::TYPOGRAPHY_GROUP_PREFIX . $property_name ] ) ) {
return null;
}
$property_name = str_replace( '_', '-', $property_name );
$value = "var( --e-global-$control[groupType]-$id-$property_name )";
if ( $control['groupPrefix'] . 'font_family' === $control['name'] ) {
$default_generic_fonts = Plugin::$instance->kits_manager->get_current_settings( 'default_generic_fonts' );
if ( $default_generic_fonts ) {
$value .= ", $default_generic_fonts";
}
}
} else {
$value = "var( --e-global-$control[type]-$id )";
}
return $value;
}
final protected function get_active_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) {
if ( ! $controls ) {
$controls = $controls_stack->get_controls();
}
if ( ! $settings ) {
$settings = $controls_stack->get_controls_settings();
}
if ( $this->is_global_parsing_supported() ) {
$settings = $this->parse_global_settings( $settings, $controls );
}
$active_controls = array_reduce(
array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls_stack, $controls, $settings ) {
$control = $controls[ $control_key ];
if ( $controls_stack->is_control_visible( $control, $settings ) ) {
$active_controls[ $control_key ] = $control;
}
return $active_controls;
}, []
);
return $active_controls;
}
final public function get_style_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) {
$controls = $this->get_active_controls( $controls_stack, $controls, $settings );
$style_controls = [];
foreach ( $controls as $control_name => $control ) {
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
if ( ! $control_obj instanceof Base_Data_Control ) {
continue;
}
$control = array_merge( $control_obj->get_settings(), $control );
if ( $control_obj instanceof Control_Repeater ) {
$style_fields = [];
foreach ( $controls_stack->get_settings( $control_name ) as $item ) {
$style_fields[] = $this->get_style_controls( $controls_stack, $control['fields'], $item );
}
$control['style_fields'] = $style_fields;
}
if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || $this->is_global_control( $controls_stack, $control_name, $controls ) || ! empty( $control['style_fields'] ) ) {
$style_controls[ $control_name ] = $control;
}
}
return $style_controls;
}
private function parse_global_settings( array $settings, array $controls ) {
foreach ( $controls as $control ) {
$control_name = $control['name'];
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
if ( ! $control_obj instanceof Base_Data_Control ) {
continue;
}
if ( $control_obj instanceof Control_Repeater ) {
foreach ( $settings[ $control_name ] as & $field ) {
$field = $this->parse_global_settings( $field, $control['fields'] );
}
continue;
}
if ( empty( $control['global']['active'] ) ) {
continue;
}
if ( empty( $settings['__globals__'][ $control_name ] ) ) {
continue;
}
$settings[ $control_name ] = 'global';
}
return $settings;
}
private function is_global_control( Controls_Stack $controls_stack, $control_name, $controls ) {
$control = $controls[ $control_name ];
$control_global_key = $control_name;
if ( ! empty( $control['groupType'] ) ) {
$control_global_key = $control['groupPrefix'] . $control['groupType'];
}
if ( empty( $controls[ $control_global_key ]['global']['active'] ) ) {
return false;
}
$globals = $controls_stack->get_settings( '__globals__' );
return ! empty( $globals[ $control_global_key ] );
}
}

View File

@@ -0,0 +1,159 @@
<?php
namespace Elementor\Core\Files\CSS;
use Elementor\Core\Kits\Manager;
use Elementor\Plugin;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor global CSS file.
*
* Elementor CSS file handler class is responsible for generating the global CSS
* file.
*
* @since 1.2.0
*/
class Global_CSS extends Base {
/**
* Elementor global CSS file handler ID.
*/
const FILE_HANDLER_ID = 'elementor-global';
const META_KEY = '_elementor_global_css';
/**
* Get CSS file name.
*
* Retrieve the CSS file name.
*
* @since 1.6.0
* @access public
*
* @return string CSS file name.
*/
public function get_name() {
return 'global';
}
/**
* Get file handle ID.
*
* Retrieve the handle ID for the global post CSS file.
*
* @since 1.2.0
* @access protected
*
* @return string CSS file handle ID.
*/
protected function get_file_handle_id() {
return self::FILE_HANDLER_ID;
}
/**
* Render CSS.
*
* Parse the CSS for all the widgets and all the scheme controls.
*
* @since 1.2.0
* @access protected
*/
protected function render_css() {
$this->render_schemes_and_globals_css();
}
/**
* Get inline dependency.
*
* Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
*
* @since 1.2.0
* @access protected
*
* @return string Name of the stylesheet.
*/
protected function get_inline_dependency() {
return 'elementor-frontend';
}
/**
* Is update required.
*
* Whether the CSS requires an update. When there are new schemes or settings
* updates.
*
* @since 1.2.0
* @access protected
*
* @return bool True if the CSS requires an update, False otherwise.
*/
protected function is_update_required() {
return $this->get_meta( 'time' ) < get_option( Settings::UPDATE_TIME_FIELD );
}
/**
* Render schemes CSS.
*
* Parse the CSS for all the widgets and all the scheme controls.
*
* @since 1.2.0
* @access private
*/
private function render_schemes_and_globals_css() {
$elementor = Plugin::$instance;
/** @var Manager $module */
$kits_manager = Plugin::$instance->kits_manager;
$custom_colors_enabled = $kits_manager->is_custom_colors_enabled();
$custom_typography_enabled = $kits_manager->is_custom_typography_enabled();
// If both default colors and typography are disabled, there is no need to render schemes and default global css.
if ( ! $custom_colors_enabled && ! $custom_typography_enabled ) {
return;
}
foreach ( $elementor->widgets_manager->get_widget_types() as $widget ) {
$controls = $widget->get_controls();
$global_controls = [];
$global_values['__globals__'] = [];
foreach ( $controls as $control ) {
$is_color_control = 'color' === $control['type'];
$is_typography_control = isset( $control['groupType'] ) && 'typography' === $control['groupType'];
// If it is a color/typography control and default colors/typography are disabled,
// don't add the default CSS.
if ( ( $is_color_control && ! $custom_colors_enabled ) || ( $is_typography_control && ! $custom_typography_enabled ) ) {
continue;
}
$global_control = $control;
// Handle group controls that don't have a default global property.
if ( ! empty( $control['groupType'] ) ) {
$global_control = $controls[ $control['groupPrefix'] . $control['groupType'] ];
}
// If the control has a default global defined, add it to the globals array
// that is used in add_control_rules.
if ( ! empty( $control['global']['default'] ) ) {
$global_values['__globals__'][ $control['name'] ] = $global_control['global']['default'];
}
if ( ! empty( $global_control['global']['default'] ) ) {
$global_controls[] = $control;
}
}
foreach ( $global_controls as $control ) {
$this->add_control_rules( $control, $controls, function( $control ) {}, [ '{{WRAPPER}}' ], [ '.elementor-widget-' . $widget->get_name() ], $global_values );
}
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Elementor\Core\Files\CSS;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Post_Local_Cache extends Post {
/**
* Meta cache
*
* @var array
*/
private $meta_cache = [];
abstract protected function get_post_id_for_data();
public function is_update_required() {
return true;
}
protected function load_meta() {
return $this->meta_cache;
}
protected function delete_meta() {
$this->meta_cache = [];
}
protected function update_meta( $meta ) {
$this->meta_cache = $meta;
}
protected function get_data() {
$document = Plugin::$instance->documents->get( $this->get_post_id_for_data() );
return $document ? $document->get_elements_data() : [];
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Elementor\Core\Files\CSS;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor post preview CSS file.
*
* Elementor CSS file handler class is responsible for generating the post
* preview CSS file.
*
* @since 1.9.0
*/
class Post_Preview extends Post_Local_Cache {
/**
* Preview ID.
*
* Holds the ID of the current post being previewed.
*
* @var int
*/
private $post_id_for_data;
/**
* Post preview CSS file constructor.
*
* Initializing the CSS file of the post preview. Set the post ID and the
* parent ID and initiate the stylesheet.
*
* @since 1.9.0
* @access public
*
* @param int $post_id Post ID.
*/
public function __construct( $post_id ) {
$this->post_id_for_data = $post_id;
$parent_id = wp_get_post_parent_id( $post_id );
parent::__construct( $parent_id );
}
protected function get_post_id_for_data() {
return $this->post_id_for_data;
}
/**
* @since 2.1.0
* @access public
* @deprecated 3.0.0 Use `Post_Preview::get_post_id_for_data()` instead
*/
protected function get_preview_id() {
_deprecated_function( __METHOD__, '3.0.0', __CLASS__ . '::get_post_id_for_data()' );
return $this->get_post_id_for_data();
}
/**
* Get file handle ID.
*
* Retrieve the handle ID for the previewed post CSS file.
*
* @since 1.9.0
* @access protected
*
* @return string CSS file handle ID.
*/
protected function get_file_handle_id() {
return 'elementor-preview-' . $this->get_post_id_for_data();
}
}

View File

@@ -0,0 +1,312 @@
<?php
namespace Elementor\Core\Files\CSS;
use Elementor\Base_Data_Control;
use Elementor\Control_Repeater;
use Elementor\Controls_Stack;
use Elementor\Element_Base;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor post CSS file.
*
* Elementor CSS file handler class is responsible for generating the single
* post CSS file.
*
* @since 1.2.0
*/
class Post extends Base {
/**
* Elementor post CSS file prefix.
*/
const FILE_PREFIX = 'post-';
const META_KEY = '_elementor_css';
/**
* Post ID.
*
* Holds the current post ID.
*
* @var int
*/
private $post_id;
protected function is_global_parsing_supported() {
return true;
}
/**
* Post CSS file constructor.
*
* Initializing the CSS file of the post. Set the post ID and initiate the stylesheet.
*
* @since 1.2.0
* @access public
*
* @param int $post_id Post ID.
*/
public function __construct( $post_id ) {
$this->post_id = $post_id;
parent::__construct( static::FILE_PREFIX . $post_id . '.css' );
}
/**
* Get CSS file name.
*
* Retrieve the CSS file name.
*
* @since 1.6.0
* @access public
*
* @return string CSS file name.
*/
public function get_name() {
return 'post';
}
/**
* Get post ID.
*
* Retrieve the ID of current post.
*
* @since 1.2.0
* @access public
*
* @return int Post ID.
*/
public function get_post_id() {
return $this->post_id;
}
/**
* Get unique element selector.
*
* Retrieve the unique selector for any given element.
*
* @since 1.2.0
* @access public
*
* @param Element_Base $element The element.
*
* @return string Unique element selector.
*/
public function get_element_unique_selector( Element_Base $element ) {
return '.elementor-' . $this->post_id . ' .elementor-element' . $element->get_unique_selector();
}
/**
* Load meta data.
*
* Retrieve the post CSS file meta data.
*
* @since 1.2.0
* @access protected
*
* @return array Post CSS file meta data.
*/
protected function load_meta() {
return get_post_meta( $this->post_id, static::META_KEY, true );
}
/**
* Update meta data.
*
* Update the global CSS file meta data.
*
* @since 1.2.0
* @access protected
*
* @param array $meta New meta data.
*/
protected function update_meta( $meta ) {
update_post_meta( $this->post_id, static::META_KEY, $meta );
}
/**
* Delete meta.
*
* Delete the file meta data.
*
* @since 2.1.0
* @access protected
*/
protected function delete_meta() {
delete_post_meta( $this->post_id, static::META_KEY );
}
/**
* Get post data.
*
* Retrieve raw post data from the database.
*
* @since 1.9.0
* @access protected
*
* @return array Post data.
*/
protected function get_data() {
$document = Plugin::$instance->documents->get( $this->post_id );
return $document ? $document->get_elements_data() : [];
}
/**
* Render CSS.
*
* Parse the CSS for all the elements.
*
* @since 1.2.0
* @access protected
*/
protected function render_css() {
$data = $this->get_data();
if ( ! empty( $data ) ) {
foreach ( $data as $element_data ) {
$element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
if ( ! $element ) {
continue;
}
$this->render_styles( $element );
}
}
}
/**
* Enqueue CSS.
*
* Enqueue the post CSS file in Elementor.
*
* This method ensures that the post was actually built with elementor before
* enqueueing the post CSS file.
*
* @since 1.2.2
* @access public
*/
public function enqueue() {
$document = Plugin::$instance->documents->get( $this->post_id );
if ( ! $document || ! $document->is_built_with_elementor() ) {
return;
}
parent::enqueue();
}
/**
* Add controls-stack style rules.
*
* Parse the CSS for all the elements inside any given controls stack.
*
* This method recursively renders the CSS for all the child elements in the stack.
*
* @since 1.6.0
* @access public
*
* @param Controls_Stack $controls_stack The controls stack.
* @param array $controls Controls array.
* @param array $values Values array.
* @param array $placeholders Placeholders.
* @param array $replacements Replacements.
* @param array $all_controls All controls.
*/
public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) {
parent::add_controls_stack_style_rules( $controls_stack, $controls, $values, $placeholders, $replacements, $all_controls );
if ( $controls_stack instanceof Element_Base ) {
foreach ( $controls_stack->get_children() as $child_element ) {
$this->render_styles( $child_element );
}
}
}
/**
* Get enqueue dependencies.
*
* Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
*
* @since 1.2.0
* @access protected
*
* @return array Name of the stylesheet.
*/
protected function get_enqueue_dependencies() {
return [ 'elementor-frontend' ];
}
/**
* Get inline dependency.
*
* Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
*
* @since 1.2.0
* @access protected
*
* @return string Name of the stylesheet.
*/
protected function get_inline_dependency() {
return 'elementor-frontend';
}
/**
* Get file handle ID.
*
* Retrieve the handle ID for the post CSS file.
*
* @since 1.2.0
* @access protected
*
* @return string CSS file handle ID.
*/
protected function get_file_handle_id() {
return 'elementor-post-' . $this->post_id;
}
/**
* Render styles.
*
* Parse the CSS for any given element.
*
* @since 1.2.0
* @access protected
*
* @param Element_Base $element The element.
*/
protected function render_styles( Element_Base $element ) {
/**
* Before element parse CSS.
*
* Fires before the CSS of the element is parsed.
*
* @since 1.2.0
*
* @param Post $this The post CSS file.
* @param Element_Base $element The element.
*/
do_action( 'elementor/element/before_parse_css', $this, $element );
$element_settings = $element->get_settings();
$this->add_controls_stack_style_rules( $element, $this->get_style_controls( $element, null, $element->get_parsed_dynamic_settings() ), $element_settings, [ '{{ID}}', '{{WRAPPER}}' ], [ $element->get_id(), $this->get_element_unique_selector( $element ) ] );
/**
* After element parse CSS.
*
* Fires after the CSS of the element is parsed.
*
* @since 1.2.0
*
* @param Post $this The post CSS file.
* @param Element_Base $element The element.
*/
do_action( 'elementor/element/parse_css', $this, $element );
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace Elementor\Core\Files;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
use Elementor\Core\Files\Assets\Files_Upload_Handler;
use Elementor\Core\Files\Assets\Json\Json_Handler;
use Elementor\Core\Files\Assets\Svg\Svg_Handler;
use Elementor\Core\Files\CSS\Global_CSS;
use Elementor\Core\Files\CSS\Post as Post_CSS;
use Elementor\Core\Responsive\Files\Frontend;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor files manager.
*
* Elementor files manager handler class is responsible for creating files.
*
* @since 1.2.0
*/
class Manager {
private $files = [];
/**
* Files manager constructor.
*
* Initializing the Elementor files manager.
*
* @since 1.2.0
* @access public
*/
public function __construct() {
$this->register_actions();
new Svg_Handler();
new Json_Handler();
}
public function get( $class, $args ) {
$id = $class . '-' . wp_json_encode( $args );
if ( ! isset( $this->files[ $id ] ) ) {
// Create an instance from dynamic args length.
$reflection_class = new \ReflectionClass( $class );
$this->files[ $id ] = $reflection_class->newInstanceArgs( $args );
}
return $this->files[ $id ];
}
/**
* On post delete.
*
* Delete post CSS immediately after a post is deleted from the database.
*
* Fired by `deleted_post` action.
*
* @since 1.2.0
* @access public
*
* @param string $post_id Post ID.
*/
public function on_delete_post( $post_id ) {
if ( ! Utils::is_post_support( $post_id ) ) {
return;
}
$css_file = Post_CSS::create( $post_id );
$css_file->delete();
}
/**
* On export post meta.
*
* When exporting data using WXR, skip post CSS file meta key. This way the
* export won't contain the post CSS file data used by Elementor.
*
* Fired by `wxr_export_skip_postmeta` filter.
*
* @since 1.2.0
* @access public
*
* @param bool $skip Whether to skip the current post meta.
* @param string $meta_key Current meta key.
*
* @return bool Whether to skip the post CSS meta.
*/
public function on_export_post_meta( $skip, $meta_key ) {
if ( Post_CSS::META_KEY === $meta_key ) {
$skip = true;
}
return $skip;
}
/**
* Clear cache.
*
* Delete all meta containing files data. And delete the actual
* files from the upload directory.
*
* @since 1.2.0
* @access public
*/
public function clear_cache() {
// Delete files.
$path = Base::get_base_uploads_dir() . Base::DEFAULT_FILES_DIR . '*';
foreach ( glob( $path ) as $file_path ) {
unlink( $file_path );
}
delete_post_meta_by_key( Post_CSS::META_KEY );
delete_option( Global_CSS::META_KEY );
delete_option( Frontend::META_KEY );
/**
* Elementor clear files.
*
* Fires after Elementor clears files
*
* @since 2.1.0
*/
do_action( 'elementor/core/files/clear_cache' );
}
public function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'enable_unfiltered_files_upload', [ $this, 'ajax_unfiltered_files_upload' ] );
}
public function ajax_unfiltered_files_upload() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
update_option( Files_Upload_Handler::OPTION_KEY, 1 );
}
/**
* Register actions.
*
* Register filters and actions for the files manager.
*
* @since 1.2.0
* @access private
*/
private function register_actions() {
add_action( 'deleted_post', [ $this, 'on_delete_post' ] );
// Ajax.
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
add_filter( 'wxr_export_skip_postmeta', [ $this, 'on_export_post_meta' ], 10, 2 );
}
}