Initial commit: Atomaste website

This commit is contained in:
2025-12-10 12:17:30 -05:00
commit 0b9e5d1605
19260 changed files with 5206382 additions and 0 deletions

View File

@@ -0,0 +1,391 @@
<?php
/**
* Astra Notices
*
* An easy to use PHP Library to add dismissible admin notices in the WordPress admin.
*
* @package Astra Notices
* @since 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'Astra_Notices' ) ) :
/**
* Astra_Notices
*
* @since 1.0.0
*/
class Astra_Notices {
/**
* Notices
*
* @access private
* @var array Notices.
* @since 1.0.0
*/
private static $version = '1.1.11';
/**
* Notices
*
* @access private
* @var array Notices.
* @since 1.0.0
*/
private static $notices = array();
/**
* Instance
*
* @access private
* @var object Class object.
* @since 1.0.0
*/
private static $instance;
/**
* Initiator
*
* @since 1.0.0
* @return object initialized object of class.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*
* @since 1.0.0
*/
public function __construct() {
add_action( 'admin_notices', array( $this, 'show_notices' ), 30 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_action( 'wp_ajax_astra-notice-dismiss', array( $this, 'dismiss_notice' ) );
add_filter( 'wp_kses_allowed_html', array( $this, 'add_data_attributes' ), 10, 2 );
}
/**
* Filters and Returns a list of allowed tags and attributes for a given context.
*
* @param array $allowedposttags array of allowed tags.
* @param string $context Context type (explicit).
* @since 1.0.0
* @return array
*/
public function add_data_attributes( $allowedposttags, $context ) {
$allowedposttags['a']['data-repeat-notice-after'] = true;
return $allowedposttags;
}
/**
* Add Notice.
*
* @since 1.0.0
* @param array $args Notice arguments.
* @return void
*/
public static function add_notice( $args = array() ) {
self::$notices[] = $args;
}
/**
* Dismiss Notice.
*
* @since 1.0.0
* @return void
*/
public function dismiss_notice() {
$notice_id = ( isset( $_POST['notice_id'] ) ) ? sanitize_key( $_POST['notice_id'] ) : '';
$repeat_notice_after = ( isset( $_POST['repeat_notice_after'] ) ) ? absint( $_POST['repeat_notice_after'] ) : '';
$nonce = ( isset( $_POST['nonce'] ) ) ? sanitize_key( $_POST['nonce'] ) : '';
$notice = $this->get_notice_by_id( $notice_id );
$capability = isset( $notice['capability'] ) ? $notice['capability'] : 'manage_options';
if ( ! apply_filters( 'astra_notices_user_cap_check', current_user_can( $capability ) ) ) {
return;
}
if ( false === wp_verify_nonce( $nonce, 'astra-notices' ) ) {
wp_send_json_error( esc_html_e( 'WordPress Nonce not validated.', 'astra' ) );
}
// Valid inputs?
if ( ! empty( $notice_id ) ) {
if ( ! empty( $repeat_notice_after ) ) {
set_transient( $notice_id, true, $repeat_notice_after );
} else {
update_user_meta( get_current_user_id(), $notice_id, 'notice-dismissed' );
}
wp_send_json_success();
}
wp_send_json_error();
}
/**
* Enqueue Scripts.
*
* @since 1.0.0
* @return void
*/
public function enqueue_scripts() {
wp_register_style( 'astra-notices', self::get_url() . 'notices.css', array(), self::$version );
wp_register_script( 'astra-notices', self::get_url() . 'notices.js', array( 'jquery' ), self::$version, true );
wp_localize_script(
'astra-notices',
'astraNotices',
array(
'_notice_nonce' => wp_create_nonce( 'astra-notices' ),
)
);
}
/**
* Sort the notices based on the given priority of the notice.
* This function is called from usort()
*
* @since 1.5.2
* @param array $notice_1 First notice.
* @param array $notice_2 Second Notice.
* @return array
*/
public function sort_notices( $notice_1, $notice_2 ) {
if ( ! isset( $notice_1['priority'] ) ) {
$notice_1['priority'] = 10;
}
if ( ! isset( $notice_2['priority'] ) ) {
$notice_2['priority'] = 10;
}
return $notice_1['priority'] - $notice_2['priority'];
}
/**
* Get all registered notices.
* Since v1.1.8 it is recommended to register the notices on
*
* @return array|null
*/
private function get_notices() {
usort( self::$notices, array( $this, 'sort_notices' ) );
return self::$notices;
}
/**
* Get notice by notice_id
*
* @param string $notice_id Notice id.
*
* @return array notice based on the notice id.
*/
private function get_notice_by_id( $notice_id ) {
if ( empty( $notice_id ) ) {
return array();
}
$notices = $this->get_notices();
$notice = wp_list_filter(
$notices,
array(
'id' => $notice_id,
)
);
return ! empty( $notice ) ? $notice[0] : array();
}
/**
* Display the notices in the WordPress admin.
*
* @since 1.0.0
* @return void
*/
public function show_notices() {
$defaults = array(
'id' => '', // Optional, Notice ID. If empty it set `astra-notices-id-<$array-index>`.
'type' => 'info', // Optional, Notice type. Default `info`. Expected [info, warning, notice, error].
'message' => '', // Optional, Message.
'show_if' => true, // Optional, Show notice on custom condition. E.g. 'show_if' => if( is_admin() ) ? true, false, .
'repeat-notice-after' => '', // Optional, Dismiss-able notice time. It'll auto show after given time.
'display-notice-after' => false, // Optional, Dismiss-able notice time. It'll auto show after given time.
'class' => '', // Optional, Additional notice wrapper class.
'priority' => 10, // Priority of the notice.
'display-with-other-notices' => true, // Should the notice be displayed if other notices are being displayed from Astra_Notices.
'is_dismissible' => true,
'capability' => 'manage_options', // User capability - This capability is required for the current user to see this notice.
);
// Count for the notices that are rendered.
$notices_displayed = 0;
$notices = $this->get_notices();
foreach ( $notices as $key => $notice ) {
$notice = wp_parse_args( $notice, $defaults );
// Show notices only for users with `manage_options` cap.
if ( ! current_user_can( $notice['capability'] ) ) {
continue;
}
$notice['id'] = self::get_notice_id( $notice, $key );
$notice['classes'] = self::get_wrap_classes( $notice );
// Notices visible after transient expire.
if ( isset( $notice['show_if'] ) && true === $notice['show_if'] ) {
// don't display the notice if it is not supposed to be displayed with other notices.
if ( 0 !== $notices_displayed && false === $notice['display-with-other-notices'] ) {
continue;
}
if ( self::is_expired( $notice ) ) {
self::markup( $notice );
++$notices_displayed;
}
}
}
}
/**
* Render a notice.
*
* @since 1.0.0
* @param array $notice Notice markup.
* @return void
*/
public static function markup( $notice = array() ) {
wp_enqueue_script( 'astra-notices' );
wp_enqueue_style( 'astra-notices' );
do_action( 'astra_notice_before_markup' );
do_action( "astra_notice_before_markup_{$notice['id']}" );
?>
<div id="<?php echo esc_attr( $notice['id'] ); ?>" class="<?php echo 'astra-notice-wrapper ' . esc_attr( $notice['classes'] ); ?>" data-repeat-notice-after="<?php echo esc_attr( $notice['repeat-notice-after'] ); ?>">
<div class="astra-notice-container">
<?php do_action( "astra_notice_inside_markup_{$notice['id']}" ); ?>
<?php echo wp_kses_post( $notice['message'] ); ?>
</div>
</div>
<?php
do_action( "astra_notice_after_markup_{$notice['id']}" );
do_action( 'astra_notice_after_markup' );
}
/**
* Get wrapper classes for a notice.
*
* @since 1.0.0
*
* @param array $notice Notice arguments.
* @return array Notice wrapper classes.
*/
private static function get_wrap_classes( $notice ) {
$classes = array( 'astra-notice', 'notice' );
if ( $notice['is_dismissible'] ) {
$classes[] = 'is-dismissible';
}
$classes[] = $notice['class'];
if ( isset( $notice['type'] ) && '' !== $notice['type'] ) {
$classes[] = 'notice-' . $notice['type'];
}
return esc_attr( implode( ' ', $classes ) );
}
/**
* Get HTML ID for a given notice.
*
* @since 1.0.0
*
* @param array $notice Notice arguments.
* @param int $key Notice array index.
* @return string HTML if for the notice.
*/
private static function get_notice_id( $notice, $key ) {
if ( isset( $notice['id'] ) && ! empty( $notice['id'] ) ) {
return $notice['id'];
}
return 'astra-notices-id-' . $key;
}
/**
* Check if the notice is expires.
*
* @since 1.0.0
*
* @param array $notice Notice arguments.
* @return boolean
*/
private static function is_expired( $notice ) {
$transient_status = get_transient( $notice['id'] );
if ( false === $transient_status ) {
if ( isset( $notice['display-notice-after'] ) && false !== $notice['display-notice-after'] ) {
if ( 'delayed-notice' !== get_user_meta( get_current_user_id(), $notice['id'], true ) &&
'notice-dismissed' !== get_user_meta( get_current_user_id(), $notice['id'], true ) ) {
set_transient( $notice['id'], 'delayed-notice', $notice['display-notice-after'] );
update_user_meta( get_current_user_id(), $notice['id'], 'delayed-notice' );
return false;
}
}
// Check the user meta status if current notice is dismissed or delay completed.
$meta_status = get_user_meta( get_current_user_id(), $notice['id'], true );
if ( empty( $meta_status ) || 'delayed-notice' === $meta_status ) {
return true;
}
}
return false;
}
/**
* Get base URL for the astra-notices.
*
* @return mixed URL.
*/
public static function get_url() {
$path = wp_normalize_path( dirname( __FILE__ ) );
$theme_dir = wp_normalize_path( get_template_directory() );
if ( strpos( $path, $theme_dir ) !== false ) {
return trailingslashit( get_template_directory_uri() . str_replace( $theme_dir, '', $path ) );
} else {
return plugin_dir_url( __FILE__ );
}
}
}
/**
* Kicking this off by calling 'get_instance()' method
*/
Astra_Notices::get_instance();
endif;

View File

@@ -0,0 +1,39 @@
.astra-review-notice-container {
display: flex;
align-items: center;
padding-top: 10px;
}
.astra-review-notice-container .dashicons {
font-size: 1.4em;
padding-left: 10px;
}
.astra-review-notice-container a {
padding-left: 5px;
text-decoration: none;
}
.astra-review-notice-container .dashicons:first-child {
padding-left: 0;
}
.astra-notice-container .notice-image img {
max-width: 90px;
}
.astra-notice-container .notice-content .notice-heading {
padding-bottom: 5px;
}
.astra-notice-container .notice-content {
margin-left: 15px;
}
.astra-notice-container {
padding-top: 10px;
padding-bottom: 10px;
display: flex;
justify-content: left;
align-items: center;
}

View File

@@ -0,0 +1,95 @@
/**
* Customizer controls toggles
*
* @package Astra
*/
( function( $ ) {
/**
* Helper class for the main Customizer interface.
*
* @since 1.0.0
* @class ASTCustomizer
*/
AstraNotices = {
/**
* Initializes our custom logic for the Customizer.
*
* @since 1.0.0
* @method init
*/
init: function()
{
this._bind();
},
/**
* Binds events for the Astra Portfolio.
*
* @since 1.0.0
* @access private
* @method _bind
*/
_bind: function()
{
$( document ).on('click', '.astra-notice-close', AstraNotices._dismissNoticeNew );
$( document ).on('click', '.astra-notice .notice-dismiss', AstraNotices._dismissNotice );
},
_dismissNotice: function( event ) {
event.preventDefault();
var repeat_notice_after = $( this ).parents('.astra-notice').data( 'repeat-notice-after' ) || '';
var notice_id = $( this ).parents('.astra-notice').attr( 'id' ) || '';
AstraNotices._ajax( notice_id, repeat_notice_after );
},
_dismissNoticeNew: function( event ) {
event.preventDefault();
var repeat_notice_after = $( this ).attr( 'data-repeat-notice-after' ) || '';
var notice_id = $( this ).parents('.astra-notice').attr( 'id' ) || '';
var $el = $( this ).parents('.astra-notice');
$el.fadeTo( 100, 0, function() {
$el.slideUp( 100, function() {
$el.remove();
});
});
AstraNotices._ajax( notice_id, repeat_notice_after );
var link = $( this ).attr( 'href' ) || '';
var target = $( this ).attr( 'target' ) || '';
if( '' !== link && '_blank' === target ) {
window.open(link , '_blank');
}
},
_ajax: function( notice_id, repeat_notice_after ) {
if( '' === notice_id ) {
return;
}
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action : 'astra-notice-dismiss',
nonce : astraNotices._notice_nonce,
notice_id : notice_id,
repeat_notice_after : parseInt( repeat_notice_after ),
},
});
}
};
$( function() {
AstraNotices.init();
} );
} )( jQuery );

