← All changes
|
jetpack_vendor/automattic/jetpack-stats/src/abilities/class-stats-abilities.php
+10
-148
16.2
→
16.3
View file →
| @@ -8,13 +8,17 @@ | ||
| 8 | 8 | */ |
| 9 | 9 | |
| 10 | 10 | namespace Automattic\Jetpack\Stats\Abilities; |
| 11 | 11 | |
| 12 | -use Automattic\Jetpack\Stats\Options; | |
| 12 | +use Automattic\Jetpack\Stats\Settings; | |
| 13 | 13 | use Automattic\Jetpack\Stats\WPCOM_Stats; |
| 14 | 14 | use Automattic\Jetpack\WP_Abilities\Registrar; |
| 15 | 15 | use WP_Error; |
| 16 | 16 | |
| 17 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 18 | + exit( 0 ); | |
| 19 | +} | |
| 20 | + | |
| 17 | 21 | /** |
| 18 | 22 | * Registers Jetpack Stats abilities with the WordPress Abilities API. |
| 19 | 23 | * |
| 20 | 24 | * Exposes a small, consolidated surface for reading Jetpack Stats traffic |
| @@ -25,24 +29,11 @@ | ||
| 25 | 29 | */ |
| 26 | 30 | class Stats_Abilities extends Registrar { |
| 27 | 31 | |
| 28 | 32 | const CATEGORY_SLUG = 'jetpack-stats'; |
| 29 | - const ERROR_PREFIX = 'jetpack_stats_'; | |
| 33 | + const ERROR_PREFIX = Settings::ERROR_PREFIX; | |
| 30 | 34 | |
| 31 | 35 | /** |
| 32 | - * Whitelist of stats_options keys exposed via the settings abilities. | |
| 33 | - * | |
| 34 | - * Internal keys (`blog_id`, `notices`, `views`, `collapse_nudges`, | |
| 35 | - * `version`, `odyssey_stats_changed_at`) are deliberately excluded — | |
| 36 | - * agents can't act on them and they'd bloat the response. | |
| 37 | - * `enable_odyssey_stats` is also excluded: it's a UI dashboard toggle | |
| 38 | - * with no meaningful agent use case. The per-key type (bool / | |
| 39 | - * role-array) is read from `Options::get_defaults()` at runtime, not | |
| 40 | - * duplicated here. | |
| 41 | - */ | |
| 42 | - const SETTINGS_KEYS = array( 'admin_bar', 'roles', 'count_roles', 'do_not_track' ); | |
| 43 | - | |
| 44 | - /** | |
| 45 | 36 | * Allowed `type` values for `get-top-content`. |
| 46 | 37 | */ |
| 47 | 38 | const TOP_CONTENT_TYPES = array( 'posts', 'referrers', 'search-terms', 'clicks', 'tags', 'authors', 'countries', 'downloads', 'video-plays' ); |
| 48 | 39 | |
| @@ -503,9 +494,9 @@ | ||
| 503 | 494 | private static function spec_update_settings(): array { |
| 504 | 495 | return array( |
| 505 | 496 | 'label' => __( 'Update Stats settings', 'jetpack-stats-pkg' ), |
| 506 | 497 | 'description' => __( |
| 507 | - 'Update one or more Jetpack Stats settings. All fields are optional; only fields present in the call are written, and unrelated keys are preserved. Idempotent — setting a value to its current state returns changed=false. Shape: { changed, settings: { admin_bar, roles, count_roles, do_not_track } }. Role slugs in `roles` and `count_roles` are validated against the site\'s registered roles; unknown slugs return jetpack_stats_invalid_role. Narrowing `roles` can revoke Stats access for whole groups of users — confirm with the user before removing roles.', | |
| 498 | + 'Update one or more Jetpack Stats settings. All fields are optional; only fields present in the call are written, and unrelated keys are preserved. Idempotent — setting a value to its current state returns changed=false. Shape: { changed, settings: { admin_bar, roles, count_roles, do_not_track } }. Role slugs added to `roles` or `count_roles` must be registered roles on the site; unknown slugs return jetpack_stats_invalid_role. A slug that is already saved is kept even when its role no longer exists. Narrowing `roles` can revoke Stats access for whole groups of users — confirm with the user before removing roles.', | |
| 508 | 499 | 'jetpack-stats-pkg' |
| 509 | 500 | ), |
| 510 | 501 | 'input_schema' => array( |
| 511 | 502 | 'type' => 'object', |
| @@ -515,9 +506,9 @@ | ||
| 515 | 506 | 'description' => __( 'Whether to show the Stats item in the admin bar for users who can view Stats.', 'jetpack-stats-pkg' ), |
| 516 | 507 | ), |
| 517 | 508 | 'roles' => array( |
| 518 | 509 | 'type' => 'array', |
| 519 | - 'description' => __( 'Role slugs that can view Stats. Must be non-empty; each slug must be a registered role.', 'jetpack-stats-pkg' ), | |
| 510 | + 'description' => __( 'Role slugs that can view Stats. Must be non-empty; each added slug must be a registered role. `administrator` is always kept.', 'jetpack-stats-pkg' ), | |
| 520 | 511 | 'items' => array( 'type' => 'string' ), |
| 521 | 512 | 'minItems' => 1, |
| 522 | 513 | ), |
| 523 | 514 | 'count_roles' => array( |
| @@ -883,9 +874,9 @@ | ||
| 883 | 874 | * @return array |
| 884 | 875 | */ |
| 885 | 876 | public static function get_settings( $input = null ) { |
| 886 | 877 | unset( $input ); |
| 887 | - return self::settings_snapshot(); | |
| 878 | + return Settings::get( Settings::KEYS ); | |
| 888 | 879 | } |
| 889 | 880 | |
| 890 | 881 | /** |
| 891 | 882 | * Execute: update-settings. |
| @@ -893,115 +884,9 @@ | ||
| 893 | 884 | * @param array|null $input Input matching the ability's input_schema. |
| 894 | 885 | * @return array|WP_Error |
| 895 | 886 | */ |
| 896 | 887 | public static function update_settings( $input = null ) { |
| 897 | - $input = is_array( $input ) ? $input : array(); | |
| 898 | - | |
| 899 | - // At least one of the whitelisted keys must be present. | |
| 900 | - $provided = array_intersect_key( $input, array_flip( self::SETTINGS_KEYS ) ); | |
| 901 | - if ( empty( $provided ) ) { | |
| 902 | - return new WP_Error( | |
| 903 | - self::ERROR_PREFIX . 'missing_setting_field', | |
| 904 | - sprintf( | |
| 905 | - /* translators: %s: comma-separated list of writable field names. */ | |
| 906 | - __( 'Provide at least one of: %s.', 'jetpack-stats-pkg' ), | |
| 907 | - implode( ', ', self::SETTINGS_KEYS ) | |
| 908 | - ) | |
| 909 | - ); | |
| 910 | - } | |
| 911 | - | |
| 912 | - // Validate role slugs against registered roles — but only load the role list | |
| 913 | - // if the caller is actually writing a role field. Boolean-only writes skip | |
| 914 | - // the wp_roles() resolution entirely. Role fields are detected from the | |
| 915 | - // option's default value type (array → role list). | |
| 916 | - $defaults = Options::get_defaults(); | |
| 917 | - $known_roles = null; | |
| 918 | - foreach ( self::SETTINGS_KEYS as $role_field ) { | |
| 919 | - if ( ! array_key_exists( $role_field, $provided ) ) { | |
| 920 | - continue; | |
| 921 | - } | |
| 922 | - if ( ! is_array( $defaults[ $role_field ] ?? null ) ) { | |
| 923 | - continue; | |
| 924 | - } | |
| 925 | - if ( null === $known_roles ) { | |
| 926 | - $known_roles = array_keys( wp_roles()->roles ); | |
| 927 | - } | |
| 928 | - if ( ! is_array( $provided[ $role_field ] ) ) { | |
| 929 | - return new WP_Error( | |
| 930 | - self::ERROR_PREFIX . 'invalid_' . $role_field, | |
| 931 | - sprintf( | |
| 932 | - /* translators: %s: the offending field name. */ | |
| 933 | - __( 'Field `%s` must be an array of role slugs.', 'jetpack-stats-pkg' ), | |
| 934 | - $role_field | |
| 935 | - ) | |
| 936 | - ); | |
| 937 | - } | |
| 938 | - // `roles` gates `view_stats` — an empty array would lock every user out, including | |
| 939 | - // the caller. Schema validation enforces minItems=1 on REST input, but direct PHP | |
| 940 | - // callers bypass that path; reject explicitly here. | |
| 941 | - if ( 'roles' === $role_field && empty( $provided[ $role_field ] ) ) { | |
| 942 | - return new WP_Error( | |
| 943 | - self::ERROR_PREFIX . 'invalid_roles', | |
| 944 | - __( 'Field `roles` must be a non-empty array of role slugs — an empty list would revoke Stats access for every user.', 'jetpack-stats-pkg' ) | |
| 945 | - ); | |
| 946 | - } | |
| 947 | - $sanitized = array(); | |
| 948 | - foreach ( $provided[ $role_field ] as $role ) { | |
| 949 | - if ( ! is_string( $role ) || '' === $role ) { | |
| 950 | - return new WP_Error( | |
| 951 | - self::ERROR_PREFIX . 'invalid_role', | |
| 952 | - sprintf( | |
| 953 | - /* translators: 1: field name, 2: comma-separated list of valid role slugs. */ | |
| 954 | - __( 'Role slugs in `%1$s` must be non-empty strings. Known roles: %2$s.', 'jetpack-stats-pkg' ), | |
| 955 | - $role_field, | |
| 956 | - implode( ', ', $known_roles ) | |
| 957 | - ) | |
| 958 | - ); | |
| 959 | - } | |
| 960 | - if ( ! in_array( $role, $known_roles, true ) ) { | |
| 961 | - return new WP_Error( | |
| 962 | - self::ERROR_PREFIX . 'invalid_role', | |
| 963 | - sprintf( | |
| 964 | - /* translators: 1: unknown role slug, 2: field name, 3: comma-separated list of valid role slugs. */ | |
| 965 | - __( 'Unknown role `%1$s` in `%2$s`. Known roles: %3$s.', 'jetpack-stats-pkg' ), | |
| 966 | - $role, | |
| 967 | - $role_field, | |
| 968 | - implode( ', ', $known_roles ) | |
| 969 | - ) | |
| 970 | - ); | |
| 971 | - } | |
| 972 | - $sanitized[] = $role; | |
| 973 | - } | |
| 974 | - $provided[ $role_field ] = array_values( array_unique( $sanitized ) ); | |
| 975 | - } | |
| 976 | - | |
| 977 | - $before = self::settings_snapshot(); | |
| 978 | - $changes = array(); | |
| 979 | - foreach ( $provided as $key => $value ) { | |
| 980 | - if ( is_bool( $defaults[ $key ] ?? null ) ) { | |
| 981 | - $value = (bool) $value; | |
| 982 | - } | |
| 983 | - $current = $before[ $key ] ?? null; | |
| 984 | - if ( $current === $value ) { | |
| 985 | - continue; | |
| 986 | - } | |
| 987 | - $changes[ $key ] = $value; | |
| 988 | - } | |
| 989 | - | |
| 990 | - // `changed` is derived from the POST-WRITE snapshot, not from `$changes` alone — | |
| 991 | - // if update_option fails or refuses to persist for any reason (DB error, | |
| 992 | - // serialization mismatch), we must not claim a change that didn't happen. | |
| 993 | - if ( ! empty( $changes ) ) { | |
| 994 | - // One merged write instead of N get+update cycles via set_option. | |
| 995 | - Options::set_options( $changes ); | |
| 996 | - } | |
| 997 | - | |
| 998 | - $after = self::settings_snapshot(); | |
| 999 | - | |
| 1000 | - return array( | |
| 1001 | - 'changed' => $after !== $before, | |
| 1002 | - 'settings' => $after, | |
| 1003 | - ); | |
| 888 | + return Settings::update( is_array( $input ) ? $input : array(), Settings::KEYS ); | |
| 1004 | 889 | } |
| 1005 | 890 | |
| 1006 | 891 | /* |
| 1007 | 892 | --------------------------------------------------------------------- |
| @@ -1007,31 +892,8 @@ | ||
| 1007 | 892 | --------------------------------------------------------------------- |
| 1008 | 893 | * Helpers |
| 1009 | 894 | * --------------------------------------------------------------------- |
| 1010 | 895 | */ |
| 1011 | - | |
| 1012 | - /** | |
| 1013 | - * Build a whitelisted snapshot of the current Stats configuration. | |
| 1014 | - * | |
| 1015 | - * @return array | |
| 1016 | - */ | |
| 1017 | - private static function settings_snapshot(): array { | |
| 1018 | - $options = Options::get_options(); | |
| 1019 | - $defaults = Options::get_defaults(); | |
| 1020 | - $out = array(); | |
| 1021 | - foreach ( self::SETTINGS_KEYS as $key ) { | |
| 1022 | - $raw = $options[ $key ] ?? null; | |
| 1023 | - $default = $defaults[ $key ] ?? null; | |
| 1024 | - if ( is_bool( $default ) ) { | |
| 1025 | - $out[ $key ] = (bool) $raw; | |
| 1026 | - } elseif ( is_array( $default ) ) { | |
| 1027 | - $out[ $key ] = is_array( $raw ) ? array_values( $raw ) : array(); | |
| 1028 | - } else { | |
| 1029 | - $out[ $key ] = $raw; | |
| 1030 | - } | |
| 1031 | - } | |
| 1032 | - return $out; | |
| 1033 | - } | |
| 1034 | 896 | |
| 1035 | 897 | /** |
| 1036 | 898 | * Compose multiple WPCOM sub-call results into a partial-tolerant envelope. |
| 1037 | 899 | * |