first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
<?php
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
/**
* Local Google Analytic
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2018 Webraftic Ltd
* @version 1.0
*/
class WGA_Plugin {
/**
* @see self::app()
* @var WCL_Plugin
*/
private static $app;
/**
* Конструктор
*
* Применяет конструктор родительского класса и записывает экземпляр текущего класса в свойство $app.
* Подробнее о свойстве $app см. self::app()
*
* @param string $plugin_path
* @param array $data
*
* @throws Exception
*/
public function __construct()
{
if( !class_exists('WCL_Plugin') ) {
throw new Exception('Plugin Clearfy is not installed!');
}
self::$app = WCL_Plugin::app();
$this->global_scripts();
if( is_admin() ) {
$this->init_activation();
$this->admin_scripts();
}
}
/**
* Статический метод для быстрого доступа к интерфейсу плагина.
*
* Позволяет разработчику глобально получить доступ к экземпляру класса плагина в любом месте
* плагина, но при этом разработчик не может вносить изменения в основной класс плагина.
*
* Используется для получения настроек плагина, информации о плагине, для доступа к вспомогательным
* классам.
*
* @return WCL_Plugin
*/
public static function app()
{
return self::$app;
}
/**
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
* @since 3.0.0
*/
private function init_activation()
{
require_once(WGA_PLUGIN_DIR . '/admin/activation.php');
self::app()->registerActivation('WGA_Activation');
}
/**
* @throws \Exception
* @since 3.0.0
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
*/
private function admin_scripts()
{
require(WGA_PLUGIN_DIR . '/admin/options.php');
require(WGA_PLUGIN_DIR . '/admin/boot.php');
}
/**
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
* @since 3.0.0
*/
private function global_scripts()
{
require(WGA_PLUGIN_DIR . '/includes/classes/class-configurate-ga.php');
new WGA_ConfigGACache(self::$app);
add_action('plugins_loaded', function () {
require(WGA_PLUGIN_DIR . '/includes/classes/class-scheduler.php');
new \WGA\Busting\Sheduller();
});
}
}

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,135 @@
<?php
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
/**
* Plugin class
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 19.02.2018, Webcraftic
*/
class WGA_Plugin extends Wbcr_Factory480_Plugin {
/**
* @see self::app()
* @var Wbcr_Factory480_Plugin
*/
private static $app;
/**
* @since 3.1.0
* @var array
*/
private $plugin_data;
/**
* Конструктор
*
* Применяет конструктор родительского класса и записывает экземпляр текущего класса в свойство $app.
* Подробнее о свойстве $app см. self::app()
*
* @param string $plugin_path
* @param array $data
*
* @throws Exception
*/
public function __construct($plugin_path, $data)
{
parent::__construct($plugin_path, $data);
self::$app = $this;
$this->plugin_data = $data;
$this->global_scripts();
if( is_admin() ) {
$this->init_activation();
$this->admin_scripts();
}
// Wordpress 6.7 fix
add_action( 'init', function () {
if ( is_admin() ) {
$this->register_pages();
}
} );
}
/**
* Статический метод для быстрого доступа к интерфейсу плагина.
*
* Позволяет разработчику глобально получить доступ к экземпляру класса плагина в любом месте
* плагина, но при этом разработчик не может вносить изменения в основной класс плагина.
*
* Используется для получения настроек плагина, информации о плагине, для доступа к вспомогательным
* классам.
*
* @return \Wbcr_Factory480_Plugin|\WGA_Plugin
*/
public static function app()
{
return self::$app;
}
/**
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
* @since 3.0.0
*/
private function init_activation()
{
require_once(WGA_PLUGIN_DIR . '/admin/activation.php');
self::app()->registerActivation('WGA_Activation');
}
/**
* Регистрирует классы страниц в плагине
*
* Мы указываем плагину, где найти файлы страниц и какое имя у их класса. Чтобы плагин
* выполнил подключение классов страниц. После регистрации, страницы будут доступные по url
* и в меню боковой панели администратора. Регистрируемые страницы будут связаны с текущим плагином
* все операции выполняемые внутри классов страниц, имеют отношение только текущему плагину.
*
* @throws \Exception
* @since 3.0.0
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
*/
private function register_pages()
{
if( $this->as_addon ) {
return;
}
if( $this->isNetworkActive() and !is_network_admin() ) {
return;
}
self::app()->registerPage('WGA_CachePage', WGA_PLUGIN_DIR . '/admin/pages/class-pages-general-settings.php');
self::app()->registerPage('WGA_MoreFeaturesPage', WGA_PLUGIN_DIR . '/admin/pages/class-pages-more-features.php');
}
/**
* @throws \Exception
* @since 3.1.0
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
*/
private function admin_scripts()
{
require(WGA_PLUGIN_DIR . '/admin/options.php');
require(WGA_PLUGIN_DIR . '/admin/boot.php');
}
/**
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
* @since 3.0.0
*/
private function global_scripts()
{
require(WGA_PLUGIN_DIR . '/includes/classes/class-configurate-ga.php');
new WGA_ConfigGACache(self::$app);
require(WGA_PLUGIN_DIR . '/includes/classes/class-scheduler.php');
new \WGA\Busting\Sheduller();
}
}

View File