View File

@@ -0,0 +1,159 @@
<?php
/**
* WP Async Request
*
* @package WP-Background-Processing
*/
if ( ! class_exists( 'Astra_WP_Async_Request' ) ) {
/**
* Abstract Astra_WP_Async_Request class.
*
* @abstract
*/
abstract class Astra_WP_Async_Request {
/**
* Prefix
*
* (default value: 'wp')
*
* @var string
*/
protected $prefix = 'wp';
/**
* Action
*
* (default value: 'async_request')
*
* @var string
*/
protected $action = 'async_request';
/**
* Identifier
*
* @var mixed
*/
protected $identifier;
/**
* Data
*
* (default value: array())
*
* @var array
*/
protected $data = array();
/**
* Initiate new async request
*/
public function __construct() {
$this->identifier = $this->prefix . '_' . $this->action;
add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
}
/**
* Set data used during the request
*
* @param array $data Data.
*
* @return $this
*/
public function data( $data ) {
$this->data = $data;
return $this;
}
/**
* Dispatch the async request
*
* @return array|WP_Error
*/
public function dispatch() {
$url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
$args = $this->get_post_args();
return wp_remote_post( esc_url_raw( $url ), $args );
}
/**
* Get query args
*
* @return array
*/
protected function get_query_args() {
if ( property_exists( $this, 'query_args' ) ) {
return $this->query_args;
}
return array(
'action' => $this->identifier,
'nonce' => wp_create_nonce( $this->identifier ),
);
}
/**
* Get query URL
*
* @return string
*/
protected function get_query_url() {
if ( property_exists( $this, 'query_url' ) ) {
return $this->query_url;
}
return admin_url( 'admin-ajax.php' );
}
/**
* Get post args
*
* @return array
*/
protected function get_post_args() {
if ( property_exists( $this, 'post_args' ) ) {
return $this->post_args;
}
return array(
'timeout' => 0.01,
'blocking' => false,
'body' => $this->data,
'cookies' => $_COOKIE,
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
);
}
/**
* Maybe handle
*
* Check for correct nonce and pass to handler.
*/
public function maybe_handle() {
// Don't lock up other requests while processing
session_write_close();
check_ajax_referer( $this->identifier, 'nonce' );
$this->handle();
wp_die();
}
/**
* Handle
*
* Override this method to perform any actions required
* during the async request.
*/
abstract protected function handle();
}
}

View File

@@ -0,0 +1,491 @@
<?php
/**
* WP Background Process
*
* @package WP-Background-Processing
*/
if ( ! class_exists( 'Astra_WP_Background_Process' ) ) {
/**
* Abstract Astra_WP_Background_Process class.
*
* @abstract
* @extends Astra_WP_Async_Request
*/
abstract class Astra_WP_Background_Process extends Astra_WP_Async_Request {
/**
* Action
*
* (default value: 'background_process')
*
* @var string
*/
protected $action = 'background_process';
/**
* Start time of current process.
*
* (default value: 0)
*
* @var int
*/
protected $start_time = 0;
/**
* Cron_hook_identifier
*
* @var mixed
*/
protected $cron_hook_identifier;
/**
* Cron_interval_identifier
*
* @var mixed
*/
protected $cron_interval_identifier;
/**
* Initiate new background process
*/
public function __construct() {
parent::__construct();
$this->cron_hook_identifier = $this->identifier . '_cron';
$this->cron_interval_identifier = $this->identifier . '_cron_interval';
add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
}
/**
* Dispatch
*
* @return void
*/
public function dispatch() {
// Schedule the cron healthcheck.
$this->schedule_event();
// Perform remote post.
return parent::dispatch();
}
/**
* Push to queue
*
* @param mixed $data Data.
*
* @return $this
*/
public function push_to_queue( $data ) {
$this->data[] = $data;
return $this;
}
/**
* Save queue
*
* @return $this
*/
public function save() {
$key = $this->generate_key();
if ( ! empty( $this->data ) ) {
update_site_option( $key, $this->data );
}
return $this;
}
/**
* Update queue
*
* @param string $key Key.
* @param array $data Data.
*
* @return $this
*/
public function update( $key, $data ) {
if ( ! empty( $data ) ) {
update_site_option( $key, $data );
}
return $this;
}
/**
* Delete queue
*
* @param string $key Key.
*
* @return $this
*/
public function delete( $key ) {
delete_site_option( $key );
return $this;
}
/**
* Generate key
*
* Generates a unique key based on microtime. Queue items are
* given a unique key so that they can be merged upon save.
*
* @param int $length Length.
*
* @return string
*/
protected function generate_key( $length = 64 ) {
// file deepcode ignore InsecureHash: This is the external library.
$unique = md5( microtime() . rand() );
$prepend = $this->identifier . '_batch_';
return substr( $prepend . $unique, 0, $length );
}
/**
* Maybe process queue
*
* Checks whether data exists within the queue and that
* the process is not already running.
*/
public function maybe_handle() {
// Don't lock up other requests while processing
session_write_close();
if ( $this->is_process_running() ) {
// Background process already running.
wp_die();
}
if ( $this->is_queue_empty() ) {
// No data to process.
wp_die();
}
check_ajax_referer( $this->identifier, 'nonce' );
$this->handle();
wp_die();
}
/**
* Is queue empty
*
* @return bool
*/
protected function is_queue_empty() {
global $wpdb;
$wpdb->ast_db_table = $wpdb->options;
$wpdb->ast_db_column = 'option_name';
if ( is_multisite() ) {
$wpdb->ast_db_table = $wpdb->sitemeta;
$wpdb->ast_db_column = 'meta_key';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->ast_db_table} WHERE {$wpdb->ast_db_column} LIKE %s ", $key ) );
return ( $count > 0 ) ? false : true;
}
/**
* Is process running
*
* Check whether the current process is already running
* in a background process.
*/
protected function is_process_running() {
if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
// Process already running.
return true;
}
return false;
}
/**
* Lock process
*
* Lock the process so that multiple instances can't run simultaneously.
* Override if applicable, but the duration should be greater than that
* defined in the time_exceeded() method.
*/
protected function lock_process() {
$this->start_time = time(); // Set start time of current process.
$lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
$lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
}
/**
* Unlock process
*
* Unlock the process so that other instances can spawn.
*
* @return $this
*/
protected function unlock_process() {
delete_site_transient( $this->identifier . '_process_lock' );
return $this;
}
/**
* Get batch
*
* @return stdClass Return the first batch from the queue
*/
protected function get_batch() {
global $wpdb;
$wpdb->ast_db_table = $wpdb->options;
$wpdb->ast_db_column = 'option_name';
$wpdb->ast_db_key_column = 'option_id';
$value_column = 'option_value';
if ( is_multisite() ) {
$wpdb->ast_db_table = $wpdb->sitemeta;
$wpdb->ast_db_column = 'meta_key';
$wpdb->ast_db_key_column = 'meta_id';
$value_column = 'meta_value';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$query = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->ast_db_table} WHERE {$wpdb->ast_db_column} LIKE %s ORDER BY {$wpdb->ast_db_key_column} ASC LIMIT 1", $key ) );
$batch = new stdClass();
$batch->key = $query->{$wpdb->ast_db_column};
$batch->data = maybe_unserialize( $query->$value_column );
return $batch;
}
/**
* Handle
*
* Pass each queue item to the task handler, while remaining
* within server memory and time limit constraints.
*/
protected function handle() {
$this->lock_process();
do {
$batch = $this->get_batch();
foreach ( $batch->data as $key => $value ) {
$task = $this->task( $value );
if ( false !== $task ) {
$batch->data[ $key ] = $task;
} else {
unset( $batch->data[ $key ] );
}
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
// Batch limits reached.
break;
}
}
// Update or delete current batch.
if ( ! empty( $batch->data ) ) {
$this->update( $batch->key, $batch->data );
} else {
$this->delete( $batch->key );
}
} while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
$this->unlock_process();
// Start next batch or complete process.
if ( ! $this->is_queue_empty() ) {
$this->dispatch();
} else {
$this->complete();
}
wp_die();
}
/**
* Memory exceeded
*
* Ensures the batch process never exceeds 90%
* of the maximum WordPress memory.
*
* @return bool
*/
protected function memory_exceeded() {
$memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
$current_memory = memory_get_usage( true );
$return = false;
if ( $current_memory >= $memory_limit ) {
$return = true;
}
return apply_filters( $this->identifier . '_memory_exceeded', $return );
}
/**
* Get memory limit
*
* @return int
*/
protected function get_memory_limit() {
if ( function_exists( 'ini_get' ) ) {
$memory_limit = ini_get( 'memory_limit' );
} else {
// Sensible default.
$memory_limit = '128M';
}
if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
// Unlimited, set to 32GB.
$memory_limit = '32000M';
}
return intval( $memory_limit ) * 1024 * 1024;
}
/**
* Time exceeded.
*
* Ensures the batch never exceeds a sensible time limit.
* A timeout limit of 30s is common on shared hosting.
*
* @return bool
*/
protected function time_exceeded() {
$finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
$return = false;
if ( time() >= $finish ) {
$return = true;
}
return apply_filters( $this->identifier . '_time_exceeded', $return );
}
/**
* Complete.
*
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
protected function complete() {
// Unschedule the cron healthcheck.
$this->clear_scheduled_event();
}
/**
* Schedule cron health check
*
* @param mixed $schedules Schedules.
* @return mixed
*/
public function schedule_cron_healthcheck( $schedules ) {
$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
if ( property_exists( $this, 'cron_interval' ) ) {
$interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
}
// Adds every 5 minutes to the existing schedules.
$schedules[ $this->identifier . '_cron_interval' ] = array(
'interval' => MINUTE_IN_SECONDS * $interval,
'display' => sprintf( /* translators: %d: Minutes interval */ __( 'Every %d Minutes', 'astra' ), $interval ),
);
return $schedules;
}
/**
* Handle cron healthcheck
*
* Restart the background process if not already running
* and data exists in the queue.
*/
public function handle_cron_healthcheck() {
if ( $this->is_process_running() ) {
// Background process already running.
exit;
}
if ( $this->is_queue_empty() ) {
// No data to process.
$this->clear_scheduled_event();
exit;
}
$this->handle();
exit;
}
/**
* Schedule event
*/
protected function schedule_event() {
if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
}
}
/**
* Clear scheduled event
*/
protected function clear_scheduled_event() {
$timestamp = wp_next_scheduled( $this->cron_hook_identifier );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
}
}
/**
* Cancel Process
*
* Stop processing queue items, clear cronjob and delete batch.
*
*/
public function cancel_process() {
if ( ! $this->is_queue_empty() ) {
$batch = $this->get_batch();
$this->delete( $batch->key );
wp_clear_scheduled_hook( $this->cron_hook_identifier );
}
}
/**
* Task
*
* Override this method to perform any actions required on each
* queue item. Return the modified item for further processing
* in the next pass through. Or, return false to remove the
* item from the queue.
*
* @param mixed $item Queue item to iterate over.
*
* @return mixed
*/
abstract protected function task( $item );
}
}

