first commit

This commit is contained in:
2026-03-05 13:07:40 +01:00
commit 64ba0721ee
25709 changed files with 4691006 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
<?php
namespace DgoraWcas\Analytics;
use DgoraWcas\Helpers;
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Analytics {
public function init() {
$allowByConstant = defined( 'DGWT_WCAS_ANALYTICS_ENABLE' ) && DGWT_WCAS_ANALYTICS_ENABLE;
// Publish this module conditionally in v1.18.0 and for everyone in v1.19.0
if ( ! $this->isModuleEnabled()
&& version_compare( DGWT_WCAS_VERSION, '1.18.99' ) < 0
&& ! $allowByConstant
) {
return;
}
if ( is_admin() ) {
// Load user interface
$ui = new UserInterface( $this );
$ui->init();
$widget = new Widget( $this, $ui );
$widget->init();
}
// Database
Database::registerTables();
$this->maybeInstallDatabase();
// Maintenance.
$maintenance = new Maintenance();
if ( $this->isModuleEnabled() ) {
$maintenance->init();
} else {
$maintenance->unschedule();
}
}
/**
* Check if the Analytics module is enabled
*
* @return bool
*/
public function isModuleEnabled() {
return DGWT_WCAS()->settings->getOption( 'analytics_enabled', 'off' ) === 'on';
}
/**
* Create the database table if necessary
*
* @return void
*/
public function maybeInstallDatabase() {
// Try to create tables after enabling Search Analytics module
add_action( 'update_option_' . DGWT_WCAS_SETTINGS_KEY, function ( $oldValue, $newValue ) {
$key = 'analytics_enabled';
$nowEnabled = isset( $newValue[ $key ] ) && $newValue[ $key ] === 'on';
$wasDisabled = ! isset( $oldValue[ $key ] ) || ( isset( $oldValue[ $key ] ) && $oldValue[ $key ] !== 'on' );
if ( $nowEnabled && $wasDisabled ) {
Database::maybeInstall();
}
}, 10, 2 );
// Try to create tables when Search Analytics module is created, but from some reasons the table wasn't created
if ( Helpers::isSettingsPage()
&& $this->isModuleEnabled()
&& ! Database::exist()
) {
Database::maybeInstall();
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace DgoraWcas\Analytics;
use WC_CSV_Exporter;
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class CSVExporter extends WC_CSV_Exporter {
private $context = '';
private $lang = '';
public function set_context( $context = '' ) {
$this->context = $context;
}
public function set_lang( $lang = '' ) {
$this->lang = $lang;
}
public function prepare_data_to_export() {
$data = new Data();
if ( ! empty( $this->lang ) ) {
$data->setLang( $this->lang );
}
$dateSuffix = date( 'Ymd-His', time() );
if ( in_array( $this->context, array( 'autocomplete', 'search-results-page' ) ) ) {
$data->setContext( $this->context );
$this->set_filename( 'fibosearch-analytics_' . $this->context . '_' . ( empty( $this->lang ) ? '' : $this->lang . '_' ) . $dateSuffix );
} else {
$this->set_filename( 'fibosearch-analytics_critical' . '_' . ( empty( $this->lang ) ? '' : $this->lang . '_' ) . $dateSuffix );
}
$this->set_column_names(
[
"phrase" => "Phrase",
"qty" => "Repetitions",
]
);
if ( empty( $this->context ) ) {
$this->row_data = $data->getCriticalSearches( PHP_INT_MAX );
} else {
$this->row_data = $data->getPhrasesWithResults( PHP_INT_MAX );
}
}
}

View File

@@ -0,0 +1,424 @@
<?php
namespace DgoraWcas\Analytics;
use DgoraWcas\Multilingual;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Data {
/**
* @var string
*/
private $format = 'Y-m-d H:i:s';
/**
* Date start in format Y-m-d H:i:s
* @var string
*/
private $dateFrom;
/**
* Date end in format Y-m-d H:i:s
* @var string
*/
private $dateTo;
/**
* Available values: 'autocomplete', 'search-results-page'
* @var string
*/
private $context;
/**
* Language
* @var string
*/
private $lang = '';
/**
* Minimum number of phrase repetitions which must occur to be recognized as critical
* @var int
*/
private $minCriticalRep = 3;
/**
* Percentage limit of searches returning results.
* Above this limit searches will be marked as satisfying
* Below this limit searches will be marked as not satisfying
* @var int
*/
private $searchesReturningResutlsGoodPercent = 70;
/**
* Constructor
*
*/
public function __construct() {
$this->setDefaultDateRange();
}
/**
* Set data range
*
* @param string $from | timestamp or Y-m-d H:i:s
* @param string $to | timestamp or Y-m-d H:i:s
*
* @return void
*/
public function setDateRange( $from = '', $to = '' ) {
$from = ! empty( $from ) && is_numeric( $from ) ? date( $this->format, $from ) : $from;
$to = ! empty( $to ) && is_numeric( $to ) ? date( $this->format, $to ) : $to;
if ( ! empty( $from ) && $this->validateDate( $from ) ) {
$this->dateFrom = $from;
}
if ( ! empty( $to ) && $this->validateDate( $to ) ) {
$this->dateTo = $to;
}
}
/**
* Set minimum number of phrase repetitions which must occur to be recognized as critical
*
* @param int $rep
*
* @return void
*/
public function minCriticalRep( $rep ) {
if ( is_numeric( $rep ) && $rep > 0 ) {
$this->minCriticalRep = $rep;
}
}
/**
* Set default data range - last 30 days
*
* @return void
*/
public function setDefaultDateRange() {
$this->dateFrom = date( $this->format, strtotime( 'today - 30 days' ) );
$this->dateTo = date( $this->format );
}
/**
* Set language
*
* @param string $lang
*
* @return void
*/
public function setLang( $lang ) {
if ( Multilingual::isMultilingual() && ! empty( $lang ) ) {
$this->lang = Multilingual::getDefaultLanguage();
}
if ( Multilingual::isLangCode( $lang ) ) {
$this->lang = $lang;
}
}
/**
* Set context
*
* @param string $context | Available values: 'autocomplete', 'search-results-page'
*
* @return void
*/
public function setContext( $context ) {
if ( ! in_array( $context, array( 'autocomplete', 'search-results-page' ) ) ) {
$context = 'autocomplete';
}
$this->context = $context;
}
/**
* Get phrases without search results
*
* @return array
*/
public function getPhrasesWithNoResults() {
return $this->getPhrases( $this->dateFrom, $this->dateTo, $this->context, false );
}
/**
* Get phrases with search results
*
* @param int $limit
* @param int $offset
*
* @return array
*/
public function getPhrasesWithResults( $limit = 10, $offset = 0 ) {
return $this->getPhrases( $this->dateFrom, $this->dateTo, $this->context, true, null, $limit, $offset );
}
/**
* Get total searches
*
* @param bool $hasResults
* @param bool $unique - count only unique values
*
* @return int
*/
public function getTotalSearches( $hasResults, $unique = false ) {
global $wpdb;
$total = 0;
$select = 'COUNT(id)';
$where = '';
if ( $unique ) {
$select = 'COUNT(DISTINCT phrase)';
}
// Context
if ( $this->context === 'autocomplete' ) {
$where .= " AND autocomplete = 1";
} else {
$where .= " AND autocomplete = 0";
}
//With results or with no results
if ( $hasResults ) {
$where .= " AND hits > 0";
} else {
$where .= " AND hits = 0";
}
// Language
$where .= $this->getLanguageSql();
$sql = $wpdb->prepare( "SELECT $select
FROM $wpdb->dgwt_wcas_stats
WHERE 1=1
$where
AND created_at > %s AND created_at < %s", $this->dateFrom, $this->dateTo );
$res = $wpdb->get_var( $sql );
if ( ! empty( $res ) && is_numeric( $res ) ) {
$total = absint( $res );
}
return $total;
}
/**
* Get SQL where clause related to language
*
* @param string $lang
*
* @return string
*/
public function getLanguageSql( $lang = '' ) {
global $wpdb;
$where = '';
if ( Multilingual::isMultilingual() ) {
if ( empty( $lang ) ) {
$lang = $this->lang;
}
if ( Multilingual::getDefaultLanguage() === $lang ) {
$where .= $wpdb->prepare( " AND (lang = %s OR lang IS NULL)", $lang );
} else {
$where .= $wpdb->prepare( " AND lang = %s", $lang );
}
}
return $where;
}
/**
* Get total critical searches
*
* @return int
*/
public function getTotalCriticalSearches() {
global $wpdb;
$total = 0;
$where = '';
// Language
$where .= $this->getLanguageSql();
$sql = $wpdb->prepare( "SELECT COUNT(*) AS total FROM (
SELECT phrase, COUNT(id) AS qty
FROM $wpdb->dgwt_wcas_stats
WHERE 1=1
AND created_at > %s AND created_at < %s
AND autocomplete = 1
AND solved = 0
AND hits = 0
$where
GROUP BY phrase
HAVING qty >= %d) AS total", $this->dateFrom, $this->dateTo, $this->minCriticalRep );
$res = $wpdb->get_var( $sql );
if ( ! empty( $res ) && is_numeric( $res ) ) {
$total = absint( $res );
}
return $total;
}
/**
* Get critical searches
*
* @param int $limit
* @param int $offset
*
* @return array
*/
public function getCriticalSearches( $limit = 10, $offset = 0 ) {
global $wpdb;
$phrases = array();
$where = '';
// Language
$where .= $this->getLanguageSql();
$sql = $wpdb->prepare( "SELECT phrase, COUNT(id) AS qty
FROM $wpdb->dgwt_wcas_stats
WHERE hits = 0
AND created_at > %s AND created_at < %s
AND autocomplete = 1
AND solved = 0
$where
GROUP BY phrase
HAVING qty >= %d
ORDER BY qty DESC, phrase ASC LIMIT %d,%d", $this->dateFrom, $this->dateTo, $this->minCriticalRep, $offset, $limit );
$res = $wpdb->get_results( $sql, ARRAY_A );
if ( ! empty( $res ) && is_array( $res ) ) {
$phrases = $res;
foreach ( $res as $key => $search ) {
$phrases[ $key ] = $search;
}
}
return $phrases;
}
/**
*
* Get search phrases with the frequency of occurrences
*
* @param string $dateFrom
* @param string $dateTo
* @param string $context
* @param bool $hasResults
* @param bool $solved
* @param int $limit
* @param int $offset
*
* @return array
*/
public function getPhrases( $dateFrom, $dateTo, $context, $hasResults, $solved = null, $limit = 10, $offset = 0 ) {
global $wpdb;
$output = array();
$where = '';
// Context
if ( $context === 'autocomplete' ) {
$where .= " AND autocomplete = 1";
} else {
$where .= " AND autocomplete = 0";
}
//With results or with no results
if ( $hasResults ) {
$where .= " AND hits > 0";
} else {
$where .= " AND hits = 0";
}
if ( is_bool( $solved ) ) {
if ( $solved ) {
$where .= " AND solved = 1";
} else {
$where .= " AND solved = 0";
}
}
// Language
$where .= $this->getLanguageSql();
$sql = $wpdb->prepare( "SELECT phrase, COUNT(id) AS qty
FROM $wpdb->dgwt_wcas_stats
WHERE 1=1
AND created_at > %s AND created_at < %s
$where
GROUP BY phrase
ORDER BY qty DESC, phrase ASC LIMIT %d,%d", $dateFrom, $dateTo, $offset, $limit );
$res = $wpdb->get_results( $sql, ARRAY_A );
if ( ! empty( $res ) && is_array( $res ) ) {
$output = $res;
}
return $output;
}
/**
* Mark as solved. Exclude the phrase from critical phrases module.
*
* @return bool
*/
function markAsSolved( $phrase ) {
global $wpdb;
$success = false;
$data = array(
'solved' => 1
);
$where = array(
'phrase' => $phrase
);
$format = array( '%s' );
if ( ! empty( $this->lang ) ) {
$where['lang'] = $this->lang;
$format[] = '%s';
}
if ( $wpdb->update( $wpdb->dgwt_wcas_stats, $data, $where, $format ) ) {
$success = true;
}
return $success;
}
/**
* Check if the date has properly format
*
* @return bool
*/
function validateDate( $date, $format = 'Y-m-d H:i:s' ) {
$d = \DateTime::createFromFormat( $format, $date );
return $d && $d->format( $format ) === $date;
}
/**
* Check if the date has properly format
*
* @param int $percentage
*
* @return bool
*/
function isSearchesReturningResutlsSatisfying( $percentage ) {
return $percentage >= $this->searchesReturningResutlsGoodPercent;
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace DgoraWcas\Analytics;
use DgoraWcas\Helpers;
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Database {
const DB_NAME = 'dgwt_wcas_stats';
const DB_VERSION = 2;
const DB_VERSION_OPTION = 'dgwt_wcas_stats_db_version';
/**
* Add table names to the $wpdb object
*
* @return void
*/
public static function registerTables() {
global $wpdb;
$wpdb->dgwt_wcas_stats = $wpdb->prefix . self::DB_NAME;
$wpdb->tables[] = self::DB_NAME;
}
/**
* Install DB if necessary
*
* @return void
*/
public static function maybeInstall() {
if ( ! self::exist() ) {
self::install();
} else {
$dbVersion = get_option( self::DB_VERSION_OPTION );
if ( absint( $dbVersion ) !== self::DB_VERSION ) {
self::install();
}
}
}
/**
* Install DB table
*
* @return void
*/
private static function install() {
global $wpdb;
$wpdb->hide_errors();
$freshInstall = ! self::exist();
$upFile = ABSPATH . 'wp-admin/includes/upgrade.php';
if ( file_exists( $upFile ) ) {
require_once( $upFile );
$collate = Helpers::getCollate( 'stats/main' );
$table = "CREATE TABLE $wpdb->dgwt_wcas_stats (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
phrase VARCHAR(255) NOT NULL,
hits INT NOT NULL,
created_at DATETIME NOT NULL DEFAULT '1000-01-01 00:00:00',
autocomplete TINYINT(1) NULL DEFAULT 1,
solved TINYINT(1) NULL DEFAULT 0,
lang VARCHAR(10) NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB $collate;";
dbDelta( $table );
if ( $freshInstall ) {
//@TODO mount index for columns if necessary
}
update_option( self::DB_VERSION_OPTION, self::DB_VERSION );
}
}
/**
* Check if the table exists
*
* @return bool
*/
public static function exist() {
global $wpdb;
return Helpers::isTableExists( $wpdb->dgwt_wcas_stats );
}
/**
* Remove DB table
*
* @return void
*/
public static function remove() {
global $wpdb;
$wpdb->hide_errors();
$wpdb->query( "DROP TABLE IF EXISTS $wpdb->dgwt_wcas_stats" );
delete_option( self::DB_VERSION_OPTION );
}
/**
* Wipe old analytics records
*
* @param int $daysAgo Minimum age of records to be deleted.
*
* @return void
*/
public static function wipeOldRecords( $daysAgo = 0 ) {
global $wpdb;
if ( ! self::exist() ) {
return;
}
if ( intval( $daysAgo ) <= 0 ) {
$daysAgo = Maintenance::ANALYTICS_EXPIRATION_IN_DAYS;
}
$wpdb->hide_errors();
// Delete expired records.
$daysAgo = date( 'Y-m-d H:i:s', strtotime( "today - $daysAgo days" ) );
$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->dgwt_wcas_stats WHERE DATE(created_at) <= %s", $daysAgo ) );
// Delete non-critical records.
if (
defined( 'DGWT_WCAS_ANALYTICS_ONLY_CRITICAL' ) &&
DGWT_WCAS_ANALYTICS_ONLY_CRITICAL
) {
$wpdb->query( "DELETE FROM $wpdb->dgwt_wcas_stats WHERE hits > 0" );
}
}
/**
* Wipe all analytics records
*
* @return void
*/
public static function wipeAllRecords() {
global $wpdb;
if ( ! self::exist() ) {
return;
}
$wpdb->hide_errors();
$wpdb->query( "DELETE FROM $wpdb->dgwt_wcas_stats" );
}
/**
* Get the number of records
*
* @return int
*/
public static function getRecordsCount() {
global $wpdb;
if ( ! self::exist() ) {
return 0;
}
$wpdb->hide_errors();
return intval( $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->dgwt_wcas_stats" ) );
}
/**
* @return string
*/
public static function getTableName() {
global $wpdb;
return $wpdb->prefix . Database::DB_NAME;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace DgoraWcas\Analytics;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Maintenance {
const HOOK = 'dgwt_wcas_analytics_maintenance';
const ANALYTICS_EXPIRATION_IN_DAYS = 30;
public function init() {
$this->schedule();
$this->listenCron();
}
/**
* Listen to cron action
*
* @return void
*/
public function listenCron() {
add_action( self::HOOK, [ $this, 'handleMaintenance' ] );
}
/**
* Schedule maintenance task
*
* @return void
*/
public function schedule() {
if ( ! wp_next_scheduled( self::HOOK ) ) {
wp_schedule_event( strtotime( 'tomorrow' ) + 2 * HOUR_IN_SECONDS, 'daily', self::HOOK );
}
}
/**
* Unschedule maintenance task
*
* @return void
*/
public function unschedule() {
$timestamp = wp_next_scheduled( self::HOOK );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, self::HOOK );
}
}
/**
* Handle maintenance task
*
* @return void
*/
public function handleMaintenance() {
$expiration = self::ANALYTICS_EXPIRATION_IN_DAYS;
if (
defined( 'DGWT_WCAS_ANALYTICS_EXPIRATION_IN_DAYS' ) &&
intval( DGWT_WCAS_ANALYTICS_EXPIRATION_IN_DAYS ) > 0
) {
$expiration = intval( DGWT_WCAS_ANALYTICS_EXPIRATION_IN_DAYS );
}
Database::wipeOldRecords( $expiration );
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace DgoraWcas\Analytics;
use DgoraWcas\Helpers ;
use DgoraWcas\Multilingual ;
// Exit if accessed directly
if ( !defined( 'ABSPATH' ) ) {
exit;
}
class Recorder
{
public function listen()
{
Database::registerTables();
add_action(
'dgwt/wcas/analytics/after_searching',
array( $this, 'listener' ),
10,
3
);
}
/**
* Validate input data and save them to the index
*
* @param string $phrase
* @param int $hits
* @param string $lang
*
* @return void
*/
public function listener( $phrase, $hits, $lang )
{
$autocomplete = true;
// Break early the search phrase is empty or has a specific shape
if ( empty($phrase) || !is_string( $phrase ) || $phrase === 'fibotests' ) {
return;
}
if ( !is_numeric( $hits ) || $hits < 0 ) {
return;
}
// Save only critical searches.
if ( defined( 'DGWT_WCAS_ANALYTICS_ONLY_CRITICAL' ) && DGWT_WCAS_ANALYTICS_ONLY_CRITICAL && $hits > 0 ) {
return;
}
// Allow to exclude critical phrases.
if ( $hits === 0 ) {
$excludedPhrases = apply_filters( 'dgwt/wcas/analytics/excluded_critical_phrases', array() );
if ( is_array( $excludedPhrases ) && in_array( $phrase, $excludedPhrases, true ) ) {
return;
}
}
// Break early when a user has specific roles.
$roles = apply_filters( 'dgwt/wcas/analytics/exclude_roles', array() );
if ( !empty($roles) ) {
foreach ( $roles as $role ) {
if ( current_user_can( $role ) ) {
return;
}
}
}
if ( Helpers::isProductSearchPage() ) {
$autocomplete = false;
}
$phrase = strtolower( substr( $phrase, 0, 255 ) );
$lang = ( !empty($lang) && Multilingual::isLangCode( $lang ) ? $lang : '' );
$this->push(
$phrase,
$hits,
$autocomplete,
$lang
);
}
/**
* Push a record to the index.
*
* @param string $phrase
* @param int $hits
* @param bool $autocomplete
* @param string $lang
*
* @return void
*/
public function push(
$phrase,
$hits,
$autocomplete,
$lang
)
{
global $wpdb ;
$data = array(
'phrase' => $phrase,
'hits' => $hits,
'created_at' => date( 'Y-m-d H:i:s', current_time( 'timestamp', true ) ),
'autocomplete' => $autocomplete,
);
$format = array(
'%s',
'%d',
'%s',
'%d'
);
if ( !empty($lang) ) {
$data['lang'] = $lang;
$format[] = '%s';
}
$wpdb->insert( $wpdb->dgwt_wcas_stats, $data, $format );
}
}

View File

@@ -0,0 +1,563 @@
<?php
namespace DgoraWcas\Analytics;
use DgoraWcas\Helpers ;
use DgoraWcas\Multilingual ;
// Exit if accessed directly
if ( !defined( 'ABSPATH' ) ) {
exit;
}
class UserInterface
{
const SECTION_ID = 'dgwt_wcas_analytics' ;
const LOAD_INTERFACE_NONCE = 'analytics-load-interface' ;
const LOAD_MORE_CRITICAL_SEARCHES_NONCE = 'analytics-load-more-critical-searches' ;
const LOAD_MORE_AUTOCOMPLETE_NONCE = 'analytics-load-more-autocomplete' ;
const LOAD_MORE_SEARCH_PAGE_NONCE = 'analytics-load-more-search-page' ;
const CRITICAL_CHECK_NONCE = 'analytics-critical-check' ;
const EXCLUDE_CRITICAL_PHRASE_NONCE = 'analytics-exclude-critical-phrase' ;
const RESET_STATS_NONCE = 'analytics-reset-stats' ;
const EXPORT_STATS_CSV_NONCE = 'analytics-export-stats-csv' ;
const CSS_CLASS_PLACEHOLDER = 'js-dgwt-wcas-stats-placeholder' ;
const CRITICAL_SEARCHES_LOAD_LIMIT = 10 ;
const TABLE_ROW_LIMIT_LIMIT = 10 ;
/**
* @var Analytics
*/
private $analytics ;
/**
* Constructor
*
* @param Analytics $analytics
*/
public function __construct( Analytics $analytics )
{
$this->analytics = $analytics;
}
/**
* Init the class
*
* @return void
*/
public function init()
{
// Draw settings page
add_filter( 'dgwt/wcas/settings/sections', array( $this, 'addSettingsSection' ) );
add_filter( 'dgwt/wcas/settings', array( $this, 'addSettingsTab' ) );
add_filter( 'dgwt/wcas/scripts/admin/localize', array( $this, 'localizeSettings' ) );
// AJAX callbacks
add_action( 'wp_ajax_dgwt_wcas_load_stats_interface', array( $this, 'loadInterface' ) );
add_action( 'wp_ajax_dgwt_wcas_laod_more_critical_searches', array( $this, 'loadMoreCriticalSearches' ) );
add_action( 'wp_ajax_dgwt_wcas_laod_more_autocomplete', array( $this, 'loadMoreAutocomplete' ) );
add_action( 'wp_ajax_dgwt_wcas_laod_more_search_page', array( $this, 'loadMoreSearchPage' ) );
add_action( 'wp_ajax_dgwt_wcas_check_critical_phrase', array( $this, 'checkCriticalPhrase' ) );
add_action( 'wp_ajax_dgwt_wcas_exclude_critical_phrase', array( $this, 'excludeCriticalPhrase' ) );
add_action( 'wp_ajax_dgwt_wcas_reset_stats', array( $this, 'resetStats' ) );
add_action( 'wp_ajax_dgwt_wcas_export_stats_csv', array( $this, 'exportStats' ) );
if ( $this->analytics->isModuleEnabled() ) {
add_action( DGWT_WCAS_SETTINGS_KEY . '-form_end_' . self::SECTION_ID, array( $this, 'tabContent' ) );
}
}
/**
* Content of "Analytics" tab on Settings page
*
* @param array $sections
*
* @return array
*/
public function addSettingsSection( $sections )
{
$sections[28] = array(
'id' => self::SECTION_ID,
'title' => __( 'Analytics', 'ajax-search-for-woocommerce' ),
);
return $sections;
}
/**
* Add "Analytics" tab on Settings page
*
* @param array $settings
*
* @return array
*/
public function addSettingsTab( $settings )
{
$searchAnalyticsLink = 'https://fibosearch.com/documentation/features/fibosearch-analytics/';
$settings[self::SECTION_ID] = apply_filters( 'dgwt/wcas/settings/section=analytics', array(
100 => array(
'name' => 'analytics_head',
'label' => __( 'Search Analytics', 'ajax-search-for-woocommerce' ),
'type' => 'head',
'class' => 'dgwt-wcas-sgs-header',
),
110 => array(
'name' => 'analytics_enabled',
'label' => __( 'Enable search analytics', 'ajax-search-for-woocommerce' ) . ' ' . Helpers::createQuestionMark( 'enable_search_analytics', sprintf( __( 'Search analytics system helps to eliminate search phrases that dont return any results. Also, allows to explore trending keywords. <a target="_blank" href="%s">Find our more</a> how to use and customize FiboSearch Analytics.', 'ajax-search-for-woocommerce' ), $searchAnalyticsLink ) ),
'type' => 'checkbox',
'class' => 'dgwt-wcas-options-cb-toggle js-dgwt-wcas-cbtgroup-analytics-critial-searches-widget',
'size' => 'small',
'default' => 'off',
),
120 => array(
'name' => 'analytics_critical_searches_widget_enabled',
'label' => __( 'Show widget with critical searches in Dashboard', 'ajax-search-for-woocommerce' ),
'type' => 'checkbox',
'class' => 'js-dgwt-wcas-cbtgroup-analytics-critial-searches-widget',
'size' => 'small',
'default' => 'off',
),
) );
return $settings;
}
/**
* Pass data to JavaScript on the settings page
*
* @param array $localize
*
* @return array
*/
public function localizeSettings( $localize )
{
$localize['analytics'] = array(
'nonce' => array(
'analytics_load_interface' => wp_create_nonce( self::LOAD_INTERFACE_NONCE ),
'load_more_critical_searches' => wp_create_nonce( self::LOAD_MORE_CRITICAL_SEARCHES_NONCE ),
'load_more_autocomplete' => wp_create_nonce( self::LOAD_MORE_AUTOCOMPLETE_NONCE ),
'load_more_search_page' => wp_create_nonce( self::LOAD_MORE_SEARCH_PAGE_NONCE ),
'check_critical_phrase' => wp_create_nonce( self::CRITICAL_CHECK_NONCE ),
'exclude_critical_phrase' => wp_create_nonce( self::EXCLUDE_CRITICAL_PHRASE_NONCE ),
'reset_stats' => wp_create_nonce( self::RESET_STATS_NONCE ),
'export_stats_csv' => wp_create_nonce( self::EXPORT_STATS_CSV_NONCE ),
),
'enabled' => $this->analytics->isModuleEnabled(),
'images' => array(
'placeholder' => DGWT_WCAS_URL . 'assets/img/admin-stats-placeholder.png',
),
'labels' => array(
'reset_stats_confirm' => __( 'Are you sure you want to reset stats?', 'ajax-search-for-woocommerce' ),
),
);
return $localize;
}
/**
* Load content for "Analytics" tab on Settings page
*
* @return void
*/
public function tabContent()
{
if ( Multilingual::isMultilingual() ) {
echo $this->getLanguageSwitcher() ;
}
echo '<div class="dgwt-wcas-analytics-body ' . self::CSS_CLASS_PLACEHOLDER . '"></div>' ;
}
/**
* Get HTML of language switcher
*
* @return string
*/
private function getLanguageSwitcher()
{
$vars = array(
'multilingual' => array(
'is-multilingual' => true,
'current-lang' => Multilingual::getCurrentLanguage(),
'langs' => array(),
),
);
foreach ( Multilingual::getLanguages() as $lang ) {
$vars['multilingual']['langs'][$lang] = Multilingual::getLanguageField( $lang, 'name' );
}
ob_start();
require DGWT_WCAS_DIR . 'partials/admin/stats/langs.php';
return ob_get_clean();
}
/**
* Load an interface (AJAX callback)
*
* @return void
*/
public function loadInterface()
{
if ( !current_user_can( ( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( self::LOAD_INTERFACE_NONCE );
$lang = ( !empty($_REQUEST['lang']) && Multilingual::isLangCode( sanitize_key( $_REQUEST['lang'] ) ) ? sanitize_key( $_REQUEST['lang'] ) : '' );
$data = array(
'html' => '',
);
ob_start();
$vars = $this->getVars( $lang );
require DGWT_WCAS_DIR . 'partials/admin/stats/stats.php';
$data['html'] = ob_get_clean();
wp_send_json_success( $data );
}
/**
* Load more critical searches
*
* @return void
*/
public function loadMoreCriticalSearches()
{
if ( !current_user_can( ( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( self::LOAD_MORE_CRITICAL_SEARCHES_NONCE );
$lang = ( !empty($_REQUEST['lang']) && Multilingual::isLangCode( sanitize_key( $_REQUEST['lang'] ) ) ? sanitize_key( $_REQUEST['lang'] ) : '' );
$offset = ( !empty($_REQUEST['loaded']) ? absint( $_REQUEST['loaded'] ) : 0 );
$html = '';
$data = new Data();
if ( !empty($lang) ) {
$data->setLang( $lang );
}
$total = $data->getTotalCriticalSearches();
$critical = $data->getCriticalSearches( self::CRITICAL_SEARCHES_LOAD_LIMIT, $offset );
if ( !empty($critical) ) {
ob_start();
$i = $offset + 1;
foreach ( $critical as $row ) {
require DGWT_WCAS_DIR . 'partials/admin/stats/critical-searches-row.php';
$i++;
}
$html = ob_get_clean();
}
$toLoad = $total - $offset - count( $critical );
$more = min( self::CRITICAL_SEARCHES_LOAD_LIMIT, $toLoad );
$data = array(
'html' => $html,
'more' => $more,
'more_label' => '',
);
if ( $more > 0 ) {
$data['more_label'] = sprintf( _n(
'load another %d phrase',
'load another %d phrases',
$more,
'ajax-search-for-woocommerce'
), $more );
}
wp_send_json_success( $data );
}
/**
* Load more autocomplete searches with results
*
* @return void
*/
public function loadMoreAutocomplete()
{
if ( !current_user_can( ( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( self::LOAD_MORE_AUTOCOMPLETE_NONCE );
$lang = ( !empty($_REQUEST['lang']) && Multilingual::isLangCode( sanitize_key( $_REQUEST['lang'] ) ) ? sanitize_key( $_REQUEST['lang'] ) : '' );
// Autocomplete
$data = new Data();
if ( !empty($lang) ) {
$data->setLang( $lang );
}
$data->setContext( 'autocomplete' );
$phrases = $data->getPhrasesWithResults( 100 );
ob_start();
$i = 1;
foreach ( $phrases as $row ) {
require DGWT_WCAS_DIR . 'partials/admin/stats/ac-searches-row.php';
$i++;
}
$html = ob_get_clean();
$data = array(
'html' => $html,
);
wp_send_json_success( $data );
}
/**
* Load more search page searches with results
*
* @return void
*/
public function loadMoreSearchPage()
{
if ( !current_user_can( ( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( self::LOAD_MORE_SEARCH_PAGE_NONCE );
$lang = ( !empty($_REQUEST['lang']) && Multilingual::isLangCode( sanitize_key( $_REQUEST['lang'] ) ) ? sanitize_key( $_REQUEST['lang'] ) : '' );
// Search page
$data = new Data();
if ( !empty($lang) ) {
$data->setLang( $lang );
}
$data->setContext( 'search-results-page' );
$phrases = $data->getPhrasesWithResults( 100 );
ob_start();
$i = 1;
foreach ( $phrases as $row ) {
require DGWT_WCAS_DIR . 'partials/admin/stats/sp-searches-row.php';
$i++;
}
$html = ob_get_clean();
$data = array(
'html' => $html,
);
wp_send_json_success( $data );
}
/**
* Check if the phrase returns results
*
* @return void
*/
public function checkCriticalPhrase()
{
if ( !current_user_can( ( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( self::CRITICAL_CHECK_NONCE );
$data = array(
'html' => '',
'status' => '',
);
$phrase = ( !empty($_REQUEST['phrase']) ? $_REQUEST['phrase'] : '' );
if ( empty($phrase) ) {
wp_send_json_error( 'empty phrase' );
}
if ( !dgoraAsfwFs()->is_premium() ) {
$res = DGWT_WCAS()->nativeSearch->getSearchResults( $phrase, true, 'autocomplete' );
if ( is_array( $res ) && isset( $res['total'] ) ) {
$total = absint( $res['total'] );
if ( $total > 0 ) {
$data['status'] = 'with-results';
$data['html'] = $this->getCriticalPhraseMessage( $data['status'], $total );
} else {
$data['status'] = 'without-results';
$data['html'] = $this->getCriticalPhraseMessage( $data['status'] );
}
} else {
$data['status'] = 'error';
$data['html'] = $this->getCriticalPhraseMessage( $data['status'] );
}
} else {
}
wp_send_json_success( $data );
}
/**
* Check critical phrase - messages on response
*
* @param string $context
* @param int $total
*
* @return string
*/
public function getCriticalPhraseMessage( $context = '' )
{
$html = '';
//This phrase returns X products.
switch ( $context ) {
case 'with-results':
$html = '<p>';
$html .= '<b class="dgwt-wcas-analytics-text-good">' . __( "Perfect!", 'ajax-search-for-woocommerce' ) . '</b>';
$html .= ' ' . __( "It's sorted.", 'ajax-search-for-woocommerce' );
$html .= ' ' . __( 'This phrase returns some results.', 'ajax-search-for-woocommerce' );
$html .= ' ' . __( 'Click the button below to remove this phrase from the list.', 'ajax-search-for-woocommerce' );
$html .= '<button class="button button-small dgwt-wcas-analytics-btn-mark js-dgwt-wcas-analytics-exclude-phrase"><span class="dashicons dashicons-yes"></span> ' . __( 'Mark this phrase as resolved', 'ajax-search-for-woocommerce' ) . '</button>';
$html .= '</p>';
break;
case 'without-results':
$html = '<p>';
$html .= '<b class="dgwt-wcas-analytics-text-poorly">' . __( "Poor!", 'ajax-search-for-woocommerce' ) . '</b>';
$html .= ' ' . __( "Still this phrase doesn't return any results. Learn how to fix it.", 'ajax-search-for-woocommerce' );
$html .= '</p>';
break;
case 'wrong-index':
$html = '<p>';
$html .= __( "Can't check the status. The search index hasn't been completed. Go to the Indexer tab and wait until the search index is completed.", 'ajax-search-for-woocommerce' );
$html .= '<button class="button button-small dgwt-wcas-analytics-btn-mark js-dgwt-wcas-analytics-check-indexer">' . __( 'Check the indexer status', 'ajax-search-for-woocommerce' ) . '</button>';
$html .= '</p>';
break;
case 'error':
$html = '<p>';
$html .= __( 'Something went wrong', 'ajax-search-for-woocommerce' );
$html .= '</p>';
break;
}
return $html;
}
/**
* Unmark a phrase as critical. AJAX callback
*
* @return void
*/
public function excludeCriticalPhrase()
{
if ( !current_user_can( ( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( self::EXCLUDE_CRITICAL_PHRASE_NONCE );
$phrase = ( !empty($_REQUEST['phrase']) ? $_REQUEST['phrase'] : '' );
$lang = ( !empty($_REQUEST['lang']) && Multilingual::isLangCode( $_REQUEST['lang'] ) ? sanitize_key( $_REQUEST['lang'] ) : '' );
if ( !empty($phrase) ) {
$data = new Data();
if ( Multilingual::isMultilingual() && !empty($lang) ) {
$data->setLang( $lang );
}
if ( $data->markAsSolved( $phrase ) ) {
wp_send_json_success( '<p>' . __( 'This phrase has been resolved! This row will disappear after refreshing the page.', 'ajax-search-for-woocommerce' ) . '</p>' );
}
}
wp_send_json_error( 'empty phrase' );
}
/**
* Reset stats. AJAX callback
*
* @return void
*/
public function resetStats()
{
if ( !current_user_can( ( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( self::RESET_STATS_NONCE );
Database::wipeAllRecords();
wp_send_json_success();
}
/**
* Export stats. AJAX callback
*
* @return void
*/
public function exportStats()
{
if ( !current_user_can( ( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) ) {
wp_die( -1, 403 );
}
check_ajax_referer( self::EXPORT_STATS_CSV_NONCE );
if ( !class_exists( 'WC_CSV_Exporter', false ) ) {
require_once WC_ABSPATH . 'includes/export/abstract-wc-csv-exporter.php';
}
$exporter = new CSVExporter();
$context = ( isset( $_GET['context'] ) ? sanitize_key( $_GET['context'] ) : '' );
$exporter->set_context( $context );
$lang = ( !empty($_REQUEST['lang']) && Multilingual::isLangCode( sanitize_key( $_REQUEST['lang'] ) ) ? sanitize_key( $_REQUEST['lang'] ) : '' );
if ( !empty($lang) ) {
$exporter->set_lang( $lang );
}
$exporter->export();
}
/**
* Prepare vars for the view
*
* @param string $lang
*
* @return array
*/
private function getVars( $lang = '' )
{
$data = new Data();
if ( Multilingual::isMultilingual() ) {
$data->setLang( $lang );
}
$mainUrl = 'https://fibosearch.com/lack-of-queries-insight-really-hurts-your-sales/';
$vars = array(
'days' => $this->getExpirationInDays(),
'autocomplete' => array(),
'search-page' => array(),
'critical-searches' => array(),
'critical-searches-total' => 0,
'critical-searches-more' => 0,
'returning-results-percent' => 0,
'returning-results-percent-poorly' => false,
'links' => array(
'synonyms' => $mainUrl . '#synonyms',
'support' => 'https://fibosearch.com/contact/',
),
'table-info' => Helpers::getTableInfo( Database::getTableName() ),
);
// Autocomplete
$data->setContext( 'autocomplete' );
$vars['autocomplete'] = array(
'with-results' => $data->getPhrasesWithResults( self::TABLE_ROW_LIMIT_LIMIT ),
'total-with-results-uniq' => $data->getTotalSearches( true, true ),
'total-without-results-uniq' => $data->getTotalSearches( false, true ),
'total-with-results' => $data->getTotalSearches( true ),
'total-without-results' => $data->getTotalSearches( false ),
'total-results' => 0,
);
$vars['autocomplete']['total-results-uniq'] = $vars['autocomplete']['total-with-results-uniq'] + $vars['autocomplete']['total-without-results-uniq'];
$vars['autocomplete']['total-results'] = $vars['autocomplete']['total-with-results'] + $vars['autocomplete']['total-without-results'];
// WooCommerce Search Results Page
$data->setContext( 'search-results-page' );
$vars['search-page'] = array(
'with-results' => $data->getPhrasesWithResults( self::TABLE_ROW_LIMIT_LIMIT ),
'total-with-results-uniq' => $data->getTotalSearches( true, true ),
'total-without-results-uniq' => $data->getTotalSearches( false, true ),
'total-with-results' => $data->getTotalSearches( true ),
'total-without-results' => $data->getTotalSearches( false ),
'total-results' => 0,
);
$vars['search-page']['total-results-uniq'] = $vars['search-page']['total-with-results-uniq'] + $vars['search-page']['total-without-results-uniq'];
$vars['search-page']['total-results'] = $vars['search-page']['total-with-results'] + $vars['search-page']['total-without-results'];
// Common
$vars['total'] = $vars['autocomplete']['total-results'];
if ( $vars['total'] > 0 ) {
$vars['returning-results-percent'] = round( $vars['autocomplete']['total-with-results'] * 100 / $vars['total'] );
$vars['returning-results-percent-satisfying'] = $data->isSearchesReturningResutlsSatisfying( $vars['returning-results-percent'] );
}
// Critical searches
$critical = $data->getCriticalSearches( self::CRITICAL_SEARCHES_LOAD_LIMIT );
if ( !empty($critical) ) {
$vars['critical-searches'] = $critical;
$vars['critical-searches-total'] = $data->getTotalCriticalSearches();
$toLoad = $vars['critical-searches-total'] - count( $critical );
$vars['critical-searches-more'] = min( self::CRITICAL_SEARCHES_LOAD_LIMIT, $toLoad );
if ( $vars['critical-searches-total'] < self::CRITICAL_SEARCHES_LOAD_LIMIT ) {
$vars['critical-searches-more'] = 0;
}
}
return $vars;
}
/**
* The records will be removed from the database after passing X days
*
* @return int
*/
public function getExpirationInDays()
{
$days = Maintenance::ANALYTICS_EXPIRATION_IN_DAYS;
if ( defined( 'DGWT_WCAS_ANALYTICS_EXPIRATION_IN_DAYS' ) && intval( DGWT_WCAS_ANALYTICS_EXPIRATION_IN_DAYS ) > 0 ) {
$days = intval( DGWT_WCAS_ANALYTICS_EXPIRATION_IN_DAYS );
}
return $days;
}
}

View File

@@ -0,0 +1,206 @@
<?php
namespace DgoraWcas\Analytics;
use DgoraWcas\Helpers;
use DgoraWcas\Multilingual;
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Widget {
/**
* @var Analytics
*/
private $analytics;
/**
* @var UserInterface
*/
private $ui;
/**
* Constructor
*
* @param Analytics $analytics
* @param UserInterface $ui
*/
public function __construct( Analytics $analytics, UserInterface $ui ) {
$this->analytics = $analytics;
$this->ui = $ui;
}
public function init() {
if ( $this->analytics->isModuleEnabled() && $this->isCriticalSearchesWidgetEnabled() ) {
if ( current_user_can( Helpers::shopManagerHasAccess() ? 'manage_woocommerce' : 'manage_options' ) ) {
add_action( 'wp_dashboard_setup', array( $this, 'addWidget' ) );
if ( Multilingual::isMultilingual() ) {
add_action( 'admin_init', array( $this, 'enqueueTabsScript' ), 5 );
}
}
}
}
/**
* Check if the Analytics widget is enabled
*
* @return bool
*/
public function isCriticalSearchesWidgetEnabled() {
return DGWT_WCAS()->settings->getOption( 'analytics_critical_searches_widget_enabled', 'off' ) === 'on';
}
public function addWidget() {
wp_add_dashboard_widget(
'fibosearch_analytics_critical_searches',
esc_html__( 'FiboSearch - Search Analytics', 'ajax-search-for-woocommerce' ),
array( $this, 'render' )
);
}
public function render() {
$data = new Data();
$vars = array(
'days' => $this->ui->getExpirationInDays(),
'critical-searches' => array(),
'critical-searches-total' => 0,
'settings-analytics-url' => admin_url( 'admin.php?page=dgwt_wcas_settings#analytics' ),
'multilingual' => array(),
);
if ( Multilingual::isMultilingual() ) {
$vars['multilingual'] = array(
'current-lang' => Multilingual::getCurrentLanguage(),
'langs' => array()
);
foreach ( Multilingual::getLanguages() as $lang ) {
$data->setLang( $lang );
$vars['multilingual']['langs'][ $lang ] = array(
'name' => Multilingual::getLanguageField( $lang, 'name' ),
'critical-searches' => $data->getCriticalSearches( UserInterface::CRITICAL_SEARCHES_LOAD_LIMIT ),
'critical-searches-total' => $data->getTotalCriticalSearches(),
);
}
} else {
$vars['critical-searches'] = $data->getCriticalSearches( UserInterface::CRITICAL_SEARCHES_LOAD_LIMIT );
$vars['critical-searches-total'] = $data->getTotalCriticalSearches();
}
ob_start();
?>
<h3><strong><?php _e( 'Critical searches without result', 'ajax-search-for-woocommerce' ); ?></strong></h3>
<?php if ( ! empty( $vars['multilingual'] ) ) { ?>
<p>
<?php _e( 'Language', 'ajax-search-for-woocommerce' ); ?>:
<span class="dgwt-wcas-widget-tab-wrapper">
<?php
$next = false;
foreach ( $vars['multilingual']['langs'] as $lang => $langData ) {
if ( $next ) {
echo ' | ';
}
printf( '<a class="dgwt-wcas-widget-tab" title="%s" href="%s">%s</a>', esc_attr( $langData['name'] ), esc_attr( '#dgwt-wcas-widget-tab-content-' . $lang ), esc_html( $langData['name'] ) );
$next = true;
}
?>
</span>
</p>
<?php
foreach ( $vars['multilingual']['langs'] as $lang => $langName ) {
?>
<div class="dgwt-wcas-widget-tab-content"
id="dgwt-wcas-widget-tab-content-<?php echo esc_attr( $lang ); ?>">
<?php $this->renderTable(
array_merge( $vars, $vars['multilingual']['langs'][ $lang ] )
); ?>
</div>
<?php
}
} else {
$this->renderTable( $vars );
}
?>
<p>
<?php printf( __( "Go to FiboSearch Settings page → %s to see more.", 'ajax-search-for-woocommerce' ), sprintf( '<a title="%2$s" href="%1$s">%2$s</a>', $vars['settings-analytics-url'], esc_attr__( 'Analytics tab', 'ajax-search-for-woocommerce' ) ) ); ?>
</p>
<?php
echo ob_get_clean();
}
private function renderTable( $vars ) {
if ( ! empty( $vars['critical-searches'] ) ) { ?>
<p>
<?php printf( _n( 'The FiboSearch analyzer found <b>1 critical search phrase</b>.', 'The FiboSearch analyzer found <b>%d critical search phrases</b>.', $vars['critical-searches-total'], 'ajax-search-for-woocommerce' ), $vars['critical-searches-total'] );
echo ' ';
printf( _n( 'These phrases have been typed by users over the last 1 day.', 'These phrases have been typed by users over the last %d days.', $vars['days'], 'ajax-search-for-woocommerce' ), $vars['days'] );
echo ' ';
_e( "These phrases don`t return any search results. It's time to fix it.", 'ajax-search-for-woocommerce' );
?>
</p>
<table class="widefat fixed">
<thead>
<tr>
<th>#</th>
<th><?php _e( 'Phrase', 'ajax-search-for-woocommerce' ); ?></th>
<th><?php _e( 'Repetitions', 'ajax-search-for-woocommerce' ); ?></th>
</tr>
</thead>
<tbody>
<?php
$i = 1;
foreach ( $vars['critical-searches'] as $row ) {
?>
<tr>
<td><?php echo $i; ?></td>
<td><?php echo esc_html( $row['phrase'] ); ?></td>
<td><?php echo esc_html( $row['qty'] ); ?></td>
</tr>
<?php
$i ++;
}
?>
</tbody>
</table>
<?php
} else {
?>
<p>
<?php printf( __( "Fantastic! The FiboSearch analyzer hasn't found any critical search phrases for the last %d days.", 'ajax-search-for-woocommerce' ), $vars['days'] ); ?>
</p>
<?php
}
}
public function enqueueTabsScript() {
wp_enqueue_style( 'jquery' );
ob_start();
?>
<script>
(function ($) {
$(document).ready(function () {
const tabs = $('.dgwt-wcas-widget-tab');
tabs.on('click', function (event) {
event.preventDefault();
$('.dgwt-wcas-widget-tab.dgwt-wcas-widget-tab-active').removeClass('dgwt-wcas-widget-tab-active');
$(this).addClass('dgwt-wcas-widget-tab-active');
$('.dgwt-wcas-widget-tab-content').hide();
$($(this).attr('href')).show();
});
if (tabs.length > 0) {
$(tabs[0]).trigger('click');
}
});
}(jQuery)
);
</script>
<?php
$script = str_replace( array( '<script>', '</script>' ), array( '', '' ), ob_get_clean() );
wp_add_inline_script( 'jquery', $script );
}
}