From 5458b61108e950310b4c0a921726ad8f8af31b53 Mon Sep 17 00:00:00 2001 From: Andrew Shevchenko Date: Thu, 5 Sep 2024 15:13:38 +0300 Subject: [PATCH] ADD: API-driven promo banners https://github.com/Crocoblock/issues-tracker/issues/11553 --- includes/admin/pages/stable-pages-manager.php | 5 +- modules/admin/module.php | 5 +- modules/modules-controller.php | 1 + modules/promo-banner/.babelrc | 3 + modules/promo-banner/.gitattributes | 5 + .../promo-banner/assets/build/index.asset.php | 1 + modules/promo-banner/assets/build/index.css | 1 + modules/promo-banner/assets/build/index.js | 1 + modules/promo-banner/assets/src/index.js | 65 ++++++ modules/promo-banner/assets/src/index.scss | 23 +++ modules/promo-banner/module.php | 187 ++++++++++++++++++ modules/promo-banner/package.json | 11 ++ modules/promo-banner/storage.php | 130 ++++++++++++ modules/promo-banner/webpack.config.js | 49 +++++ 14 files changed, 485 insertions(+), 2 deletions(-) create mode 100644 modules/promo-banner/.babelrc create mode 100644 modules/promo-banner/.gitattributes create mode 100644 modules/promo-banner/assets/build/index.asset.php create mode 100644 modules/promo-banner/assets/build/index.css create mode 100644 modules/promo-banner/assets/build/index.js create mode 100644 modules/promo-banner/assets/src/index.js create mode 100644 modules/promo-banner/assets/src/index.scss create mode 100644 modules/promo-banner/module.php create mode 100644 modules/promo-banner/package.json create mode 100644 modules/promo-banner/storage.php create mode 100644 modules/promo-banner/webpack.config.js diff --git a/includes/admin/pages/stable-pages-manager.php b/includes/admin/pages/stable-pages-manager.php index 32f03982f..658408906 100644 --- a/includes/admin/pages/stable-pages-manager.php +++ b/includes/admin/pages/stable-pages-manager.php @@ -81,7 +81,10 @@ public function add_static_pages() { $utm->set_content( $utm->get_license_and_theme() ); $pages[] = array( - 'title' => __( 'Go PRO', 'jet-form-builder' ), + 'title' => apply_filters( + 'jet-form-builder/admin/pages/go-pro-title', + __( 'Go PRO', 'jet-form-builder' ) + ), 'capability' => 'manage_options', 'slug' => $utm->add_query( JET_FORM_BUILDER_SITE . '/pricing/' ), ); diff --git a/modules/admin/module.php b/modules/admin/module.php index 528f4e426..e14bb9fa3 100644 --- a/modules/admin/module.php +++ b/modules/admin/module.php @@ -140,7 +140,10 @@ public function modify_plugin_action_links( array $links ): array { $url = $utm->add_query( JET_FORM_BUILDER_SITE . '/pricing/' ); - $label = __( 'Go Pro', 'jet-form-builder' ); + $label = apply_filters( + 'jet-form-builder/admin/go-pro-link-title', + __( 'Go Pro', 'jet-form-builder' ) + ); $links['go_pro'] = "{$label}"; diff --git a/modules/modules-controller.php b/modules/modules-controller.php index da6aef200..1d19c1eb3 100644 --- a/modules/modules-controller.php +++ b/modules/modules-controller.php @@ -60,6 +60,7 @@ public function rep_instances(): array { new Validation\Module(), new Wysiwyg\Module(), new Switcher\Module(), + new Promo_Banner\Module(), ); } diff --git a/modules/promo-banner/.babelrc b/modules/promo-banner/.babelrc new file mode 100644 index 000000000..bedc22b83 --- /dev/null +++ b/modules/promo-banner/.babelrc @@ -0,0 +1,3 @@ +{ + "extends": "../../.babelrc" +} \ No newline at end of file diff --git a/modules/promo-banner/.gitattributes b/modules/promo-banner/.gitattributes new file mode 100644 index 000000000..ddf207cf5 --- /dev/null +++ b/modules/promo-banner/.gitattributes @@ -0,0 +1,5 @@ +assets/src export-ignore +package.json export-ignore +.babelrc export-ignore +webpack.config.js export-ignore +.gitattributes export-ignore \ No newline at end of file diff --git a/modules/promo-banner/assets/build/index.asset.php b/modules/promo-banner/assets/build/index.asset.php new file mode 100644 index 000000000..4ea02d098 --- /dev/null +++ b/modules/promo-banner/assets/build/index.asset.php @@ -0,0 +1 @@ + array(), 'version' => '1b38af3fde1368c66296'); diff --git a/modules/promo-banner/assets/build/index.css b/modules/promo-banner/assets/build/index.css new file mode 100644 index 000000000..a7ae189a9 --- /dev/null +++ b/modules/promo-banner/assets/build/index.css @@ -0,0 +1 @@ +.jfb-promo-banner{margin:15px 0 5px;position:relative}.jfb-promo-banner.has-screen-links{margin:35px 0 5px}.jfb-promo-banner__dismiss{position:absolute;right:0;top:50%;margin-top:-18px;margin-right:10px;width:36px;height:36px;display:flex;justify-content:center;align-items:center}.jfb-promo-banner__dismiss svg{width:24px;height:24px} diff --git a/modules/promo-banner/assets/build/index.js b/modules/promo-banner/assets/build/index.js new file mode 100644 index 000000000..5717106fe --- /dev/null +++ b/modules/promo-banner/assets/build/index.js @@ -0,0 +1 @@ +(()=>{"use strict";var e;e=jQuery,window.jfbPromoBanner&&e(window).load((()=>{!function(n){const o=document.createElement("div"),r=document.createElement("a");o.classList.add("jfb-promo-banner"),window.jfbPromoBanner.classes&&o.classList.add(window.jfbPromoBanner.classes),r.classList.add("jfb-promo-banner__dismiss"),r.setAttribute("href","#"),r.setAttribute("aria-label","Dismiss JetFormBuilder Promo Banner"),r.setAttribute("role","button"),r.innerHTML='',o.innerHTML=n,o.appendChild(r);const t=document.getElementById("wpbody-content"),a=document.querySelector(".wrap");a?a.prepend(o):t.prepend(o),e(o).on("click",".jfb-promo-banner__dismiss",(n=>{n.preventDefault(),o.remove(),e.ajax({url:window.ajaxurl,type:"POST",dataType:"json",data:{action:window.jfbPromoBanner.action,hash:window.jfbPromoBanner.hash,nonce:window.jfbPromoBanner.nonce}})}))}(window.jfbPromoBanner.banner)}))})(); \ No newline at end of file diff --git a/modules/promo-banner/assets/src/index.js b/modules/promo-banner/assets/src/index.js new file mode 100644 index 000000000..e6d01b229 --- /dev/null +++ b/modules/promo-banner/assets/src/index.js @@ -0,0 +1,65 @@ +import './index.scss'; + +( function( $ ) { + + "use strict"; + + const JFBPromoCreateBanner = function( bannerHTML ) { + + const banner = document.createElement( 'div' ); + const bannerDismiss = document.createElement( 'a' ); + + banner.classList.add( 'jfb-promo-banner' ); + + if ( window.jfbPromoBanner.classes ) { + banner.classList.add( window.jfbPromoBanner.classes ); + } + + bannerDismiss.classList.add( 'jfb-promo-banner__dismiss' ); + + bannerDismiss.setAttribute( 'href', '#' ); + bannerDismiss.setAttribute( 'aria-label', 'Dismiss JetFormBuilder Promo Banner' ); + bannerDismiss.setAttribute( 'role', 'button' ); + bannerDismiss.innerHTML = ''; + + banner.innerHTML = bannerHTML; + banner.appendChild( bannerDismiss ); + + const bodyContent = document.getElementById( 'wpbody-content' ); + const wrap = document.querySelector( '.wrap' ); + + if ( wrap ) { + wrap.prepend( banner ); + } else { + bodyContent.prepend( banner ); + } + + $( banner ).on( 'click', '.jfb-promo-banner__dismiss', ( event ) => { + + event.preventDefault(); + banner.remove(); + + $.ajax({ + url: window.ajaxurl, + type: 'POST', + dataType: 'json', + data: { + action: window.jfbPromoBanner.action, + hash: window.jfbPromoBanner.hash, + nonce: window.jfbPromoBanner.nonce, + }, + }); + + }); + + } + + if ( window.jfbPromoBanner ) { + + $( window ).load( () => { + JFBPromoCreateBanner( window.jfbPromoBanner.banner ); + } ); + + } + +} ( jQuery ) ); diff --git a/modules/promo-banner/assets/src/index.scss b/modules/promo-banner/assets/src/index.scss new file mode 100644 index 000000000..8781440a8 --- /dev/null +++ b/modules/promo-banner/assets/src/index.scss @@ -0,0 +1,23 @@ +.jfb-promo-banner { + margin: 15px 0 5px; + position: relative; + &.has-screen-links { + margin: 35px 0 5px; + } + &__dismiss { + position: absolute; + right: 0; + top: 50%; + margin-top: -18px; + margin-right: 10px; + width: 36px; + height: 36px; + display: flex; + justify-content:center; + align-items:center; + svg { + width: 24px; + height: 24px; + } + } +} diff --git a/modules/promo-banner/module.php b/modules/promo-banner/module.php new file mode 100644 index 000000000..500d75f69 --- /dev/null +++ b/modules/promo-banner/module.php @@ -0,0 +1,187 @@ +get_handle(), array( $this, 'process_banner_dismiss' ) ); + + add_filter( 'jet-form-builder/admin/pages/go-pro-title', array( $this, 'add_promo_disounts' ) ); + add_filter( 'jet-form-builder/admin/go-pro-link-title', array( $this, 'add_promo_disounts' ) ); + } + + public function remove_hooks() { + + remove_action( 'admin_enqueue_scripts', array( $this, 'register_banner' ) ); + remove_action( 'wp_ajax_' . $this->get_handle(), array( $this, 'process_banner_dismiss' ) ); + + remove_filter( 'jet-form-builder/admin/pages/go-pro-title', array( $this, 'add_promo_disounts' ) ); + remove_filter( 'jet-form-builder/admin/go-pro-link-title', array( $this, 'add_promo_disounts' ) ); + } + + /** + * Get storage instance + * + * @return [type] [description] + */ + public function get_storage() { + + if ( null === $this->storage ) { + $this->storage = new Storage( $this->get_handle() ); + } + + return $this->storage; + } + + /** + * Add promo discount text + * + * @param string $text Default text. + */ + public function add_promo_disounts( $text ) { + + $promo_value = $this->get_storage()->get_promo_value(); + + if ( $promo_value ) { + $text .= ' - ' . $promo_value; + } + + return $text; + } + + /** + * Dismiss banner + * + * @return [type] [description] + */ + public function process_banner_dismiss() { + + if ( empty( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], $this->get_handle() ) ) { + wp_send_json_error( + esc_html__( 'The page is expired. Pleaser reload it and try again.', 'jet-form-builder' ) + ); + } + + $hash = ! empty( $_REQUEST['hash'] ) ? sanitize_text_field( $_REQUEST['hash'] ) : false; + + if ( ! $hash ) { + wp_send_json_error( esc_html__( 'There is no banner hash do dimiss.', 'jet-form-builder' ) ); + } + + $storage = $this->get_storage(); + + if ( ! $storage->is_banner_dismissed( $hash ) ) { + $storage->dismiss_banner( $hash ); + } + + wp_send_json_success(); + + } + + /** + * Check if is allowed page to show banner on + * + * @return boolean + */ + public function is_allowed_page() { + + if ( + ! empty( $_GET['post_type'] ) + && jet_form_builder()->post_type->slug() === $_GET['post_type'] + ) { + return true; + } + + return false; + } + + /** + * Register banner to show on the page + * + * @return bool + */ + public function register_banner() { + + if ( ! $this->is_allowed_page() ) { + return; + } + + $storage = $this->get_storage(); + $banner = $storage->get_banner_html(); + + if ( ! $banner ) { + return; + } + + $hash = $storage->get_banner_hash( $banner ); + + if ( $storage->is_banner_dismissed( $hash ) ) { + return; + } + + $script_asset = require_once $this->get_dir( 'assets/build/index.asset.php' ); + + if ( true === $script_asset ) { + return; + } + + wp_enqueue_style( + $this->get_handle(), + $this->get_url( 'assets/build/index.css' ), + array(), + $script_asset['version'] + ); + + wp_enqueue_script( + $this->get_handle(), + $this->get_url( 'assets/build/index.js' ), + $script_asset['dependencies'], + $script_asset['version'], + true + ); + + $addition_classes = ''; + + if ( get_current_screen()->get_help_tabs() || get_current_screen()->show_screen_options() ) { + $addition_classes = 'has-screen-links'; + } + + wp_localize_script( $this->get_handle(), 'jfbPromoBanner', array( + 'banner' => $banner, + 'hash' => $hash, + 'nonce' => wp_create_nonce( $this->get_handle() ), + 'action' => $this->get_handle(), + 'classes' => $addition_classes, + ) ); + + } +} diff --git a/modules/promo-banner/package.json b/modules/promo-banner/package.json new file mode 100644 index 000000000..87cecd75d --- /dev/null +++ b/modules/promo-banner/package.json @@ -0,0 +1,11 @@ +{ + "name": "jet-form-builder-promo-banner", + "description": "Promo banner handler for JetFormBuilder Plugin", + "version": "1.0.0", + "license": "MIT", + "private": true, + "scripts": { + "build": "npx webpack build --mode production", + "dev": "npx webpack build --mode development -w" + } +} diff --git a/modules/promo-banner/storage.php b/modules/promo-banner/storage.php new file mode 100644 index 000000000..8dc49c9dd --- /dev/null +++ b/modules/promo-banner/storage.php @@ -0,0 +1,130 @@ +storage_key = $storage_key; + } + + /** + * Get hash for banner HTML + * + * @param string $banner_html banner content + * @return string + */ + public function get_banner_hash( $banner_html ) { + return md5( $banner_html ); + } + + /** + * Check if given banner was dismissed before by it's hash + * + * @param string $banner_hash Hash string. + * @return boolean + */ + public function is_banner_dismissed( $banner_hash ) { + + // banners visible only for loggen in users + if ( ! is_user_logged_in() ) { + return true; + } + + $dismissed_banners = get_user_meta( get_current_user_id(), $this->get_storage_key(), false ); + + if ( ! empty( $dismissed_banners ) && in_array( $banner_hash, $dismissed_banners ) ) { + return true; + } + + return false; + } + + /** + * Add banner to the list of dismissed banners for current user + * + * @param [type] $banner_hash [description] + * @return [type] [description] + */ + public function dismiss_banner( $banner_hash ) { + add_user_meta( get_current_user_id(), $this->get_storage_key(), $banner_hash ); + } + + /** + * Transient cache key + * + * @return [type] [description] + */ + public function get_storage_key() { + return $this->storage_key; + } + + /** + * Returns promo discount value with '%' sign in the end + * @return [type] [description] + */ + public function get_promo_value() { + + $promo_data = $this->get_promo_data(); + return isset( $promo_data['promo'] ) ? $promo_data['promo'] : false; + } + + /** + * Get banner HTML from transient cache or remotely. + * + * @return string|int + */ + public function get_banner_html() { + + $promo_data = $this->get_promo_data(); + return isset( $promo_data['banner'] ) ? $promo_data['banner'] : false; + } + + /** + * Get all data for current promos + * + * @return array + */ + public function get_promo_data() { + + $data = get_transient( $this->get_storage_key() ); + + if ( false === $data ) { + $data = $this->get_remote_data(); + } + + set_transient( $this->get_storage_key(), $data, 6 * HOUR_IN_SECONDS ); + + return ! empty( $data ) && is_array( $data ) ? $data : []; + + } + + /** + * Get remote content of the banner from API + * If there is no active promo right now - will be returned -1 + * + * @return string|int + */ + public function get_remote_data() { + + $response = wp_remote_get( $this->api_url ); + $body = wp_remote_retrieve_body( $response ); + + if ( $body && ! is_wp_error( $body ) ) { + $body = json_decode( $body, true ); + } else { + $body = []; + } + + return is_array( $body ) ? $body : []; + + } + +} diff --git a/modules/promo-banner/webpack.config.js b/modules/promo-banner/webpack.config.js new file mode 100644 index 000000000..f540b5d1a --- /dev/null +++ b/modules/promo-banner/webpack.config.js @@ -0,0 +1,49 @@ +const WPExtractorPlugin = require( + '@wordpress/dependency-extraction-webpack-plugin', +); + +const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' ); + +const path = require( 'path' ); +const devMode = !process.argv.join( ':' ). + includes( '--mode:production' ); + +module.exports = { + context: path.resolve( __dirname, 'assets/src' ), + entry: { + 'index': './index.js', + }, + output: { + path: path.resolve( __dirname, 'assets/build' ), + }, + devtool: devMode ? 'inline-cheap-module-source-map' : false, + resolve: { + extensions: [ '.js', '.jsx', '.scss' ], + }, + module: { + rules: [ + { + test: /\.js(x)?$/, + use: [ + 'babel-loader', + '@wyw-in-js/webpack-loader', + ], + exclude: /node_modules/, + }, + { + test: /\.scss$/, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + 'sass-loader', + ], + }, + ], + }, + plugins: [ + new WPExtractorPlugin(), + new MiniCssExtractPlugin({ + filename: 'index.css', // Name of the extracted CSS file + }), + ], +};