Skip to content
298 changes: 298 additions & 0 deletions inc/class-header-footer-elementor.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ function () {

add_action( 'astra_notice_before_markup_header-footer-elementor-rating', [ $this, 'rating_notice_css' ] );

// Add Elementor preview notice
add_action( 'wp_footer', [ $this, 'elementor_preview_notice' ] );

require_once HFE_DIR . 'inc/class-hfe-analytics.php';

}
Expand Down Expand Up @@ -268,6 +271,301 @@ public function rating_notice_css() {
wp_enqueue_style( 'hfe-admin-style', HFE_URL . 'assets/css/admin-header-footer-elementor.css', [], HFE_VER );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: SQL Injection Risk in the preview nonce verification step.

Why: The method currently checks for a nonce on the preview request which adds a layer of security. However, if this nonce isn't properly validated or if the structure of the nonce is predictable, it could lead to vulnerabilities. Always ensure that nonce validation follows WordPress best practices to mitigate risks effectively.

How: Consider further securing nonce verification with better validation measures. Ensure the 'preview_nonce' is unique per request and hidden, and implement logging for failed nonce checks to help identify potential abuse.

}

/**
* Display Elementor preview notice in footer when in preview mode.
*
* @since 2.4.9
* @return void
*/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: Potential for Denial of Service if many notices are created/removed quickly due to localStorage handling and DOM manipulation.

Why: The JavaScript to manage the notice could lead to performance issues if users quickly interact with the notification multiple times, potentially resulting in heavy DOM manipulation or localStorage writes that could degrade user experience, especially on lower-power devices.

How: Consider debouncing the dismiss function to limit how quickly it can be triggered or implemented a flag to prevent multiple dismissals in quick succession. This change would minimize potential performance impacts.

public function elementor_preview_notice() {
// Show notice only for page post type in preview mode
if ( ! $this->should_show_preview_notice() ) {
return;
}

?>
<style>
@keyframes slideInFromBottom {
0% {
transform: translateY(100%);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}

@keyframes slideOutToBottom {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(100%);
opacity: 0;
}
}

.hfe-promo-notice {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #000;
color: #ffffff;
padding: 10px 20px;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
font-weight: 500;
animation: slideInFromBottom 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 255, 255, 0.2);
display: none;
}

.hfe-promo-notice.show {
display: block;
}

.hfe-promo-notice.hide {
animation: slideOutToBottom 0.4s cubic-bezier(0.55, 0.06, 0.68, 0.19) forwards;
}

.hfe-promo-notice-container {
display: flex;
align-items: center;
justify-content: space-around;
}

.hfe-promo-notice-content {
display: flex;
align-items: center;
/* flex: 1; */
}