View File

@@ -0,0 +1,160 @@
<?php
/**
* Init
*
* @since 1.0.0
* @package NPS Survey
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'Astra_Nps_Notice' ) ) {
/**
* Admin
*/
class Astra_Nps_Notice {
/**
* Instance
*
* @since 1.0.0
* @var (Object) Astra_Nps_Notice
*/
private static $instance = null;
/**
* Constructor.
*
* @since 1.0.0
*/
private function __construct() {
// Allow users to disable NPS survey via a filter.
if ( apply_filters( 'astra_nps_survey_disable', false ) ) {
return;
}
// Added filter to allow overriding the URL externally.
add_filter( 'nps_survey_build_url', static function( $url ) {
return get_template_directory_uri() . '/inc/lib/nps-survey/dist/';
} );
// Bail early if soft while labeling is enabled.
if (
defined( 'ASTRA_EXT_VER' ) &&
is_callable( 'Astra_Ext_White_Label_Markup::get_whitelabel_string' ) &&
'astra' !== strtolower( Astra_Ext_White_Label_Markup::get_whitelabel_string( 'astra', 'name', 'astra' ) )
) {
return;
}
// Return if white labelled is enabled.
if ( astra_is_white_labelled() ) {
return;
}
add_action( 'admin_footer', array( $this, 'render_astra_nps_survey' ), 999 );
add_filter( 'nps_survey_allowed_screens', static function( $screens ) {
// Restrict other NPS popups on Astra specific pages.
if ( ! self::is_nps_showing() ) {
return $screens;
}
// Add new screen IDs to the array.
$screens[] = 'toplevel_page_astra';
$screens[] = 'astra_page_theme-builder-free';
$screens[] = 'astra_page_theme-builder';
return $screens;
});
add_action( 'admin_enqueue_scripts', array( $this, 'register_assets' ) );
}
/**
* Get Instance
*
* @since 1.0.0
*
* @return object Class object.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Check if NPS is showing.
*
* @since 4.8.7
* @return bool
*/
public static function is_nps_showing() {
$astra_nps_options = get_option( Nps_Survey::get_nps_id( 'astra' ), array() );
$display_after = isset( $astra_nps_options['display_after'] ) && is_int( $astra_nps_options['display_after'] ) ? $astra_nps_options['display_after'] : 0;
return Nps_Survey::is_show_nps_survey_form( 'astra', $display_after );
}
/**
* Register admin scripts for NPS visibility condition.
*
* @param String $hook Screen name where the hook is fired.
* @since 4.8.7
* @return void
*/
public static function register_assets( ): void {
if ( self::is_nps_showing() ) {
// Intentionally hiding the other NPS popups when visible along with the Astra NPS.
$css_file = is_rtl() ? 'nps-visibility-rtl.css' : 'nps-visibility.css';
wp_enqueue_style( 'astra-nps-visibility', ASTRA_THEME_URI . 'inc/assets/css/' . $css_file, array(), ASTRA_THEME_VERSION );
}
}
/**
* Render NPS Survey
*
* @return void
*/
public function render_astra_nps_survey(): void {
Nps_Survey::show_nps_notice(
'nps-survey-astra',
array(
'show_if' => defined( 'ASTRA_THEME_VERSION' ),
'dismiss_timespan' => 2 * WEEK_IN_SECONDS,
'display_after' => get_option('astra_nps_show') ? 0 : 2 * WEEK_IN_SECONDS,
'plugin_slug' => 'astra',
'message' => array(
// Step 1 i.e rating input.
'logo' => esc_url( ASTRA_THEME_URI . 'inc/assets/images/astra-logo.svg'),
'plugin_name' => __( 'Astra', 'astra' ),
'nps_rating_message' => __( 'How likely are you to recommend #pluginname to your friends or colleagues?', 'astra' ),
// Step 2A i.e. positive.
'feedback_title' => __( 'Thanks a lot for your feedback! 😍', 'astra' ),
'feedback_content' => __( 'Could you please do us a favor and give us a 5-star rating on WordPress? It would help others choose Astra with confidence. Thank you!', 'astra' ),
'plugin_rating_link' => esc_url( 'https://wordpress.org/support/theme/astra/reviews/#new-post' ),
'plugin_rating_button_string' => __( 'Rate the Theme', 'astra' ),
// Step 2B i.e. negative.
'plugin_rating_title' => __( 'Thank you for your feedback', 'astra' ),
'plugin_rating_content' => __( 'We value your input. How can we improve your experience?', 'astra' ),
),
)
);
}
}
/**
* Kicking this off by calling 'get_instance()' method
*/
Astra_Nps_Notice::get_instance();
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* Init
*
* @since 1.0.0
* @package NPS Survey
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'Astra_Nps_Survey' ) ) :
/**
* Admin
*/
class Astra_Nps_Survey {
/**
* Instance
*
* @since 1.0.0
* @var (Object) Astra_Nps_Survey
*/
private static $instance = null;
/**
* Get Instance
*
* @since 1.0.0
*
* @return object Class object.
*/
public static function get_instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*
* @since 1.0.0
*/
private function __construct() {
// Allow users to disable NPS survey via a filter.
if ( apply_filters( 'astra_nps_survey_disable', false ) ) {
return;
}
$this->version_check();
add_action( 'init', array( $this, 'load' ), 999 );
}
/**
* Version Check
*
* @return void
*/
public function version_check() {
$file = realpath( dirname( __FILE__ ) . '/nps-survey/version.json' );
// Is file exist?
if ( is_file( $file ) ) {
// @codingStandardsIgnoreStart
$file_data = json_decode( file_get_contents( $file ), true );
// @codingStandardsIgnoreEnd
global $nps_survey_version, $nps_survey_init;
$path = realpath( dirname( __FILE__ ) . '/nps-survey/nps-survey.php' );
$version = isset( $file_data['nps-survey'] ) ? $file_data['nps-survey'] : 0;
if ( null === $nps_survey_version ) {
$nps_survey_version = '1.0.0';
}
// Compare versions.
if ( version_compare( $version, $nps_survey_version, '>=' ) ) {
$nps_survey_version = $version;
$nps_survey_init = $path;
}
}
}
/**
* Load latest plugin
*
* @return void
*/
public function load() {
global $nps_survey_version, $nps_survey_init;
if ( is_file( realpath( $nps_survey_init ) ) ) {
include_once realpath( $nps_survey_init );
}
}
}
/**
* Kicking this off by calling 'get_instance()' method
*/
Astra_Nps_Survey::get_instance();
endif;

View File

