diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml new file mode 100644 index 0000000..c3a0901 --- /dev/null +++ b/.github/workflows/coding-standards.yml @@ -0,0 +1,37 @@ +name: Coding Standards + +# When to run. +on: + pull_request: + types: + - opened + - synchronize + push: + branches: + - master + +jobs: + coding-standards: + name: Coding Standards / PHP ${{ matrix.php-versions }} + runs-on: ubuntu-latest + + strategy: + matrix: + php-versions: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] + + steps: + - name: Checkout Plugin + uses: actions/checkout@v4 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none + tools: cs2pr + + - name: Install Composer dependencies + run: composer update --no-interaction --no-progress + + - name: Run PHP Coding Standards + run: vendor/bin/phpcs -q --standard=phpcs.xml --report=checkstyle ./ | cs2pr diff --git a/.gitignore b/.gitignore index f1ff8a5..937b472 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ - .svn **/.DS_Store +composer.lock +/vendor/ \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..049054b --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "usefathom/fathom-analytics", + "description": "Fathom Analytics for WordPress.", + "type": "wordpress-plugin", + "license": "GPL-2.0-or-later", + "require": { + "php": ">=5.4" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "wp-coding-standards/wpcs": "^3.0.0" + }, + "scripts": { + "phpcs": "vendor/bin/phpcs ./ -s -v", + "phpcbf": "vendor/bin/phpcbf ./ -s -v" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/fathom-analytics.php b/fathom-analytics.php index cd6696b..1fb0071 100644 --- a/fathom-analytics.php +++ b/fathom-analytics.php @@ -1,5 +1,5 @@ . */ +// Exit if accessed directly. +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + const FATHOM_PLUGIN_VERSION = '3.3.1'; const FATHOM_SITE_ID_OPTION_NAME = 'fathom_site_id'; const FATHOM_EXCLUDE_ROLES_OPTION_NAME = 'fathom_exclude_roles'; @@ -75,10 +80,36 @@ function fathom_get_excluded_roles() return $excluded_roles; } +/** + * Sanitize a checkbox option to 1 or 0. + * + * @since 3.3.2 + */ +function fathom_sanitize_checkbox( $value ) +{ + return empty( $value ) ? 0 : 1; +} + +/** + * Sanitize the excluded roles option to an array of valid role slugs. + * + * @since 3.3.2 + */ +function fathom_sanitize_excluded_roles( $roles ) +{ + if ( ! is_array( $roles ) ) { + return array(); + } + + $valid_roles = array_keys( wp_roles()->get_names() ); + + return array_values( array_intersect( $valid_roles, array_map( 'sanitize_key', $roles ) ) ); +} + /** * Determine if the current user has any of the excluded roles. * - * @return void + * @return bool */ function fathom_is_excluded_from_tracking() { if ( ! is_user_logged_in() ) { @@ -87,7 +118,7 @@ function fathom_is_excluded_from_tracking() { $user = wp_get_current_user(); - return array_intersect( fathom_get_excluded_roles(), $user->roles ); + return (bool) array_intersect( fathom_get_excluded_roles(), $user->roles ); } /** @@ -99,7 +130,7 @@ function fathom_enqueue_js_snippet() return; } - wp_enqueue_script( 'fathom-snippet', 'https://cdn.usefathom.com/script.js', array(), null, array( 'strategy' => 'defer' ) ); + wp_enqueue_script( 'fathom-snippet', 'https://cdn.usefathom.com/script.js', array(), null, array( 'strategy' => 'defer' ) ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- External CDN script, version intentionally omitted. } /** @@ -116,7 +147,7 @@ function fathom_enqueue_js_snippet() function fathom_add_data_attributes_to_js_script( $tag, $handle, $src ) { if ( 'fathom-snippet' === $handle ) { - $attributes = ' data-site="' . fathom_get_site_id() . '" ' . exclude_fathom_script_from_cookiebot(); + $attributes = ' data-site="' . esc_attr( fathom_get_site_id() ) . '" ' . exclude_fathom_script_from_cookiebot(); if ( get_option( FATHOM_IGNORE_CANONICAL ) ) { $attributes .= ' data-canonical="false"'; @@ -139,7 +170,7 @@ function fathom_add_data_attributes_to_js_script( $tag, $handle, $src ) function fathom_get_menu_icon() { $icon = ''; - return sprintf( 'data:image/svg+xml;base64,%s', base64_encode( $icon ) ); + return sprintf( 'data:image/svg+xml;base64,%s', base64_encode( $icon ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Encoding an inline SVG for the menu icon. } /** @@ -163,10 +194,11 @@ function fathom_stats_page() { function fathom_print_stats_page() { if (!empty(get_option(FATHOM_SITE_ID_OPTION_NAME))) { - wp_enqueue_script('fathom-iframresize', plugins_url('iframeResizer.min.js', __FILE__)); - wp_enqueue_script('fathom-stats-iframe', plugins_url('fathom-stats-iframe.js', __FILE__)); + wp_enqueue_script( 'fathom-iframe-resizer', plugins_url( 'iframeResizer.min.js', __FILE__ ), array(), FATHOM_PLUGIN_VERSION, false ); + wp_enqueue_script( 'fathom-stats-iframe', plugins_url( 'fathom-stats-iframe.js', __FILE__ ), array( 'fathom-iframe-resizer' ), FATHOM_PLUGIN_VERSION, false ); echo '
'; - echo ''; + $stats_src = 'https://app.usefathom.com/share/' . get_option(FATHOM_SITE_ID_OPTION_NAME) . '/wordpress?password=' . hash('sha256', get_option(FATHOM_PRIVATE_SHARE_PASSWORD)); + echo ''; echo '
'; } else { echo '
You have not configured Fathom. Go to Settings -> Fathom Analytics to configure this page.
'; @@ -183,11 +215,11 @@ function fathom_register_settings() add_settings_section( 'default', "Fathom Analytics", 'fathom_settings_intro', 'fathom-analytics' ); // register options - register_setting( 'fathom', FATHOM_SITE_ID_OPTION_NAME, array( 'type' => 'string' ) ); - register_setting( 'fathom', FATHOM_PRIVATE_SHARE_PASSWORD, array( 'type' => 'string' ) ); - register_setting( 'fathom', FATHOM_EXCLUDE_ROLES_OPTION_NAME, array( 'type' => 'multi_checkbox' ) ); - register_setting( 'fathom', FATHOM_SHOW_ANALYTICS_MENU_ITEM, array( 'type' => 'boolean', 'default' => 1 ) ); - register_setting( 'fathom', FATHOM_IGNORE_CANONICAL, array( 'type' => 'boolean', 'default' => 0 ) ); + register_setting( 'fathom', FATHOM_SITE_ID_OPTION_NAME, array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '' ) ); + register_setting( 'fathom', FATHOM_PRIVATE_SHARE_PASSWORD, array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '' ) ); + register_setting( 'fathom', FATHOM_EXCLUDE_ROLES_OPTION_NAME, array( 'type' => 'array', 'sanitize_callback' => 'fathom_sanitize_excluded_roles', 'default' => array() ) ); + register_setting( 'fathom', FATHOM_SHOW_ANALYTICS_MENU_ITEM, array( 'type' => 'boolean', 'sanitize_callback' => 'fathom_sanitize_checkbox', 'default' => 1 ) ); + register_setting( 'fathom', FATHOM_IGNORE_CANONICAL, array( 'type' => 'boolean', 'sanitize_callback' => 'fathom_sanitize_checkbox', 'default' => 0 ) ); // register settings fields add_settings_field( FATHOM_SITE_ID_OPTION_NAME, __( 'Site ID', 'fathom-analytics' ), 'fathom_print_site_id_setting_field', 'fathom-analytics', 'default'); @@ -202,18 +234,18 @@ function fathom_register_settings() * * @param array $args Display arguments. * - * @return string + * @return void * * @since 3.1.0 */ function fathom_settings_intro( $args ) { $intro = sprintf( '