.hfe-promo-notice-icon {
display: inline-flex;
width: 24px;
height: 24px;
margin-right: 12px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: CSS can potentially affect performance with many animate styles in rapid succession.

Why: Heavy animations can impact performance, especially on older devices or with multiple elements being animated simultaneously. This can lead to jank and a poor user experience.

How: Consider simplifying animations or limiting them based on user interaction. Alternatively, conditionally load animation styles only when necessary to reduce initial load time.


.hfe-promo-notice-text {
flex: 1;
line-height: 1.4;
}

.hfe-promo-notice-title {
font-weight: 600;
margin-bottom: 2px;
font-size: 15px;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: Inconsistent visibility checks for notice removal leading to user confusion.

Why: If the notice visibility is not properly managed, users may experience inconsistent behavior when interacting with the notification banner, impeding its user-friendliness.

How: Refactor the script to ensure clear visibility states upon interaction. Additionally, add comments to explain each section of the script for future developers.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: The use of inline styles may hinder maintainability and consistency in styling.

Why: Inline styles can lead to difficulties in managing consistent theming and maintenance as the codebase grows.

How: Extract styles into dedicated CSS files or utilize existing stylesheets, ensuring they're scoped correctly, to facilitate easier updates and increases in maintainability.


.hfe-promo-notice-description {
opacity: 0.9;
font-size: 20px;
}

.hfe-promo-notice-cta {
margin-left: 16px;
padding: 8px 16px;
background: rgb(96, 5, 255);
border: 1px solid rgb(96, 5, 255);
color: #ffffff;
text-decoration: none;
border-radius: 25px;
font-size: 13px;
font-weight: 600;
transition: all 0.2s ease;
white-space: nowrap;
}

.hfe-promo-notice-cta:hover {
background:rgb(96, 5, 255);
color: #ffffff;
text-decoration: none;
}

.hfe-promo-notice-close {
margin-left: 16px;
position: absolute;
right:5px;
background: none;
border: none;
color: rgba(255, 255, 255, 0.8);
font-size: 20px;
cursor: pointer;
padding: 4px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s ease;
flex-shrink: 0;
}

.hfe-promo-notice-close:hover {
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
}

@media (max-width: 768px) {
.hfe-promo-notice {
padding: 12px 16px;
font-size: 13px;
}

.hfe-promo-notice-container {
flex-direction: column;
align-items: stretch;
gap: 12px;
}

.hfe-promo-notice-content {
flex-direction: column;
align-items: flex-start;
text-align: left;
}

.hfe-promo-notice-icon {
margin-right: 8px;
width: 20px;
height: 20px;
font-size: 12px;
}

.hfe-promo-notice-cta {
margin-left: 0;
margin-top: 8px;
align-self: flex-start;
}

.hfe-promo-notice-close {
position: absolute;
top: 8px;
right: 8px;
margin-left: 0;
}
}

@media (max-width: 480px) {
.hfe-promo-notice-container {
padding-right: 40px;
}

.hfe-promo-notice-title {
font-size: 14px;
}

.hfe-promo-notice-description {
font-size: 12px;
}
}
</style>

<div id="hfe-promo-notice" class="hfe-promo-notice">
<div class="hfe-promo-notice-container">
<div class="hfe-promo-notice-content">
<!-- <div class="hfe-promo-notice-icon">🚀</div> -->
<div class="hfe-promo-notice-text">
<!-- <div class="hfe-promo-notice-title">Unlock More Elementor Widgets!</div> -->
<div class="hfe-promo-notice-description">Psst… want to save hours? Get 300+ professionally built templates.</div>
</div>
<a href="https://ultimateelementor.com/pricing/?utm_source=preview&utm_medium=notice&utm_campaign=uae-lite"
target="_blank"
class="hfe-promo-notice-cta">
Unlock Now
</a>
</div>
<button class="hfe-promo-notice-close" onclick="hfePromoNotice.dismiss()">&times;</button>
</div>
</div>

<script>window.hfePromoNotice={storageKey:'hfe_promo_notice_dismissed',oneMonthMs:2592000000,init:function(){this.shouldShow()&&this.show()},shouldShow:function(){try{const a=localStorage.getItem(this.storageKey);if(!a)return!0;const b=parseInt(a),c=Date.now();return c-b>=this.oneMonthMs}catch(a){return!0}},show:function(){const a=document.getElementById('hfe-promo-notice');a&&a.classList.add('show')},dismiss:function(){const a=document.getElementById('hfe-promo-notice');a&&(a.classList.add('hide'),setTimeout(()=>{a.remove()},400));try{localStorage.setItem(this.storageKey,Date.now().toString())}catch(a){console.log('Could not save dismissal state')}}};"loading"===document.readyState?document.addEventListener('DOMContentLoaded',function(){hfePromoNotice.init()}):hfePromoNotice.init();</script>
<?php
}

/**
* Check if preview notice should be shown for page post type only.
*
* @since 2.4.9
* @return bool
*/
private function should_show_preview_notice() {
// Don't show if UAE Pro is already installed/activated
if ( is_plugin_active( 'ultimate-elementor/ultimate-elementor.php' ) ||
file_exists( WP_PLUGIN_DIR . '/ultimate-elementor/ultimate-elementor.php' ) ) {
return false;
}

// Basic preview check
if ( ! isset( $_GET['preview'] ) || sanitize_text_field( wp_unslash( $_GET['preview'] ) ) !== 'true' ) {
return false;
}

// Must have preview_id
if ( ! isset( $_GET['preview_id'] ) ) {
return false;
}

$preview_id = intval( sanitize_text_field( wp_unslash( $_GET['preview_id'] ) ) );

// Verify preview nonce for security (if available)
if ( isset( $_GET['preview_nonce'] ) ) {
$preview_nonce = sanitize_text_field( wp_unslash( $_GET['preview_nonce'] ) );
if ( ! wp_verify_nonce( $preview_nonce, 'post_preview_' . $preview_id ) ) {
return false;
}
}

// Check if it's a page post type (not header/footer templates)
$post_type = get_post_type( $preview_id );
if ( $post_type !== 'page' ) {
return false;
}

// Exclude header/footer templates from UAE
$template_type = get_post_meta( $preview_id, 'ehf_template_type', true );
if ( ! empty( $template_type ) ) {
return false; // This is a header/footer template, don't show notice
}

// Optional: Check if page uses Elementor
if ( ! $this->is_elementor_page( $preview_id ) ) {
return false;
}

// Optional: Allow filtering for custom conditions
return apply_filters( 'hfe_show_preview_notice', true, $preview_id );
}

/**
* Check if the page is built with Elementor.
*
* @since 2.4.9
* @param int $post_id Post ID to check.
* @return bool
*/
private function is_elementor_page( $post_id ) {
if ( ! class_exists( '\Elementor\Plugin' ) ) {
return false;
}

$elementor_data = get_post_meta( $post_id, '_elementor_data', true );
return ! empty( $elementor_data );
}

/**
* Prints the admin notics when Elementor is not installed or activated or version outdated.
*
Expand Down