@@ -0,0 +1,562 @@
<?php
namespace WGA\Busting;
use FilesystemIterator;
use IteratorIterator;
use RegexIterator;
/**
* This class configures the google analytics cache
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2020 CreativeMotion Ltd
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
abstract class Abstract_Cache {
/**
* Cache busting files base path
*
* @var string
*/
protected $busting_path;
/**
* Cache busting base URL
*
* @var string
*/
protected $busting_url;
/**
* Filename for the cache busting file.
*
* @var string
*/
protected $filename;
/**
* Filesystem object.
*
* @var object
* @since 3.2.0
*/
protected $filesystem = false;
/**
* Constructor.
*
* @param string $busting_path Path to the busting directory.
* @param string $busting_url URL of the busting directory.
* @since 3.2.0
*/
public function __construct($busting_path, $busting_url)
{
/*
* Define the timeouts for the connections. Only available after the constructor is called
* to allow for per-transport overriding of the default.
*/
if( !defined('FS_CONNECT_TIMEOUT') ) {
define('FS_CONNECT_TIMEOUT', 30);
}
if( !defined('FS_TIMEOUT') ) {
define('FS_TIMEOUT', 30);
}
// Set the permission constants if not already set.
if( !defined('FS_CHMOD_DIR') ) {
define('FS_CHMOD_DIR', (fileperms(ABSPATH) & 0777 | 0755));
}
if( !defined('FS_CHMOD_FILE') ) {
define('FS_CHMOD_FILE', (fileperms(ABSPATH . 'index.php') & 0777 | 0644));
}
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
$this->filesystem = new \WP_Filesystem_Direct(new \StdClass());
}
/**
* Tell if the cache busting should happen.
*
* @return bool
* @since 3.2.0
*
*/
private function is_allowed()
{
//if( defined('DONOTROCKETOPTIMIZE') && DONOTROCKETOPTIMIZE ) {
//return false;
//}
return true;
}
/**
* Saves the content of the URL to bust to the busting file.
*
* @param string $url URL to get the content from.
* @return bool
* @since 3.2.0
*/
public function refresh_save($url)
{
// Before doing anything, make sure the busting file can be created.
if( !$this->is_busting_dir_writable() ) {
return false;
}
// Get remote content.
$content = $this->get_remote_contents($url);
if( !$content ) {
// Could not get the remote contents.
return false;
}
$version = md5($content);
$path = $this->get_busting_file_path($version);
return $this->update_file_contents($path, $content);
}
/**
* Performs the replacement process.
*
* @param string $html HTML content.
* @return string
* @since 3.2.0
*/
abstract public function replace_url($html);
/**
* Searches for element(s) in the DOM
*
* @param string $pattern Pattern to match.
* @param string $html HTML content.
* @return string
* @since 3.2.0
*/
abstract protected function find($pattern, $html);
/**
* Saves the content of the URL to bust to the busting file if it doesn't exist yet.
*
* @param string $url URL to get the content from.
* @return bool
* @since 3.2.0
*/
public function save($url)
{
if( $this->get_busting_version() ) {
// We have a local copy.
\WGA_Plugin::app()->logger->info('Found local file. Busting path ' . $this->get_busting_path());
return true;
}
if( $this->refresh_save($url) ) {
// We downloaded a fresh copy.
\WGA_Plugin::app()->logger->info('New copy downloaded. Busting path ' . $this->get_busting_path());
return true;
}
return false;
}
/**
* Deletes the busting file.
*
* @return bool True on success. False on failure.
* @since 3.2.0
*/
public function delete()
{
$files = $this->get_all_files();
if( false === $files ) {
// Error.
return false;
}
$this->file_version = null;
if( !$files ) {
// No local files yet.
return true;
}
return $this->delete_files(array_keys($files));
}
/**
* Get the version of the current busting file.
*
* @return string|bool Version of the file. False if the file does not exist.
*
*
* @since 3.2.0
*/
protected function get_busting_version()
{
if( !empty($this->file_version) ) {
return $this->file_version;
}
$files = $this->get_all_files();
if( !$files ) {
return false;
}
$this->file_version = reset($files);
return $this->file_version;
}
/**
* Get all cached files in the directory.
* In a perfect world, there should be only one.
*
* @return bool|array A list of file names (as array keys) and versions (as array values). False on failure.
* @since 3.2.0
* @access private
*
*/
private function get_all_files()
{
$dir_path = rtrim($this->busting_path, '\\/');
if( !$this->filesystem->exists($dir_path) ) {
return [];
}
if( !$this->filesystem->is_readable($dir_path) ) {
\WGA_Plugin::app()->logger->error('Directory is not readable. Path ' . $dir_path);
return false;
}
$pattern = '/' . sprintf($this->escape_file_name($this->filename_pattern), '([a-f0-9]{32}|local)') . '/';
$entries = $this->get_dir_files_by_regex($dir_path, $pattern);
$list = [];
foreach($entries as $entry) {
$filename = $entry->getFilename();
preg_match($pattern, $filename, $file_details_match);
if( !empty($file_details_match[1]) ) {
$list[$filename] = $file_details_match[1];
}
}
return $list;
}
/**
* Get the final URL for the current cache busting file.
*
* @return string|bool URL of the file. False if the file does not exist.
* @since 3.2.0
* @access protected
*
*/
public function get_busting_url()
{
return $this->get_busting_file_url($this->get_busting_version());
}
/**
* Get the path to the current cache busting file.
*
* @return string|bool URL of the file. False if the file does not exist.
*
*
* @since 3.2.0
* @access protected
*/
protected function get_busting_path()
{
return $this->get_busting_file_path($this->get_busting_version());
}
/**
* Get the final URL for a cache busting file.
*
* @param string $version The file version.
* @return string|bool URL of the file with this version. False if no versions are provided.
* @since 3.2.0
* @access private
*
*
*/
private function get_busting_file_url($version)
{
if( !$version ) {
return false;
}
$filename = $this->get_busting_file_name($version);
return $this->busting_url . $filename;
}
/**
* Get the local file name.
*
* @param string $version The file version.
* @return string|bool The name of the file with this version. False if no versions are provided.
* @since 3.2.0
* @access private
*/
private function get_busting_file_name($version)
{
if( !$version ) {
return false;
}
return sprintf($this->filename_pattern, $version);
}
/**
* Get the local file path.
*
* @param string $version The file version.
* @return string|bool Path to the file with this version. False if no versions are provided.
* @since 3.2.0
* @access private
*/
private function get_busting_file_path($version)
{
if( !$version ) {
return false;
}
return $this->busting_path . $this->get_busting_file_name($version);
}
/**
* Escape a file name, to be used in a regex pattern (delimiter is `/`).
* `%s` conversion specifications are protected.
*
* @param string $filename_pattern The file name.
* @return string
* @since 3.2.0
* @access private
*
*/
private function escape_file_name($filename_pattern)
{
return preg_quote($filename_pattern, '/');
}
/**
* Delete busting files.
*
* @param array $files A list of file names.
* @return bool True if files have been deleted (or no files have been provided). False on failure.
* @since 3.2.0
*/
private function delete_files($files)
{
if( !$files ) {
return true;
}
$has_deleted = false;
$error_paths = [];
foreach($files as $file_name) {
if( !$this->filesystem->delete($this->busting_path . $file_name, false, 'f') ) {
$error_paths[] = $this->busting_path . $file_name;
} else {
$has_deleted = true;
}
}
if( $error_paths ) {
\WGA_Plugin::app()->logger->error('Local file(s) could not be deleted. Path ' . $error_paths);
}
return $has_deleted;
}
/**
* Add new contents to a file. If the file doesn't exist, it is created.
*
* @param string $file_path Path to the file to update.
* @param string $file_contents New contents.
* @return string|bool The file contents on success. False on failure.
*
*
* @since 3.2.0
* @access private
*/
private function update_file_contents($file_path, $file_contents)
{
if( !$this->is_busting_dir_writable() ) {
return false;
}
if( !$this->filesystem->put_contents($file_path, $file_contents) ) {
\WGA_Plugin::app()->logger->error('Contents could not be written into file. Path ' . $file_path);
return false;
}
return $file_contents;
}
/**
* Tell if the directory containing the busting file is writable.
*
* @return bool
*
*
* @since 3.2
* @access private
*/
private function is_busting_dir_writable()
{
if( !$this->filesystem->exists($this->busting_path) ) {
wp_mkdir_p($this->busting_path);
}
if( !$this->filesystem->is_writable($this->busting_path) ) {
\WGA_Plugin::app()->logger->error('Directory is not writable. Paths ' . $this->busting_path);
return false;
}
return true;
}
/** ----------------------------------------------------------------------------------------- */
/** GET LOCAL/REMOTE CONTENTS =============================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get a file contents. If the file doesn't exist, new contents are fetched remotely.
*
* @param string $file_path Path to the file.
* @param string $file_url URL to the remote file.
* @return string|bool The contents on success, false on failure.
*
* @since 3.2.0
*/
private function get_file_or_remote_contents($file_path, $file_url)
{
$content = $this->get_file_contents($file_path);
if( $content ) {
// We have a local file.
return $content;
}
return $this->get_remote_contents($file_url);
}
/**
* Get a file contents.
*
* @param string $file_path Path to the file.
* @return string|bool The contents on success, false on failure.
* @since 3.2.0
* @access private
*
*
*/
private function get_file_contents($file_path)
{
if( !$this->filesystem->exists($file_path) ) {
\WGA_Plugin::app()->logger->error('Local file does not exist. Path ' . $file_path);
return false;
}
if( !$this->filesystem->is_readable($file_path) ) {
\WGA_Plugin::app()->logger->error('Local file is not readable. Path' . $file_path);
return false;
}
$content = $this->filesystem->get_contents($file_path);
if( !$content ) {
\WGA_Plugin::app()->logger->error('Local file is empty. Path' . $file_path);
return false;
}
return $content;
}
/**
* Get the contents of a URL.
*
* @param string $url The URL to request.
* @return string|bool The contents on success. False on failure.
* @since 3.2.0
* @access private
*
*
*/
private function get_remote_contents($url)
{
try {
$response = wp_remote_get($url);
} catch( \Exception $e ) {
\WGA_Plugin::app()->logger->error('Remote file could not be fetched. Response ' . $e->getMessage());
return false;
}
if( is_wp_error($response) ) {
\WGA_Plugin::app()->logger->error('Remote file could not be fetched. Response ' . $response->get_error_message());
return false;
}
$contents = wp_remote_retrieve_body($response);
if( !$contents ) {
\WGA_Plugin::app()->logger->error('Remote file could not be fetched. Response ' . $response->get_error_message());
return false;
}
return $contents;
}
/**
* Gets Directory files matches regex.
*
* @param string $dir Directory to search for files inside it.
* @param string $regex Regular expression for files need to be searched for.
*
* @return array|RegexIterator List of files matches this regular expression.
* @since 3.6.3
* @access private
*
*/
function get_dir_files_by_regex($dir, $regex)
{ // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
try {
$iterator = new IteratorIterator(new FilesystemIterator($dir));
return new RegexIterator($iterator, $regex);
} catch( \Exception $e ) {
return [];
}
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
* This class configures the google analytics cache
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2017 Webraftic Ltd
* @version 1.0
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class WGA_ConfigGACache extends WBCR\Factory_Templates_134\Configurate {
public function registerActionsAndFilters() {
if ( $this->getPopulateOption( 'ga_cache' ) ) {
add_filter( 'cron_schedules', [ $this, 'cron_additions' ] );
// Load update script to schedule in wp_cron.
add_action( 'wbcr/gac/update_analytic_library', [ $this, 'update_local_analytic' ] );
if ( ! is_admin() ) {
$this->add_google_analitics_script();
}
}
}
/**
* Extends the recurrence interval of cron tasks. In the core,
* the number of recurrence intervals for cron tasks is limited.
* Therefore, we create 3 additional recurrences weekly,
* twicemonthly, monthly.
*
* @param array $schedules an array of already recorded recurrences
*
* @return mixed
*/
public function cron_additions( $schedules ) {
$schedules['weekly'] = [
'interval' => DAY_IN_SECONDS * 7,
'display' => __( 'Once Weekly' ),
];
$schedules['twicemonthly'] = [
'interval' => DAY_IN_SECONDS * 14,
'display' => __( 'Twice Monthly' ),
];
$schedules['monthly'] = [
'interval' => DAY_IN_SECONDS * 30,
'display' => __( 'Once Monthly' ),
];
return $schedules;
}
/**
* Enables update-local-ga.php, which creates and updates
* the Google analytics library locally on the user's site.
* This method performs via cron and manually if the library
* file has not yet been created.
*
* @since 3.0.1
* @return void
*/
public function update_local_analytic() {
include( WGA_PLUGIN_DIR . '/includes/update-local-ga.php' );
}
/**
* Generates tracking code based on the user options set. Then it just
* prints this code on the page. The code can be printed to the header
* or footer, depending on which action called this method.
*
* @since 3.0.1
* @return void
*/
public function print_google_analytics() {
$tracking_id = $this->getPopulateOption( 'ga_tracking_id' );
$track_admin = (int) $this->getPopulateOption( 'ga_track_admin' );
// If user is admin we don't want to render the tracking code, when option is disabled.
if ( empty( $tracking_id ) || ( current_user_can( 'manage_options' ) && ( ! $track_admin ) ) ) {
return;
}
$adjusted_bounce_rate = (int) $this->getPopulateOption( 'ga_adjusted_bounce_rate', 0 );
$anonymize_ip = (int) $this->getPopulateOption( 'ga_anonymize_ip', 0 );
$disable_display_features = (int) $this->getPopulateOption( 'ga_disable_display_features', 0 );
echo "<!-- Google Analytics Local by " . $this->plugin->getPluginTitle() . " -->" . PHP_EOL;
echo "<script>" . PHP_EOL;
echo "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','" . esc_url( $this->get_local_analytic_file_url() ) . "','ga');" . PHP_EOL;
/**
* Allows you to complement the current configuration analytics.
* For example, one of the users wanted to add google adwords ID
* to this code.
*
* When using this action, you must enter only javascript code,
* without opening and closing tags.
*
* @since 3.0.1
*/
do_action( 'wbcr/gac/print_analytic_options', [
'tracking_id' => $tracking_id,
'track_admin' => $track_admin,
'adjusted_bounce_rate' => $adjusted_bounce_rate,
'anonymize_ip' => $anonymize_ip,
'disable_display_features' => $disable_display_features
] );
echo "ga('create', '" . esc_attr( $tracking_id ) . "', 'auto');" . PHP_EOL;
echo $disable_display_features ? "ga('set', 'displayFeaturesTask', null);" . PHP_EOL : '';
echo $anonymize_ip ? "ga('set', 'anonymizeIp', true);" . PHP_EOL : '';
echo "ga('send', 'pageview');";
echo $adjusted_bounce_rate ? PHP_EOL . 'setTimeout("ga(' . "'send','event','adjusted bounce rate','" . $adjusted_bounce_rate . " seconds')" . '"' . ',' . $adjusted_bounce_rate * 1000 . ');' : '';
echo PHP_EOL . '</script>' . PHP_EOL;
echo "<!-- end Google Analytics Local by " . $this->plugin->getPluginTitle() . " -->" . PHP_EOL;
}
/**
* Inserts tracking code in header and footer. Before insertion,
* it executes the wbcr_ga_update_local_script action to update
* Google local analytics library.
*
* @since 3.0.1
* @return void
*/
private function add_google_analitics_script() {
$tracking_id = $this->getPopulateOption( 'ga_tracking_id' );
if ( ! empty( $tracking_id ) ) {
$local_ga_file = $this->get_local_analytic_file_path();
// If file is not created yet, create now!
if ( $local_ga_file && ! file_exists( $local_ga_file ) ) {
ob_start();
$this->update_local_analytic();
ob_end_clean();
}
$enqueue_order = $this->getPopulateOption( 'ga_enqueue_order', 0 );
if ( $this->getPopulateOption( 'ga_script_position', 'footer' ) == 'header' ) {
add_action( 'wp_head', [ $this, 'print_google_analytics' ], $enqueue_order );
} else {
add_action( 'wp_footer', [ $this, 'print_google_analytics' ], $enqueue_order );
}
}
}
/**
* Get uploads dir
*
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
* @since 3.1.1
*/
private function get_uploads_dir() {
$upload_dir = wp_upload_dir();
if ( true === $upload_dir['error'] ) {
return null;
}
return (object) $upload_dir;
}
/**
* Get the path to an encrypted google analytics file
*
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
* @since 3.1.1
* @return string|null
*/
private function get_local_analytic_file_url() {
if ( $this->get_uploads_dir() ) {
return untrailingslashit( $this->get_uploads_dir()->baseurl ) . '/wga-cache/local-ga.js';
}
return null;
}
/**
* Get the url to an encrypted google analytics file
*
* @author Alexander Kovalev <alex.kovalevv@gmail.com>
* @since 3.1.1
* @return string|null
*/
private function get_local_analytic_file_path() {
if ( $this->get_uploads_dir() ) {
return untrailingslashit( $this->get_uploads_dir()->basedir ) . '/wga-cache/local-ga.js';
}
return null;
}
}

View File

@@ -0,0 +1,555 @@
<?php
namespace WGA\Busting;
/**
* This class configures the google analytics cache
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2017 Webraftic Ltd
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
class Facebook_SDK {
/**
* Facebook SDK URL.
* %s is a locale like "en_US".
*
* @var string
* @since 3.2.0
*/
protected $url = 'https://connect.facebook.net/%s/sdk.js';
/**
* Filename for the cache busting file.
* %s is a locale like "en_US".
*
* @var string
* @since 3.2.0
*/
protected $filename = 'fbsdk-%s.js';
/**
* Flag to track the replacement.
*
* @var bool
* @since 3.2.0
*/
protected $is_replaced = false;
/**
* Filesystem object.
*
* @var object
* @since 3.2.0
*/
protected $filesystem = false;
/**
* Constructor.
*
* @param string $busting_path Path to the busting directory.
* @param string $busting_url URL of the busting directory.
* @since 3.2.0
*
*/
public function __construct($busting_path, $busting_url)
{
/** Warning: the file name and script URL are dynamic, and must be run through sprintf(). */
$this->busting_path = $busting_path . 'facebook-tracking/';
$this->busting_url = $busting_url . 'facebook-tracking/';
/*
* Define the timeouts for the connections. Only available after the constructor is called
* to allow for per-transport overriding of the default.
*/
if( !defined('FS_CONNECT_TIMEOUT') ) {
define('FS_CONNECT_TIMEOUT', 30);
}
if( !defined('FS_TIMEOUT') ) {
define('FS_TIMEOUT', 30);
}
// Set the permission constants if not already set.
if( !defined('FS_CHMOD_DIR') ) {
define('FS_CHMOD_DIR', (fileperms(ABSPATH) & 0777 | 0755));
}
if( !defined('FS_CHMOD_FILE') ) {
define('FS_CHMOD_FILE', (fileperms(ABSPATH . 'index.php') & 0777 | 0644));
}
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
$this->filesystem = new \WP_Filesystem_Direct(new \StdClass());
}
/**
* Perform the URL replacement process.
*
* @param string $html HTML contents.
* @return string HTML contents.
* @since 3.2.0
*
*/
public function replace_url($html)
{
$this->is_replaced = false;
$tag = $this->find('<script[^>]*?>(.*)<\/script>', $html);
if( !$tag ) {
return $html;
}
\WGA_Plugin::app()->logger->info('FACEBOOK SDK CACHING PROCESS STARTED. Tag ' . $tag);
$locale = $this->get_locale_from_url($tag);
$remote_url = $this->get_url($locale);
if( !$this->save($remote_url) ) {
return $html;
}
$file_url = $this->get_busting_file_url($locale);
$replace_tag = preg_replace('@(?:https?:)?//connect\.facebook\.net/[a-zA-Z_-]+/sdk\.js@i', $file_url, $tag, -1, $count);
if( !$count || false === strpos($html, $tag) ) {
\WGA_Plugin::app()->logger->error('The facebook sdk local file URL could not be replaced in the page contents.');
return $html;
}
$html = str_replace($tag, $replace_tag, $html);
$file_path = $this->get_busting_file_path($locale);
$xfbml = $this->get_xfbml_from_url($tag); // Default value should be set to false.
$app_id = $this->get_appId_from_url($tag); // APP_ID is the only required value.
$url_version = $this->get_version_from_url($tag);
$version = false === $url_version ? 'v5.0' : $url_version; // If version is not available set it to the latest: v.5.0.
if( false !== $app_id ) {
// Add FB async init.
$fb_async_script = '<script>window.fbAsyncInit = function fbAsyncInit () {FB.init({appId: \'' . $app_id . '\',xfbml: ' . $xfbml . ',version: \'' . $version . '\'})}</script>';
$html = str_replace('</body>', $fb_async_script . '</body>', $html);
}
$this->is_replaced = true;
\WGA_Plugin::app()->logger->info('Facebook SDK caching process succeeded. File ' . $file_path);
return $html;
}
/**
* Tell if the replacement was sucessful or not.
*
* @return bool
* @since 3.2.0
*
*/
public function is_replaced()
{
return $this->is_replaced;
}
/**
* Search for an element in the DOM.
*
* @param string $pattern Pattern to match.
* @param string $html HTML contents.
* @return string|bool The matched HTML on success. False if nothing is found.
* @since 3.2.0
*
*/
protected function find($pattern, $html)
{
preg_match_all('/' . $pattern . '/Umsi', $html, $matches, PREG_SET_ORDER);
if( empty($matches) ) {
return false;
}
foreach($matches as $match) {
if( trim($match[1]) && preg_match('@//connect\.facebook\.net/[a-zA-Z_-]+/sdk\.js@i', $match[1]) ) {
return $match[0];
}
}
return false;
}
/**
* Save the contents of a URL into a local file if it doesn't exist yet.
*
* @param string $url URL to get the contents from.
* @return bool True on success. False on failure.
* @since 3.2.0
*
*/
public function save($url)
{
$locale = $this->get_locale_from_url($url);
$path = $this->get_busting_file_path($locale);
if( $this->filesystem->exists($path) ) {
// If a previous version is present, keep it.
return true;
}
return $this->refresh_save($url);
}
/**
* Save the contents of a URL into a local file.
*
* @param string $url URL to get the contents from.
* @return bool True on success. False on failure.
* @since 3.2.0
*
*/
public function refresh_save($url)
{
$content = $this->get_file_content($url);
if( !$content ) {
// Error, we couldn't fetch the file contents.
return false;
}
$locale = $this->get_locale_from_url($url);
$path = $this->get_busting_file_path($locale);
return (bool)$this->update_file_contents($path, $content);
}
/**
* Add new contents to a file. If the file doesn't exist, it is created.
*
* @param string $file_path Path to the file to update.
* @param string $file_contents New contents.
* @return string|bool The file contents on success. False on failure.
* @since 3.2.0
*
*/
private function update_file_contents($file_path, $file_contents)
{
if( !$this->filesystem->exists($this->busting_path) ) {
wp_mkdir_p($this->busting_path);
}
if( !$this->filesystem->put_contents($file_path, $file_contents) ) {
\WGA_Plugin::app()->logger->error('Contents could not be written into file. File path ' . $file_path);
return false;
}
return $file_contents;
}
/**
* Look for existing local files and update their contents if there's a new version available.
* Actually, if a more recent version exists on the FB side, it will delete all local files and hit the home page to recreate them.
*
* @return bool True on success. False on failure.
* @since 3.2.0
*
*/
public function refresh()
{
$files = $this->get_files();
if( !$files ) {
// No files (or there's an error).
return false !== $files;
}
$error_paths = [];
$pattern = $this->escape_file_name($this->filename);
$pattern = sprintf($pattern, '(?<locale>[a-zA-Z_-]+)');
foreach($files as $file) {
preg_match('/^' . $pattern . '$/', $file, $matches);
$remote_url = $this->get_url($matches['locale']);
if( !$this->refresh_save($remote_url) ) {
$error_paths[] = $this->get_busting_file_path($matches['locale']);
}
}
if( $error_paths ) {
\WGA_Plugin::app()->logger->error('Local file(s) could not be updated. Paths ' . $error_paths);
}
return !$error_paths;
}
/**
* Delete all Facebook SDK busting files.
*
* @return bool True on success. False on failure.
* @since 3.2.0
*
*/
public function delete()
{
$filesystem = $this->filesystem;
$files = $this->get_files();
if( !$files ) {
// No files (or there's an error).
return false !== $files;
}
$error_paths = [];
foreach($files as $file_name) {
if( !$filesystem->delete($this->busting_path . $file_name, false, 'f') ) {
$error_paths[] = $this->busting_path . $file_name;
}
}
if( $error_paths ) {
\WGA_Plugin::app()->logger->error('Local file(s) could not be deleted. Paths ' . $error_paths);
}
return !$error_paths;
}
/**
* Get all cached files in the directory.
*
* @return array|bool A list of file names. False on failure.
* @since 3.2.0
*
*/
private function get_files()
{
$filesystem = $this->filesystem;
$dir_path = rtrim($this->busting_path, '\\/');
if( !$filesystem->exists($dir_path) ) {
return [];
}
if( !$filesystem->is_writable($dir_path) ) {
\WGA_Plugin::app()->logger->error('Facebook sdk: Directory is not writable. Path ' . $dir_path);
return false;
}
$dir = $filesystem->dirlist($dir_path);
if( false === $dir ) {
\WGA_Plugin::app()->logger->error('Facebook sdk: Could not get the directory contents. Path ' . $dir_path);
return false;
}
if( !$dir ) {
return [];
}
$list = [];
$pattern = $this->escape_file_name($this->filename);
$pattern = sprintf($pattern, '[a-zA-Z_-]+');
foreach($dir as $entry) {
if( 'f' !== $entry['type'] ) {
continue;
}
if( preg_match('/^' . $pattern . '$/', $entry['name'], $matches) ) {
$list[$entry['name']] = $entry['name'];
}
}
return $list;
}
/**
* Get the remote Facebook SDK URL.
*
* @param string $locale A locale string, like 'en_US'.
* @return string
* @since 3.2.0
*
*/
public function get_url($locale)
{
return sprintf($this->url, $locale);
}
/**
* Extract the locale from a URL to bust.
*
* @param string $url Any string containing the URL to bust.
* @return string|bool The locale on success. False on failure.
* @since 3.2.0
*
*/
private function get_locale_from_url($url)
{
$pattern = '@//connect\.facebook\.net/(?<locale>[a-zA-Z_-]+)/sdk\.js@i';
if( !preg_match($pattern, $url, $matches) ) {
return false;
}
return $matches['locale'];
}
/**
* Extract XFBML from a URL to bust.
*
* @param string $url Any string containing the URL to bust.
* @return string|bool The XFBML on success. False on failure.
* @since 3.4.3
*
*/
private function get_xfbml_from_url($url)
{
$pattern = '@//connect\.facebook\.net/(?<locale>[a-zA-Z_-]+)/sdk\.js#(?:.+&)?xfbml=(?<xfbml>[0-9]+)@i';
if( !preg_match($pattern, $url, $matches) ) {
return false;
}
return $matches['xfbml'];
}
/**
* Extract appId from a URL to bust.
*
* @param string $url Any string containing the URL to bust.
* @return string|bool The appId on success. False on failure.
* @since 3.4.3
*
*/
private function get_appId_from_url($url)
{
$pattern = '@//connect\.facebook\.net/(?<locale>[a-zA-Z_-]+)/sdk\.js#(?:.+&)?appId=(?<appId>[0-9]+)@i';
if( !preg_match($pattern, $url, $matches) ) {
return false;
}
return $matches['appId'];
}
/**
* Extract version from a URL to bust.
*
* @param string $url Any string containing the URL to bust.
* @return string|bool The version on success. False on failure.
* @since 3.4.3
*
*/
private function get_version_from_url($url)
{
$pattern = '@//connect\.facebook\.net/(?<locale>[a-zA-Z_-]+)/sdk\.js#(?:.+&)?version=(?<version>[a-zA-Z0-9.]+)@i';
if( !preg_match($pattern, $url, $matches) ) {
return false;
}
return $matches['version'];
}
/**
* Get the local Facebook SDK URL.
*
* @param string $locale A locale string, like 'en_US'.
* @return string
* @since 3.2.0
*
*/
private function get_busting_file_url($locale)
{
$filename = $this->get_busting_file_name($locale);
return $this->busting_url . $filename;
}
/**
* Get the local Facebook SDK file name.
*
* @param string $locale A locale string, like 'en_US'.
* @return string
* @since 3.2.0
*
*/
private function get_busting_file_name($locale)
{
return sprintf($this->filename, $locale);
}
/**
* Get the local Facebook SDK file path.
*
* @param string $locale A locale string, like 'en_US'.
* @return string
* @since 3.2.0
*
*/
private function get_busting_file_path($locale)
{
return $this->busting_path . $this->get_busting_file_name($locale);
}
/**
* Get the contents of a URL.
*
* @param string $url The URL to request.
* @return string|bool The contents on success. False on failure.
* @since 3.2.0
*
*/
protected function get_file_content($url)
{
try {
$response = wp_remote_get($url);
} catch( \Exception $e ) {
\WGA_Plugin::app()->logger->error('Remote file could not be fetched. Response ' . $e->getMessage());
return false;
}
if( is_wp_error($response) ) {
\WGA_Plugin::app()->logger->error('Remote file could not be fetched. Response ' . $response->get_error_message());
return false;
}
$contents = wp_remote_retrieve_body($response);
if( !$contents ) {
\WGA_Plugin::app()->logger->error('Remote file could not be fetched. Response ' . $response->get_error_message());
return false;
}
return $contents;
}
/**
* Escape a file name, to be used in a regex pattern (delimiter is `/`).
* `%s` conversion specifications are protected.
*
* @param string $file_name The file name.
* @return string
* @since 3.2.0
*
*/
private function escape_file_name($file_name)
{
$file_name = explode('%s', $file_name);
$file_name = array_map('preg_quote', $file_name);
return implode('%s', $file_name);
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace WGA\Busting;
/**
* This class configures the google analytics cache
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2017 Webraftic Ltd
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
class Google_Analytics_Cache extends Abstract_Cache {
/**
* Google Analytics URL.
*
* @var string
* @since 3.2.0
*/
protected $url = 'https://www.google-analytics.com/analytics.js';
/**
* File name (local).
* %s is a "version": a md5 hash of the file contents.
*
* @var string
* @since 3.2.0
*/
protected $filename_pattern = 'ga-%s.js';
/**
* Current file version (local): a md5 hash of the file contents.
*
* @var string
* @since 3.2.0
*/
protected $file_version;
/**
* Flag to track the replacement.
*
* @var bool
* @since 3.2.0
*/
protected $is_replaced = false;
/**
* Constructor.
*
* @param string $busting_path Path to the busting directory.
* @param string $busting_url URL of the busting directory.
* @since 3.1
* @access public
*
*/
public function __construct($busting_path, $busting_url)
{
parent::__construct($busting_path, $busting_url);
$this->busting_path = $busting_path . 'google-tracking/';
$this->busting_url = $busting_url . 'google-tracking/';
}
/**
* Performs the replacement process.
*
* @param string $html HTML content.
* @return string
* @since 3.2.0
*
*/
public function replace_url($html)
{
$this->is_replaced = false;
$tag = $this->find('<script\s*(?<attr>[^>]*)?>(?<content>[^<]+)?<\/script>', $html);
if( !$tag ) {
return $html;
}
\WGA_Plugin::app()->logger->info('GOOGLE ANALYTICS CACHING PROCESS STARTED. TAG #' . $tag);
if( !$this->save($this->url) ) {
return $html;
}
$replace_tag = preg_replace('/(?:https?:)?\/\/www\.google-analytics\.com\/analytics\.js/i', $this->get_busting_url(), $tag);
$html = str_replace($tag, $replace_tag, $html);
$this->is_replaced = true;
\WGA_Plugin::app()->logger->info('Google Analytics caching process succeeded. Busting path ' . $this->get_busting_path());
return $html;
}
/**
* Tell if the replacement was sucessful or not.
*
* @return bool
*
* @since 3.2.0
*/
public function is_replaced()
{
return $this->is_replaced;
}
/**
* Get the Google Analytics URL.
*
* @return string
* @author Remy Perona
*
* @since 3.1
* @access public
*/
public function get_url()
{
return $this->url;
}
/**
* Searches for element(s) in the DOM.
*
* @param string $pattern Pattern to match.
* @param string $html HTML content.
* @return string
* @author Remy Perona
*
* @since 3.1
* @access public
*/
protected function find($pattern, $html)
{
preg_match_all('/' . $pattern . '/is', $html, $all_matches, PREG_SET_ORDER);
$matches = array_map(function ($match) {
if( empty($match['content']) || (!preg_match('/src\s*=\s*[\'"]\s*(?:https?:)?\/\/www\.google-analytics\.com\/analytics\.js\s*[\'"]/i', $match['attr'] . $match['content']) && false === strpos($match['content'], 'GoogleAnalyticsObject')) ) {
return;
}
return $match[0];
}, $all_matches);
$matches = array_values(array_filter($matches));
if( !$matches ) {
return false;
}
return $matches[0];
}
}

View File

@@ -0,0 +1,156 @@
<?php
namespace WGA\Busting;
/**
* This class configures the google analytics cache
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2017 Webraftic Ltd
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
class Google_Tag_Manager_Cache extends Abstract_Cache {
/**
* Context used for the logger.
*
* @var string
* @since 3.2.4
*/
const LOGGER_CONTEXT = 'gg tag manager';
/**
* File name (local).
* %s is a "version": a md5 hash of the file contents.
*
* @var string
* @since 3.2.4
* @access protected
*/
protected $filename_pattern = 'gtm-%s.js';
/**
* Current file version (local): a md5 hash of the file contents.
*
* @var string
* @since 3.2.4
* @access protected
*/
protected $file_version;
/**
* Filesystem object.
*
* @var object
* @since 3.2.4
* @access protected
*/
protected $filesystem = false;
/**
* Google Analytics object.
*
* @var object
* @since 3.2.4
* @access protected
*/
protected $ga_busting = false;
/**
* Constructor.
*
* @param string $busting_path Path to the busting directory.
* @param string $busting_url URL of the busting directory.
* @param Google_Analytics_Cache $ga_busting A GoogleAnalytics instance.
* @since 3.1
* @access public
*
*/
public function __construct($busting_path, $busting_url, Google_Analytics_Cache $ga_busting)
{
parent::__construct($busting_path, $busting_url);
$blog_id = get_current_blog_id();
$this->busting_path = $busting_path . $blog_id . '/';
$this->busting_url = $busting_url . $blog_id . '/';
$this->ga_busting = $ga_busting;
}
/**
* Performs the replacement process.
*
* @param string $html HTML content.
* @return string
* @since 3.1
* @access public
*
*/
public function replace_url($html)
{
$script = $this->find('<script(\s+[^>]+)?\s+src\s*=\s*[\'"]\s*?((?:https?:)?\/\/www\.googletagmanager\.com(?:.+)?)\s*?[\'"]([^>]+)?\/?>', $html);
if( !$script ) {
return $html;
}
// replace relative protocol // with full https://.
$gtm_url = preg_replace('/^\/\//', 'https://', $script[2]);
\WGA_Plugin::app()->logger->info('GOOGLE TAG MANAGER CACHING PROCESS STARTED. Script ' . $script);
if( !$this->save($gtm_url) ) {
return $html;
}
$replace_script = str_replace($script[2], $this->get_busting_url(), $script[0]);
$replace_script = str_replace('<script', '<script data-no-minify="1"', $replace_script);
$html = str_replace($script[0], $replace_script, $html);
\WGA_Plugin::app()->logger->info('Google Tag Manager caching process succeeded. Path ' . $this->get_busting_path());
return $html;
}
/**
* Searches for element(s) in the DOM.
*
* @param string $pattern Pattern to match.
* @param string $html HTML content.
* @return string
* @since 3.1
* @access public
*
*/
protected function find($pattern, $html)
{
preg_match_all('/' . $pattern . '/Umsi', $html, $matches, PREG_SET_ORDER);
if( empty($matches) ) {
return false;
}
return $matches[0];
}
/**
* Replaces the Google Analytics URL by the local copy inside the gtm-local.js file content
*
* @param string $content JavaScript content.
* @return string
* @since 3.1
*
*/
protected function replace_ga_url($content)
{
if( !$this->ga_busting->save($this->ga_busting->get_url()) ) {
return $content;
}
return str_replace($this->ga_busting->get_url(), $this->ga_busting->get_busting_url(), $content);
}
}