@@ -0,0 +1,402 @@
<?php
/**
* Download Docs locally.
*
* @package Astra
* @since 4.6.0
*/
/**
* Process Docs from locally.
*/
class Astra_Docs_Loader {
/**
* The remote URL.
*
* @since 4.6.0
* @var string
*/
protected $remote_url;
/**
* Base path.
*
* @since 4.6.0
* @var string
*/
protected $base_path;
/**
* Base URL.
*
* @since 4.6.0
* @var string
*/
protected $base_url;
/**
* Subfolder name.
*
* @since 4.6.0
* @var string
*/
protected $subfolder_name;
/**
* The docs folder.
*
* @since 4.6.0
* @var string
*/
protected $docs_folder;
/**
* The local stylesheet's path.
*
* @since 4.6.0
* @var string
*/
protected $local_stylesheet_path;
/**
* The local stylesheet's URL.
*
* @since 4.6.0
* @var string
*/
protected $local_docs_json_url;
/**
* The remote CSS.
*
* @since 4.6.0
* @var string
*/
protected $remote_styles;
/**
* The final docs data.
*
* @since 4.6.0
* @var string
*/
protected $docs_data;
/**
* Cleanup routine frequency.
*/
const CLEANUP_FREQUENCY = 'weekly';
/**
* Constructor.
*
* Get a new instance of the object for a new URL.
*
* @since 4.6.0
* @param string $url The remote URL.
* @param string $subfolder_name The subfolder name.
*/
public function __construct( $url = '', $subfolder_name = 'bsf-docs' ) {
$this->remote_url = $url;
$this->subfolder_name = $subfolder_name;
// Add a cleanup routine.
$this->schedule_cleanup();
add_action( 'astra_delete_docs_folder', array( $this, 'astra_delete_docs_folder' ) );
}
/**
* Get the local URL which contains the styles.
*
* Fallback to the remote URL if we were unable to write the file locally.
*
* @since 4.6.0
* @return string
*/
public function get_url() {
// Check if the local stylesheet exists.
if ( $this->local_file_exists() ) {
// Attempt to update the stylesheet. Return the local URL on success.
if ( $this->write_json() ) {
return $this->get_local_docs_json_url();
}
}
$astra_docs_url = file_exists( $this->get_local_docs_file_path() ) ? $this->get_local_docs_json_url() : $this->remote_url;
return $astra_docs_url;
}
/**
* Get the local stylesheet URL.
*
* @since 4.6.0
* @return string
*/
public function get_local_docs_json_url() {
if ( ! $this->local_docs_json_url ) {
$this->local_docs_json_url = str_replace(
$this->get_base_path(),
$this->get_base_url(),
$this->get_local_docs_file_path()
);
}
return $this->local_docs_json_url;
}
/**
* Get remote data locally.
*
* @since 4.6.0
* @return string
*/
public function get_remote_data() {
// If we already have the local file, return its contents.
$local_docs_contents = $this->get_local_docs_contents();
if ( $local_docs_contents ) {
return $local_docs_contents;
}
// Get the remote URL contents.
$this->remote_styles = $this->get_remote_url_contents();
$this->docs_data = $this->remote_styles;
$this->write_json();
return $this->docs_data;
}
/**
* Get local stylesheet contents.
*
* @since 4.6.0
* @return string|false Returns the remote URL contents.
*/
public function get_local_docs_contents() {
$local_path = $this->get_local_docs_file_path();
// Check if the local file exists.
if ( $this->local_file_exists() ) {
// Attempt to update the file. Return false on fail.
if ( ! $this->write_json() ) {
return false;
}
}
ob_start();
include $local_path;
return ob_get_clean();
}
/**
* Get remote file contents.
*
* @since 4.6.0
* @return string Returns the remote URL contents.
*/
public function get_remote_url_contents() {
/**
* The user-agent we want to use.
*
* The default user-agent is the only one compatible with woff (not woff2)
* which also supports unicode ranges.
*/
$user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8';
// Get the response.
$response = wp_remote_get( $this->remote_url, array( 'user-agent' => $user_agent ) );
// Early exit if there was an error.
if ( is_wp_error( $response ) ) {
return '';
}
// Get the CSS from our response.
$contents = wp_remote_retrieve_body( $response );
return $contents;
}
/**
* Write the CSS to the filesystem.
*
* @since 4.6.0
* @return string|false Returns the absolute path of the file on success, or false on fail.
*/
protected function write_json() {
$file_path = $this->get_local_docs_file_path();
$filesystem = $this->get_filesystem();
if ( ! defined( 'FS_CHMOD_DIR' ) ) {
define( 'FS_CHMOD_DIR', ( 0755 & ~ umask() ) );
}
// If the folder doesn't exist, create it.
if ( ! file_exists( $this->get_docs_folder() ) ) {
$this->get_filesystem()->mkdir( $this->get_docs_folder(), FS_CHMOD_DIR );
}
// If the file doesn't exist, create it. Return false if it can not be created.
if ( ! $filesystem->exists( $file_path ) && ! $filesystem->touch( $file_path ) ) {
return false;
}
// If we got this far, we need to write the file.
// Get the CSS.
if ( ! $this->docs_data ) {
$this->get_remote_data();
}
// Put the contents in the file. Return false if that fails.
if ( ! $filesystem->put_contents( $file_path, $this->docs_data ) ) {
return false;
}
return $file_path;
}
/**
* Get the stylesheet path.
*
* @since 4.6.0
* @return string
*/
public function get_local_docs_file_path() {
if ( ! $this->local_stylesheet_path ) {
$this->local_stylesheet_path = $this->get_docs_folder() . '/' . $this->get_local_docs_filename() . '.json';
}
return $this->local_stylesheet_path;
}
/**
* Get the local stylesheet filename.
*
* This is a hash, generated from the site-URL, the wp-content path and the URL.
* This way we can avoid issues with sites changing their URL, or the wp-content path etc.
*
* @since 4.6.0
* @return string
*/
public function get_local_docs_filename() {
return apply_filters( 'astra_local_docs_file_name', 'docs' );
}
/**
* Check if the local stylesheet exists.
*
* @since 4.6.0
* @return bool
*/
public function local_file_exists() {
return ( ! file_exists( $this->get_local_docs_file_path() ) );
}
/**
* Get the base path.
*
* @since 4.6.0
* @return string
*/
public function get_base_path() {
if ( ! $this->base_path ) {
$this->base_path = apply_filters( 'astra_local_docs_base_path', $this->get_filesystem()->wp_content_dir() . 'uploads' );
}
return $this->base_path;
}
/**
* Get the base URL.
*
* @since 4.6.0
* @return string
*/
public function get_base_url() {
if ( ! $this->base_url ) {
$this->base_url = apply_filters( 'astra_local_docs_base_url', content_url() . '/uploads' );
}
return $this->base_url;
}
/**
* Get the folder for docs.
*
* @return string
*/
public function get_docs_folder() {
if ( ! $this->docs_folder ) {
$this->docs_folder = $this->get_base_path();
$this->docs_folder .= '/' . $this->subfolder_name;
}
return $this->docs_folder;
}
/**
* Schedule a cleanup.
*
* Deletes the docs file on a regular basis.
* This way docs file will get updated regularly,
* and we avoid edge cases where unused files remain in the server.
*
* @since 4.6.0
* @return void
*/
public function schedule_cleanup() {
if ( ! wp_next_scheduled( 'astra_delete_docs_folder' ) && ! wp_installing() ) {
wp_schedule_event( time(), self::CLEANUP_FREQUENCY, 'astra_delete_docs_folder' ); // phpcs:ignore WPThemeReview.PluginTerritory.ForbiddenFunctions.cron_functionality_wp_schedule_event
}
}
/**
* Delete the documentation folder.
*
* This runs as part of a cleanup routine.
*
* @since 4.6.0
* @return bool
*/
public function astra_delete_docs_folder() {
// Delete previously created supportive options.
return $this->get_filesystem()->delete( $this->get_docs_folder(), true );
}
/**
* Get the filesystem.
*
* @since 4.6.0
* @return \WP_Filesystem_Base
*/
protected function get_filesystem() {
// We are using WP_Filesystem for managing local doc files which is necessary for the proper functionality of the theme -- This is an extension version of TRT webfont library.
global $wp_filesystem;
// If the filesystem has not been instantiated yet, do it here.
if ( ! $wp_filesystem ) {
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' ); // PHPCS:ignore WPThemeReview.CoreFunctionality.FileInclude.FileIncludeFound
}
WP_Filesystem();
}
return $wp_filesystem;
}
}
/**
* Create instance of Astra_Docs_Loader class.
*
* @param string $docs_rest_url Knowledge Base URL to set data.
* @param string $subfolder_name Subfolder name.
*
* @return object
* @since 4.6.0
*/
function astra_docs_loader_instance( $docs_rest_url = '', $subfolder_name = 'bsf-docs' ) {
return new Astra_Docs_Loader( $docs_rest_url, $subfolder_name );
}

View File

@@ -0,0 +1,18 @@
Version 1.0.4 - 13-12-2024
- Improvement: Optimized file loading to prevent duplicate loads, enhancing performance.
Version 1.0.3 - 10-12-2024
- Fix: Fixed library update issue.
Version 1.0.2 - 09-12-2024
- Improvement: NPS popup will now be permanently dismissed when closed for the second time.
- Improvement: Added an option to customize the rate button text for plugins/themes.
- Fix: Resolved CSS conflicts with other plugins.
Version 1.0.1 - 20-11-2024
- New: Added filter to 'nps_survey_allowed_screens' to allow custom screens.
- New: Added filter to 'nps_survey_build_url' update build url for themes.
Version 1.0.0 - 23-09-2024
- New: Initial release.

View File