%s

', - __( 'If you are enjoying the Fathom plugin for Wordpress, please leave us a ⭐️⭐️⭐️⭐️⭐️ rating. Huge thanks in advance :)', 'fathom-analytics' ) + __( 'If you are enjoying the Fathom plugin for WordPress, please leave us a ⭐️⭐️⭐️⭐️⭐️ rating. Huge thanks in advance :)', 'fathom-analytics' ) ); if ( get_option( FATHOM_CUSTOM_DOMAIN_OPTION_NAME ) ) { - if ( isset( $_GET['action'] ) && 'remove_custom_domain' === $_GET['action'] ) { + if ( isset( $_GET['action'], $_GET['_wpnonce'] ) && 'remove_custom_domain' === sanitize_text_field( wp_unslash( $_GET['action'] ) ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'fathom_remove_custom_domain' ) ) { delete_option( FATHOM_CUSTOM_DOMAIN_OPTION_NAME ); } else { $intro .= sprintf( @@ -221,14 +253,14 @@ function fathom_settings_intro( $args ) { __( 'As of May 9, 2023, we can no longer support custom domains - you can read more here.', 'fathom-analytics' ), sprintf( '%s', - esc_url( add_query_arg( 'action', 'remove_custom_domain' ) ), + esc_url( wp_nonce_url( add_query_arg( 'action', 'remove_custom_domain' ), 'fathom_remove_custom_domain' ) ), __( 'Got it!', 'fathom-analytics' ) ) ); } } - echo $intro; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo wp_kses_post( $intro ); } /** @@ -237,7 +269,7 @@ function fathom_settings_intro( $args ) { function fathom_print_settings_page() { echo '
'; - echo sprintf('
', esc_attr(admin_url('options.php'))); + printf('', esc_url(admin_url('options.php'))); settings_fields('fathom'); do_settings_sections('fathom-analytics'); submit_button(); @@ -251,8 +283,8 @@ function fathom_print_settings_page() function fathom_print_display_analytics_menu_setting_field($args = array()) { $value = get_option(FATHOM_SHOW_ANALYTICS_MENU_ITEM); - echo sprintf('', FATHOM_SHOW_ANALYTICS_MENU_ITEM, FATHOM_SHOW_ANALYTICS_MENU_ITEM); - echo '