View File

@@ -0,0 +1,210 @@
<?php
namespace WGA\Busting;
/**
* This class configures the google analytics cache
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2017 Webraftic Ltd
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
class Sheduller {
protected $ga_processor;
protected $gtm_processor;
protected $fbpix_processor;
protected $fbsdk_processor;
/**
* Constructor
*/
public function __construct()
{
require_once WGA_PLUGIN_DIR . '/includes/classes/class-abstract-cache.php';
require_once WGA_PLUGIN_DIR . '/includes/classes/class-google-analytics-cache.php';
require_once WGA_PLUGIN_DIR . '/includes/classes/class-yandex-metrika-cache.php';
require_once WGA_PLUGIN_DIR . '/includes/classes/class-google-tag-manager-cache.php';
require_once WGA_PLUGIN_DIR . '/includes/classes/class-facebook-sdk.php';
require_once WGA_PLUGIN_DIR . '/includes/classes/class-facebook-cache.php';
$uploads_dir = wp_get_upload_dir();
if( !$uploads_dir ) {
return false;
}
$busting_path = trailingslashit($uploads_dir['basedir']) . WGA_PLUGIN_CACHE_FOLDER . '/';
$busting_url = trailingslashit($uploads_dir['baseurl']) . WGA_PLUGIN_CACHE_FOLDER . '/';
if( \WGA_Plugin::app()->getPopulateOption('yandex_metrika_cache') ) {
$this->ym_processor = new Yandex_Metrika_Cache($busting_path, $busting_url);
}
if( \WGA_Plugin::app()->getPopulateOption('google_analytics_cache') ) {
$this->ga_processor = new Google_Analytics_Cache($busting_path, $busting_url);
$this->gtm_processor = new Google_Tag_Manager_Cache($busting_path, $busting_url, $this->ga_processor);
}
if( \WGA_Plugin::app()->getPopulateOption('facebook_cache') ) {
$this->fbpix_processor = new Facebook_Pixel_Cache($busting_path, $busting_url);
$this->fbsdk_processor = new Facebook_SDK($busting_path, $busting_url);
}
add_action('cron_schedules', [$this, 'add_schedule']);
add_action('wclearfy/google_tracking_cache_update', [$this, 'update_tracking_cache']);
add_action('init', [$this, 'schedule_tracking_cache_update']);
add_action('template_redirect', function () {
ob_start([$this, 'cache_busting_google_tracking']);
}, 1);
add_action('wclearfy_delete_cache', [$this, 'delete_tracking_cache']);
}
/**
* Processes the cache busting on the HTML content
*
* Google Analytics replacement is performed first, and if no replacement occured, Google Tag Manager replacement is performed.
*
* @param string $html HTML content.
* @return string
* @since 3.1
*
*/
public function cache_busting_google_tracking($html)
{
if( !$this->is_busting_active() ) {
return $html;
}
if( \WGA_Plugin::app()->getPopulateOption('yandex_metrika_cache') ) {
$html = $this->ym_processor->replace_url($html);
}
if( \WGA_Plugin::app()->getPopulateOption('google_analytics_cache') ) {
$html = $this->ga_processor->replace_url($html);
$html = $this->gtm_processor->replace_url($html);
}
if( \WGA_Plugin::app()->getPopulateOption('facebook_cache') ) {
$html = $this->fbpix_processor->replace_url($html);
$html = $this->fbsdk_processor->replace_url($html);
}
return $html;
}
/**
* Adds weekly interval to cron schedules
*
* @param $schedules array An array of intervals used by cron jobs.
* @return []
* @since 3.2.0
*
*/
public function add_schedule($schedules)
{
if( !$this->is_busting_active() ) {
return $schedules;
}
$schedules['weekly'] = [
'interval' => 604800,
'display' => __('weekly', 'clearfy'),
];
return $schedules;
}
/**
* Schedules the auto-update of Google Analytics cache busting file
*
* @return void
* @since 3.2.0
*/
public function schedule_tracking_cache_update()
{
if( !$this->is_busting_active() ) {
return;
}
if( !wp_next_scheduled('wclearfy/google_tracking_cache_update') ) {
wp_schedule_event(time(), 'weekly', 'wclearfy/google_tracking_cache_update');
}
}
/**
* Updates Google Analytics cache busting file
*
* @return bool
* @since 3.2.0
*/
public function update_tracking_cache()
{
if( !$this->is_busting_active() ) {
return false;
}
if( \WGA_Plugin::app()->getPopulateOption('yandex_metrika_cache') ) {
$this->ym_processor->refresh_save($this->ym_processor->get_url());
}
if( \WGA_Plugin::app()->getPopulateOption('google_analytics_cache') ) {
$this->ga_processor->refresh_save($this->ga_processor->get_url());
}
if( \WGA_Plugin::app()->getPopulateOption('facebook_cache') ) {
$this->fbsdk_processor->refresh();
$this->fbpix_processor->refresh_all();
}
return true;
}
/**
* Deletes the GA busting file.
*
* @return bool
* @since 3.2.0
*/
public function delete_tracking_cache()
{
if( !$this->is_busting_active() ) {
return false;
}
$result = false;
if( \WGA_Plugin::app()->getPopulateOption('facebook_cache') ) {
$result = $this->fbsdk_processor->delete() && $this->fbpix_processor->delete_all();
}
if( \WGA_Plugin::app()->getPopulateOption('google_analytics_cache') ) {
$result = $result && $this->ga_processor->delete() && $this->gtm_processor->delete();
}
if( \WGA_Plugin::app()->getPopulateOption('yandex_metrika_cache') ) {
$result = $result && $this->ym_processor->delete();
}
return $result;
}
/**
* Tell if the cache busting option is active.
*
* @return bool
* @since 3.2.0
*
*/
private function is_busting_active()
{
return (\WGA_Plugin::app()->getPopulateOption('yandex_metrika_cache') || \WGA_Plugin::app()->getPopulateOption('google_analytics_cache') || \WGA_Plugin::app()->getPopulateOption('facebook_cache'));
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace WGA\Busting;
/**
* This class configures the google analytics cache
*
* @author Alex Kovalev <alex.kovalevv@gmail.com>, Github: https://github.com/alexkovalevv
* @copyright (c) 2017 Webraftic Ltd
* @version 1.0
*/
// Exit if accessed directly
if( !defined('ABSPATH') ) {
exit;
}
class Yandex_Metrika_Cache extends Abstract_Cache {
/**
* Google Analytics URL.
*
* @var string
* @since 3.2.0
*/
protected $url = 'https://mc.yandex.ru/metrika/tag.js';
/**
* File name (local).
* %s is a "version": a md5 hash of the file contents.
*
* @var string
* @since 3.2.0
*/
protected $filename_pattern = 'ym-tag-%s.js';
/**
* Current file version (local): a md5 hash of the file contents.
*
* @var string
* @since 3.2.0
*/
protected $file_version;
/**
* Flag to track the replacement.
*
* @var bool
* @since 3.2.0
*/
protected $is_replaced = false;
/**
* Constructor.
*
* @param string $busting_path Path to the busting directory.
* @param string $busting_url URL of the busting directory.
* @since 3.1
* @access public
*
*/
public function __construct($busting_path, $busting_url)
{
parent::__construct($busting_path, $busting_url);
$this->busting_path = $busting_path . 'yandex-tracking/';
$this->busting_url = $busting_url . 'yandex-tracking/';
}
/**
* Performs the replacement process.
*
* @param string $html HTML content.
* @return string
* @since 3.2.0
*
*/
public function replace_url($html)
{
$this->is_replaced = false;
$tag = $this->find('<script\s*(?<attr>[^>]*)?>(?<content>[^<]+)?<\/script>', $html);
if( !$tag ) {
return $html;
}
\WGA_Plugin::app()->logger->info('YANDEX METRIKA CACHING PROCESS STARTED. TAG #' . $tag);
if( !$this->save($this->url) ) {
return $html;
}
$replace_tag = preg_replace('/(?:https?:)?\/\/mc\.yandex\.ru\/metrika\/tag\.js/i', $this->get_busting_url(), $tag);
$html = str_replace($tag, $replace_tag, $html);
$this->is_replaced = true;
\WGA_Plugin::app()->logger->info('Yandex metrika caching process succeeded. Busting path ' . $this->get_busting_path());
return $html;
}
/**
* Tell if the replacement was sucessful or not.
*
* @return bool
*
* @since 3.2.0
*/
public function is_replaced()
{
return $this->is_replaced;
}
/**
* Get the Google Analytics URL.
*
* @return string
* @author Remy Perona
*
* @since 3.1
* @access public
*/
public function get_url()
{
return $this->url;
}
/**
* Searches for element(s) in the DOM.
*
* @param string $pattern Pattern to match.
* @param string $html HTML content.
* @return string
* @author Remy Perona
*
* @since 3.1
* @access public
*/
protected function find($pattern, $html)
{
preg_match_all('/' . $pattern . '/is', $html, $all_matches, PREG_SET_ORDER);
$matches = array_map(function ($match) {
if( empty($match['content']) || (!preg_match('/\"script\",\s*[\'"](?:https?:)?\/\/mc\.yandex\.ru\/metrika\/tag\.js[\'"]/i', $match['attr'] . $match['content'])) ) {
return false;
}
return $match[0];
}, $all_matches);
$matches = array_values(array_filter($matches));
if( !$matches ) {
return false;
}
return $matches[0];
}
}

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.

View File

@@ -0,0 +1,92 @@
<?php
/**
* Script to update local-ga.js-file
* Credits go to: Matthew Horne | http://diywpblog.com/leverage-browser-cache-optimize-google-analytics/
* I adjusted this script to work with wp-cron. It will still run in crontab, though.
* To run the script in crontab, remove the code on lines 8 and 9.
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Remote file to download.
$remote_file = 'https://www.google-analytics.com/analytics.js';
$upload_dir = wp_upload_dir();
if ( true === $upload_dir['error'] ) {
return;
}
$cache_dir = untrailingslashit( $upload_dir['basedir'] ) . '/wga-cache';
if ( ! file_exists( $cache_dir ) ) {
if ( ! wp_mkdir_p( $cache_dir ) ) {
return;
}
}
$local_file = $cache_dir . '/local-ga.js';
// Connection time out.
$conn_timeout = 10;
$url = wp_parse_url( $remote_file );
$host = $url['host'];
$path = isset( $url['path'] ) ? $url['path'] : '/';
if ( isset( $url['query'] ) ) {
$path .= '?' . $url['query'];
}
$port = isset( $url['port'] ) ? $url['port'] : '80';
$fp = @fsockopen( $host, '80', $errno, $errstr, $conn_timeout );
if ( ! $fp ) {
// On connection failure return the cached file (if exists).
if ( file_exists( $local_file ) ) {
readfile( $local_file );
}
} else {
// Send the header information
$header = "GET $path HTTP/1.0\r\n";
$header .= "Host: $host\r\n";
$header .= "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6\r\n";
$header .= "Accept: */*\r\n";
$header .= "Accept-Language: en-us,en;q=0.5\r\n";
$header .= "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n";
$header .= "Keep-Alive: 300\r\n";
$header .= "Connection: keep-alive\r\n";
$header .= "Referer: http://$host\r\n\r\n";
fputs( $fp, $header );
$response = '';
// Get the response from the remote server.
while( $line = fread( $fp, 4096 ) ) {
$response .= $line;
}
// Close the connection.
fclose( $fp );
// Remove the headers.
$pos = strpos( $response, "\r\n\r\n" );
$response = substr( $response, $pos + 4 );
// Return the processed response.
echo $response;
// Save the response to the local file.
if ( ! file_exists( $local_file ) ) {
// Try to create the file, if doesn't exist.
fopen( $local_file, 'w' );
}
if ( is_writable( $local_file ) ) {
$fp = fopen( $local_file, 'w' );
if ( $fp ) {
fwrite( $fp, $response );
fclose( $fp );
}
}
}// End if().