options = $options; $this->api = $api; $this->auth = $auth; $this->byo = $byo; } /** * Register menu, actions, notices, and cron refresh. */ public function register() { add_action( 'admin_menu', array( $this, 'add_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); add_action( 'admin_post_' . self::ACTION_DISCONNECT, array( $this, 'handle_disconnect' ) ); add_action( 'admin_post_' . self::ACTION_REFRESH, array( $this, 'handle_refresh' ) ); add_action( 'admin_post_' . self::ACTION_SAVE, array( $this, 'handle_save' ) ); add_action( 'admin_notices', array( $this, 'render_notice' ) ); add_action( OSSEngine_Options::CRON_HOOK, array( $this, 'cron_refresh' ) ); } /** * Add Settings → OSSEngine. */ public function add_menu() { add_options_page( __( 'OSSEngine Analytics Connector', 'ossengine-analytics-connector' ), __( 'OSSEngine Analytics', 'ossengine-analytics-connector' ), 'manage_options', self::PAGE_SLUG, array( $this, 'render' ) ); } /** * Enqueue the settings-screen script + scoped styles (plugin screen only). * * @param string $hook_suffix Current admin page hook suffix. */ public function enqueue_admin_assets( $hook_suffix ) { if ( 'settings_page_' . self::PAGE_SLUG !== $hook_suffix ) { return; } wp_register_script( 'ossengine-analytics-connector-admin', plugins_url( 'assets/js/ossengine-admin.js', OSSENGINE_PLUGIN_FILE ), array(), OSSENGINE_VERSION, true ); wp_enqueue_script( 'ossengine-analytics-connector-admin' ); wp_register_style( 'ossengine-analytics-connector-admin', false, array(), OSSENGINE_VERSION ); wp_enqueue_style( 'ossengine-analytics-connector-admin' ); wp_add_inline_style( 'ossengine-analytics-connector-admin', '.ossengine-analytics-connector-lead{max-width:720px}.ossengine-analytics-connector-info{margin:12px 0;padding:12px 16px}.ossengine-analytics-connector-list{list-style:disc;margin:8px 0 8px 20px}.ossengine-analytics-connector-ordered{margin:8px 0 8px 20px}.ossengine-analytics-connector-errorbox{margin:12px 0}' ); } /** * Disconnect: permanently delete the Umami website and its analytics data, * revoke server-side, and clear the local token. * * Requires the admin to have typed exactly "DELETE" in the confirmation * form; anything else aborts before any request is sent. */ public function handle_disconnect() { $this->require_capability(); check_admin_referer( self::ACTION_DISCONNECT ); $confirmation = isset( $_POST['confirmation'] ) ? sanitize_text_field( wp_unslash( $_POST['confirmation'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified by check_admin_referer() above. if ( 'DELETE' !== $confirmation ) { $this->options->set_notice( 'error', __( 'Type DELETE to confirm permanent deletion. Nothing was disconnected.', 'ossengine-analytics-connector' ) ); $this->redirect(); } $connection = $this->options->get_connection(); $token = $this->options->get_site_token(); if ( '' !== $token && '' !== $connection['integration_id'] ) { $result = $this->api->disconnect( $token, $connection['integration_id'], 'DELETE' ); if ( is_wp_error( $result ) && 'ossengine_expired' !== $result->get_error_code() && 'ossengine_not_found' !== $result->get_error_code() ) { $this->options->set_notice( 'error', $result->get_error_message() ); $this->redirect(); } } $this->options->reset_connection(); $this->options->set_notice( 'updated', __( 'Disconnected. The Umami website and its analytics data have been permanently deleted.', 'ossengine-analytics-connector' ) ); $this->redirect(); } /** * Manual status/config refresh (no per-page-view API calls otherwise). */ public function handle_refresh() { $this->require_capability(); check_admin_referer( self::ACTION_REFRESH ); $connection = $this->options->get_connection(); $token = $this->options->get_site_token(); if ( 'managed' !== $connection['mode'] || '' === $token || '' === $connection['integration_id'] ) { $this->options->set_notice( 'error', __( 'Nothing to refresh. Connect OSSEngine Analytics first.', 'ossengine-analytics-connector' ) ); $this->redirect(); } $status = $this->api->get_status( $token, $connection['integration_id'] ); if ( is_wp_error( $status ) ) { $this->options->set_notice( 'error', $status->get_error_message() ); $this->redirect(); } $this->auth->refresh_config( $token, $connection['integration_id'] ); $this->options->set_notice( 'updated', __( 'Connection status refreshed.', 'ossengine-analytics-connector' ) ); $this->redirect(); } /** * Save tracking toggles and BYO settings. */ public function handle_save() { $this->require_capability(); check_admin_referer( self::ACTION_SAVE ); $this->options->update_connection( array( 'tracking_enabled' => ! empty( $_POST['tracking_enabled'] ), // phpcs:ignore WordPress.Security.NonceVerification.Missing 'exclude_admin' => ! empty( $_POST['exclude_admin'] ), // phpcs:ignore WordPress.Security.NonceVerification.Missing ) ); $byo_enabled = ! empty( $_POST['byo_enabled'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $byo_url = isset( $_POST['byo_url'] ) ? esc_url_raw( sanitize_text_field( wp_unslash( $_POST['byo_url'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified by check_admin_referer() above. $byo_site = isset( $_POST['byo_website_id'] ) ? sanitize_text_field( wp_unslash( $_POST['byo_website_id'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing update_option( OSSEngine_WooCommerce::OPTION_ENABLED, OSSEngine_WooCommerce::is_available() && ! empty( $_POST['woo_enabled'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing ); $result = $this->byo->save( $byo_enabled, $byo_url, $byo_site ); if ( is_wp_error( $result ) ) { $this->options->set_notice( 'error', $result->get_error_message() ); } else { $this->options->set_notice( 'updated', __( 'Settings saved.', 'ossengine-analytics-connector' ) ); } $this->redirect(); } /** * Weekly cron: revalidate managed connection without blocking pages. */ public function cron_refresh() { $connection = $this->options->get_connection(); $token = $this->options->get_site_token(); if ( 'managed' !== $connection['mode'] || '' === $token || '' === $connection['integration_id'] ) { return; } $this->auth->refresh_config( $token, $connection['integration_id'] ); } /** * Render the queued admin notice, if any. */ public function render_notice() { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || 'settings_page_' . self::PAGE_SLUG !== $screen->id ) { return; } $notice = $this->options->take_notice(); if ( null === $notice ) { return; } printf( '
%s
| status_label( $connection ) ); ?> | |
| tracking_label( $connection, $byo ) ); ?>
|
|
snippet_preview( $connection ) ); ?>
|
|
| woo_label( $woo_status ) ); ?> |
block) showing admins what to look for in page source; not a printed script tag.
$connection['script_url'],
$connection['ossengine_website_id']
);
}
/**
* Analytics app URL (the hosted Umami app itself).
*
* Previously this pointed at the account dashboard analytics page
* (dashboard.umamiengine.com/analytics). It now opens the Umami app
* directly (app.umamiengine.com) where stats are viewed.
*
* @return string
*/
private function analytics_url() {
return $this->options->analytics_app_url();
}
/**
* Signup URL for new UmamiEngine customers.
*
* @return string
*/
private function signup_url() {
return $this->options->signup_url();
}
/**
* Nonced admin-post URL for an action.
*
* @param string $action Action name.
* @return string
*/
private function action_url( $action ) {
return wp_nonce_url(
add_query_arg( array( 'action' => $action ), admin_url( 'admin-post.php' ) ),
$action
);
}
/**
* Enforce capability.
*/
private function require_capability() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to manage analytics settings.', 'ossengine-analytics-connector' ), 403 );
}
}
/**
* Redirect back to the settings page.
*/
private function redirect() {
wp_safe_redirect(
add_query_arg( array( 'page' => self::PAGE_SLUG ), admin_url( 'options-general.php' ) )
);
exit;
}
}