' . __('Pro: Display the Fathom Tab in the sidebar (This is only available if you have enabled site sharing)', 'fathom-analytics') . '

'; + printf('', esc_attr( FATHOM_SHOW_ANALYTICS_MENU_ITEM ), esc_attr( FATHOM_SHOW_ANALYTICS_MENU_ITEM )); + echo '

' . esc_html__('Pro: Display the Fathom Tab in the sidebar (This is only available if you have enabled site sharing)', 'fathom-analytics') . '

'; } /** @@ -267,8 +299,8 @@ function fathom_print_display_analytics_menu_setting_field($args = array()) function fathom_print_ignore_canonical_setting_field($args = array()) { $value = get_option(FATHOM_IGNORE_CANONICAL); - echo sprintf('', FATHOM_IGNORE_CANONICAL, FATHOM_IGNORE_CANONICAL); - echo '

' . __('If there\'s a canonical link in place, we use it instead of the current URL. Check this to use the current URL instead', 'fathom-analytics') . '

'; + printf('', esc_attr( FATHOM_IGNORE_CANONICAL ), esc_attr( FATHOM_IGNORE_CANONICAL )); + echo '

' . esc_html__('If there\'s a canonical link in place, we use it instead of the current URL. Check this to use the current URL instead', 'fathom-analytics') . '

