diff --git a/assets/src/js/admin/main.js b/assets/src/js/admin/main.js index a870b69f..c8b7e6c9 100644 --- a/assets/src/js/admin/main.js +++ b/assets/src/js/admin/main.js @@ -4,10 +4,6 @@ * Admin JS */ document.addEventListener('DOMContentLoaded', () => { - if (!document.location.href.includes('plausible_analytics')) { - return; - } - let plausible = { /** * Properties @@ -15,11 +11,12 @@ document.addEventListener('DOMContentLoaded', () => { nonceElem: document.getElementById('_wpnonce'), nonce: '', showWizardElem: document.getElementById('show_wizard'), - domainNameElem: document.getElementById('domain_name'), - apiTokenElem: document.getElementById('api_token'), createAPITokenElems: document.getElementsByClassName('plausible-create-api-token'), buttonElems: document.getElementsByClassName('plausible-analytics-button'), stepElems: document.getElementsByClassName('plausible-analytics-wizard-next-step'), + credentialsInputs: document.querySelectorAll('.plausible-analytics-credentials input'), + languageDomainPulldown: document.getElementById('language_domain'), + connectButtons: document.querySelectorAll('.plausible-analytics-credentials .plausible-analytics-connect-button'), /** * Bind events. @@ -41,14 +38,6 @@ document.addEventListener('DOMContentLoaded', () => { this.showWizardElem.addEventListener('click', this.showWizard); } - if (this.domainNameElem !== null) { - this.domainNameElem.addEventListener('keyup', this.disableConnectButton); - } - - if (this.apiTokenElem !== null) { - this.apiTokenElem.addEventListener('keyup', this.disableConnectButton); - } - if (this.createAPITokenElems.length > 0) { for (let i = 0; i < this.createAPITokenElems.length; i++) { this.createAPITokenElems[i].addEventListener('click', this.createAPIToken); @@ -77,6 +66,22 @@ document.addEventListener('DOMContentLoaded', () => { } } + if (this.credentialsInputs.length > 0) { + for (let i = 0; i < this.credentialsInputs.length; i++) { + this.credentialsInputs[i].addEventListener('keyup', this.disableConnectButton); + } + } + + if (this.languageDomainPulldown !== null) { + this.languageDomainPulldown.addEventListener('change', this.switchLanguageDomain); + } + + if (this.connectButtons.length > 0) { + for (let i = 0; i < this.connectButtons.length; i++) { + this.connectButtons[i].addEventListener('click', this.saveOption); + } + } + /** * Run once on pageload. */ @@ -348,14 +353,15 @@ document.addEventListener('DOMContentLoaded', () => { }, /** - * Save value of input or text area to DB. + * Save the value of the input or text area to DB. * * @param e */ saveOption: function (e) { - const button = e.target; - const section = button.closest('.plausible-analytics-section'); - const inputs = section.querySelectorAll('input, textarea'); + const button = e.target.closest('button'); + const isCredentials = button.closest('.plausible-analytics-credentials'); + const section = isCredentials || button.closest('.plausible-analytics-section'); + const inputs = section.querySelectorAll(isCredentials ? 'input' : 'input, textarea'); const form = new FormData(); let options = []; @@ -369,8 +375,10 @@ document.addEventListener('DOMContentLoaded', () => { form.append('options', JSON.stringify(options)); form.append('_nonce', plausible.nonce); - if (button.children.length > 0) { - button.children[0].classList.remove('hidden'); + let spinner = button.querySelector('svg'); + + if (spinner) { + spinner.classList.remove('hidden'); } button.setAttribute('disabled', 'disabled'); @@ -378,6 +386,24 @@ document.addEventListener('DOMContentLoaded', () => { plausible.ajax(form, button); }, + /** + * Switch Language Domain. + * + * @param e + */ + switchLanguageDomain: function (e) { + const selectedKey = e.target.value; + const credentials = document.querySelectorAll('.plausible-analytics-credentials'); + + credentials.forEach(function (credentialsSet) { + if (credentialsSet.dataset.languageDomainKey === selectedKey) { + credentialsSet.classList.remove('hidden'); + } else { + credentialsSet.classList.add('hidden'); + } + }); + }, + /** * Disable options based on the capabilities retrieved from the API. * @@ -413,7 +439,7 @@ document.addEventListener('DOMContentLoaded', () => { */ validateInput: function (input) { // Strip http(s)://(www.) from domain_name before sending it. - if (input.name === 'domain_name' && input.value.match(/^(https?:\/\/)?(www.)?/).length > 0) { + if ((input.name === 'domain_name' || input.name.startsWith('domain_name[')) && input.value.match(/^(https?:\/\/)?(www.)?/).length > 0) { input.value = input.value.replace(/^(https?:\/\/)?(www.)?/, ''); } @@ -461,21 +487,39 @@ document.addEventListener('DOMContentLoaded', () => { }, /** - * Disable Connect button if Domain Name or API Token field is empty. + * Disable the Connect button if the Domain Name or API Token field is empty. * * @param e */ disableConnectButton: function (e) { let target = e.target; - let button = document.getElementById('connect_plausible_analytics'); + let credentials = target.closest('.plausible-analytics-credentials'); + let button; let buttonIsHref = false; - if (button === null) { - let slide_id = document.location.hash; - button = document.querySelector(slide_id + ' .plausible-analytics-wizard-next-step'); - buttonIsHref = true; + if (credentials !== null) { + button = credentials.querySelector('.plausible-analytics-connect-button'); + let allFilled = Array.from(credentials.querySelectorAll('input')).every(input => input.value.trim() !== ''); + + if (button === null) { + return; + } + + button.disabled = !allFilled; + + button.childNodes.forEach(function (node) { + if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() === plausible_analytics_i18n.connected) { + node.textContent = ' ' + plausible_analytics_i18n.connect; + } + }); + + return; } + let slide_id = document.location.hash; + button = document.querySelector(slide_id + ' .plausible-analytics-wizard-next-step'); + buttonIsHref = true; + if (button === null) { return; } @@ -485,7 +529,7 @@ document.addEventListener('DOMContentLoaded', () => { button.disabled = false; } else { button.classList.remove('pointer-events-none'); - button.classList.replace('bg-gray-200', 'bg-indigo-600') + button.classList.replace('bg-gray-200', 'bg-indigo-600'); } return; @@ -493,10 +537,10 @@ document.addEventListener('DOMContentLoaded', () => { if (!buttonIsHref) { button.disabled = true; - button.innerHTML = button.innerHTML.replace('Connected', 'Connect'); + button.textContent = button.textContent.replace('Connected', 'Connect'); } else { button.classList += ' pointer-events-none'; - button.classList.replace('bg-indigo-600', 'bg-gray-200') + button.classList.replace('bg-indigo-600', 'bg-gray-200'); } }, @@ -508,7 +552,13 @@ document.addEventListener('DOMContentLoaded', () => { createAPIToken: function (e) { e.preventDefault(); - let domain = document.getElementById('domain_name').value; + let domainElem = document.querySelector('.plausible-analytics-credentials:not(.hidden) [id^="domain_name"]'); + + if (domainElem === null) { + domainElem = document.querySelector('input[id^="domain_name"]'); + } + + let domain = domainElem ? domainElem.value : ''; domain = domain.replaceAll('/', '%2F'); window.open(`${plausible_analytics_hosted_domain}/${domain}/settings/integrations?new_token=WordPress`, '_blank', 'location=yes,height=768,width=1024,scrollbars=yes,status=no'); @@ -610,17 +660,24 @@ document.addEventListener('DOMContentLoaded', () => { ).then(response => { if (button) { if (button.children.length > 0) { - button.children[0].classList += ' hidden'; + let spinner = button.querySelector('svg'); + if (spinner) { + spinner.classList.add('hidden'); + } } - if (button.id === 'connect_plausible_analytics' && response.status === 200) { - button.innerText = plausible_analytics_i18n.connected; + if (button.classList.contains('plausible-analytics-connect-button') && response.status === 200) { + button.childNodes.forEach(function (node) { + if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') { + node.textContent = ' ' + plausible_analytics_i18n.connected; + } + }); } else { button.removeAttribute('disabled'); } } - // We still want the data, if it's a Payment Required error. + // We still want the data if it's a Payment Required error. if (response.status === 200 || response.status === 402) { return response.json(); } diff --git a/assets/src/js/admin/notice.js b/assets/src/js/admin/notice.js new file mode 100644 index 00000000..306c200b --- /dev/null +++ b/assets/src/js/admin/notice.js @@ -0,0 +1,14 @@ +document.addEventListener('DOMContentLoaded', function () { + document.addEventListener('click', function (e) { + if (!e.target.closest('.plausible-analytics-multilang-notice .notice-dismiss')) { + return; + } + + var form = new FormData(); + + form.append('action', 'plausible_analytics_dismiss_multilang_notice'); + form.append('_nonce', plausible_analytics_notice.nonce); + + fetch(ajaxurl, {method: 'POST', body: form}); + }); +}); diff --git a/src/Admin/Actions.php b/src/Admin/Actions.php index b5b44313..8b690ab3 100644 --- a/src/Admin/Actions.php +++ b/src/Admin/Actions.php @@ -22,27 +22,73 @@ class Actions { public function __construct() { add_action( 'admin_enqueue_scripts', [ $this, 'register_assets' ] ); add_action( 'admin_init', [ $this, 'maybe_redirect_to_wizard' ] ); + add_action( 'admin_notices', [ $this, 'show_unconfigured_multilang_domains_notice' ] ); + } + + /** + * Redirect to the Configuration Wizard on the first boot. + * + * @return void + */ + public function maybe_redirect_to_wizard() { + // Make sure it only runs when requested by (an admin in) a browser. + if ( wp_doing_ajax() || wp_doing_cron() || ! current_user_can( 'manage_options' ) ) { + return; + } + + // If we're already on the Settings page, there's no need to redirect. + if ( array_key_exists( 'page', $_GET ) && $_GET['page'] === 'plausible_analytics' ) { + return; + } + + // Self-hosters should never be redirected to the settings screen, because the wizard isn't shown to them. + $wizard_done = get_option( 'plausible_analytics_wizard_done', false ) || ! empty( Helpers::get_settings()['self_hosted_domain'] ); + + if ( ! $wizard_done ) { + $url = admin_url( 'options-general.php?page=plausible_analytics#welcome_slide' ); + + wp_redirect( $url ); + + exit; + } } /** * Register Assets. * - * @since 1.0.0 - * @since 1.3.0 Don't load CSS admin-wide. JS needs to load admin-wide, since we're throwing admin-wide, dismissable notices. - * @access public + * @since v1.0.0 + * @since v2.6.0 Notice.js is loaded on all Admin-pages. All other assets are only loaded on Plausible Analytics related pages. + * * @return void */ public function register_assets( $current_page ) { - if ( $current_page === 'settings_page_plausible_analytics' || $current_page === 'dashboard_page_plausible_analytics_statistics' ) { - wp_enqueue_style( - 'plausible-admin', - PLAUSIBLE_ANALYTICS_PLUGIN_URL . 'assets/dist/css/plausible-admin.css', - '', - filemtime( PLAUSIBLE_ANALYTICS_PLUGIN_DIR . 'assets/dist/css/plausible-admin.css' ), - 'all' - ); + wp_enqueue_script( + 'plausible-admin-notice', + PLAUSIBLE_ANALYTICS_PLUGIN_URL . 'assets/dist/js/plausible-admin-notice.js', + [], + filemtime( PLAUSIBLE_ANALYTICS_PLUGIN_DIR . 'assets/dist/js/plausible-admin-notice.js' ), + [ 'in_footer' => true ] + ); + + wp_localize_script( + 'plausible-admin-notice', + 'plausible_analytics_notice', + [ + 'nonce' => wp_create_nonce( 'plausible_analytics_dismiss_multilang_notice' ), + ] + ); + + if ( $current_page !== 'settings_page_plausible_analytics' && $current_page !== 'dashboard_page_plausible_analytics_statistics' ) { + return; } + wp_enqueue_style( + 'plausible-admin', + PLAUSIBLE_ANALYTICS_PLUGIN_URL . 'assets/dist/css/plausible-admin.css', + '', + filemtime( PLAUSIBLE_ANALYTICS_PLUGIN_DIR . 'assets/dist/css/plausible-admin.css' ), + ); + wp_register_script( 'plausible-admin', PLAUSIBLE_ANALYTICS_PLUGIN_URL . 'assets/dist/js/plausible-admin.js', @@ -51,7 +97,14 @@ public function register_assets( $current_page ) { [ 'in_footer' => true ] ); - wp_localize_script( 'plausible-admin', 'plausible_analytics_i18n', [ 'connected' => __( 'Connected', 'plausible-analytics' ) ] ); + wp_localize_script( + 'plausible-admin', + 'plausible_analytics_i18n', + [ + 'connected' => __( 'Connected', 'plausible-analytics' ), + 'connect' => __( 'Connect', 'plausible-analytics' ), + ] + ); wp_enqueue_script( 'plausible-admin' ); @@ -59,30 +112,41 @@ public function register_assets( $current_page ) { } /** - * Redirect to Configuration Wizard on first boot. + * Show a notice when multilang domain-per-language mode is active. * * @return void */ - public function maybe_redirect_to_wizard() { - // Make sure it only runs when requested by (an admin in) a browser. - if ( wp_doing_ajax() || wp_doing_cron() || ! current_user_can( 'manage_options' ) ) { + public function show_unconfigured_multilang_domains_notice() { + if ( ! Helpers::is_language_per_domain_mode() ) { return; } - // If we're already on the Settings page, there's no need to redirect. - if ( array_key_exists( 'page', $_GET ) && $_GET[ 'page' ] === 'plausible_analytics' ) { + $multilang_plugin = Helpers::get_multilang_plugin_name(); + + if ( empty( $multilang_plugin ) ) { return; } - // Self-hosters should never be redirected to the settings screen, because the wizard isn't shown to them. - $wizard_done = get_option( 'plausible_analytics_wizard_done', false ) || ! empty( Helpers::get_settings()[ 'self_hosted_domain' ] ); - - if ( ! $wizard_done ) { - $url = admin_url( 'options-general.php?page=plausible_analytics#welcome_slide' ); - - wp_redirect( $url ); + if ( ! current_user_can( 'manage_options' ) ) { + return; + } - exit; + if ( get_option( 'plausible_analytics_multilang_notice_dismissed' ) ) { + return; } + + $settings_url = admin_url( 'options-general.php?page=plausible_analytics' ); + ?> +
+

+ settings screen.', 'plausible-analytics' ), + $multilang_plugin, + $settings_url + ); ?> +

+
+ client = $client; - if ( ! $this->client ) { - $this->client_factory = new ClientFactory(); - $this->client = $this->client_factory->build(); - } - $this->custom_event_goals = [ EnhancedMeasurements::FOUR_O_FOUR => __( '404', 'plausible-analytics' ), EnhancedMeasurements::CLOAKED_AFFILIATE_LINKS => __( 'Cloaked Link: Click', 'plausible-analytics' ), @@ -100,15 +93,15 @@ public function __construct( $client = null ) { /** * Action & filter hooks. + * * @return void - * @throws ApiException + * * @codeCoverageIgnore */ private function init() { - if ( ! $this->client instanceof Client || ! $this->client->validate_api_token() ) { - return; // @codeCoverageIgnore - } - + /** This hook should always be registered because it handles fresh installs. */ + add_action( 'add_option_plausible_analytics_settings', [ $this, 'provision_on_connect' ], 10, 2 ); + add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_provision_on_connect' ], 10, 2 ); add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_create_shared_link' ], 10, 2 ); add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_create_goals' ], 10, 2 ); add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_delete_goals' ], 11, 2 ); @@ -118,122 +111,44 @@ private function init() { } /** - * Create shared link when Enable Analytics Dashboard option is enabled. - * - * @param $old_settings - * @param $settings - */ - public function maybe_create_shared_link( $old_settings, $settings ) { - if ( empty( $settings['enable_analytics_dashboard'] ) ) { - return; // @codeCoverageIgnore - } - - $this->client->create_shared_link(); - } - - /** - * Create Custom Event Goals for enabled Enhanced Measurements. - * - * @param $old_settings - * @param $settings - */ - public function maybe_create_goals( $old_settings, $settings ) { - $enhanced_measurements = array_filter( $settings['enhanced_measurements'] ); - - if ( empty( $enhanced_measurements ) ) { - return; // @codeCoverageIgnore - } - - $custom_event_keys = array_keys( $this->custom_event_goals ); - $goals = []; - - foreach ( $enhanced_measurements as $measurement ) { - if ( ! in_array( $measurement, $custom_event_keys ) ) { - continue; // @codeCoverageIgnore - } - - $goals[] = $this->create_goal_request( $this->custom_event_goals[ $measurement ] ); - } - - $this->create_goals( $goals ); - } - - /** - * @param string $name Event Name - * @param string $type CustomEvent|Revenue|Pageview - * @param string $currency Required if $type is Revenue + * Searches an array for the presence of $string within each element's value. Strips currencies using a regex, e.g. + * (USD), because these are added to revenue goals by Plausible. * - * @return GoalCreateRequestCustomEvent|GoalCreateRequestPageview|GoalCreateRequestRevenue - */ - public function create_goal_request( $name, $type = 'CustomEvent', $currency = '', $path = '' ) { - $props = [ - 'goal' => [ - 'event_name' => $name, - ], - 'goal_type' => "Goal.$type", - ]; - - if ( $type === 'Revenue' ) { - $props['goal']['currency'] = $currency; - } - - if ( $type === 'Pageview' ) { - unset( $props['goal']['event_name'] ); - - $props['goal']['path'] = $path; - } - - switch ( $type ) { - case 'Pageview': - return new Client\Model\GoalCreateRequestPageview( $props ); - case 'Revenue': - return new Client\Model\GoalCreateRequestRevenue( $props ); - default: // CustomEvent - return new Client\Model\GoalCreateRequestCustomEvent( $props ); - } - } - - /** - * Create the goals using the API client and updates the IDs in the database. + * @param string $string + * @param array $haystack * - * @param array $goals + * @return false|mixed * - * @return void + * @codeCoverageIgnore Because it can't be unit tested. */ - public function create_goals( $goals ) { - if ( empty( $goals ) ) { - return; // @codeCoverageIgnore + public function array_search_contains( $string, $haystack ) { + if ( preg_match( '/\([A-Z]*?\)/', $string ) ) { + $string = preg_replace( '/ \([A-Z]*?\)/', '', $string ); } - $create_request = new Client\Model\GoalCreateRequestBulkGetOrCreate(); - $create_request->setGoals( $goals ); - $response = $this->client->create_goals( $create_request ); - - if ( $response->valid() ) { - $goals = $response->getGoals(); - $ids = get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ); - - foreach ( $goals as $goal ) { - $goal = $goal->getGoal(); - $ids[ $goal->getId() ] = $goal->getDisplayName(); - } - - if ( ! empty( $ids ) ) { - update_option( 'plausible_analytics_enhanced_measurements_goal_ids', $ids ); + foreach ( $haystack as $key => $value ) { + if ( str_contains( $value, $string ) ) { + return $key; } } + + return false; } /** * Creates a funnel and creates goals if they don't exist. * - * @param $name - * @param $steps + * @param $name + * @param $steps + * @param null $client + * @param string $key + * @param null $all_ids + * + * @return array|null * - * @return void * @codeCoverageIgnore Because this method should be mocked in tests if needed. */ - public function create_funnel( $name, $steps ) { + public function create_funnel( $name, $steps, $client = null, $key = 'default', $all_ids = null ) { $create_request = new Client\Model\FunnelCreateRequest( [ 'funnel' => [ @@ -243,13 +158,21 @@ public function create_funnel( $name, $steps ) { ] ); - $funnel = $this->client->create_funnel( $create_request ); + if ( ! $client ) { + $client = $this->client; + } + + if ( $all_ids === null ) { + $all_ids = $this->normalize_option( get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ) ); + } + + $funnel = $client->create_funnel( $create_request ); if ( ! $funnel instanceof Client\Model\Funnel || ! $funnel->valid() ) { - return; + return $all_ids; } - $ids = get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ); + $ids = $all_ids[ $key ] ?? []; $steps = $funnel->getFunnel()->getSteps(); foreach ( $steps as $step ) { @@ -261,138 +184,173 @@ public function create_funnel( $name, $steps ) { } if ( ! empty( $ids ) ) { - update_option( 'plausible_analytics_enhanced_measurements_goal_ids', $ids ); + $all_ids[ $key ] = $ids; + update_option( 'plausible_analytics_enhanced_measurements_goal_ids', $all_ids ); } + + return $all_ids; } /** - * Delete Custom Event Goals when an Enhanced Measurement is disabled. + * Normalizes a per-domain option value. * - * @param $old_settings - * @param $settings + * @param mixed $value * - * @codeCoverageIgnore Because we don't want to test if the API is working. + * @return array */ - public function maybe_delete_goals( $old_settings, $settings ) { - $enhanced_measurements_old = array_filter( $old_settings['enhanced_measurements'] ); - $enhanced_measurements = array_filter( $settings['enhanced_measurements'] ); - $disabled_settings = array_diff( $enhanced_measurements_old, $enhanced_measurements ); + public function normalize_option( $value ) { + if ( empty( $value ) || ! is_array( $value ) ) { + return []; + } - if ( empty( $disabled_settings ) ) { - return; + $is_legacy = false; + + foreach ( $value as $item ) { + if ( ! is_array( $item ) ) { + $is_legacy = true; + break; + } } - $goals = get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ); + if ( $is_legacy ) { + return [ 'default' => $value ]; + } - foreach ( $goals as $id => $name ) { - $key = array_search( $name, $this->custom_event_goals ); + return $value; + } - if ( ! in_array( $key, $disabled_settings ) ) { - continue; // @codeCoverageIgnore - } + /** + * Create the shared link when the Enable Analytics Dashboard option is enabled. + * + * @param array $_ Not used (old settings) + * @param array $settings Current settings + * + * @codeCoverageIgnore Because we don't want to test if the API is working. + */ + public function maybe_create_shared_link( $_, $settings ) { + $clients = $this->get_clients(); - $this->client->delete_goal( $id ); + if ( empty( $clients ) ) { + return; + } - unset( $goals[ $id ] ); + if ( empty( $settings['enable_analytics_dashboard'] ) ) { + return; // @codeCoverageIgnore } - // Refresh the stored IDs in the DB. - update_option( 'plausible_analytics_enhanced_measurements_goal_ids', $goals ); + foreach ( $clients as $key => $client ) { + $client->create_shared_link( $key ); + } } /** - * Searches an array for the presence of $string within each element's value. Strips currencies using a regex, e.g. - * (USD), because these are added to revenue goals by Plausible. + * Get an array of [ $key => Client ] for every configured domain with a non-empty API token. * - * @param string $string - * @param array $haystack + * @param bool $force_refresh * - * @return false|mixed - * @codeCoverageIgnore Because it can't be unit tested. + * @return Client[] + * + * @codeCoverageIgnore Because it should be mocked when running tests. */ - public function array_search_contains( $string, $haystack ) { - if ( preg_match( '/\([A-Z]*?\)/', $string ) ) { - $string = preg_replace( '/ \([A-Z]*?\)/', '', $string ); + public function get_clients( $force_refresh = false ) { + if ( $this->clients_cache !== null && ! $force_refresh ) { + return $this->clients_cache; } - foreach ( $haystack as $key => $value ) { - if ( str_contains( $value, $string ) ) { + if ( $this->client instanceof Client ) { + return $this->clients_cache = [ 'default' => $this->client ]; + } + + $settings = Helpers::get_settings(); + + if ( empty( $settings['api_token'] ) || ! is_array( $settings['api_token'] ) ) { + return $this->clients_cache = []; + } + + $clients = []; + + foreach ( $settings['api_token'] as $key => $token ) { + if ( empty( $token ) ) { + continue; + } + + $filter = function () use ( $key ) { return $key; + }; + + add_filter( 'plausible_analytics_current_language_domain_key', $filter ); + + $client = ( new ClientFactory( '', $key ) )->build(); + + if ( $client instanceof Client && $client->validate_api_token() ) { + $clients[ $key ] = $client; } + + remove_filter( 'plausible_analytics_current_language_domain_key', $filter ); } - return false; + return $this->clients_cache = $clients; } /** - * @param array $old_settings - * @param array $settings + * Delete Custom Event Goals when an Enhanced Measurement is disabled. + * + * @param $old_settings + * @param $settings * - * @return void * @codeCoverageIgnore Because we don't want to test if the API is working. */ - public function maybe_create_custom_properties( $old_settings, $settings ) { - $enhanced_measurements = $settings['enhanced_measurements']; + public function maybe_delete_goals( $old_settings, $settings ) { + $clients = $this->get_clients(); - if ( ! EnhancedMeasurements::is_enabled( EnhancedMeasurements::PAGEVIEW_PROPS, $enhanced_measurements ) && - ! EnhancedMeasurements::is_enabled( EnhancedMeasurements::ECOMMERCE_REVENUE, $enhanced_measurements ) && - ! EnhancedMeasurements::is_enabled( EnhancedMeasurements::SEARCH_QUERIES, $enhanced_measurements ) && - ! EnhancedMeasurements::is_enabled( EnhancedMeasurements::QUERY_PARAMS, $enhanced_measurements ) ) { - return; // @codeCoverageIgnore + if ( empty( $clients ) ) { + return; } - $create_request = new Client\Model\CustomPropEnableRequestBulkEnable(); - $properties = []; + $enhanced_measurements_old = $old_settings['enhanced_measurements'] ?? []; - /** - * Enable Custom Properties for Authors & Categories option. - */ - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::PAGEVIEW_PROPS, $enhanced_measurements ) ) { - foreach ( $this->custom_pageview_properties as $property ) { - $properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] ); - } + if ( ! is_array( $enhanced_measurements_old ) ) { + $enhanced_measurements_old = []; } - /** - * Create Custom Properties for WooCommerce integration. - */ - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::ECOMMERCE_REVENUE, $enhanced_measurements ) && ( Integrations::is_wc_active() || Integrations::is_edd_active() ) ) { - foreach ( self::CUSTOM_PROPERTIES as $property ) { - $properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] ); - } + $enhanced_measurements_old = array_filter( $enhanced_measurements_old ); + + $enhanced_measurements = $settings['enhanced_measurements'] ?? []; + + if ( ! is_array( $enhanced_measurements ) ) { + $enhanced_measurements = []; } - /** - * Create Custom Properties for Query Parameters option. - */ - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::QUERY_PARAMS, $enhanced_measurements ) ) { - foreach ( Helpers::get_settings()['query_params'] ?? [] as $query_param ) { - $properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $query_param ] ] ); - } + $enhanced_measurements = array_filter( $enhanced_measurements ); + + $disabled_settings = array_diff( $enhanced_measurements_old, $enhanced_measurements ); + + if ( empty( $disabled_settings ) ) { + return; } - /** - * Create Custom Properties for Search Queries option. - */ - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::SEARCH_QUERIES, $enhanced_measurements ) ) { - $caps = get_option( 'plausible_analytics_api_token_caps', [] ); + $all_ids = $this->normalize_option( get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ) ); - foreach ( $this->custom_search_properties as $property ) { - if ( empty( $caps[ Capabilities::PROPS ] ) && ( $property === 'result_count' || $property == 'search_source' ) ) { - continue; + foreach ( $clients as $domain_key => $client ) { + $goals = $all_ids[ $domain_key ] ?? []; + + foreach ( $goals as $id => $name ) { + $key = array_search( $name, $this->custom_event_goals ); + + if ( ! in_array( $key, $disabled_settings ) ) { + continue; // @codeCoverageIgnore } - $properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] ); + $client->delete_goal( $id ); + + unset( $goals[ $id ] ); } - } - if ( empty( $properties ) ) { - return; // @codeCoverageIgnore + $all_ids[ $domain_key ] = $goals; } - $create_request->setCustomProps( $properties ); - - $this->client->enable_custom_property( $create_request ); + // Refresh the stored IDs in the DB. + update_option( 'plausible_analytics_enhanced_measurements_goal_ids', $all_ids ); } /** @@ -404,7 +362,7 @@ public function maybe_create_custom_properties( $old_settings, $settings ) { * @return array */ public function maybe_enable_customer_user_roles( $settings ) { - $enhanced_measurements = $settings['enhanced_measurements']; + $enhanced_measurements = $settings['enhanced_measurements'] ?? []; if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::ECOMMERCE_REVENUE, $enhanced_measurements ) ) { if ( Integrations::is_wc_active() && ! in_array( 'customer', $settings['tracked_user_roles'] ) ) { @@ -423,12 +381,74 @@ public function maybe_enable_customer_user_roles( $settings ) { return $settings; } + /** + * Wrapper for @see self::maybe_provision_on_connect() which fires exclusively on fresh installations. + * + * @param mixed $_ Not used (old settings) + * @param array $settings Current settings + * + * @return void + * + * @codeCoverageIgnore Because it's just a wrapper. + */ + public function provision_on_connect( $_, $settings ) { + $this->maybe_provision_on_connect( [], $settings ); + + self::$is_fresh_install = true; + } + + /** + * Run provisioning for all enabled Enhanced Measurements if a new Language Domain is configured. + * + * @param $old_settings + * @param $settings + * + * @return void + * + * @codeCoverageIgnore + */ + public function maybe_provision_on_connect( $old_settings, $settings ) { + if ( self::$is_fresh_install ) { + return; + } + + $old_tokens = is_array( $old_settings['api_token'] ?? null ) ? $old_settings['api_token'] : [ 'default' => $old_settings['api_token'] ?? '' ]; + $new_tokens = is_array( $settings['api_token'] ?? null ) ? $settings['api_token'] : [ 'default' => $settings['api_token'] ?? '' ]; + + $changed_keys = []; + + foreach ( $new_tokens as $key => $token ) { + if ( ! empty( $token ) && ( ! isset( $old_tokens[ $key ] ) || $old_tokens[ $key ] !== $token ) ) { + $changed_keys[] = $key; + } + } + + if ( empty( $changed_keys ) ) { + return; + } + + /** Make sure all API client objects are present. */ + $this->get_clients( true ); + $this->update_tracker_script_config( $old_settings, $settings ); + $this->maybe_create_goals( $old_settings, $settings ); + $this->maybe_create_custom_properties( $old_settings, $settings ); + } + /** * Updates the tracker script config based on the enabled enhanced measurements. * - * @return array The updated tracker script config. + * @param mixed $_ Not used (old settings) + * @param array $settings Current settings + * + * @return array Updated tracker script config. */ - public function update_tracker_script_config( $old_settings, $settings ) { + public function update_tracker_script_config( $_, $settings ) { + $enhanced_measurements = $settings['enhanced_measurements'] ?? []; + + if ( ! is_array( $enhanced_measurements ) ) { + $enhanced_measurements = []; // @codeCoverageIgnore + } + $config = [ 'file_downloads' => false, 'form_submissions' => false, @@ -437,27 +457,251 @@ public function update_tracker_script_config( $old_settings, $settings ) { 'outbound_links' => false, ]; - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::FILE_DOWNLOADS, $settings['enhanced_measurements'] ) ) { + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::FILE_DOWNLOADS, $enhanced_measurements ) ) { $config['file_downloads'] = true; } - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::FORM_COMPLETIONS, $settings['enhanced_measurements'] ) ) { + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::FORM_COMPLETIONS, $enhanced_measurements ) ) { $config['form_submissions'] = true; } - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::HASH_BASED_ROUTING, $settings['enhanced_measurements'] ) ) { + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::HASH_BASED_ROUTING, $enhanced_measurements ) ) { $config['hash_based_routing'] = true; } - if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::OUTBOUND_LINKS, $settings['enhanced_measurements'] ) ) { + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::OUTBOUND_LINKS, $enhanced_measurements ) ) { $config['outbound_links'] = true; } - $config = [ 'tracker_script_configuration' => $config ]; - $request = new Client\Model\TrackerScriptConfigurationUpdateRequest( $config ); + $config = [ 'tracker_script_configuration' => $config ]; + + $clients = $this->get_clients(); + + if ( empty( $clients ) ) { + return $config; + } - $this->client->update_tracker_script_configuration( $request ); + foreach ( $clients as $client ) { + $request = new Client\Model\TrackerScriptConfigurationUpdateRequest( $config ); + $client->update_tracker_script_configuration( $request ); + } + + /** Required for unit tests. */ return $config; } + + /** + * Create Custom Event Goals for enabled Enhanced Measurements. + * + * @param array $_ Not used (old settings) + * @param array $settings Current settings + */ + public function maybe_create_goals( $_, $settings ) { + $clients = $this->get_clients(); + + if ( empty( $clients ) ) { + return; + } + + $enhanced_measurements = $settings['enhanced_measurements'] ?? []; + + if ( ! is_array( $enhanced_measurements ) ) { + $enhanced_measurements = []; // @codeCoverageIgnore + } + + $enhanced_measurements = array_filter( $enhanced_measurements ); + + if ( empty( $enhanced_measurements ) ) { + return; // @codeCoverageIgnore + } + + $custom_event_keys = array_keys( $this->custom_event_goals ); + $goals = []; + + foreach ( $enhanced_measurements as $measurement ) { + if ( ! in_array( $measurement, $custom_event_keys ) ) { + continue; // @codeCoverageIgnore + } + + $goals[] = $this->create_goal_request( $this->custom_event_goals[ $measurement ] ); + } + + $all_ids = $this->normalize_option( get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ) ); + + foreach ( $clients as $key => $client ) { + $all_ids = $this->create_goals( $goals, $client, $key, $all_ids ); + } + + update_option( 'plausible_analytics_enhanced_measurements_goal_ids', $all_ids ); + } + + /** + * @param string $name Event Name + * @param string $type CustomEvent|Revenue|Pageview + * @param string $currency Required if $type is Revenue + * + * @return GoalCreateRequestCustomEvent|GoalCreateRequestPageview|GoalCreateRequestRevenue + */ + public function create_goal_request( $name, $type = 'CustomEvent', $currency = '', $path = '' ) { + $props = [ + 'goal' => [ + 'event_name' => $name, + ], + 'goal_type' => "Goal.$type", + ]; + + if ( $type === 'Revenue' ) { + $props['goal']['currency'] = $currency; + } + + if ( $type === 'Pageview' ) { + unset( $props['goal']['event_name'] ); + + $props['goal']['path'] = $path; + } + + switch ( $type ) { + case 'Pageview': + return new Client\Model\GoalCreateRequestPageview( $props ); + case 'Revenue': + return new Client\Model\GoalCreateRequestRevenue( $props ); + default: // CustomEvent + return new Client\Model\GoalCreateRequestCustomEvent( $props ); + } + } + + /** + * Create the goals using the API client and updates the IDs in the database. + * + * @param array $goals + * + * @return array + */ + public function create_goals( $goals, $client = null, $key = 'default', $all_ids = null ) { + if ( empty( $goals ) ) { + return $all_ids ?? []; // @codeCoverageIgnore + } + + if ( ! $client ) { + $client = $this->client; // @codeCoverageIgnore + } + + if ( $all_ids === null ) { + $all_ids = $this->normalize_option( get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ) ); // @codeCoverageIgnore + } + + $create_request = new Client\Model\GoalCreateRequestBulkGetOrCreate(); + $create_request->setGoals( $goals ); + + $response = $client->create_goals( $create_request ); + + if ( $response && $response->valid() ) { + $goals = $response->getGoals(); + $ids = $all_ids[ $key ] ?? []; + + foreach ( $goals as $goal ) { + $goal = $goal->getGoal(); + $ids[ $goal->getId() ] = $goal->getDisplayName(); + } + + if ( ! empty( $ids ) ) { + $all_ids[ $key ] = $ids; + /** IDs are stored in the DB after the loop. @see self::maybe_create_goals() */ + } + } + + return $all_ids; + } + + /** + * @param array $_ Not used (old settings) + * @param array $settings Current settings + * + * @return void + * @codeCoverageIgnore Because we don't want to test it if the API is working. + */ + public function maybe_create_custom_properties( $_, $settings ) { + $clients = $this->get_clients(); + + if ( empty( $clients ) ) { + return; + } + + $enhanced_measurements = $settings['enhanced_measurements'] ?? []; + + if ( ! is_array( $enhanced_measurements ) ) { + $enhanced_measurements = []; + } + + if ( ! EnhancedMeasurements::is_enabled( EnhancedMeasurements::PAGEVIEW_PROPS, $enhanced_measurements ) && + ! EnhancedMeasurements::is_enabled( EnhancedMeasurements::ECOMMERCE_REVENUE, $enhanced_measurements ) && + ! EnhancedMeasurements::is_enabled( EnhancedMeasurements::SEARCH_QUERIES, $enhanced_measurements ) && + ! EnhancedMeasurements::is_enabled( EnhancedMeasurements::QUERY_PARAMS, $enhanced_measurements ) ) { + return; // @codeCoverageIgnore + } + + $create_request = new Client\Model\CustomPropEnableRequestBulkEnable(); + $properties = []; + + /** + * Enable Custom Properties for the Authors & Categories option. + */ + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::PAGEVIEW_PROPS, $enhanced_measurements ) ) { + foreach ( $this->custom_pageview_properties as $property ) { + $properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] ); + } + } + + /** + * Create Custom Properties for WooCommerce integration. + */ + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::ECOMMERCE_REVENUE, $enhanced_measurements ) && ( Integrations::is_wc_active() || Integrations::is_edd_active() ) ) { + foreach ( self::CUSTOM_PROPERTIES as $property ) { + $properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] ); + } + } + + /** + * Create Custom Properties for Query Parameters option. + */ + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::QUERY_PARAMS, $enhanced_measurements ) ) { + foreach ( Helpers::get_settings()['query_params'] ?? [] as $query_param ) { + $properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $query_param ] ] ); + } + } + + $clients = $this->get_clients(); + + if ( empty( $clients ) ) { + return; + } + + foreach ( $clients as $key => $client ) { + $domain_properties = $properties; + + /** + * Create the Custom Properties for the Search Queries option. + */ + if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::SEARCH_QUERIES, $enhanced_measurements ) ) { + $caps = $all_caps[ $key ] ?? []; + + foreach ( $this->custom_search_properties as $property ) { + if ( empty( $caps[ Capabilities::PROPS ] ) && ( $property === 'result_count' || $property == 'search_source' ) ) { + continue; + } + + $domain_properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] ); + } + } + + if ( empty( $domain_properties ) ) { + continue; // @codeCoverageIgnore + } + + $create_request->setCustomProps( $domain_properties ); + + $client->enable_custom_property( $create_request ); + } + } } diff --git a/src/Admin/Provisioning/Integrations.php b/src/Admin/Provisioning/Integrations.php index 58813972..b7723e25 100644 --- a/src/Admin/Provisioning/Integrations.php +++ b/src/Admin/Provisioning/Integrations.php @@ -49,31 +49,35 @@ private function init() { } /** - * @param array $event_goals + * @param array $event_goals * @param string $funnel_name * * @return void * @codeCoverageIgnore We don't want to test the API. */ public function create_integration_funnel( $event_goals, $funnel_name ) { - $goals = []; + $goals = []; + $all_ids = $this->provisioning->normalize_option( + get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ) + ); foreach ( $event_goals as $event_key => $event_goal ) { - // Don't add this goal to the funnel. Create it separately instead. if ( $event_key === 'remove-from-cart' ) { - $this->provisioning->create_goals( [ $this->provisioning->create_goal_request( $event_goal ) ] ); + foreach ( $this->provisioning->get_clients() as $key => $client ) { + $all_ids = $this->provisioning->create_goals( + [ $this->provisioning->create_goal_request( $event_goal ) ], + $client, + $key, + $all_ids + ); + } continue; } if ( $event_key === 'purchase' ) { - if ( \Plausible\Analytics\WP\Integrations::is_edd_active() ) { - $currency = edd_get_currency(); - } else { - $currency = get_woocommerce_currency(); - } - - $goals[] = $this->provisioning->create_goal_request( $event_goal, 'Revenue', $currency ); + $currency = \Plausible\Analytics\WP\Integrations::is_edd_active() ? edd_get_currency() : get_woocommerce_currency(); + $goals[] = $this->provisioning->create_goal_request( $event_goal, 'Revenue', $currency ); continue; } @@ -88,7 +92,9 @@ public function create_integration_funnel( $event_goals, $funnel_name ) { $goals[] = $this->provisioning->create_goal_request( $event_goal ); } - $this->provisioning->create_funnel( $funnel_name, $goals ); + foreach ( $this->provisioning->get_clients() as $key => $client ) { + $all_ids = $this->provisioning->create_funnel( $funnel_name, $goals, $client, $key, $all_ids ); + } } /** @@ -97,21 +103,33 @@ public function create_integration_funnel( $event_goals, $funnel_name ) { * @param object $integration The integration object containing event goals to be deleted. * * @return void + * + * @codeCoverageIgnore We don't want to test the API. */ public function delete_integration_goals( $integration ) { - $goals = get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ); + $all_ids = $this->provisioning->normalize_option( + get_option( 'plausible_analytics_enhanced_measurements_goal_ids', [] ) + ); + + foreach ( $this->provisioning->get_clients() as $domain_key => $client ) { + $goals = $all_ids[ $domain_key ] ?? []; - foreach ( $goals as $id => $name ) { - $key = $this->provisioning->array_search_contains( $name, $integration->event_goals ); + foreach ( $goals as $id => $name ) { + $key = $this->provisioning->array_search_contains( $name, $integration->event_goals ); - if ( $key ) { - $this->provisioning->client->delete_goal( $id ); + if ( $key ) { + $client->delete_goal( $id ); + unset( $goals[ $id ] ); + } + } - unset( $goals[ $id ] ); + if ( empty( $goals ) ) { + unset( $all_ids[ $domain_key ] ); + } else { + $all_ids[ $domain_key ] = $goals; } } - // Refresh the stored IDs in the DB. - update_option( 'plausible_analytics_enhanced_measurements_goal_ids', $goals ); + update_option( 'plausible_analytics_enhanced_measurements_goal_ids', $all_ids ); } } diff --git a/src/Admin/Provisioning/Integrations/EDD.php b/src/Admin/Provisioning/Integrations/EDD.php index 138ad5c8..d5b9bf3c 100644 --- a/src/Admin/Provisioning/Integrations/EDD.php +++ b/src/Admin/Provisioning/Integrations/EDD.php @@ -45,7 +45,7 @@ private function init() { * Creates an EDD purchase funnel if enhanced measurement is enabled and EDD is active. * * @param array $old_settings The previous settings before the update. - * @param array $settings The updated settings array. + * @param array $settings The updated settings array. * * @return void * @@ -66,14 +66,20 @@ public function maybe_create_edd_funnel( $old_settings, $settings ) { * * required no. of goals is no longer met. * * @param array $old_settings The previous settings before the update. - * @param array $settings The current updated settings. + * @param array $settings The current updated settings. * * @return void * * @codeCoverageIgnore Because it interacts with the Plugins API. */ public function maybe_delete_edd_goals( $old_settings, $settings ) { - $enhanced_measurements = array_filter( $settings['enhanced_measurements'] ); + $enhanced_measurements = $settings['enhanced_measurements'] ?? []; + + if ( ! is_array( $enhanced_measurements ) ) { + $enhanced_measurements = []; + } + + $enhanced_measurements = array_filter( $enhanced_measurements ); if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::ECOMMERCE_REVENUE, $enhanced_measurements ) ) { return; diff --git a/src/Admin/Provisioning/Integrations/WooCommerce.php b/src/Admin/Provisioning/Integrations/WooCommerce.php index 8ae3ba1d..55e70b40 100644 --- a/src/Admin/Provisioning/Integrations/WooCommerce.php +++ b/src/Admin/Provisioning/Integrations/WooCommerce.php @@ -46,7 +46,7 @@ private function init() { * and creates the funnel if the conditions are met. * * @param array $old_settings The previous settings before the update. - * @param array $settings The updated settings to check for enhanced measurement and WooCommerce integration. + * @param array $settings The updated settings to check for enhanced measurement and WooCommerce integration. * * @return void * @@ -63,8 +63,8 @@ public function maybe_create_woocommerce_funnel( $old_settings, $settings ) { } /** - * Delete all custom WooCommerce event goals if Revenue setting is disabled. The funnel is deleted when the minimum - * required no. of goals is no longer met. + * Delete all custom WooCommerce event goals if the Revenue setting is disabled. The funnel is deleted when the minimum + * required number of goals is no longer met. * * @param $old_settings * @param $settings @@ -74,7 +74,13 @@ public function maybe_create_woocommerce_funnel( $old_settings, $settings ) { * @codeCoverageIgnore Because we don't want to test if the API is working. */ public function maybe_delete_woocommerce_goals( $old_settings, $settings ) { - $enhanced_measurements = array_filter( $settings['enhanced_measurements'] ); + $enhanced_measurements = $settings['enhanced_measurements'] ?? []; + + if ( ! is_array( $enhanced_measurements ) ) { + $enhanced_measurements = []; + } + + $enhanced_measurements = array_filter( $enhanced_measurements ); // Setting is enabled, no need to continue. if ( EnhancedMeasurements::is_enabled( EnhancedMeasurements::ECOMMERCE_REVENUE, $enhanced_measurements ) || ! Integrations::is_wc_active() ) { diff --git a/src/Admin/Settings/API.php b/src/Admin/Settings/API.php index 66f85a24..447fbbfd 100644 --- a/src/Admin/Settings/API.php +++ b/src/Admin/Settings/API.php @@ -20,532 +20,337 @@ class API { /** * Admin Setting Fields. * + * @var array * @since 1.3.0 * @access public - * @var array */ public $fields = []; /** * Slide IDs and Titles * - * @since v2.0.0 * @var string[] $slides + * @since v2.0.0 */ private $slides = []; /** * Slide IDs and Descriptions * - * @since v2.0.0 * @var array $slides_description + * @since v2.0.0 */ private $slides_description = []; /** - * Render Fields. + * Render Checkbox Field. * * @since 1.3.0 * @access public - * @return void + * @return string */ - public function settings_page() { - wp_nonce_field( 'plausible_analytics_toggle_option' ); - - $settings = Helpers::get_settings(); - $followed_wizard = get_option( 'plausible_analytics_wizard_done' ) || ! empty( $settings['self_hosted_domain'] ); - - /** - * On-boarding wizard. - */ - if ( ! $followed_wizard ) { - $this->slides = [ - 'welcome' => __( 'Welcome to Plausible Analytics', 'plausible-analytics' ), - 'domain_name' => __( 'Confirm domain', 'plausible-analytics' ), - 'api_token' => __( 'Create Plugin Token', 'plausible-analytics' ), - 'enable_analytics_dashboard' => __( 'View the stats in your WP dashboard', 'plausible-analytics' ), - 'enhanced_measurements' => __( 'Enhanced measurements', 'plausible-analytics' ), - 'proxy_enabled' => __( 'Enable proxy', 'plausible-analytics' ), - 'success' => __( 'Success!', 'plausible-analytics' ), - ]; - $this->slides_description = [ - 'welcome' => sprintf( - // translators: 1: URL to Plausible website, 2: URL to Plausible registration page. - __( - '

Plausible Analytics is an easy to use, open source, lightweight and privacy-friendly alternative to Google Analytics. We\'re super excited to have you on board!

To use our plugin, you need to register an account. To explore the product, we offer you a free 30-day trial. No credit card is required to sign up for the trial.

Already have an account? Please do follow the following steps to get the most out of your Plausible experience.

', - 'plausible-analytics' - ), - 'https://plausible.io/?utm_source=WordPress&utm_medium=Referral&utm_campaign=WordPress+plugin', - 'https://plausible.io/register?utm_source=WordPress&utm_medium=Referral&utm_campaign=WordPress+plugin' - ), - 'domain_name' => __( - 'Confirm your domain name as you\'ve added it to your Plausible account.', - 'plausible-analytics' - ), - 'api_token' => __( - 'Create a Plugin Token (link opens in a new window) that we\'ll use to automate your setup process. Paste the Plugin Token in the field below and click "Next".', - 'plausible-analytics' - ), - 'enable_analytics_dashboard' => __( - 'Would you like to view your site\'s stats in your WordPress dashboard?', - 'plausible-analytics' - ), - 'enhanced_measurements' => __( 'Enable enhanced measurements to automatically track actions that visitors take on your site.', 'plausible-analytics' ), - 'proxy_enabled' => __( - 'Run our script as a first party connection from your domain name to count visitors who use ad blockers', - 'plausible-analytics' - ), - 'success' => sprintf( - // translators: 1: URL to Plausible statistics dashboard, 2: URL to plugin settings, 3: URL to Plausible documentation, 4: URL to Plausible contact. - __( - '

Congrats! Your traffic is now being counted without compromising the user experience and privacy of your visitors. You can now check out your intuitive, fast-loading and privacy-friendly dashboard.

Note that visits from logged in users aren\'t tracked. If you want to track visits for certain user roles, then please specify them in the plugin\'s settings.

Need help? Our documentation is the best place to find most answers right away.

Still haven\'t found the answer you\'re looking for? We\'re here to help. Please contact our support.

', - 'plausible-analytics' - ), - admin_url( 'index.php?page=plausible_analytics_statistics' ), - admin_url( 'admin-ajax.php?action=plausible_analytics_quit_wizard&_nonce=' ) . wp_create_nonce( 'plausible_analytics_quit_wizard' ) . '&redirect=tracked_user_roles', - 'https://plausible.io/docs?utm_source=WordPress&utm_medium=Referral&utm_campaign=WordPress+plugin', - 'https://plausible.io/contact?utm_source=WordPress&utm_medium=Referral&utm_campaign=WordPress+plugin' - ), - ]; - - if ( empty( $settings['enable_analytics_dashboard'] ) ) { - $this->slides_description['success'] = sprintf( - // translators: 1: URL to plugin settings, 2: URL to Plausible documentation, 3: URL to Plausible contact. - __( - '

Congrats! Your traffic is now being counted without compromising the user experience and privacy of your visitors.

Note that visits from logged in users aren\'t tracked. If you want to track visits for certain user roles, then please specify them in the plugin\'s settings.

Need help? Our documentation is the best place to find most answers right away.

Still haven\'t found the answer you\'re looking for? We\'re here to help. Please contact our support.

', - 'plausible-analytics' - ), - admin_url( 'admin-ajax.php?action=plausible_analytics_quit_wizard&_nonce=' ) . wp_create_nonce( 'plausible_analytics_quit_wizard' ) . '&redirect=tracked_user_roles', - 'https://plausible.io/docs?utm_source=WordPress&utm_medium=Referral&utm_campaign=WordPress+plugin', - 'https://plausible.io/contact?utm_source=WordPress&utm_medium=Referral&utm_campaign=WordPress+plugin' - ); - } - - $this->show_wizard(); - - return; - } + public function render_checkbox_field( array $field, $is_list = false ) { + ob_start(); + $value = ! empty( $field['value'] ) ? $field['value'] : 'on'; + $settings = Helpers::get_settings(); + $slug = ! empty( $settings[ $field['slug'] ] ) ? $settings[ $field['slug'] ] : ''; + $id = $field['slug'] . '_' . str_replace( '-', '_', sanitize_title( $field['label'] ) ); + $checked = ! empty( $field['checked'] ) ? 'checked="checked"' : + ( is_array( $slug ) ? checked( $value, in_array( $value, $slug, false ) ? $value : false, false ) : checked( $value, $slug, false ) ); + $disabled = ! empty( $field['disabled'] ) ? 'disabled' : ''; + $caps = ! empty( $field['caps'] ) ? $field['caps'] : []; + $addtl_opts = ! empty( $field['addtl_opts'] ); + ?> +
+ + + + + + + +
+ '' ]; + $slug = $group['slug'] ?? ''; ?> -
- -
- render_notices_field(); ?> -
- -
-
- -
-
- fields[ $current_tab ] as $tab => $field ): ?> -
- _content" class="plausible-analytics-section !mt-1 mx-14"> +
+
+
+
    + $value ) : ?> +
  1. + render_text_field( [ 'value' => $value, 'slug' => esc_attr( "{$slug}[$key]" ), 'classes' => 'flex-1' ] ); ?> + ')" class=" ml-2 cursor-pointer text-red-800 hover:text-red-500 dark:text-red-500 dark:hover:text-red-400"> + + +
  2. + +
+ render_button_field( [ 'slug' => 'save-' . $slug, 'label' => __( 'Save', 'plausible-analytics' ) ] ); ?> + +
+ -
- -
-
-
- - + /** + * Render Text Field. + * + * @since 1.3.0 + * @access public + * @return string + */ + public function render_text_field( array $field ) { + ob_start(); + $value = ! empty( $field['value'] ) ? $field['value'] : ''; + $placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : ''; + $disabled = ! empty( $field['disabled'] ) ? 'disabled' : ''; + $classes = ! empty ( $field['classes'] ) ? $field['classes'] : ''; + ?> +
+ + + +
+ />
-
-
- -
- -
- Plausible logo -
- render_notices_field(); ?> - -
-

- -

-
- -
-
-
- slides ); - $i = 0; - ?> - slides as $id => $title ): ?> - - -
+ /** + * Render just the label, and allow insertion of anything using the hook beside it. + * + * @since 1.3.0 + * + * @param array $field + * + * @return string|false + */ + public function render_hook_field( array $field ) { + $hook_type = $field['hook_type'] ?? 'warning'; + $box_class = 'bg-yellow-50 dark:bg-yellow-100'; + $text_class = 'text-yellow-700 dark:text-yellow-800'; + $persist_message = ''; + + if ( $hook_type === 'success' ) { + $box_class = 'bg-green-50 dark:bg-green-100'; + $text_class = 'text-green-700 dark:text-green-800'; + } + + if ( ! empty( $field['slug'] ) && ( $field['slug'] === 'option_not_available_in_ce' || $field['slug'] === 'option_disabled_by_missing_api_token' ) ) { + $persist_message = 'plausible-analytics-persist'; + } + + ob_start(); + ?> +
+
+
+
+ + + + + + + + +
- -
- -
-