Skip to content
361 changes: 361 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,364 @@ 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() ) {

Choose a reason for hiding this comment

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

What: Unsafe reliance on browser localStorage without fallbacks.

Why: If a user's browser has localStorage disabled, the promotional message will continually reappear, which could frustrate users. It's important to ensure that critical functionality remains smooth regardless of the user’s browser settings.

How: Implement a fallback mechanism that enables the temporary hiding of the notice without relying on localStorage.

?>
<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: #93003f;
border: 1px solid #93003f;
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: #8f1a4c;
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 = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you minify this js and add it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

storageKey: 'hfe_promo_notice_dismissed',
oneWeekMs: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds

init: function() {
if (this.shouldShow()) {
this.show();
}
},

shouldShow: function() {
try {
const dismissedData = localStorage.getItem(this.storageKey);
if (!dismissedData) {
return true; // Never dismissed before
}

const dismissedTime = parseInt(dismissedData);
const currentTime = Date.now();
const timeDiff = currentTime - dismissedTime;

// Show again if more than one week has passed
return timeDiff >= this.oneWeekMs;
} catch (e) {
// If localStorage is not available, always show
return true;
}
},

show: function() {
const notice = document.getElementById('hfe-promo-notice');
if (notice) {
notice.classList.add('show');
}
},

dismiss: function() {
const notice = document.getElementById('hfe-promo-notice');
if (notice) {
notice.classList.add('hide');

// Store dismissal timestamp
try {
localStorage.setItem(this.storageKey, Date.now().toString());
} catch (e) {
// Handle localStorage not available
console.log('Could not save dismissal state');
}

// Remove from DOM after animation
setTimeout(() => {
notice.remove();
}, 400);
}
}
};

// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
hfePromoNotice.init();
});
} else {
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'] ) || $_GET['preview'] !== 'true' ) {
return false;
}

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

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

// Verify preview nonce for security (if available)
if ( isset( $_GET['preview_nonce'] ) ) {
if ( ! wp_verify_nonce( $_GET['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
Loading