summaryrefslogtreecommitdiff
path: root/inc/lib
diff options
context:
space:
mode:
authorZach van Rijn <me@zv.io>2021-07-21 14:54:07 -0500
committerZach van Rijn <me@zv.io>2021-07-21 14:54:07 -0500
commit9d4123cee1867ee7199b06bdc92d40611f547ecc (patch)
tree6d864e2725242863afed1f8ba12d9c7a9bc63a69 /inc/lib
downloadblog-ng-9d4123cee1867ee7199b06bdc92d40611f547ecc.tar.gz
blog-ng-9d4123cee1867ee7199b06bdc92d40611f547ecc.tar.bz2
blog-ng-9d4123cee1867ee7199b06bdc92d40611f547ecc.tar.xz
blog-ng-9d4123cee1867ee7199b06bdc92d40611f547ecc.zip
Initial unmodified import from Astra (Version: 3.6.5) @ /wp-content/themes/astra/.
Diffstat (limited to 'inc/lib')
-rw-r--r--inc/lib/batch-processing/class-astra-wp-async-request.php163
-rw-r--r--inc/lib/batch-processing/class-astra-wp-background-process.php506
-rw-r--r--inc/lib/notices/class-astra-notices.php365
-rw-r--r--inc/lib/notices/notices.js95
-rw-r--r--inc/lib/webfont/class-astra-webfont-loader.php685
5 files changed, 1814 insertions, 0 deletions
diff --git a/inc/lib/batch-processing/class-astra-wp-async-request.php b/inc/lib/batch-processing/class-astra-wp-async-request.php
new file mode 100644
index 0000000..eee0d6e
--- /dev/null
+++ b/inc/lib/batch-processing/class-astra-wp-async-request.php
@@ -0,0 +1,163 @@
+<?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
+ * @access protected
+ */
+ protected $prefix = 'wp';
+
+ /**
+ * Action
+ *
+ * (default value: 'async_request')
+ *
+ * @var string
+ * @access protected
+ */
+ protected $action = 'async_request';
+
+ /**
+ * Identifier
+ *
+ * @var mixed
+ * @access protected
+ */
+ protected $identifier;
+
+ /**
+ * Data
+ *
+ * (default value: array())
+ *
+ * @var array
+ * @access protected
+ */
+ 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();
+
+ }
+}
diff --git a/inc/lib/batch-processing/class-astra-wp-background-process.php b/inc/lib/batch-processing/class-astra-wp-background-process.php
new file mode 100644
index 0000000..98c7486
--- /dev/null
+++ b/inc/lib/batch-processing/class-astra-wp-background-process.php
@@ -0,0 +1,506 @@
+<?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
+ * @access protected
+ */
+ protected $action = 'background_process';
+
+ /**
+ * Start time of current process.
+ *
+ * (default value: 0)
+ *
+ * @var int
+ * @access protected
+ */
+ protected $start_time = 0;
+
+ /**
+ * Cron_hook_identifier
+ *
+ * @var mixed
+ * @access protected
+ */
+ protected $cron_hook_identifier;
+
+ /**
+ * Cron_interval_identifier
+ *
+ * @var mixed
+ * @access protected
+ */
+ 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
+ *
+ * @access public
+ * @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 ) {
+ $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;
+
+ $table = $wpdb->options;
+ $column = 'option_name';
+
+ if ( is_multisite() ) {
+ $table = $wpdb->sitemeta;
+ $column = 'meta_key';
+ }
+
+ $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
+
+ $count = $wpdb->get_var( $wpdb->prepare( "
+ SELECT COUNT(*)
+ FROM {$table}
+ WHERE {$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;
+
+ $table = $wpdb->options;
+ $column = 'option_name';
+ $key_column = 'option_id';
+ $value_column = 'option_value';
+
+ if ( is_multisite() ) {
+ $table = $wpdb->sitemeta;
+ $column = 'meta_key';
+ $key_column = 'meta_id';
+ $value_column = 'meta_value';
+ }
+
+ $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
+
+ $query = $wpdb->get_row( $wpdb->prepare( "
+ SELECT *
+ FROM {$table}
+ WHERE {$column} LIKE %s
+ ORDER BY {$key_column} ASC
+ LIMIT 1
+ ", $key ) );
+
+ $batch = new stdClass();
+ $batch->key = $query->$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 healthcheck
+ *
+ * @access public
+ * @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( __( '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 );
+
+ }
+}
diff --git a/inc/lib/notices/class-astra-notices.php b/inc/lib/notices/class-astra-notices.php
new file mode 100644
index 0000000..3b5743d
--- /dev/null
+++ b/inc/lib/notices/class-astra-notices.php
@@ -0,0 +1,365 @@
+<?php
+/**
+ * Astra Sites Notices
+ *
+ * Closing notice on click on `astra-notice-close` class.
+ *
+ * If notice has the data attribute `data-repeat-notice-after="%2$s"` then notice close for that SPECIFIC TIME.
+ * If notice has NO data attribute `data-repeat-notice-after="%2$s"` then notice close for the CURRENT USER FOREVER.
+ *
+ * > Create custom close notice link in the notice markup. E.g.
+ * `<a href="#" data-repeat-notice-after="<?php echo MONTH_IN_SECONDS; ?>" class="astra-notice-close">`
+ * It close the notice for 30 days.
+ *
+ * @package Astra Sites
+ * @since 1.4.0
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit; // Exit if accessed directly.
+}
+
+if ( ! class_exists( 'Astra_Notices' ) ) :
+
+ /**
+ * Astra_Notices
+ *
+ * @since 1.4.0
+ */
+ class Astra_Notices {
+
+ /**
+ * Notices
+ *
+ * @access private
+ * @var array Notices.
+ * @since 1.4.0
+ */
+ private static $version = '1.1.5';
+
+ /**
+ * Notices
+ *
+ * @access private
+ * @var array Notices.
+ * @since 1.4.0
+ */
+ private static $notices = array();
+
+ /**
+ * Instance
+ *
+ * @access private
+ * @var object Class object.
+ * @since 1.4.0
+ */
+ private static $instance;
+
+ /**
+ * Initiator
+ *
+ * @since 1.4.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.4.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.4.0
+ * @return Array
+ */
+ public function add_data_attributes( $allowedposttags, $context ) {
+ $allowedposttags['a']['data-repeat-notice-after'] = true;
+
+ return $allowedposttags;
+ }
+
+ /**
+ * Add Notice.
+ *
+ * @since 1.4.0
+ * @param array $args Notice arguments.
+ * @return void
+ */
+ public static function add_notice( $args = array() ) {
+ self::$notices[] = $args;
+ }
+
+ /**
+ * Dismiss Notice.
+ *
+ * @since 1.4.0
+ * @return void
+ */
+ public function dismiss_notice() {
+
+ if ( ! apply_filters( 'astra_notices_user_cap_check', current_user_can( 'manage_options' ) ) ) {
+ return;
+ }
+
+ $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'] ) : '';
+
+ 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.4.0
+ * @return void
+ */
+ public function enqueue_scripts() {
+ wp_register_script( 'astra-notices', self::_get_uri() . 'notices.js', array( 'jquery' ), self::$version, true );
+ wp_localize_script(
+ 'astra-notices',
+ 'astraNotices',
+ array(
+ '_notice_nonce' => wp_create_nonce( 'astra-notices' ),
+ )
+ );
+ }
+
+ /**
+ * Rating priority sort
+ *
+ * @since 1.5.2
+ * @param array $array1 array one.
+ * @param array $array2 array two.
+ * @return array
+ */
+ public function sort_notices( $array1, $array2 ) {
+ if ( ! isset( $array1['priority'] ) ) {
+ $array1['priority'] = 10;
+ }
+ if ( ! isset( $array2['priority'] ) ) {
+ $array2['priority'] = 10;
+ }
+
+ return $array1['priority'] - $array2['priority'];
+ }
+
+ /**
+ * Notice Types
+ *
+ * @since 1.4.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,
+ );
+
+ // Count for the notices that are rendered.
+ $notices_displayed = 0;
+
+ // sort the array with priority.
+ usort( self::$notices, array( $this, 'sort_notices' ) );
+
+ foreach ( self::$notices as $key => $notice ) {
+
+ $notice = wp_parse_args( $notice, $defaults );
+
+ $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;
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Markup Notice.
+ *
+ * @since 1.4.0
+ * @param array $notice Notice markup.
+ * @return void
+ */
+ public static function markup( $notice = array() ) {
+
+ wp_enqueue_script( '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 esc_attr( $notice['classes'] ); ?>" data-repeat-notice-after="<?php echo esc_attr( $notice['repeat-notice-after'] ); ?>">
+ <div class="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' );
+
+ }
+
+ /**
+ * Notice classes.
+ *
+ * @since 1.4.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 Notice ID.
+ *
+ * @since 1.4.0
+ *
+ * @param array $notice Notice arguments.
+ * @param int $key Notice array index.
+ * @return string Notice id.
+ */
+ private static function get_notice_id( $notice, $key ) {
+ if ( isset( $notice['id'] ) && ! empty( $notice['id'] ) ) {
+ return $notice['id'];
+ }
+
+ return 'astra-notices-id-' . $key;
+ }
+
+ /**
+ * Is notice expired?
+ *
+ * @since 1.4.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 URI
+ *
+ * @return mixed URL.
+ */
+ public static function _get_uri() { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
+ $path = wp_normalize_path( dirname( __FILE__ ) );
+ $theme_dir = wp_normalize_path( get_template_directory() );
+ $plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
+
+ if ( strpos( $path, $theme_dir ) !== false ) {
+ return trailingslashit( get_template_directory_uri() . str_replace( $theme_dir, '', $path ) );
+ } elseif ( strpos( $path, $plugin_dir ) !== false ) {
+ return plugin_dir_url( __FILE__ );
+ } elseif ( strpos( $path, dirname( plugin_basename( __FILE__ ) ) ) !== false ) {
+ return plugin_dir_url( __FILE__ );
+ }
+
+ return; // phpcs:ignore Squiz.PHP.NonExecutableCode.ReturnNotRequired
+ }
+
+ }
+
+ /**
+ * Kicking this off by calling 'get_instance()' method
+ */
+ Astra_Notices::get_instance();
+
+endif;
diff --git a/inc/lib/notices/notices.js b/inc/lib/notices/notices.js
new file mode 100644
index 0000000..3903b30
--- /dev/null
+++ b/inc/lib/notices/notices.js
@@ -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 );
diff --git a/inc/lib/webfont/class-astra-webfont-loader.php b/inc/lib/webfont/class-astra-webfont-loader.php
new file mode 100644
index 0000000..0a020ff
--- /dev/null
+++ b/inc/lib/webfont/class-astra-webfont-loader.php
@@ -0,0 +1,685 @@
+<?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.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $font_format = 'woff2';
+
+ /**
+ * The remote URL.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $remote_url;
+
+ /**
+ * Base path.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $base_path;
+
+ /**
+ * Base URL.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $base_url;
+
+ /**
+ * Subfolder name.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $subfolder_name;
+
+ /**
+ * The fonts folder.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $fonts_folder;
+
+ /**
+ * The local stylesheet's path.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $local_stylesheet_path;
+
+ /**
+ * The local stylesheet's URL.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $local_stylesheet_url;
+
+ /**
+ * The remote CSS.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @var string
+ */
+ protected $remote_styles;
+
+ /**
+ * The final CSS.
+ *
+ * @access protected
+ * @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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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 paths to URLs.
+ foreach ( $files as $remote => $local ) {
+ $files[ $remote ] = str_replace(
+ $this->get_base_path(),
+ $this->get_base_url(),
+ $local
+ );
+ }
+
+ $this->css = str_replace(
+ array_keys( $files ),
+ array_values( $files ),
+ $this->remote_styles
+ );
+
+ $this->write_stylesheet();
+
+ return $this->css;
+ }
+
+ /**
+ * Get local stylesheet contents.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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 ) );
+
+ // 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;
+ }
+
+ /**
+ * Download files mentioned in our CSS locally.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ */
+ 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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access protected
+ * @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 ( ! $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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @since 3.6.0
+ * @return bool
+ */
+ public function local_file_exists() {
+ return ( ! file_exists( $this->get_local_stylesheet_path() ) );
+ }
+
+ /**
+ * Get the base path.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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;
+ }
+
+ /**
+ * Get the folder for fonts.
+ *
+ * @access public
+ * @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();
+ }
+ }
+ 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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access public
+ * @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.
+ *
+ * @access protected
+ * @since 3.6.0
+ * @return \WP_Filesystem_Base
+ */
+ protected function get_filesystem() {
+ 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 );
+}