@@ -0,0 +1,485 @@
<?php
/**
* NPS Survey Script
* File to handle behaviour and content of NPS popup
*
* @package {{package}}
*/
/**
* Nps_Survey
*/
class Nps_Survey {
/**
* Instance
*
* @access private
* @var object Class Instance.
* @since 1.0.0
*/
private static $instance = null;
/**
* Initiator
*
* @since 1.0.0
* @return object initialized object of class.
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*
* @since 1.0.0
*/
public function __construct() {
add_action( 'admin_enqueue_scripts', array( $this, 'editor_load_scripts' ) );
add_action( 'rest_api_init', array( $this, 'register_route' ) );
}
/**
* Render NPS Survey.
*
* @param string $id ID of the root element, should start with nps-survey- .
* @param array<mixed> $vars Variables to be passed to the NPS.
* @since 1.0.0
* @return void
*/
public static function show_nps_notice( string $id, array $vars = [] ) {
if ( ! isset( $vars['plugin_slug'] ) || ! is_string( $vars['plugin_slug'] ) ) {
return;
}
$plugin_slug = $vars['plugin_slug'];
$display_after = is_int( $vars['display_after'] ) ? $vars['display_after'] : 0;
if ( ! self::is_show_nps_survey_form( $plugin_slug, $display_after ) ) {
return;
}
?><div data-id="<?php echo esc_attr( $id ); ?>" class="nps-survey-root" data-vars="<?php echo esc_attr( strval( wp_json_encode( $vars ) ) ); ?>"></div>
<?php
}
/**
* Generate and return the Google fonts url.
*
* @since 1.0.2
* @return string
*/
public static function google_fonts_url() {
$fonts_url = '';
$font_families = array(
'Figtree:400,500,600,700',
);
$query_args = array(
'family' => rawurlencode( implode( '|', $font_families ) ),
'subset' => rawurlencode( 'latin,latin-ext' ),
);
$fonts_url = add_query_arg( $query_args, '//fonts.googleapis.com/css' );
return $fonts_url;
}
/**
* Load script.
*
* @since 1.0.0
* @return void
*/
public static function editor_load_scripts() {
if ( ! is_admin() ) {
return;
}
$screen = get_current_screen();
$screen_id = $screen ? $screen->id : '';
// Added a filter to allow adding additional screens from outside.
$allowed_screens = apply_filters(
'nps_survey_allowed_screens',
[
'dashboard',
'themes',
'options-general',
'plugins',
]
);
if ( ! in_array( $screen_id, $allowed_screens, true ) ) {
return;
}
$handle = 'nps-survey-script';
$build_path = NPS_SURVEY_DIR . 'dist/';
$default_build_url = NPS_SURVEY_URL . 'dist/';
// Use a filter to allow $build_url to be modified externally.
$build_url = apply_filters( 'nps_survey_build_url', $default_build_url );
$script_asset_path = $build_path . 'main.asset.php';
$script_info = file_exists( $script_asset_path )
? include $script_asset_path
: array(
'dependencies' => array(),
'version' => NPS_SURVEY_VER,
);
$script_dep = array_merge( $script_info['dependencies'], array( 'jquery' ) );
wp_enqueue_script(
$handle,
$build_url . 'main.js',
$script_dep,
$script_info['version'],
true
);
$data = apply_filters(
'nps_survey_vars',
[
'ajaxurl' => esc_url( admin_url( 'admin-ajax.php' ) ),
'_ajax_nonce' => wp_create_nonce( 'nps-survey' ),
]
);
// Add localize JS.
wp_localize_script(
'nps-survey-script',
'npsSurvey',
$data
);
wp_enqueue_style( 'nps-survey-style', $build_url . '/style-main.css', array(), NPS_SURVEY_VER );
wp_style_add_data( 'nps-survey-style', 'rtl', 'replace' );
wp_enqueue_style( 'nps-survey-google-fonts', self::google_fonts_url(), array(), 'all' );
}
/**
* Load all the required files in the importer.
*
* @since 1.0.0
* @return void
*/
public static function register_route() {
register_rest_route(
self::get_api_namespace(),
'/rating/',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( __CLASS__, 'submit_rating' ),
'permission_callback' => array( __CLASS__, 'get_item_permissions_check' ),
'args' => array(),
),
)
);
register_rest_route(
self::get_api_namespace(),
'/dismiss-nps-survey/',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( __CLASS__, 'dismiss_nps_survey_panel' ),
'permission_callback' => array( __CLASS__, 'get_item_permissions_check' ),
'args' => array(),
),
)
);
}
/**
* Get the API URL.
*
* @since 1.0.0
*
* @return string
*/
public static function get_api_domain() {
return trailingslashit( defined( 'NPS_SURVEY_REMOTE_URL' ) ? NPS_SURVEY_REMOTE_URL : apply_filters( 'nps_survey_api_domain', 'https://websitedemos.net/' ) );
}
/**
* Get api namespace
*
* @since 1.0.0
* @return string
*/
public static function get_api_namespace() {
return 'nps-survey/v1';
}
/**
* Get API headers
*
* @since 1.0.0
* @return array<string, string>
*/
public static function get_api_headers() {
return array(
'Content-Type' => 'application/json',
'Accept' => 'application/json',
);
}
/**
* Check whether a given request has permission to read notes.
*
* @param object $request WP_REST_Request Full details about the request.
* @return object|boolean
*/
public static function get_item_permissions_check( $request ) {
if ( ! current_user_can( 'manage_options' ) ) {
return new \WP_Error(
'gt_rest_cannot_access',
__( 'Sorry, you are not allowed to do that.', 'astra' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Submit Ratings.
*
* @param \WP_REST_Request $request Request object.
* @return void
*/
public static function submit_rating( $request ) {
$nonce = $request->get_header( 'X-WP-Nonce' );
// Verify the nonce.
if ( ! wp_verify_nonce( sanitize_text_field( (string) $nonce ), 'wp_rest' ) ) {
wp_send_json_error(
array(
'data' => __( 'Nonce verification failed.', 'astra' ),
'status' => false,
)
);
}
$api_endpoint = self::get_api_domain() . 'wp-json/starter-templates/v1/nps-survey/';
$current_user = wp_get_current_user();
$post_data = array(
'rating' => ! empty( $request['rating'] ) ? sanitize_text_field( strval( $request['rating'] ) ) : '',
'comment' => ! empty( $request['comment'] ) ? sanitize_text_field( strval( $request['comment'] ) ) : '',
'email' => $current_user->user_email,
'first_name' => $current_user->first_name ?? $current_user->display_name,
'last_name' => $current_user->last_name ?? '',
'source' => ! empty( $request['plugin_slug'] ) ? sanitize_text_field( strval( $request['plugin_slug'] ) ) : '',
'plugin_slug' => ! empty( $request['plugin_slug'] ) ? sanitize_text_field( strval( $request['plugin_slug'] ) ) : '',
);
$request_args = array(
'body' => wp_json_encode( $post_data ),
'headers' => self::get_api_headers(),
'timeout' => 60,
);
$response = wp_safe_remote_post( $api_endpoint, $request_args );
if ( is_wp_error( $response ) ) {
// There was an error in the request.
wp_send_json_error(
array(
'data' => 'Failed ' . $response->get_error_message(),
'status' => false,
)
);
}
$response_code = wp_remote_retrieve_response_code( $response );
if ( 200 === $response_code ) {
$nps_form_status = array(
'dismiss_count' => 0,
'dismiss_permanently' => true,
'dismiss_step' => '',
);
update_option( self::get_nps_id( strval( $request['plugin_slug'] ) ), $nps_form_status );
wp_send_json_success(
array(
'status' => true,
)
);
} else {
wp_send_json_error(
array(
'status' => false,
)
);
}
}
/**
* Dismiss NPS Survey.
*
* @param \WP_REST_Request $request Request object.
* @return void
*/
public static function dismiss_nps_survey_panel( $request ) {
$nonce = $request->get_header( 'X-WP-Nonce' );
// Verify the nonce.
if ( ! wp_verify_nonce( sanitize_text_field( (string) $nonce ), 'wp_rest' ) ) {
wp_send_json_error(
array(
'data' => __( 'Nonce verification failed.', 'astra' ),
'status' => false,
)
);
}
$nps_form_status = self::get_nps_survey_dismiss_status( strval( $request['plugin_slug'] ) );
// Add dismiss timespan.
$nps_form_status['dismiss_timespan'] = $request['dismiss_timespan'];
// Add dismiss date.
$nps_form_status['dismiss_time'] = time();
// Update dismiss count.
$nps_form_status['dismiss_count'] = $nps_form_status['dismiss_count'] + 1;
$nps_form_status['dismiss_step'] = $request['current_step'];
// Dismiss Permanantly.
if ( $nps_form_status['dismiss_count'] >= 2 ) {
$nps_form_status['dismiss_permanently'] = true;
}
update_option( self::get_nps_id( strval( $request['plugin_slug'] ) ), $nps_form_status );
wp_send_json_success(
array(
'status' => true,
)
);
}
/**
* Get dismiss status of NPS Survey.
*
* @param string $plugin_slug slug of unique NPS Survey.
* @return array<string, mixed>
*/
public static function get_nps_survey_dismiss_status( string $plugin_slug ) {
$default_status = get_option(
self::get_nps_id( $plugin_slug ),
array(
'dismiss_count' => 0,
'dismiss_permanently' => false,
'dismiss_step' => '',
'dismiss_time' => '',
'dismiss_timespan' => null,
'first_render_time' => null,
)
);
if ( ! is_array( $default_status ) ) {
return array();
}
$status = array(
'dismiss_count' => ! empty( $default_status['dismiss_count'] ) ? $default_status['dismiss_count'] : 0,
'dismiss_permanently' => ! empty( $default_status['dismiss_permanently'] ) ? $default_status['dismiss_permanently'] : false,
'dismiss_step' => ! empty( $default_status['dismiss_step'] ) ? $default_status['dismiss_step'] : '',
'dismiss_time' => ! empty( $default_status['dismiss_time'] ) ? $default_status['dismiss_time'] : '',
'dismiss_timespan' => ! empty( $default_status['dismiss_timespan'] ) ? $default_status['dismiss_timespan'] : null,
'first_render_time' => ! empty( $default_status['first_render_time'] ) ? $default_status['first_render_time'] : null,
);
return $status;
}
/**
* Show status of NPS Survey.
*
* @param string $plugin_slug slug of unique NPS Survey.
* @param int $display_after number of days after which NPS Survey should be displayed.
* @return boolean
*/
public static function is_show_nps_survey_form( string $plugin_slug, int $display_after ) {
$current_time = time();
$status = self::get_nps_survey_dismiss_status( $plugin_slug );
if ( $status['dismiss_permanently'] ) {
return false;
}
$first_render_time = $status['first_render_time'];
if ( 0 !== $display_after ) {
if ( null === $first_render_time ) {
$status['first_render_time'] = $current_time;
update_option( self::get_nps_id( $plugin_slug ), $status );
$status = self::get_nps_survey_dismiss_status( $plugin_slug );
return false;
}
if ( $display_after + $first_render_time > $current_time ) {
return false;
}
}
// Retrieve the stored date time stamp from wp_options.
$stored_date_timestamp = $status['dismiss_time'];
$dismiss_timespan = $status['dismiss_timespan'];
if ( $stored_date_timestamp ) {
$current_time = time();
// time difference of current time and the time user dismissed the nps.
$time_difference = $current_time - $stored_date_timestamp;
// Check if two weeks have passed.
if ( $time_difference <= $dismiss_timespan ) {
return false;
}
}
return true;
}
/**
* Get NPS Dismiss Option Name.
*
* @param string $plugin_slug Plugin name.
* @return string
*/
public static function get_nps_id( $plugin_slug ) {
return 'nps-survey-' . $plugin_slug;
}
}
/**
* Kicking this off by calling 'get_instance()' method
*/
Nps_Survey::get_instance();

View File

@@ -0,0 +1,29 @@
{
"name": "brainstormforce/nps-survey",
"type": "wordpress-plugin",
"description": "NPS Survey Plugin",
"require-dev": {
"squizlabs/php_codesniffer": "^3.5",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.1",
"phpcompatibility/php-compatibility": "^9.3",
"wp-coding-standards/wpcs": "^2.2",
"phpstan/phpstan": "^1.9",
"szepeviktor/phpstan-wordpress": "^1.1",
"php-stubs/wordpress-stubs": "^6.1",
"php-stubs/generator": "^0.8.2",
"automattic/vipwpcs": "^2.3"
},
"scripts": {
"format": "vendor/bin/phpcbf",
"lint": "vendor/bin/phpcs",
"test": "vendor/bin/phpunit",
"phpstan": "vendor/bin/phpstan --memory-limit=2048M analyse",
"gen-stubs": "vendor/bin/generate-stubs artifact/phpstan/nps-survey/ --out=tests/php/stubs/nps-survey-stubs.php && rm -rf artifact/phpstan",
"update-stubs": "rm -f tests/php/stubs/nps-survey-stubs.php && bash bin/build-folder-phpstan.sh && composer gen-stubs"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@@ -0,0 +1 @@
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '1d2887c1739029e6a045');

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,119 @@
.nps-survey-root .invisible {visibility: hidden
}.nps-survey-root .fixed {position: fixed
}.nps-survey-root .absolute {position: absolute
}.nps-survey-root .relative {position: relative
}.nps-survey-root .inset-0 {inset: 0px
}.nps-survey-root .bottom-2 {bottom: 0.5rem
}.nps-survey-root .right-2 {left: 0.5rem
}.nps-survey-root .right-3 {left: 0.75rem
}.nps-survey-root .top-3 {top: 0.75rem
}.nps-survey-root .isolate {isolation: isolate
}.nps-survey-root .z-10 {z-index: 10
}.nps-survey-root .mx-0 {margin-right: 0px;margin-left: 0px
}.nps-survey-root .my-0 {margin-top: 0px;margin-bottom: 0px
}.nps-survey-root .mb-0 {margin-bottom: 0px
}.nps-survey-root .mt-1 {margin-top: 0.25rem
}.nps-survey-root .mt-2 {margin-top: 0.5rem
}.nps-survey-root .mt-3 {margin-top: 0.75rem
}.nps-survey-root .mt-5 {margin-top: 1.25rem
}.nps-survey-root .block {display: block
}.nps-survey-root .flex {display: flex
}.nps-survey-root .inline-flex {display: inline-flex
}.nps-survey-root .size-5 {width: 1.25rem;height: 1.25rem
}.nps-survey-root .size-6 {width: 1.5rem;height: 1.5rem
}.nps-survey-root .h-11 {height: 2.75rem
}.nps-survey-root .h-5 {height: 1.25rem
}.nps-survey-root .h-\[2\.625rem\] {height: 2.625rem
}.nps-survey-root .w-4 {width: 1rem
}.nps-survey-root .w-5 {width: 1.25rem
}.nps-survey-root .w-full {width: 100%
}.nps-survey-root .max-w-\[30rem\] {max-width: 30rem
}.nps-survey-root .flex-1 {flex: 1 1 0%
}@keyframes spin {to {transform: rotate(-360deg)
}
}.nps-survey-root .animate-spin {animation: spin 1s linear infinite
}.nps-survey-root .cursor-not-allowed {cursor: not-allowed
}.nps-survey-root .cursor-pointer {cursor: pointer
}.nps-survey-root .cursor-progress {cursor: progress
}.nps-survey-root .items-center {align-items: center
}.nps-survey-root .justify-start {justify-content: flex-start
}.nps-survey-root .justify-center {justify-content: center
}.nps-survey-root .justify-between {justify-content: space-between
}.nps-survey-root .gap-2 {gap: 0.5rem
}.nps-survey-root .rounded {border-radius: 0.25rem
}.nps-survey-root .rounded-md {border-radius: 0.375rem
}.nps-survey-root .border {border-width: 1px
}.nps-survey-root .border-0 {border-width: 0px
}.nps-survey-root .border-solid {border-style: solid
}.nps-survey-root .border-none {border-style: none
}.nps-survey-root .border-border-tertiary {--tw-border-opacity: 1;border-color: rgb(216 223 233 / var(--tw-border-opacity))
}.nps-survey-root .border-button-disabled {--tw-border-opacity: 1;border-color: rgb(229 231 235 / var(--tw-border-opacity))
}.nps-survey-root .border-nps-button-background {--tw-border-opacity: 1;border-color: rgb(34 113 177 / var(--tw-border-opacity))
}.nps-survey-root .border-transparent {border-color: transparent
}.nps-survey-root .border-white {--tw-border-opacity: 1;border-color: rgb(255 255 255 / var(--tw-border-opacity))
}.nps-survey-root .border-zip-body-text {--tw-border-opacity: 1;border-color: rgb(var(--zip-body-text) / var(--tw-border-opacity))
}.nps-survey-root .bg-nps-button-background {--tw-bg-opacity: 1;background-color: rgb(34 113 177 / var(--tw-bg-opacity))
}.nps-survey-root .bg-transparent {background-color: transparent
}.nps-survey-root .bg-white {--tw-bg-opacity: 1;background-color: rgb(255 255 255 / var(--tw-bg-opacity))
}.nps-survey-root .p-4 {padding: 1rem
}.nps-survey-root .px-4 {padding-right: 1rem;padding-left: 1rem
}.nps-survey-root .px-5 {padding-right: 1.25rem;padding-left: 1.25rem
}.nps-survey-root .px-6 {padding-right: 1.5rem;padding-left: 1.5rem
}.nps-survey-root .py-1\.5 {padding-top: 0.375rem;padding-bottom: 0.375rem
}.nps-survey-root .py-2 {padding-top: 0.5rem;padding-bottom: 0.5rem
}.nps-survey-root .py-3 {padding-top: 0.75rem;padding-bottom: 0.75rem
}.nps-survey-root .pl-0 {padding-right: 0px
}.nps-survey-root .pl-3 {padding-right: 0.75rem
}.nps-survey-root .pl-4 {padding-right: 1rem
}.nps-survey-root .pl-5 {padding-right: 1.25rem
}.nps-survey-root .pl-6 {padding-right: 1.5rem
}.nps-survey-root .pr-3 {padding-left: 0.75rem
}.nps-survey-root .pr-4 {padding-left: 1rem
}.nps-survey-root .pr-5 {padding-left: 1.25rem
}.nps-survey-root .pr-6 {padding-left: 1.5rem
}.nps-survey-root .text-base {font-size: 1rem;line-height: 1.5rem
}.nps-survey-root .text-lg {font-size: 1.125rem;line-height: 1.75rem
}.nps-survey-root .text-sm {font-size: 0.875rem;line-height: 1.25rem
}.nps-survey-root .text-xs {font-size: 0.75rem;line-height: 1rem
}.nps-survey-root .font-bold {font-weight: 700
}.nps-survey-root .font-medium {font-weight: 500
}.nps-survey-root .font-normal {font-weight: 400
}.nps-survey-root .font-semibold {font-weight: 600
}.nps-survey-root .leading-5 {line-height: 1.25rem
}.nps-survey-root .leading-6 {line-height: 1.5rem
}.nps-survey-root .leading-7 {line-height: 1.75rem
}.nps-survey-root .text-border-secondary {--tw-text-opacity: 1;color: rgb(107 114 128 / var(--tw-text-opacity))
}.nps-survey-root .text-nps-button-background {--tw-text-opacity: 1;color: rgb(34 113 177 / var(--tw-text-opacity))
}.nps-survey-root .text-secondary-text {--tw-text-opacity: 1;color: rgb(156 163 175 / var(--tw-text-opacity))
}.nps-survey-root .text-white {--tw-text-opacity: 1;color: rgb(255 255 255 / var(--tw-text-opacity))
}.nps-survey-root .text-zip-app-heading {--tw-text-opacity: 1;color: rgb(var(--zip-app-heading) / var(--tw-text-opacity))
}.nps-survey-root .text-zip-app-inactive-icon {--tw-text-opacity: 1;color: rgb(var(--zip-app-inactive-icon) / var(--tw-text-opacity))
}.nps-survey-root .text-zip-body-text {--tw-text-opacity: 1;color: rgb(var(--zip-body-text) / var(--tw-text-opacity))
}.nps-survey-root .underline {text-decoration-line: underline
}.nps-survey-root .no-underline {text-decoration-line: none
}.nps-survey-root .opacity-25 {opacity: 0.25
}.nps-survey-root .opacity-50 {opacity: 0.5
}.nps-survey-root .opacity-70 {opacity: 0.7
}.nps-survey-root .opacity-75 {opacity: 0.75
}.nps-survey-root .shadow-lg {--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)
}.nps-survey-root .shadow-sm {--tw-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);--tw-shadow-colored: 0px 1px 2px 0px var(--tw-shadow-color);box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)
}.nps-survey-root .transition {transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);transition-duration: 150ms
}.nps-survey-root .transition-colors {transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);transition-duration: 150ms
}.nps-survey-root .duration-150 {transition-duration: 150ms
}.nps-survey-root .ease-in-out {transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)
}.nps-survey-root {font-size: 1rem;line-height: 1.5rem
}.nps-survey-root * {box-sizing: border-box;font-family: Figtree, sans-serif
}.nps-survey-root .hover\:cursor-pointer:hover {cursor: pointer
}.nps-survey-root .hover\:bg-gray-50:hover {--tw-bg-opacity: 1;background-color: rgb(249 250 251 / var(--tw-bg-opacity))
}.nps-survey-root .focus\:z-10:focus {z-index: 10
}.nps-survey-root .focus\:ring-1:focus {--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)
}.nps-survey-root .focus\:ring-nps-button-background:focus {--tw-ring-opacity: 1;--tw-ring-color: rgb(34 113 177 / var(--tw-ring-opacity))
}.nps-survey-root .focus-visible\:outline:focus-visible {outline-style: solid
}.nps-survey-root .focus-visible\:outline-2:focus-visible {outline-width: 2px
}.nps-survey-root .focus-visible\:outline-offset-2:focus-visible {outline-offset: 2px
}@media (min-width: 640px) {.nps-survey-root .sm\:rounded-lg {border-radius: 0.5rem
}.nps-survey-root .sm\:p-5 {padding: 1.25rem
}.nps-survey-root .sm\:text-sm {font-size: 0.875rem;line-height: 1.25rem
}.nps-survey-root .sm\:leading-6 {line-height: 1.5rem
}
}

View File

@@ -0,0 +1,119 @@
.nps-survey-root .invisible {visibility: hidden
}.nps-survey-root .fixed {position: fixed
}.nps-survey-root .absolute {position: absolute
}.nps-survey-root .relative {position: relative
}.nps-survey-root .inset-0 {inset: 0px
}.nps-survey-root .bottom-2 {bottom: 0.5rem
}.nps-survey-root .right-2 {right: 0.5rem
}.nps-survey-root .right-3 {right: 0.75rem
}.nps-survey-root .top-3 {top: 0.75rem
}.nps-survey-root .isolate {isolation: isolate
}.nps-survey-root .z-10 {z-index: 10
}.nps-survey-root .mx-0 {margin-left: 0px;margin-right: 0px
}.nps-survey-root .my-0 {margin-top: 0px;margin-bottom: 0px
}.nps-survey-root .mb-0 {margin-bottom: 0px
}.nps-survey-root .mt-1 {margin-top: 0.25rem
}.nps-survey-root .mt-2 {margin-top: 0.5rem
}.nps-survey-root .mt-3 {margin-top: 0.75rem
}.nps-survey-root .mt-5 {margin-top: 1.25rem
}.nps-survey-root .block {display: block
}.nps-survey-root .flex {display: flex
}.nps-survey-root .inline-flex {display: inline-flex
}.nps-survey-root .size-5 {width: 1.25rem;height: 1.25rem
}.nps-survey-root .size-6 {width: 1.5rem;height: 1.5rem
}.nps-survey-root .h-11 {height: 2.75rem
}.nps-survey-root .h-5 {height: 1.25rem
}.nps-survey-root .h-\[2\.625rem\] {height: 2.625rem
}.nps-survey-root .w-4 {width: 1rem
}.nps-survey-root .w-5 {width: 1.25rem
}.nps-survey-root .w-full {width: 100%
}.nps-survey-root .max-w-\[30rem\] {max-width: 30rem
}.nps-survey-root .flex-1 {flex: 1 1 0%
}@keyframes spin {to {transform: rotate(360deg)
}
}.nps-survey-root .animate-spin {animation: spin 1s linear infinite
}.nps-survey-root .cursor-not-allowed {cursor: not-allowed
}.nps-survey-root .cursor-pointer {cursor: pointer
}.nps-survey-root .cursor-progress {cursor: progress
}.nps-survey-root .items-center {align-items: center
}.nps-survey-root .justify-start {justify-content: flex-start
}.nps-survey-root .justify-center {justify-content: center
}.nps-survey-root .justify-between {justify-content: space-between
}.nps-survey-root .gap-2 {gap: 0.5rem
}.nps-survey-root .rounded {border-radius: 0.25rem
}.nps-survey-root .rounded-md {border-radius: 0.375rem
}.nps-survey-root .border {border-width: 1px
}.nps-survey-root .border-0 {border-width: 0px
}.nps-survey-root .border-solid {border-style: solid
}.nps-survey-root .border-none {border-style: none
}.nps-survey-root .border-border-tertiary {--tw-border-opacity: 1;border-color: rgb(216 223 233 / var(--tw-border-opacity))
}.nps-survey-root .border-button-disabled {--tw-border-opacity: 1;border-color: rgb(229 231 235 / var(--tw-border-opacity))
}.nps-survey-root .border-nps-button-background {--tw-border-opacity: 1;border-color: rgb(34 113 177 / var(--tw-border-opacity))
}.nps-survey-root .border-transparent {border-color: transparent
}.nps-survey-root .border-white {--tw-border-opacity: 1;border-color: rgb(255 255 255 / var(--tw-border-opacity))
}.nps-survey-root .border-zip-body-text {--tw-border-opacity: 1;border-color: rgb(var(--zip-body-text) / var(--tw-border-opacity))
}.nps-survey-root .bg-nps-button-background {--tw-bg-opacity: 1;background-color: rgb(34 113 177 / var(--tw-bg-opacity))
}.nps-survey-root .bg-transparent {background-color: transparent
}.nps-survey-root .bg-white {--tw-bg-opacity: 1;background-color: rgb(255 255 255 / var(--tw-bg-opacity))
}.nps-survey-root .p-4 {padding: 1rem
}.nps-survey-root .px-4 {padding-left: 1rem;padding-right: 1rem
}.nps-survey-root .px-5 {padding-left: 1.25rem;padding-right: 1.25rem
}.nps-survey-root .px-6 {padding-left: 1.5rem;padding-right: 1.5rem
}.nps-survey-root .py-1\.5 {padding-top: 0.375rem;padding-bottom: 0.375rem
}.nps-survey-root .py-2 {padding-top: 0.5rem;padding-bottom: 0.5rem
}.nps-survey-root .py-3 {padding-top: 0.75rem;padding-bottom: 0.75rem
}.nps-survey-root .pl-0 {padding-left: 0px
}.nps-survey-root .pl-3 {padding-left: 0.75rem
}.nps-survey-root .pl-4 {padding-left: 1rem
}.nps-survey-root .pl-5 {padding-left: 1.25rem
}.nps-survey-root .pl-6 {padding-left: 1.5rem
}.nps-survey-root .pr-3 {padding-right: 0.75rem
}.nps-survey-root .pr-4 {padding-right: 1rem
}.nps-survey-root .pr-5 {padding-right: 1.25rem
}.nps-survey-root .pr-6 {padding-right: 1.5rem
}.nps-survey-root .text-base {font-size: 1rem;line-height: 1.5rem
}.nps-survey-root .text-lg {font-size: 1.125rem;line-height: 1.75rem
}.nps-survey-root .text-sm {font-size: 0.875rem;line-height: 1.25rem
}.nps-survey-root .text-xs {font-size: 0.75rem;line-height: 1rem
}.nps-survey-root .font-bold {font-weight: 700
}.nps-survey-root .font-medium {font-weight: 500
}.nps-survey-root .font-normal {font-weight: 400
}.nps-survey-root .font-semibold {font-weight: 600
}.nps-survey-root .leading-5 {line-height: 1.25rem
}.nps-survey-root .leading-6 {line-height: 1.5rem
}.nps-survey-root .leading-7 {line-height: 1.75rem
}.nps-survey-root .text-border-secondary {--tw-text-opacity: 1;color: rgb(107 114 128 / var(--tw-text-opacity))
}.nps-survey-root .text-nps-button-background {--tw-text-opacity: 1;color: rgb(34 113 177 / var(--tw-text-opacity))
}.nps-survey-root .text-secondary-text {--tw-text-opacity: 1;color: rgb(156 163 175 / var(--tw-text-opacity))
}.nps-survey-root .text-white {--tw-text-opacity: 1;color: rgb(255 255 255 / var(--tw-text-opacity))
}.nps-survey-root .text-zip-app-heading {--tw-text-opacity: 1;color: rgb(var(--zip-app-heading) / var(--tw-text-opacity))
}.nps-survey-root .text-zip-app-inactive-icon {--tw-text-opacity: 1;color: rgb(var(--zip-app-inactive-icon) / var(--tw-text-opacity))
}.nps-survey-root .text-zip-body-text {--tw-text-opacity: 1;color: rgb(var(--zip-body-text) / var(--tw-text-opacity))
}.nps-survey-root .underline {text-decoration-line: underline
}.nps-survey-root .no-underline {text-decoration-line: none
}.nps-survey-root .opacity-25 {opacity: 0.25
}.nps-survey-root .opacity-50 {opacity: 0.5
}.nps-survey-root .opacity-70 {opacity: 0.7
}.nps-survey-root .opacity-75 {opacity: 0.75
}.nps-survey-root .shadow-lg {--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)
}.nps-survey-root .shadow-sm {--tw-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);--tw-shadow-colored: 0px 1px 2px 0px var(--tw-shadow-color);box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)
}.nps-survey-root .transition {transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);transition-duration: 150ms
}.nps-survey-root .transition-colors {transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);transition-duration: 150ms
}.nps-survey-root .duration-150 {transition-duration: 150ms
}.nps-survey-root .ease-in-out {transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)
}.nps-survey-root {font-size: 1rem;line-height: 1.5rem
}.nps-survey-root * {box-sizing: border-box;font-family: Figtree, sans-serif
}.nps-survey-root .hover\:cursor-pointer:hover {cursor: pointer
}.nps-survey-root .hover\:bg-gray-50:hover {--tw-bg-opacity: 1;background-color: rgb(249 250 251 / var(--tw-bg-opacity))
}.nps-survey-root .focus\:z-10:focus {z-index: 10
}.nps-survey-root .focus\:ring-1:focus {--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)
}.nps-survey-root .focus\:ring-nps-button-background:focus {--tw-ring-opacity: 1;--tw-ring-color: rgb(34 113 177 / var(--tw-ring-opacity))
}.nps-survey-root .focus-visible\:outline:focus-visible {outline-style: solid
}.nps-survey-root .focus-visible\:outline-2:focus-visible {outline-width: 2px
}.nps-survey-root .focus-visible\:outline-offset-2:focus-visible {outline-offset: 2px
}@media (min-width: 640px) {.nps-survey-root .sm\:rounded-lg {border-radius: 0.5rem
}.nps-survey-root .sm\:p-5 {padding: 1.25rem
}.nps-survey-root .sm\:text-sm {font-size: 0.875rem;line-height: 1.25rem
}.nps-survey-root .sm\:leading-6 {line-height: 1.5rem
}
}

View File

@@ -0,0 +1,97 @@
<?php
/**
* Plugin Loader.
*
* @package {{package}}
* @since {{since}}
*/
namespace NPS_Survey;
/**
* Plugin_Loader
*
* @since 1.0.0
*/
class NPS_Survey_Plugin_Loader {
/**
* Instance
*
* @access private
* @var object Class Instance.
* @since 1.0.0
*/
private static $instance;
/**
* Initiator
*
* @since 1.0.0
* @return object initialized object of class.
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*
* @since 1.0.0
*/
public function __construct() {
spl_autoload_register( [ $this, 'autoload' ] );
add_action( 'wp_loaded', [ $this, 'load_files' ] );
}
/**
* Autoload classes.
*
* @param string $class class name.
* @return void
*/
public function autoload( $class ) {
if ( 0 !== strpos( $class, __NAMESPACE__ ) ) {
return;
}
$class_to_load = $class;
$filename = strtolower(
strval(
preg_replace(
[ '/^' . __NAMESPACE__ . '\\\/', '/([a-z])([A-Z])/', '/_/', '/\\\/' ],
[ '', '$1-$2', '-', DIRECTORY_SEPARATOR ],
$class_to_load
)
)
);
$file = NPS_SURVEY_DIR . $filename . '.php';
// if the file redable, include it.
if ( is_readable( $file ) ) {
require_once $file;
}
}
/**
* Load Files
*
* @since 1.0.0
*
* @return void
*/
public function load_files() {
require_once NPS_SURVEY_DIR . 'classes/nps-survey-script.php';
}
}
/**
* Kicking this off by calling 'get_instance()' method
*/
NPS_Survey_Plugin_Loader::get_instance();

View File

@@ -0,0 +1,26 @@
<?php
/**
* Plugin Name: NPS Survey
* Description: It is a nps survey library.
* Author: Brainstorm Force
* Version: 1.0.4
* License: GPL v2
* Text Domain: nps-survey
*
* @package {{package}}
*/
/**
* Set constants
* Check of plugin constant is already defined
*/
if ( defined( 'NPS_SURVEY_FILE' ) ) {
return;
}
define( 'NPS_SURVEY_FILE', __FILE__ );
define( 'NPS_SURVEY_BASE', plugin_basename( NPS_SURVEY_FILE ) );
define( 'NPS_SURVEY_DIR', plugin_dir_path( NPS_SURVEY_FILE ) );
define( 'NPS_SURVEY_URL', plugins_url( '/', NPS_SURVEY_FILE ) );
define( 'NPS_SURVEY_VER', '1.0.4' );
require_once 'nps-survey-plugin-loader.php';

View File

@@ -0,0 +1,3 @@
{
"nps-survey": "1.0.4"
}

View File

@@ -0,0 +1,719 @@
<?php
/**
* Download webfonts locally.
*
* @package WPTT/webfont-loader
* @license https://opensource.org/licenses/MIT
*
* @since 3.6.0
*/
/**
* Download webfonts locally.
*/
class Astra_WebFont_Loader {
/**
* The font-format.
*
* Use "woff" or "woff2".
* This will change the user-agent user to make the request.
*
* @since 3.6.0
* @var string
*/
protected $font_format = 'woff2';
/**
* The remote URL.
*
* @since 3.6.0
* @var string
*/
protected $remote_url;
/**
* Base path.
*
* @since 3.6.0
* @var string
*/
protected $base_path;
/**
* Base URL.
*
* @since 3.6.0
* @var string
*/
protected $base_url;
/**
* Subfolder name.
*
* @since 3.6.0
* @var string
*/
protected $subfolder_name;
/**
* Current blog id.
*
* @multisite
* @since 4.6.0
* @var int
*/
protected $current_blog_id;
/**
* The fonts folder.
*
* @since 3.6.0
* @var string
*/
protected $fonts_folder;
/**
* The local stylesheet's path.
*
* @since 3.6.0
* @var string
*/
protected $local_stylesheet_path;
/**
* The local stylesheet's URL.
*
* @since 3.6.0
* @var string
*/
protected $local_stylesheet_url;
/**
* The remote CSS.
*
* @since 3.6.0
* @var string
*/
protected $remote_styles;
/**
* The final CSS.
*
* @since 3.6.0
* @var string
*/
protected $css;
/**
* Cleanup routine frequency.
*/
const CLEANUP_FREQUENCY = 'monthly';
/**
* Constructor.
*
* Get a new instance of the object for a new URL.
*
* @since 3.6.0
* @param string $url The remote URL.
*/
public function __construct( $url = '' ) {
$this->remote_url = $url;
// Add a cleanup routine.
$this->schedule_cleanup();
add_action( 'astra_delete_fonts_folder', array( $this, 'astra_delete_fonts_folder' ) );
}
/**
* Get the local URL which contains the styles.
*
* Fallback to the remote URL if we were unable to write the file locally.
*
* @since 3.6.0
* @return string
*/
public function get_url() {
// Check if the local stylesheet exists.
if ( $this->local_file_exists() ) {
// Attempt to update the stylesheet. Return the local URL on success.
if ( $this->write_stylesheet() ) {
return $this->get_local_stylesheet_url();
}
}
$astra_font_url = file_exists( $this->get_local_stylesheet_path() ) ? $this->get_local_stylesheet_url() : $this->remote_url;
// If the local file exists, return its URL, with a fallback to the remote URL.
astra_update_option( 'astra_font_url', wp_json_encode( $astra_font_url ) );
return $astra_font_url;
}
/**
* Get the local stylesheet URL.
*
* @since 3.6.0
* @return string
*/
public function get_local_stylesheet_url() {
if ( ! $this->local_stylesheet_url ) {
$this->local_stylesheet_url = str_replace(
$this->get_base_path(),
$this->get_base_url(),
$this->get_local_stylesheet_path()
);
}
return $this->local_stylesheet_url;
}
/**
* Get styles with fonts downloaded locally.
*
* @since 3.6.0
* @return string
*/
public function get_styles() {
// If we already have the local file, return its contents.
$local_stylesheet_contents = $this->get_local_stylesheet_contents();
if ( $local_stylesheet_contents ) {
return $local_stylesheet_contents;
}
// Get the remote URL contents.
$this->remote_styles = $this->get_remote_url_contents();
// Get an array of locally-hosted files.
$files = $this->get_local_files_from_css();
$convert_to_url = apply_filters( 'astra_convert_fonts_to_url', true );
foreach ( $files as $remote => $local ) {
if ( $convert_to_url ) {
$url = str_replace(
$this->get_base_path(),
$this->get_base_url(),
$local
);
} else {
$url = $local;
}
$files[ $remote ] = apply_filters( 'astra_webfont_loader_file_url', $url, $remote, $local );
}
$this->css = str_replace(
array_keys( $files ),
array_values( $files ),
$this->remote_styles
);
$this->write_stylesheet();
return $this->css;
}
/**
* Get local stylesheet contents.
*
* @since 3.6.0
* @return string|false Returns the remote URL contents.
*/
public function get_local_stylesheet_contents() {
$local_path = $this->get_local_stylesheet_path();
// Check if the local stylesheet exists.
if ( $this->local_file_exists() ) {
// Attempt to update the stylesheet. Return false on fail.
if ( ! $this->write_stylesheet() ) {
return false;
}
}
ob_start();
include $local_path; // PHPCS:ignore WPThemeReview.CoreFunctionality.FileInclude.FileIncludeFound
return ob_get_clean();
}
/**
* Get remote file contents.
*
* @since 3.6.0
* @return string Returns the remote URL contents.
*/
public function get_remote_url_contents() {
/**
* The user-agent we want to use.
*
* The default user-agent is the only one compatible with woff (not woff2)
* which also supports unicode ranges.
*/
$user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8';
// Switch to a user-agent supporting woff2 if we don't need to support IE.
if ( 'woff2' === $this->font_format ) {
$user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0';
}
// Get the response.
$response = wp_remote_get( $this->remote_url, array( 'user-agent' => $user_agent ) );
// Get the HTTP status code.
$status_code = wp_remote_retrieve_response_code( $response );
// Early exit if there was an error or the font does not exists on google fonts.
if ( is_wp_error( $response ) || 200 !== $status_code ) {
return '';
}
// Get the CSS from our response.
$contents = wp_remote_retrieve_body( $response );
return $contents;
}
/**
* Download files mentioned in our CSS locally.
*
* @since 3.6.0
* @return array Returns an array of remote URLs and their local counterparts.
*/
public function get_local_files_from_css() {
$font_files = $this->get_remote_files_from_css();
$stored = get_site_option( 'ast_downloaded_font_files', array() );
$change = false; // If in the end this is true, we need to update the cache option.
if ( ! defined( 'FS_CHMOD_DIR' ) ) {
define( 'FS_CHMOD_DIR', ( 0755 & ~ umask() ) );
}
// If the fonts folder don't exist, create it.
if ( ! file_exists( $this->get_fonts_folder() ) ) {
$this->get_filesystem()->mkdir( $this->get_fonts_folder(), FS_CHMOD_DIR );
}
foreach ( $font_files as $font_family => $files ) {
// The folder path for this font-family.
$folder_path = $this->get_fonts_folder() . '/' . $font_family;
// If the folder doesn't exist, create it.
if ( ! file_exists( $folder_path ) ) {
$this->get_filesystem()->mkdir( $folder_path, FS_CHMOD_DIR );
}
foreach ( $files as $url ) {
// Get the filename.
$filename = basename( wp_parse_url( $url, PHP_URL_PATH ) );
$font_path = $folder_path . '/' . $filename;
// Check if the file already exists.
if ( file_exists( $font_path ) ) {
// Skip if already cached.
if ( isset( $stored[ $url ] ) ) {
continue;
}
// Add file to the cache and change the $changed var to indicate we need to update the option.
$stored[ $url ] = $font_path;
$change = true;
// Since the file exists we don't need to proceed with downloading it.
continue;
}
/**
* If we got this far, we need to download the file.
*/
// require file.php if the download_url function doesn't exist.
if ( ! function_exists( 'download_url' ) ) {
require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' ); // PHPCS:ignore WPThemeReview.CoreFunctionality.FileInclude.FileIncludeFound
}
// Download file to temporary location.
$tmp_path = download_url( $url );
// Make sure there were no errors.
if ( is_wp_error( $tmp_path ) ) {
continue;
}
// Move temp file to final destination.
$success = $this->get_filesystem()->move( $tmp_path, $font_path, true );
if ( $success ) {
$stored[ $url ] = $font_path;
$change = true;
}
}
}
// If there were changes, update the option.
if ( $change ) {
// Cleanup the option and then save it.
foreach ( $stored as $url => $path ) {
if ( ! file_exists( $path ) ) {
unset( $stored[ $url ] );
}
}
update_site_option( 'ast_downloaded_font_files', $stored );
}
return $stored;
}
/**
* Get the font files and preload them.
*/
public function preload_local_fonts() {
// Make sure variables are set.
// Get the remote URL contents.
$styles = $this->get_styles();
// Get an array of locally-hosted files.
$local_font = array();
$font_files = $this->get_remote_files_from_css( $styles );
foreach ( $font_files as $font_family => $files ) {
if ( is_array( $files ) ) {
$local_font[] = end( $files );
}
}
// Caching this for further optimization.
update_site_option( 'astra_local_font_files', $local_font );
foreach ( $local_font as $key => $local_font ) {
if ( $local_font ) {
echo '<link rel="preload" href="' . esc_url( $local_font ) . '" as="font" type="font/' . esc_attr( $this->font_format ) . '" crossorigin>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
}
/**
* Get font files from the CSS.
*
* @since 3.6.0
* @param string $remote_styles Remote stylesheet data.
*
* @return array Returns an array of font-families and the font-files used.
*/
public function get_remote_files_from_css( $remote_styles = '' ) {
if ( '' === $remote_styles ) {
$remote_styles = $this->remote_styles;
}
$font_faces = explode( '@font-face', $remote_styles );
$result = array();
// Loop all our font-face declarations.
foreach ( $font_faces as $font_face ) {
// Make sure we only process styles inside this declaration.
$style = explode( '}', $font_face );
$style = isset( $style[0] ) ? $style[0] : '';
// Sanity check.
if ( false === strpos( $style, 'font-family' ) ) {
continue;
}
// Get an array of our font-families.
preg_match_all( '/font-family.*?\;/', $style, $matched_font_families );
// Get an array of our font-files.
preg_match_all( '/url\(.*?\)/i', $style, $matched_font_files );
// Get the font-family name.
$font_family = 'unknown';
if ( isset( $matched_font_families[0] ) && isset( $matched_font_families[0][0] ) ) {
$font_family = rtrim( ltrim( $matched_font_families[0][0], 'font-family:' ), ';' );
$font_family = trim( str_replace( array( "'", ';' ), '', $font_family ) );
$font_family = sanitize_key( strtolower( str_replace( ' ', '-', $font_family ) ) );
}
// Make sure the font-family is set in our array.
if ( ! isset( $result[ $font_family ] ) ) {
$result[ $font_family ] = array();
}
// Get files for this font-family and add them to the array.
foreach ( $matched_font_files as $match ) {
// Sanity check.
if ( ! isset( $match[0] ) ) {
continue;
}
// Add the file URL.
$result[ $font_family ][] = rtrim( ltrim( $match[0], 'url(' ), ')' );
}
// Make sure we have unique items.
// We're using array_flip here instead of array_unique for improved performance.
$result[ $font_family ] = array_flip( array_flip( $result[ $font_family ] ) );
}
return $result;
}
/**
* Write the CSS to the filesystem.
*
* @since 3.6.0
* @return string|false Returns the absolute path of the file on success, or false on fail.
*/
protected function write_stylesheet() {
$file_path = $this->get_local_stylesheet_path();
$filesystem = $this->get_filesystem();
if ( ! defined( 'FS_CHMOD_DIR' ) ) {
define( 'FS_CHMOD_DIR', ( 0755 & ~ umask() ) );
}
// If the folder doesn't exist, create it.
if ( ! file_exists( $this->get_fonts_folder() ) ) {
$this->get_filesystem()->mkdir( $this->get_fonts_folder(), FS_CHMOD_DIR );
}
// If the file doesn't exist, create it. Return false if it can not be created.
if ( ! $filesystem->exists( $file_path ) && ! $filesystem->touch( $file_path ) ) {
return false;
}
// If we got this far, we need to write the file.
// Get the CSS.
if ( null === $this->css ) {
$this->get_styles();
}
// Put the contents in the file. Return false if that fails.
if ( ! $filesystem->put_contents( $file_path, $this->css ) ) {
return false;
}
return $file_path;
}
/**
* Get the stylesheet path.
*
* @since 3.6.0
* @return string
*/
public function get_local_stylesheet_path() {
if ( ! $this->local_stylesheet_path ) {
$this->local_stylesheet_path = $this->get_fonts_folder() . '/' . $this->get_local_stylesheet_filename() . '.css';
}
return $this->local_stylesheet_path;
}
/**
* Get the local stylesheet filename.
*
* This is a hash, generated from the site-URL, the wp-content path and the URL.
* This way we can avoid issues with sites changing their URL, or the wp-content path etc.
*
* @since 3.6.0
* @return string
*/
public function get_local_stylesheet_filename() {
return apply_filters( 'astra_local_font_file_name', 'astra-local-fonts' );
}
/**
* Set the font-format to be used.
*
* @since 3.6.0
* @param string $format The format to be used. Use "woff" or "woff2".
* @return void
*/
public function set_font_format( $format = 'woff2' ) {
$this->font_format = apply_filters( 'astra_local_google_fonts_format', $format );
}
/**
* Check if the local stylesheet exists.
*
* @since 3.6.0
* @return bool
*/
public function local_file_exists() {
return ( ! file_exists( $this->get_local_stylesheet_path() ) );
}
/**
* Get the base path.
*
* @since 3.6.0
* @return string
*/
public function get_base_path() {
if ( ! $this->base_path ) {
$this->base_path = apply_filters( 'astra_local_fonts_base_path', $this->get_filesystem()->wp_content_dir() );
}
return $this->base_path;
}
/**
* Get the base URL.
*
* @since 3.6.0
* @return string
*/
public function get_base_url() {
if ( ! $this->base_url ) {
$this->base_url = apply_filters( 'astra_local_fonts_base_url', content_url() );
}
return $this->base_url;
}
/**
* Get the subfolder name.
*
* @since 3.6.0
* @return string
*/
public function get_subfolder_name() {
if ( ! $this->subfolder_name ) {
$this->subfolder_name = apply_filters( 'astra_local_fonts_directory_name', 'astra-local-fonts' );
}
return $this->subfolder_name;
}
/**
* Returns the current blog id if current WordPress setup is a multisite setup.
*
* @since 4.6.0
* @return void|int Returns integer if current WP setup is multisite.
*/
public function get_current_blog_id() {
if ( ! is_multisite() ) {
return;
}
if ( ! $this->current_blog_id ) {
$this->current_blog_id = apply_filters( 'astra_local_fonts_current_blog_id', get_current_blog_id() );
}
return $this->current_blog_id;
}
/**
* Get the folder for fonts.
*
* @return string
*/
public function get_fonts_folder() {
if ( ! $this->fonts_folder ) {
$this->fonts_folder = $this->get_base_path();
if ( $this->get_subfolder_name() ) {
$this->fonts_folder .= '/' . $this->get_subfolder_name();
}
/**
* Fix: Local google fonts issue in multisite.
*
* This block of code primarily does 3 things:
* 1. Checks if we have blog id.
* 2. If we have blog id, then checks if subfolder "$this->get_subfolder_name()" exists. If not, creates the subfolder.
* 3. Then, finally, creates the child folders inside the subfolder by the current blog id.
*
* Ref: GH Issue: #5291, [AST-3438]
* @since 4.6.0
*/
if ( $this->get_current_blog_id() ) {
if ( ! file_exists( $this->fonts_folder ) ) {
// Lets create subfolder first if it does not exists.
$this->get_filesystem()->mkdir( $this->get_fonts_folder(), FS_CHMOD_DIR );
}
/**
* This helps us to isolate the google fonts files according to the sub sites.
* Which will also helps in isolated flushing of local font files without effecting
* the font files and generated css files of other sub sites.
*/
$this->fonts_folder .= '/' . $this->get_current_blog_id();
}
}
return $this->fonts_folder;
}
/**
* Schedule a cleanup.
*
* Deletes the fonts files on a regular basis.
* This way font files will get updated regularly,
* and we avoid edge cases where unused files remain in the server.
*
* @since 3.6.0
* @return void
*/
public function schedule_cleanup() {
if ( ! is_multisite() || ( is_multisite() && is_main_site() ) ) {
if ( ! wp_next_scheduled( 'astra_delete_fonts_folder' ) && ! wp_installing() ) {
wp_schedule_event( time(), self::CLEANUP_FREQUENCY, 'astra_delete_fonts_folder' ); // phpcs:ignore WPThemeReview.PluginTerritory.ForbiddenFunctions.cron_functionality_wp_schedule_event
}
}
}
/**
* Delete the fonts folder.
*
* This runs as part of a cleanup routine.
*
* @since 3.6.0
* @return bool
*/
public function astra_delete_fonts_folder() {
// Delete previously created supportive options.
astra_delete_option( 'astra_font_url' );
delete_site_option( 'astra_local_font_files' );
return $this->get_filesystem()->delete( $this->get_fonts_folder(), true );
}
/**
* Get the filesystem.
*
* @since 3.6.0
* @return \WP_Filesystem_Base
*/
protected function get_filesystem() {
// Using WP_Filesystem to manage the local download of fonts. This ensures proper functionality of the theme by handling file operations securely and consistently -- This is a TRT-recommended webfont library.
global $wp_filesystem;
// If the filesystem has not been instantiated yet, do it here.
if ( ! $wp_filesystem ) {
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' ); // PHPCS:ignore WPThemeReview.CoreFunctionality.FileInclude.FileIncludeFound
}
WP_Filesystem();
}
return $wp_filesystem;
}
}
/**
* Create instance of Astra_WebFont_Loader class.
*
* @param string $font_url Google font URL to set data.
* @return object
* @since 3.6.0
*/
function astra_webfont_loader_instance( $font_url = '' ) {
return new Astra_WebFont_Loader( $font_url );
}