From f56e712f65c1c1dcdec3a66c61aa52db35626176 Mon Sep 17 00:00:00 2001 From: Amirreza Nazemi Date: Wed, 5 Nov 2025 17:31:33 +0330 Subject: [PATCH 1/3] feat(elementor): add Elementor template shortcode and asset loader --- includes/types/anys/elementor.php | 124 ++++++++++++++++++++++++++++++ includes/utilities.php | 60 +++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 includes/types/anys/elementor.php diff --git a/includes/types/anys/elementor.php b/includes/types/anys/elementor.php new file mode 100644 index 0000000..fa34fdb --- /dev/null +++ b/includes/types/anys/elementor.php @@ -0,0 +1,124 @@ +post_modified_gmt ) . '_' . ( $template_type ?: 'na' ); +$cached = get_transient( $cache_key ); + +// Return cached version if found. +if ( false !== $cached ) { + echo anys_wrap_output( $cached, $attributes ); // raw + + return; +} + +try { + // Render template. + $html = \Elementor\Plugin::instance()->frontend->get_builder_content_for_display( $id, true ); + + // Wrap section if needed. + if ( 'section' === $template_type ) { + $html = '
' . $html . '
'; + } + + // Empty check. + if ( ! $html ) { + $value = esc_html__( 'Template is empty or cannot be rendered.', 'anys' ); + + echo wp_kses_post( anys_wrap_output( $value, $attributes ) ); + + return; + } + + // Save to cache. + set_transient( $cache_key, $html, MINUTE_IN_SECONDS * 10 ); + + // Output final HTML. + echo anys_wrap_output( $html, $attributes ); // raw +} catch ( \Throwable ) { + // Handle render error. + $value = esc_html__( 'An error occurred while rendering the template.', 'anys' ); + echo wp_kses_post( anys_wrap_output( $value, $attributes ) ); +} diff --git a/includes/utilities.php b/includes/utilities.php index 212327d..ba91586 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -549,3 +549,63 @@ function anys_date_i18n_jalali( $format, $timestamp = false, $gmt = false ) { // Core filter is applied for consistency. return apply_filters( 'date_i18n', $formatted_output, (string) $format, $resolved_timestamp, $gmt ); } + +/** + * Conditionally enqueues Elementor assets for template rendering. + * + * @param array $attributes Shortcode attributes (merged and filtered). + * + * @since NEXT + */ +function anys_maybe_enqueue_elementor_assets( array $attributes ) : void { + // Validate context. + if ( 'elementor' !== strtolower( (string) ( $attributes['type'] ?? '' ) ) ) { + return; + } + if ( 'template' !== strtolower( (string) ( $attributes['name'] ?? '' ) ) ) { + return; + } + + // Check Elementor. + if ( ! did_action( 'elementor/loaded' ) || ! class_exists( '\Elementor\Plugin' ) ) { + return; + } + + // Validate template id. + $id = isset( $attributes['id'] ) ? absint( $attributes['id'] ) : 0; + if ( ! $id ) { + return; + } + + // Enqueue core frontend CSS/JS. + wp_enqueue_style( 'elementor-frontend' ); + wp_enqueue_style( 'elementor-icons' ); + wp_enqueue_script( 'elementor-frontend' ); + + // Enqueue Pro assets if available. + if ( did_action( 'elementor_pro/init' ) ) { + wp_enqueue_style( 'elementor-pro-frontend' ); + wp_enqueue_script( 'elementor-pro-frontend' ); + } + + // Let Elementor enqueue extra assets. + $frontend = \Elementor\Plugin::instance()->frontend ?? null; + if ( $frontend ) { + if ( method_exists( $frontend, 'enqueue_styles' ) ) { + $frontend->enqueue_styles(); + } + if ( method_exists( $frontend, 'enqueue_scripts' ) ) { + $frontend->enqueue_scripts(); + } + } + + // Enqueue per-template CSS. + if ( class_exists( '\Elementor\Core\Files\CSS\Post' ) ) { + try { + $css = \Elementor\Core\Files\CSS\Post::create( $id ); + $css->enqueue(); + } catch ( \Throwable ) { + // Silently ignored. + } + } +} From ca1ea0d28c2e761db350f7d0830abc7a77080b05 Mon Sep 17 00:00:00 2001 From: Amirreza Nazemi Date: Sat, 8 Nov 2025 18:10:19 +0330 Subject: [PATCH 2/3] Sync branch structure with latest updates --- .../modules/shortcodes/types/elementor.php | 221 ++++++++++++++++++ includes/modules/utilities.php | 59 ----- includes/types/anys/elementor.php | 124 ---------- 3 files changed, 221 insertions(+), 183 deletions(-) create mode 100644 includes/modules/shortcodes/types/elementor.php delete mode 100644 includes/types/anys/elementor.php diff --git a/includes/modules/shortcodes/types/elementor.php b/includes/modules/shortcodes/types/elementor.php new file mode 100644 index 0000000..df5b272 --- /dev/null +++ b/includes/modules/shortcodes/types/elementor.php @@ -0,0 +1,221 @@ + '', + 'id' => 0, + 'before' => '', + 'after' => '', + 'fallback' => '', + 'format' => '', + ]; + } + + /** + * Renders the shortcode. + * + * @since NEXT + * + * @param array $attributes Shortcode attributes. + * @param string $content Enclosed content (optional). + * + * @return string + */ + public function render( array $attributes, string $content ) { + // Defaults merged. + $attributes = $this->get_attributes( $attributes ); + + // Dynamic attributes parsed. + $attributes = anys_parse_dynamic_attributes( $attributes ); + + // Provider validated. + $provider_name = strtolower( (string) ( $attributes['name'] ?? '' ) ); + if ( $provider_name !== 'template' ) { + return ''; + } + + // Elementor presence validated. + if ( ! did_action( 'elementor/loaded' ) || ! class_exists( '\Elementor\Plugin' ) ) { + $value = esc_html__( 'Elementor is not active.', 'anys' ); + $output = anys_wrap_output( $value, $attributes ); + return wp_kses_post( (string) $output ); + } + + // ID validated. + $template_id = (int) ( $attributes['id'] ?? 0 ); + if ( $template_id <= 0 ) { + $value = esc_html__( 'Missing or invalid "id" attribute.', 'anys' ); + $output = anys_wrap_output( $value, $attributes ); + return wp_kses_post( (string) $output ); + } + + // Template post fetched. + $template_post = get_post( $template_id ); + if ( ! $template_post ) { + $value = esc_html__( 'Template not found.', 'anys' ); + $output = anys_wrap_output( $value, $attributes ); + return wp_kses_post( (string) $output ); + } + + // Post type validated. + if ( get_post_type( $template_post ) !== 'elementor_library' ) { + $value = esc_html__( 'The provided ID is not an Elementor template.', 'anys' ); + $output = anys_wrap_output( $value, $attributes ); + return wp_kses_post( (string) $output ); + } + + // Assets enqueued when needed. + $this->anys_maybe_enqueue_elementor_assets( $attributes ); + + // Template type read. + $template_type = get_post_meta( $template_id, '_elementor_template_type', true ); // 'section' | 'page' + if ( $template_type && ! in_array( $template_type, [ 'section', 'page' ], true ) ) { + $value = esc_html__( 'Unsupported Elementor template type.', 'anys' ); + $output = anys_wrap_output( $value, $attributes ); + return wp_kses_post( (string) $output ); + } + + // Recursion prevented. + if ( is_singular( 'elementor_library' ) && get_the_ID() === $template_id ) { + $value = esc_html__( 'Recursive rendering detected.', 'anys' ); + $output = anys_wrap_output( $value, $attributes ); + return wp_kses_post( (string) $output ); + } + + // Cache key built. + $cache_key = sprintf( + 'anys_elem_tpl_%d_%s_%s', + $template_id, + (string) strtotime( $template_post->post_modified_gmt ), + $template_type ? $template_type : 'na' + ); + + // Cached HTML returned if available (raw). + $cached_html = get_transient( $cache_key ); + if ( $cached_html !== false ) { + // Raw HTML is intentionally not escaped to keep Elementor markup intact. + return anys_wrap_output( $cached_html, $attributes ); + } + + try { + // Template rendered. + $html = \Elementor\Plugin::instance()->frontend->get_builder_content_for_display( $template_id, true ); + + // Section wrapped when needed. + if ( $template_type === 'section' ) { + $html = '
' . $html . '
'; + } + + // Empty state handled. + if ( ! $html ) { + $value = esc_html__( 'Template is empty or cannot be rendered.', 'anys' ); + $output = anys_wrap_output( $value, $attributes ); + return wp_kses_post( (string) $output ); + } + + // Cached for 10 minutes. + set_transient( $cache_key, $html, MINUTE_IN_SECONDS * 10 ); + + // Wrapped raw HTML returned. + return anys_wrap_output( $html, $attributes ); + } catch ( \Throwable $e ) { + $value = esc_html__( 'An error occurred while rendering the template.', 'anys' ); + $output = anys_wrap_output( $value, $attributes ); + return wp_kses_post( (string) $output ); + } + } + + /** + * Conditionally enqueues Elementor assets for template rendering. + * + * @param array $attributes Shortcode attributes (merged and filtered). + * + * @since NEXT + */ + private function anys_maybe_enqueue_elementor_assets( array $attributes ) : void { + // Validate context. + if ( 'elementor' !== strtolower( (string) ( $attributes['type'] ?? '' ) ) ) { + return; + } + if ( 'template' !== strtolower( (string) ( $attributes['name'] ?? '' ) ) ) { + return; + } + + // Check Elementor. + if ( ! did_action( 'elementor/loaded' ) || ! class_exists( '\Elementor\Plugin' ) ) { + return; + } + + // Validate template id. + $id = isset( $attributes['id'] ) ? absint( $attributes['id'] ) : 0; + if ( ! $id ) { + return; + } + + // Enqueue core frontend CSS/JS. + wp_enqueue_style( 'elementor-frontend' ); + wp_enqueue_style( 'elementor-icons' ); + wp_enqueue_script( 'elementor-frontend' ); + + // Enqueue Pro assets if available. + if ( did_action( 'elementor_pro/init' ) ) { + wp_enqueue_style( 'elementor-pro-frontend' ); + wp_enqueue_script( 'elementor-pro-frontend' ); + } + + // Let Elementor enqueue extra assets. + $frontend = \Elementor\Plugin::instance()->frontend ?? null; + if ( $frontend ) { + if ( method_exists( $frontend, 'enqueue_styles' ) ) { + $frontend->enqueue_styles(); + } + if ( method_exists( $frontend, 'enqueue_scripts' ) ) { + $frontend->enqueue_scripts(); + } + } + + // Enqueue per-template CSS. + if ( class_exists( '\Elementor\Core\Files\CSS\Post' ) ) { + try { + $css = \Elementor\Core\Files\CSS\Post::create( $id ); + $css->enqueue(); + } catch ( \Throwable ) { + // Silently ignored. + } + } + } +} diff --git a/includes/modules/utilities.php b/includes/modules/utilities.php index 5ce499e..6451344 100644 --- a/includes/modules/utilities.php +++ b/includes/modules/utilities.php @@ -550,62 +550,3 @@ function anys_date_i18n_jalali( $format, $timestamp = false, $gmt = false ) { return apply_filters( 'date_i18n', $formatted_output, (string) $format, $resolved_timestamp, $gmt ); } -/** - * Conditionally enqueues Elementor assets for template rendering. - * - * @param array $attributes Shortcode attributes (merged and filtered). - * - * @since NEXT - */ -function anys_maybe_enqueue_elementor_assets( array $attributes ) : void { - // Validate context. - if ( 'elementor' !== strtolower( (string) ( $attributes['type'] ?? '' ) ) ) { - return; - } - if ( 'template' !== strtolower( (string) ( $attributes['name'] ?? '' ) ) ) { - return; - } - - // Check Elementor. - if ( ! did_action( 'elementor/loaded' ) || ! class_exists( '\Elementor\Plugin' ) ) { - return; - } - - // Validate template id. - $id = isset( $attributes['id'] ) ? absint( $attributes['id'] ) : 0; - if ( ! $id ) { - return; - } - - // Enqueue core frontend CSS/JS. - wp_enqueue_style( 'elementor-frontend' ); - wp_enqueue_style( 'elementor-icons' ); - wp_enqueue_script( 'elementor-frontend' ); - - // Enqueue Pro assets if available. - if ( did_action( 'elementor_pro/init' ) ) { - wp_enqueue_style( 'elementor-pro-frontend' ); - wp_enqueue_script( 'elementor-pro-frontend' ); - } - - // Let Elementor enqueue extra assets. - $frontend = \Elementor\Plugin::instance()->frontend ?? null; - if ( $frontend ) { - if ( method_exists( $frontend, 'enqueue_styles' ) ) { - $frontend->enqueue_styles(); - } - if ( method_exists( $frontend, 'enqueue_scripts' ) ) { - $frontend->enqueue_scripts(); - } - } - - // Enqueue per-template CSS. - if ( class_exists( '\Elementor\Core\Files\CSS\Post' ) ) { - try { - $css = \Elementor\Core\Files\CSS\Post::create( $id ); - $css->enqueue(); - } catch ( \Throwable ) { - // Silently ignored. - } - } -} diff --git a/includes/types/anys/elementor.php b/includes/types/anys/elementor.php deleted file mode 100644 index fa34fdb..0000000 --- a/includes/types/anys/elementor.php +++ /dev/null @@ -1,124 +0,0 @@ -post_modified_gmt ) . '_' . ( $template_type ?: 'na' ); -$cached = get_transient( $cache_key ); - -// Return cached version if found. -if ( false !== $cached ) { - echo anys_wrap_output( $cached, $attributes ); // raw - - return; -} - -try { - // Render template. - $html = \Elementor\Plugin::instance()->frontend->get_builder_content_for_display( $id, true ); - - // Wrap section if needed. - if ( 'section' === $template_type ) { - $html = '
' . $html . '
'; - } - - // Empty check. - if ( ! $html ) { - $value = esc_html__( 'Template is empty or cannot be rendered.', 'anys' ); - - echo wp_kses_post( anys_wrap_output( $value, $attributes ) ); - - return; - } - - // Save to cache. - set_transient( $cache_key, $html, MINUTE_IN_SECONDS * 10 ); - - // Output final HTML. - echo anys_wrap_output( $html, $attributes ); // raw -} catch ( \Throwable ) { - // Handle render error. - $value = esc_html__( 'An error occurred while rendering the template.', 'anys' ); - echo wp_kses_post( anys_wrap_output( $value, $attributes ) ); -} From 99b8677cf89632911947c085f1d198ddbc212f2b Mon Sep 17 00:00:00 2001 From: Amirreza Nazemi Date: Sun, 9 Nov 2025 15:33:43 +0330 Subject: [PATCH 3/3] chore: revise inline comments to consistent third-person style --- .../modules/shortcodes/types/elementor.php | 84 ++++++++----------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/includes/modules/shortcodes/types/elementor.php b/includes/modules/shortcodes/types/elementor.php index df5b272..0cf6b90 100644 --- a/includes/modules/shortcodes/types/elementor.php +++ b/includes/modules/shortcodes/types/elementor.php @@ -7,7 +7,7 @@ use AnyS\Traits\Singleton; /** - * Renders a saved Elementor template by ID and includes required assets. + * Renders an Elementor template by ID and enqueues required assets. * * Handles the `[anys type="elementor" name="template" id="123"]` shortcode. * @@ -16,37 +16,25 @@ final class Elementor extends Base { use Singleton; - /** - * Returns the shortcode type. - * - * @since NEXT - * - * @return string - */ + /** Returns shortcode type. */ public function get_type() { return 'elementor'; } - /** - * Returns the default shortcode attributes. - * - * @since NEXT - * - * @return array - */ + /** Returns default shortcode attributes. */ protected function get_defaults() { return [ - 'name' => '', - 'id' => 0, - 'before' => '', - 'after' => '', + 'name' => '', + 'id' => 0, + 'before' => '', + 'after' => '', 'fallback' => '', - 'format' => '', + 'format' => '', ]; } /** - * Renders the shortcode. + * Renders shortcode output. * * @since NEXT * @@ -55,27 +43,27 @@ protected function get_defaults() { * * @return string */ - public function render( array $attributes, string $content ) { - // Defaults merged. + public function render( array $attributes, string $content = '' ) { + // Merges defaults. $attributes = $this->get_attributes( $attributes ); - // Dynamic attributes parsed. + // Parses dynamic attributes. $attributes = anys_parse_dynamic_attributes( $attributes ); - // Provider validated. + // Validates provider. $provider_name = strtolower( (string) ( $attributes['name'] ?? '' ) ); if ( $provider_name !== 'template' ) { return ''; } - // Elementor presence validated. + // Validates Elementor presence. if ( ! did_action( 'elementor/loaded' ) || ! class_exists( '\Elementor\Plugin' ) ) { $value = esc_html__( 'Elementor is not active.', 'anys' ); $output = anys_wrap_output( $value, $attributes ); return wp_kses_post( (string) $output ); } - // ID validated. + // Validates template ID. $template_id = (int) ( $attributes['id'] ?? 0 ); if ( $template_id <= 0 ) { $value = esc_html__( 'Missing or invalid "id" attribute.', 'anys' ); @@ -83,7 +71,7 @@ public function render( array $attributes, string $content ) { return wp_kses_post( (string) $output ); } - // Template post fetched. + // Fetches template post. $template_post = get_post( $template_id ); if ( ! $template_post ) { $value = esc_html__( 'Template not found.', 'anys' ); @@ -91,17 +79,17 @@ public function render( array $attributes, string $content ) { return wp_kses_post( (string) $output ); } - // Post type validated. + // Validates post type. if ( get_post_type( $template_post ) !== 'elementor_library' ) { $value = esc_html__( 'The provided ID is not an Elementor template.', 'anys' ); $output = anys_wrap_output( $value, $attributes ); return wp_kses_post( (string) $output ); } - // Assets enqueued when needed. + // Enqueues assets when needed. $this->anys_maybe_enqueue_elementor_assets( $attributes ); - // Template type read. + // Reads template type. $template_type = get_post_meta( $template_id, '_elementor_template_type', true ); // 'section' | 'page' if ( $template_type && ! in_array( $template_type, [ 'section', 'page' ], true ) ) { $value = esc_html__( 'Unsupported Elementor template type.', 'anys' ); @@ -109,14 +97,14 @@ public function render( array $attributes, string $content ) { return wp_kses_post( (string) $output ); } - // Recursion prevented. + // Prevents recursion. if ( is_singular( 'elementor_library' ) && get_the_ID() === $template_id ) { $value = esc_html__( 'Recursive rendering detected.', 'anys' ); $output = anys_wrap_output( $value, $attributes ); return wp_kses_post( (string) $output ); } - // Cache key built. + // Builds cache key. $cache_key = sprintf( 'anys_elem_tpl_%d_%s_%s', $template_id, @@ -124,7 +112,7 @@ public function render( array $attributes, string $content ) { $template_type ? $template_type : 'na' ); - // Cached HTML returned if available (raw). + // Returns cached raw HTML when available. $cached_html = get_transient( $cache_key ); if ( $cached_html !== false ) { // Raw HTML is intentionally not escaped to keep Elementor markup intact. @@ -132,25 +120,25 @@ public function render( array $attributes, string $content ) { } try { - // Template rendered. + // Renders template. $html = \Elementor\Plugin::instance()->frontend->get_builder_content_for_display( $template_id, true ); - // Section wrapped when needed. + // Wraps section when needed. if ( $template_type === 'section' ) { $html = '
' . $html . '
'; } - // Empty state handled. + // Handles empty state. if ( ! $html ) { $value = esc_html__( 'Template is empty or cannot be rendered.', 'anys' ); $output = anys_wrap_output( $value, $attributes ); return wp_kses_post( (string) $output ); } - // Cached for 10 minutes. + // Caches for 10 minutes. set_transient( $cache_key, $html, MINUTE_IN_SECONDS * 10 ); - // Wrapped raw HTML returned. + // Returns wrapped raw HTML. return anys_wrap_output( $html, $attributes ); } catch ( \Throwable $e ) { $value = esc_html__( 'An error occurred while rendering the template.', 'anys' ); @@ -166,8 +154,8 @@ public function render( array $attributes, string $content ) { * * @since NEXT */ - private function anys_maybe_enqueue_elementor_assets( array $attributes ) : void { - // Validate context. + private function anys_maybe_enqueue_elementor_assets( array $attributes ) { + // Validates context. if ( 'elementor' !== strtolower( (string) ( $attributes['type'] ?? '' ) ) ) { return; } @@ -175,29 +163,29 @@ private function anys_maybe_enqueue_elementor_assets( array $attributes ) : void return; } - // Check Elementor. + // Checks Elementor. if ( ! did_action( 'elementor/loaded' ) || ! class_exists( '\Elementor\Plugin' ) ) { return; } - // Validate template id. + // Validates template ID. $id = isset( $attributes['id'] ) ? absint( $attributes['id'] ) : 0; if ( ! $id ) { return; } - // Enqueue core frontend CSS/JS. + // Enqueues core frontend CSS/JS. wp_enqueue_style( 'elementor-frontend' ); wp_enqueue_style( 'elementor-icons' ); wp_enqueue_script( 'elementor-frontend' ); - // Enqueue Pro assets if available. + // Enqueues Pro assets when available. if ( did_action( 'elementor_pro/init' ) ) { wp_enqueue_style( 'elementor-pro-frontend' ); wp_enqueue_script( 'elementor-pro-frontend' ); } - // Let Elementor enqueue extra assets. + // Lets Elementor enqueue extra assets. $frontend = \Elementor\Plugin::instance()->frontend ?? null; if ( $frontend ) { if ( method_exists( $frontend, 'enqueue_styles' ) ) { @@ -208,13 +196,13 @@ private function anys_maybe_enqueue_elementor_assets( array $attributes ) : void } } - // Enqueue per-template CSS. + // Enqueues per-template CSS. if ( class_exists( '\Elementor\Core\Files\CSS\Post' ) ) { try { $css = \Elementor\Core\Files\CSS\Post::create( $id ); $css->enqueue(); } catch ( \Throwable ) { - // Silently ignored. + // Silently ignores errors. } } }