'; } /** @@ -278,14 +310,14 @@ function fathom_print_share_password_setting_field($args = array()) { $value = get_option(FATHOM_PRIVATE_SHARE_PASSWORD); $placeholder = ''; - echo sprintf('', FATHOM_PRIVATE_SHARE_PASSWORD, FATHOM_PRIVATE_SHARE_PASSWORD, esc_attr($value), esc_attr($placeholder)); - echo '

' . __('Required if you have shared your dashboard privately. Publicly shared dashboards do not need a password.', 'fathom-analytics') . '

'; + printf('', esc_attr( FATHOM_PRIVATE_SHARE_PASSWORD ), esc_attr( FATHOM_PRIVATE_SHARE_PASSWORD ), esc_attr($value), esc_attr($placeholder)); + echo '

' . wp_kses_post( __('Required if you have shared your dashboard privately. Publicly shared dashboards do not need a password.', 'fathom-analytics') ) . '

'; } /** * Exclude Roles Field * - * @param array $args + * @param array $args Display arguments. * * @return void */ @@ -300,14 +332,14 @@ function fathom_print_exclude_roles_setting_field( $args = array() ) { $excluded_roles = fathom_get_excluded_roles(); echo '
'; - foreach ( $roles as $role => $label ) { - $checked = in_array( $role, $excluded_roles, true ) ? 'checked' : ''; - echo sprintf( '', FATHOM_EXCLUDE_ROLES_OPTION_NAME, $role, $role, $checked ); - echo sprintf( '
', $role, $label ); - } + foreach ( $roles as $role => $label ) { + $checked = in_array( $role, $excluded_roles, true ) ? 'checked' : ''; + printf( '', esc_attr( FATHOM_EXCLUDE_ROLES_OPTION_NAME ), esc_attr( $role ), esc_attr( $role ), esc_attr( $checked ) ); + printf( '
', esc_attr( $role ), esc_html( $label ) ); + } echo '
'; - echo '

' . __( 'Choose the roles you would like to exclude from tracking.', 'fathom-analytics' ) . '

'; + echo '

' . esc_html__( 'Choose the roles you would like to exclude from tracking.', 'fathom-analytics' ) . '

'; } /** @@ -317,8 +349,8 @@ function fathom_print_site_id_setting_field($args = array()) { $value = get_option(FATHOM_SITE_ID_OPTION_NAME); $placeholder = 'ABCDEF'; - echo sprintf('', FATHOM_SITE_ID_OPTION_NAME, FATHOM_SITE_ID_OPTION_NAME, esc_attr($value), esc_attr($placeholder)); - echo '

' . __('This is the unique Tracking ID for your site', 'fathom-analytics') . '

'; + printf('', esc_attr( FATHOM_SITE_ID_OPTION_NAME ), esc_attr( FATHOM_SITE_ID_OPTION_NAME ), esc_attr($value), esc_attr($placeholder)); + echo '

' . wp_kses_post( __('This is the unique Tracking ID for your site', 'fathom-analytics') ) . '

'; } add_action( 'wp_enqueue_scripts', 'fathom_enqueue_js_snippet' ); @@ -475,7 +507,7 @@ function fathom_exclude_from_litespeed( $excluded ) { */ function allow_fathom_script($allowed, $handle) { - if ($handle === 'fathom-snippet') { + if ( 'fathom-snippet' === $handle ) { return true; } return $allowed; @@ -492,9 +524,9 @@ function allow_fathom_script($allowed, $handle) { */ function exclude_fathom_script_from_cookiebot() { - if ( in_array( 'cookiebot/cookiebot.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { + if ( in_array( 'cookiebot/cookiebot.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ), true ) ) { return 'data-cookieconsent="ignore"'; } else { return ''; } -} \ No newline at end of file +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..e897d5e --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,66 @@ + + + WordPress Coding Standards for the Fathom Analytics WordPress Plugin + + + . + + + /.github/* + /assets/* + /vendor/* + + + *.js + *.css + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +