Файловый менеджер - Редактировать - /var/www/xthruster/html/wp-content/uploads/flags/Core.tar
Назад
Dashboard_Sharing/View_Only_Pointer.php 0000644 00000004000 14720521221 0014220 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Dashboard_Sharing\View_Only_Pointer * * @package Google\Site_Kit * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Dashboard_Sharing; use Google\Site_Kit\Core\Admin\Pointer; use Google\Site_Kit\Core\Permissions\Permissions; /** * Class for view-only pointer. * * @since 1.83.0. * @access private * @ignore */ final class View_Only_Pointer { const SLUG = 'googlesitekit-view-only-pointer'; /** * Registers functionality through WordPress hooks. * * @since 1.83.0 */ public function register() { add_filter( 'googlesitekit_admin_pointers', function ( $pointers ) { $pointers[] = $this->get_view_only_pointer(); return $pointers; } ); } /** * Gets the view-only pointer. * * @since 1.83.0. * * @return Pointer Admin notice instance. */ private function get_view_only_pointer() { return new Pointer( self::SLUG, array( 'title' => __( 'You now have access to Site Kit', 'google-site-kit' ), 'content' => __( 'Check Site Kit’s dashboard to find out how much traffic your site is getting, your most popular pages, top keywords people use to find your site on Search, and more.', 'google-site-kit' ), 'target_id' => 'toplevel_page_googlesitekit-dashboard', 'active_callback' => function ( $hook_suffix ) { if ( 'index.php' !== $hook_suffix || current_user_can( Permissions::AUTHENTICATE ) || ! current_user_can( Permissions::VIEW_SPLASH ) ) { return false; } $dismissed_wp_pointers = get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ); if ( ! $dismissed_wp_pointers ) { return true; } $dismissed_wp_pointers = explode( ',', $dismissed_wp_pointers ); if ( in_array( self::SLUG, $dismissed_wp_pointers, true ) ) { return false; } return true; }, ) ); } } Dashboard_Sharing/Activity_Metrics/Activity_Metrics.php 0000644 00000003371 14720521221 0017363 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics\Activity_Metrics * * @package Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\User_Options; /** * Class for handling active consumers. * * @since 1.82.0 * @access private * @ignore */ class Activity_Metrics { /** * Active_Consumers instance. * * @since 1.82.0 * @var Active_Consumers */ protected $active_consumers; /** * Constructor. * * @since 1.82.0 * * @param Context $context Plugin context. * @param User_Options $user_options Optional. User option API. Default is a new instance. */ public function __construct( Context $context, User_Options $user_options = null ) { $this->active_consumers = new Active_Consumers( $user_options ?: new User_Options( $context ) ); } /** * Registers functionality. * * @since 1.82.0 */ public function register() { $this->active_consumers->register(); } /** * Get active consumers for refresh token. * * @since 1.87.0 * * @return array Array of active consumers formatted for refresh token. */ public function get_for_refresh_token() { $active_consumers = $this->active_consumers->get(); if ( empty( $active_consumers ) ) { return array(); } $formatted_consumers = array(); foreach ( $active_consumers as $id => $roles ) { $formatted_consumers[] = $id . ':' . implode( ',', $roles ); } return array( 'active_consumers' => implode( ' ', $formatted_consumers ), ); } } Dashboard_Sharing/Activity_Metrics/Active_Consumers.php 0000644 00000004543 14720521221 0017354 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics\Active_Consumers * * @package Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics; use Closure; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class for representing active consumers for an access token. * * @since 1.82.0 * @access private * @ignore */ class Active_Consumers extends User_Setting { /** * The user option name for this setting. */ const OPTION = 'googlesitekit_active_consumers'; /** * Gets the expected value type. * * @since 1.82.0 * * @return string The type name. */ protected function get_type() { return 'array'; } /** * Gets the default value. * * @since 1.82.0 * * @return array The default value. */ protected function get_default() { return array(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.82.0 * * @return Closure */ protected function get_sanitize_callback() { return function ( $value ) { // If the new value is not an array, preserve current value. if ( ! is_array( $value ) ) { return $this->get(); } $results = array(); foreach ( $value as $id => $roles ) { // If any of the IDs isn't an integer, do not include that item. if ( ! is_int( $id ) ) { continue; } // If any of the array values isn't an array, do not include that item. if ( ! is_array( $roles ) ) { continue; } $user_roles = array(); foreach ( $roles as $role ) { // If the nested role item is a string, include that role item. if ( is_string( $role ) ) { $user_roles[] = $role; } } if ( ! empty( $user_roles ) ) { $results[ $id ] = $user_roles; } } return $results; }; } /** * Adds a consumer to the active consumers list. * * @since 1.87.0 * * @param int $user_id User ID. * @param string[] $roles User roles. */ public function add( $user_id, $roles ) { $active_consumers = $this->get(); if ( ! array_key_exists( $user_id, $active_consumers ) ) { $active_consumers[ $user_id ] = $roles; $this->set( $active_consumers ); } } } Dashboard_Sharing/Dashboard_Sharing.php 0000644 00000003416 14720521221 0014161 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Dashboard_Sharing\Dashboard_Sharing * * @package Google\Site_Kit\Core\Dashboard_Sharing * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Dashboard_Sharing; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics\Activity_Metrics; /** * Class for handling Dashboard Sharing. * * @since 1.82.0 * @access private * @ignore */ class Dashboard_Sharing { /** * Plugin context. * * @since 1.82.0 * @var Context */ private $context; /** * User_Options object. * * @since 1.82.0 * * @var User_Options */ private $user_options = null; /** * Activity_Metrics instance. * * @since 1.82.0 * @var Activity_Metrics */ protected $activity_metrics; /** * View_Only_Pointer instance. * * @since 1.83.0 * @var View_Only_Pointer */ protected $view_only_pointer; /** * Constructor. * * @since 1.82.0 * * @param Context $context Plugin context. * @param User_Options $user_options Optional. User Option API instance. Default is a new instance. */ public function __construct( Context $context, User_Options $user_options = null ) { $this->context = $context; $this->user_options = $user_options ?: new User_Options( $this->context ); $this->activity_metrics = new Activity_Metrics( $this->context, $this->user_options ); $this->view_only_pointer = new View_Only_Pointer(); } /** * Registers functionality. * * @since 1.82.0 */ public function register() { $this->activity_metrics->register(); $this->view_only_pointer->register(); } } Admin/Pointers.php 0000644 00000005566 14720521221 0010120 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Pointers * * @package Google\Site_Kit\Core\Admin * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; use Google\Site_Kit\Core\Util\BC_Functions; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; /** * Class for managing pointers. * * @since 1.83.0 * @access private * @ignore */ class Pointers { use Method_Proxy_Trait; /** * Registers functionality through WordPress hooks. * * @since 1.83.0 */ public function register() { add_action( 'admin_enqueue_scripts', $this->get_method_proxy( 'enqueue_pointers' ) ); } /** * Enqueues pointer scripts. * * @since 1.83.0 * * @param string $hook_suffix The current admin page. */ private function enqueue_pointers( $hook_suffix ) { if ( empty( $hook_suffix ) ) { return; } $pointers = $this->get_pointers(); if ( empty( $pointers ) ) { return; } $active_pointers = array_filter( $pointers, function ( Pointer $pointer ) use ( $hook_suffix ) { return $pointer->is_active( $hook_suffix ); } ); if ( empty( $active_pointers ) ) { return; } wp_enqueue_style( 'wp-pointer' ); wp_enqueue_script( 'wp-pointer' ); add_action( 'admin_print_footer_scripts', function () use ( $active_pointers ) { foreach ( $active_pointers as $pointer ) { $this->print_pointer_script( $pointer ); } } ); } /** * Gets pointers. * * @since 1.83.0 * * @return Pointer[] Array of pointers. */ private function get_pointers() { /** * Filters the list of available pointers. * * @since 1.83.0 * * @param array $pointers List of Pointer instances. */ $pointers = apply_filters( 'googlesitekit_admin_pointers', array() ); return array_filter( $pointers, function ( $pointer ) { return $pointer instanceof Pointer; } ); } /** * Prints script for a given pointer. * * @since 1.83.0 * * @param Pointer $pointer Pointer to print. */ private function print_pointer_script( $pointer ) { $content = $pointer->get_content(); if ( empty( $content ) ) { return; } $slug = $pointer->get_slug(); BC_Functions::wp_print_inline_script_tag( sprintf( ' jQuery( function() { var options = { content: "<h3>%s</h3>%s", position: %s, pointerWidth: 420, close: function() { jQuery.post( window.ajaxurl, { pointer: "%s", action: "dismiss-wp-pointer", } ); } }; jQuery( "#%s" ).pointer( options ).pointer( "open" ); } ); ', esc_js( $pointer->get_title() ), $content, wp_json_encode( $pointer->get_position() ), esc_js( $slug ), esc_js( $pointer->get_target_id() ) ), array( 'id' => $slug, ) ); } } Admin/Plugin_Action_Links.php 0000644 00000002453 14720521221 0012200 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Plugin_Action_Links * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Permissions\Permissions; /** * Class for managing plugin action links. * * @since 1.41.0 * @access private * @ignore */ class Plugin_Action_Links { /** * Plugin context. * * @since 1.41.0 * @var Context */ private $context; /** * Constructor. * * @since 1.41.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Registers functionality through WordPress hooks. * * @since 1.41.0 */ public function register() { add_filter( 'plugin_action_links_' . GOOGLESITEKIT_PLUGIN_BASENAME, function ( $links ) { if ( current_user_can( Permissions::MANAGE_OPTIONS ) ) { $settings_link = sprintf( '<a href="%s">%s</a>', esc_url( $this->context->admin_url( 'settings' ) ), esc_html__( 'Settings', 'google-site-kit' ) ); array_unshift( $links, $settings_link ); } return $links; } ); } } Admin/Pointer.php 0000644 00000007031 14720521221 0007722 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Pointer * * @package Google\Site_Kit * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; /** * Class representing a single pointer. * * @since 1.83.0 * @access private * @ignore */ final class Pointer { /** * Unique pointer slug. * * @since 1.83.0 * @var string */ private $slug; /** * Pointer arguments. * * @since 1.83.0 * @var array */ private $args = array(); /** * Constructor. * * @since 1.83.0 * * @param string $slug Unique pointer slug. * @param array $args { * Associative array of pointer arguments. * * @type string $title Required. Pointer title. * @type string $content Required. Pointer content. May contain inline HTML tags. * @type string $target_id Required. ID of the element the pointer should be attached to. * @type string|array $position Optional. Position of the pointer. Can be 'top', 'bottom', 'left', 'right', * or an array of `edge` and `align`. Default 'top'. * @type callable $active_callback Optional. Callback function to determine whether the pointer is active in * the current context. The current admin screen's hook suffix is passed to * the callback. Default is that the pointer is active unconditionally. * } */ public function __construct( $slug, array $args ) { $this->slug = $slug; $this->args = wp_parse_args( $args, array( 'title' => '', 'content' => '', 'target_id' => '', 'position' => 'top', 'active_callback' => null, ) ); } /** * Gets the pointer slug. * * @since 1.83.0 * * @return string Unique pointer slug. */ public function get_slug() { return $this->slug; } /** * Gets the pointer title. * * @since 1.83.0 * * @return string Pointer title. */ public function get_title() { return $this->args['title']; } /** * Gets the pointer content. * * @since 1.83.0 * * @return string Pointer content. */ public function get_content() { if ( is_callable( $this->args['content'] ) ) { return call_user_func( $this->args['content'] ); } else { return '<p>' . wp_kses( $this->args['content'], 'googlesitekit_admin_pointer' ) . '</p>'; } } /** * Gets the pointer target ID. * * @since 1.83.0 * * @return string Pointer target ID. */ public function get_target_id() { return $this->args['target_id']; } /** * Gets the pointer position. * * @since 1.83.0 * * @return string|array Pointer position. */ public function get_position() { return $this->args['position']; } /** * Checks whether the pointer is active. * * This method executes the active callback in order to determine whether the pointer should be active or not. * * @since 1.83.0 * * @param string $hook_suffix The current admin screen hook suffix. * @return bool True if the pointer is active, false otherwise. */ public function is_active( $hook_suffix ) { if ( empty( $this->args['title'] ) || empty( $this->args['content'] ) || empty( $this->args['target_id'] ) ) { return false; } if ( ! is_callable( $this->args['active_callback'] ) ) { return true; } return (bool) call_user_func( $this->args['active_callback'], $hook_suffix ); } } Admin/Dashboard.php 0000644 00000005650 14720521221 0010176 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Dashboard * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Assets; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Util\Requires_Javascript_Trait; /** * Class to handle all wp-admin Dashboard related functionality. * * @since 1.0.0 * @access private * @ignore */ final class Dashboard { use Requires_Javascript_Trait; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Assets Instance. * * @since 1.0.0 * @var Assets */ private $assets; /** * Modules instance. * * @since 1.7.0 * @var Modules */ private $modules; /** * Authentication instance. * * @since 1.120.0 * @var Authentication */ private $authentication; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. * @param Assets $assets Optional. Assets API instance. Default is a new instance. * @param Modules $modules Optional. Modules instance. Default is a new instance. */ public function __construct( Context $context, Assets $assets = null, Modules $modules = null ) { $this->context = $context; $this->assets = $assets ?: new Assets( $this->context ); $this->modules = $modules ?: new Modules( $this->context ); $this->authentication = new Authentication( $this->context ); } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ public function register() { add_action( 'wp_dashboard_setup', function () { $this->add_widgets(); } ); } /** * Add a Site Kit by Google widget to the WordPress admin dashboard. * * @since 1.0.0 */ private function add_widgets() { if ( ! current_user_can( Permissions::VIEW_WP_DASHBOARD_WIDGET ) ) { return; } // Enqueue styles. $this->assets->enqueue_asset( 'googlesitekit-wp-dashboard-css' ); // Enqueue scripts. $this->assets->enqueue_asset( 'googlesitekit-wp-dashboard' ); $this->modules->enqueue_assets(); wp_add_dashboard_widget( 'google_dashboard_widget', __( 'Site Kit Summary', 'google-site-kit' ), function () { $this->render_googlesitekit_wp_dashboard(); } ); } /** * Render the Site Kit WordPress Dashboard widget. * * @since 1.0.0 * @since 1.120.0 Added the `data-view-only` attribute. */ private function render_googlesitekit_wp_dashboard() { $this->render_noscript_html(); $is_view_only = ! $this->authentication->is_authenticated(); ?> <div id="js-googlesitekit-wp-dashboard" data-view-only="<?php echo esc_attr( $is_view_only ); ?>" class="googlesitekit-plugin"></div> <?php } } Admin/Notice.php 0000644 00000006232 14720521221 0007525 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Notice * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; /** * Class representing a single notice. * * @since 1.0.0 * @access private * @ignore */ final class Notice { const TYPE_SUCCESS = 'success'; const TYPE_INFO = 'info'; const TYPE_WARNING = 'warning'; const TYPE_ERROR = 'error'; /** * Unique notice slug. * * @since 1.0.0 * @var string */ private $slug; /** * Notice arguments. * * @since 1.0.0 * @var array */ private $args = array(); /** * Constructor. * * @since 1.0.0 * * @param string $slug Unique notice slug. * @param array $args { * Associative array of notice arguments. * * @type string $content Required notice content. May contain inline HTML tags. * @type string $type Notice type. Either 'success', 'info', 'warning', 'error'. Default 'info'. * @type callable $active_callback Callback function to determine whether the notice is active in the * current context. The current admin screen's hook suffix is passed to * the callback. Default is that the notice is active unconditionally. * @type bool $dismissible Whether the notice should be dismissible. Default false. * } */ public function __construct( $slug, array $args ) { $this->slug = $slug; $this->args = wp_parse_args( $args, array( 'content' => '', 'type' => self::TYPE_INFO, 'active_callback' => null, 'dismissible' => false, ) ); } /** * Gets the notice slug. * * @since 1.0.0 * * @return string Unique notice slug. */ public function get_slug() { return $this->slug; } /** * Checks whether the notice is active. * * This method executes the active callback in order to determine whether the notice should be active or not. * * @since 1.0.0 * * @param string $hook_suffix The current admin screen hook suffix. * @return bool True if the notice is active, false otherwise. */ public function is_active( $hook_suffix ) { if ( ! $this->args['content'] ) { return false; } if ( ! $this->args['active_callback'] ) { return true; } return (bool) call_user_func( $this->args['active_callback'], $hook_suffix ); } /** * Renders the notice. * * @since 1.0.0 */ public function render() { if ( is_callable( $this->args['content'] ) ) { $content = call_user_func( $this->args['content'] ); if ( empty( $content ) ) { return; } } else { $content = '<p>' . wp_kses( $this->args['content'], 'googlesitekit_admin_notice' ) . '</p>'; } $class = 'notice notice-' . $this->args['type']; if ( $this->args['dismissible'] ) { $class .= ' is-dismissible'; } ?> <div id="<?php echo esc_attr( 'googlesitekit-notice-' . $this->slug ); ?>" class="<?php echo esc_attr( $class ); ?>"> <?php echo $content; /* phpcs:ignore WordPress.Security.EscapeOutput */ ?> </div> <?php } } Admin/Plugin_Row_Meta.php 0000644 00000002366 14720521221 0011343 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Plugin_Row_Meta * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; /** * Class for managing plugin row meta. * * @since 1.24.0 * @access private * @ignore */ class Plugin_Row_Meta { /** * Registers functionality through WordPress hooks. * * @since 1.24.0 */ public function register() { add_filter( 'plugin_row_meta', function ( $meta, $plugin_file ) { if ( GOOGLESITEKIT_PLUGIN_BASENAME === $plugin_file ) { return array_merge( $meta, $this->get_plugin_row_meta() ); } return $meta; }, 10, 2 ); } /** * Builds an array of anchor elements to be shown in the plugin row. * * @since 1.24.0 * * @return string[] Array of links as HTML strings. */ private function get_plugin_row_meta() { return array( '<a href="https://wordpress.org/support/plugin/google-site-kit/reviews/#new-post">' . __( 'Rate Site Kit', 'google-site-kit' ) . '</a>', '<a href="https://wordpress.org/support/plugin/google-site-kit/#new-post">' . __( 'Support', 'google-site-kit' ) . '</a>', ); } } Admin/Available_Tools.php 0000644 00000003022 14720521221 0011336 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Available_Tools * * @package Google\Site_Kit\Core\Admin * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; use Google\Site_Kit\Core\Util\Reset; /** * Class for extending available tools for Site Kit. * * @since 1.30.0 * @access private * @ignore */ class Available_Tools { use Method_Proxy_Trait; /** * Registers functionality through WordPress hooks. * * @since 1.30.0 */ public function register() { add_action( 'tool_box', $this->get_method_proxy( 'render_tool_box' ) ); } /** * Renders tool box output. * * @since 1.30.0 */ private function render_tool_box() { if ( ! current_user_can( Permissions::SETUP ) ) { return; } ?> <div class="card"> <h2 class="title"><?php esc_html_e( 'Reset Site Kit', 'google-site-kit' ); ?></h2> <p> <?php esc_html_e( 'Resetting will disconnect all users and remove all Site Kit settings and data within WordPress. You and any other users who wish to use Site Kit will need to reconnect to restore access.', 'google-site-kit' ) ?> </p> <p> <a class="button button-primary" href="<?php echo esc_url( Reset::url() ); ?>" > <?php esc_html_e( 'Reset Site Kit', 'google-site-kit' ); ?> </a> </p> </div> <?php } } Admin/Standalone.php 0000644 00000004564 14720521221 0010402 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Standalone * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Stylesheet; /** * Class managing standalone mode. * * @since 1.8.0 * @access private * @ignore */ final class Standalone { /** * Plugin context. * * @since 1.8.0 * * @var Context */ private $context; /** * Constructor. * * @since 1.8.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Standalone mode * * @since 1.8.0 */ public function register() { if ( ! $this->is_standalone() ) { return; } /** * Appends the standalone admin body class. * * @since 1.8.0 * * @param string $admin_body_classes Admin body classes. * @return string Filtered admin body classes. */ add_filter( 'admin_body_class', function ( $admin_body_classes ) { return "{$admin_body_classes} googlesitekit-standalone"; } ); remove_action( 'in_admin_header', 'wp_admin_bar_render', 0 ); add_filter( 'admin_footer_text', '__return_empty_string', PHP_INT_MAX ); add_filter( 'update_footer', '__return_empty_string', PHP_INT_MAX ); add_action( 'admin_head', function () { $this->print_standalone_styles(); } ); } /** * Detects if we are in Google Site Kit standalone mode. * * @since 1.8.0 * * @return boolean True when in standalone mode, else false. */ public function is_standalone() { global $pagenow; $page = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'page' ) ?: '' ); $standalone = $this->context->input()->filter( INPUT_GET, 'googlesitekit-standalone', FILTER_VALIDATE_BOOLEAN ); return ( 'admin.php' === $pagenow && false !== strpos( $page, 'googlesitekit' ) && $standalone ); } /** * Enqueues styles for standalone mode. * * @since 1.8.0 */ private function print_standalone_styles() { ?> <style type="text/css"> html { padding-top: 0 !important; } body.googlesitekit-standalone #adminmenumain { display: none; } body.googlesitekit-standalone #wpcontent { margin-left: 0; } </style> <?php } } Admin/Notices.php 0000644 00000003261 14720521221 0007707 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Notices * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; /** * Class managing admin notices. * * @since 1.0.0 * @access private * @ignore */ final class Notices { /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ public function register() { $callback = function () { global $hook_suffix; if ( empty( $hook_suffix ) ) { return; } $this->render_notices( $hook_suffix ); }; add_action( 'admin_notices', $callback ); add_action( 'network_admin_notices', $callback ); } /** * Renders admin notices. * * @since 1.0.0 * * @param string $hook_suffix The current admin screen hook suffix. */ private function render_notices( $hook_suffix ) { $notices = $this->get_notices(); if ( empty( $notices ) ) { return; } /** * Notice object. * * @var Notice $notice Notice object. */ foreach ( $notices as $notice ) { if ( ! $notice->is_active( $hook_suffix ) ) { continue; } $notice->render(); } } /** * Gets available admin notices. * * @since 1.0.0 * * @return array List of Notice instances. */ private function get_notices() { /** * Filters the list of available admin notices. * * @since 1.0.0 * * @param array $notices List of Notice instances. */ $notices = apply_filters( 'googlesitekit_admin_notices', array() ); return array_filter( $notices, function ( $notice ) { return $notice instanceof Notice; } ); } } Admin/Screen.php 0000644 00000016626 14720521221 0007533 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Screen * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Assets; use Google\Site_Kit\Core\Util\Google_Icon; use Google\Site_Kit\Core\Util\Requires_Javascript_Trait; /** * Class representing a single screen. * * @since 1.0.0 * @access private * @ignore */ final class Screen { use Requires_Javascript_Trait; const MENU_SLUG = 'googlesitekit'; /** * Unique screen slug. * * @since 1.0.0 * @var string */ private $slug; /** * Screen arguments. * * @since 1.0.0 * @var array */ private $args = array(); /** * Constructor. * * @since 1.0.0 * * @param string $slug Unique screen slug. * @param array $args { * Associative array of screen arguments. * * @type callable $render_callback Required callback to render the page content. * @type string $title Required screen title. * @type string $capability Capability required to access the screen. Default is 'manage_options'. * @type string $menu_title Title to display in the menu (only if $add_to_menu is true). Default is * the value of $title. * @type string $parent_slug Slug of the parent menu screen (only if $add_to_menu is true). Default * empty string (which means it will be a top-level page). * @type callable $enqueue_callback Callback to enqueue additional scripts or stylesheets. The base admin * script and stylesheet will always be enqueued. Default null. * @type callable $initialize_callback Callback to run actions when initializing the screen, before headers are * sent and markup is generated. Default null. * } */ public function __construct( $slug, array $args ) { $this->slug = $slug; $this->args = wp_parse_args( $args, array( 'render_callback' => null, 'title' => '', 'capability' => 'manage_options', 'menu_title' => '', 'parent_slug' => self::MENU_SLUG, 'enqueue_callback' => null, 'initialize_callback' => null, ) ); if ( empty( $this->args['menu_title'] ) ) { $this->args['menu_title'] = $this->args['title']; } $this->args['title'] = __( 'Site Kit by Google', 'google-site-kit' ) . ' ' . $this->args['title']; } /** * Gets the unique screen slug. * * @since 1.0.0 * * @return string Unique screen slug. */ public function get_slug() { return $this->slug; } /** * Adds the screen to the WordPress admin backend. * * @since 1.0.0 * * @param Context $context Plugin context, used for URL generation. * @return string Hook suffix of the screen, or empty string if not added. */ public function add( Context $context ) { static $menu_slug = null; if ( ! $this->args['title'] ) { return ''; } // A parent slug of null means the screen will not appear in the menu. $parent_slug = null; // If parent slug is provided, use it as parent. if ( ! empty( $this->args['parent_slug'] ) ) { $parent_slug = $this->args['parent_slug']; // If parent slug is 'googlesitekit', append to main Site Kit menu. if ( self::MENU_SLUG === $parent_slug ) { // If this is null, it means no menu has been added yet. if ( null === $menu_slug ) { add_menu_page( $this->args['title'], __( 'Site Kit', 'google-site-kit' ), $this->args['capability'], $this->slug, '', 'data:image/svg+xml;base64,' . Google_Icon::to_base64() ); $menu_slug = $this->slug; /** * An SVG icon file needs to be colored (filled) based on the theme color setting. * * This exists in js as wp.svgPainter() per: * https://github.com/WordPress/WordPress/blob/5.7/wp-admin/js/svg-painter.js * * The downside of the js approach is that we get a brief flash of an unstyled icon * until the JS runs. * * A user can pick a custom Admin Color Scheme, which is only available in admin_init * or later actions. add_menu_page runs on the admin_menu action, which precedes admin_init * per https://codex.wordpress.org/Plugin_API/Action_Reference * * WordPress provides some color schemes out of the box, but they can also be added via * wp_admin_css_color() * * Our workaround is to set the icon and subsequently replace it in current_screen, which is * what we do in the following action. */ add_action( 'current_screen', function () { global $menu, $_wp_admin_css_colors; if ( ! is_array( $menu ) ) { return; } $color_scheme = get_user_option( 'admin_color' ) ?: 'fresh'; // If we're on one of the sitekit pages, use the 'current' color, otherwise use the 'base' color. // @see wp_admin_css_color(). $color_key = false === strpos( get_current_screen()->id, 'googlesitekit' ) ? 'base' : 'current'; if ( empty( $_wp_admin_css_colors[ $color_scheme ]->icon_colors[ $color_key ] ) ) { return; } $color = $_wp_admin_css_colors[ $color_scheme ]->icon_colors[ $color_key ]; foreach ( $menu as &$item ) { if ( 'googlesitekit-dashboard' === $item[2] ) { $item[6] = 'data:image/svg+xml;base64,' . Google_Icon::to_base64( Google_Icon::with_fill( $color ) ); break; } } }, 100 ); } // Set parent slug to actual slug of main Site Kit menu. $parent_slug = $menu_slug; } } // If submenu item or not in menu, use add_submenu_page(). return (string) add_submenu_page( $parent_slug, $this->args['title'], $this->args['menu_title'], $this->args['capability'], $this->slug, function () use ( $context ) { $this->render( $context ); } ); } /** * Runs actions when initializing the screen, before sending headers and generating markup. * * @since 1.0.0 * * @param Context $context Plugin context. */ public function initialize( Context $context ) { if ( ! $this->args['initialize_callback'] ) { return; } call_user_func( $this->args['initialize_callback'], $context ); } /** * Enqueues assets for the screen. * * @since 1.0.0 * * @param Assets $assets Assets instance to rely on for enqueueing assets. */ public function enqueue_assets( Assets $assets ) { // Enqueue base admin screen stylesheet. $assets->enqueue_asset( 'googlesitekit-admin-css' ); $cb = is_callable( $this->args['enqueue_callback'] ) ? $this->args['enqueue_callback'] : function ( Assets $assets ) { $assets->enqueue_asset( $this->slug ); }; call_user_func( $cb, $assets ); } /** * Renders the screen content. * * @since 1.0.0 * * @param Context $context Plugin context. */ private function render( Context $context ) { $cb = is_callable( $this->args['render_callback'] ) ? $this->args['render_callback'] : function () { printf( '<div id="js-%s" class="googlesitekit-page"></div>', esc_attr( $this->slug ) ); }; echo '<div class="googlesitekit-plugin">'; $this->render_noscript_html(); call_user_func( $cb, $context ); echo '</div>'; } } Admin/Screens.php 0000644 00000033364 14720521221 0007714 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Screens * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Assets; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Dismissals\Dismissed_Items; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Util\Feature_Flags; /** * Class managing admin screens. * * @since 1.0.0 * @access private * @ignore */ final class Screens { const PREFIX = 'googlesitekit-'; const PARENT_SLUG_NULL = self::PREFIX . 'null'; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Assets API instance. * * @since 1.0.0 * @var Assets */ private $assets; /** * Modules instance. * * @since 1.7.0 * @var Modules */ private $modules; /** * Authentication instance. * * @since 1.72.0 * @var Authentication */ private $authentication; /** * Associative array of $hook_suffix => $screen pairs. * * @since 1.0.0 * @var array */ private $screens = array(); /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. * @param Assets $assets Optional. Assets API instance. Default is a new instance. * @param Modules $modules Optional. Modules instance. Default is a new instance. * @param Authentication $authentication Optional. Authentication instance. Default is a new instance. */ public function __construct( Context $context, Assets $assets = null, Modules $modules = null, Authentication $authentication = null ) { $this->context = $context; $this->assets = $assets ?: new Assets( $this->context ); $this->modules = $modules ?: new Modules( $this->context ); $this->authentication = $authentication ?: new Authentication( $this->context ); } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ public function register() { if ( $this->context->is_network_mode() ) { add_action( 'network_admin_menu', function () { $this->add_screens(); } ); } add_action( 'admin_menu', function () { $this->add_screens(); } ); add_action( 'admin_enqueue_scripts', function ( $hook_suffix ) { $this->enqueue_screen_assets( $hook_suffix ); } ); add_action( 'admin_page_access_denied', function () { // Redirect dashboard to splash if no dashboard access (yet). $this->no_access_redirect_dashboard_to_splash(); // Redirect splash to (shared) dashboard if splash is dismissed. $this->no_access_redirect_splash_to_dashboard(); // Redirect module pages to dashboard. $this->no_access_redirect_module_to_dashboard(); } ); // Ensure the menu icon always is rendered correctly, without enqueueing a global CSS file. add_action( 'admin_head', function () { ?> <style type="text/css"> #adminmenu .toplevel_page_googlesitekit-dashboard img { width: 16px; } #adminmenu .toplevel_page_googlesitekit-dashboard.current img, #adminmenu .toplevel_page_googlesitekit-dashboard.wp-has-current-submenu img { opacity: 1; } </style> <?php } ); $remove_notices_callback = function () { global $hook_suffix; if ( empty( $hook_suffix ) ) { return; } if ( isset( $this->screens[ $hook_suffix ] ) ) { remove_all_actions( current_action() ); } }; add_action( 'admin_notices', $remove_notices_callback, -9999 ); add_action( 'network_admin_notices', $remove_notices_callback, -9999 ); add_action( 'all_admin_notices', $remove_notices_callback, -9999 ); add_filter( 'custom_menu_order', '__return_true' ); add_filter( 'menu_order', function ( array $menu_order ) { // Move the Site Kit dashboard menu item to be one after the index.php item if it exists. $dashboard_index = array_search( 'index.php', $menu_order, true ); $sitekit_index = false; foreach ( $menu_order as $key => $value ) { if ( strpos( $value, self::PREFIX ) === 0 ) { $sitekit_index = $key; $sitekit_value = $value; break; } } if ( false === $dashboard_index || false === $sitekit_index ) { return $menu_order; } unset( $menu_order[ $sitekit_index ] ); array_splice( $menu_order, $dashboard_index + 1, 0, $sitekit_value ); return $menu_order; } ); } /** * Gets the Screen instance for a given hook suffix. * * @since 1.11.0 * * @param string $hook_suffix The hook suffix associated with the screen to retrieve. * @return Screen|null Screen instance if available, otherwise null; */ public function get_screen( $hook_suffix ) { return isset( $this->screens[ $hook_suffix ] ) ? $this->screens[ $hook_suffix ] : null; } /** * Adds all screens to the admin. * * @since 1.0.0 */ private function add_screens() { $screens = $this->get_screens(); array_walk( $screens, array( $this, 'add_screen' ) ); } /** * Adds the given screen to the admin. * * @since 1.0.0 * * @param Screen $screen Screen to add. */ private function add_screen( Screen $screen ) { $hook_suffix = $screen->add( $this->context ); if ( empty( $hook_suffix ) ) { return; } add_action( "load-{$hook_suffix}", function () use ( $screen ) { $screen->initialize( $this->context ); } ); $this->screens[ $hook_suffix ] = $screen; } /** * Enqueues assets if a plugin screen matches the given hook suffix. * * @since 1.0.0 * * @param string $hook_suffix Hook suffix for the current admin screen. */ private function enqueue_screen_assets( $hook_suffix ) { if ( ! isset( $this->screens[ $hook_suffix ] ) ) { return; } $this->screens[ $hook_suffix ]->enqueue_assets( $this->assets ); $this->modules->enqueue_assets(); } /** * Redirects from the dashboard to the splash screen if permissions to access the dashboard are currently not met. * * Dashboard permission access is conditional based on whether the user has successfully authenticated. When * e.g. accessing the dashboard manually or having it open in a separate tab while disconnecting in the other tab, * it is a better user experience to redirect to the splash screen so that the user can re-authenticate. * * The only time the dashboard should fail with the regular WordPress permissions error is when the current user is * not eligible for accessing Site Kit entirely, i.e. if they are not allowed to authenticate. * * @since 1.12.0 */ private function no_access_redirect_dashboard_to_splash() { global $plugin_page; // At this point, our preferred `$hook_suffix` is not set, and the dashboard page will not even be registered, // so we need to rely on the `$plugin_page` global here. if ( ! isset( $plugin_page ) || self::PREFIX . 'dashboard' !== $plugin_page ) { return; } if ( current_user_can( Permissions::VIEW_SPLASH ) ) { wp_safe_redirect( $this->context->admin_url( 'splash' ) ); exit; } } /** * Redirects from the splash to the dashboard screen if permissions to access the splash are currently not met. * * Admins always have the ability to view the splash page, so this redirects non-admins who have access * to view the shared dashboard if the splash has been dismissed. * Currently the dismissal check is built into the capability for VIEW_SPLASH so this is implied. * * @since 1.77.0 */ private function no_access_redirect_splash_to_dashboard() { global $plugin_page; if ( ! isset( $plugin_page ) || self::PREFIX . 'splash' !== $plugin_page ) { return; } if ( current_user_can( Permissions::VIEW_DASHBOARD ) ) { wp_safe_redirect( $this->context->admin_url() ); exit; } } /** * Redirects module pages to the dashboard or splash based on user capability. * * @since 1.69.0 */ private function no_access_redirect_module_to_dashboard() { global $plugin_page; $legacy_module_pages = array( self::PREFIX . 'module-adsense', self::PREFIX . 'module-analytics', self::PREFIX . 'module-search-console', ); if ( ! in_array( $plugin_page, $legacy_module_pages, true ) ) { return; } // Note: the use of add_query_arg is intentional below because it preserves // the current query parameters in the URL. if ( current_user_can( Permissions::VIEW_DASHBOARD ) ) { wp_safe_redirect( add_query_arg( 'page', self::PREFIX . 'dashboard' ) ); exit; } if ( current_user_can( Permissions::VIEW_SPLASH ) ) { wp_safe_redirect( add_query_arg( 'page', self::PREFIX . 'splash' ) ); exit; } } /** * Gets available admin screens. * * @since 1.0.0 * * @return array List of Screen instances. */ private function get_screens() { $show_splash_in_menu = current_user_can( Permissions::VIEW_SPLASH ) && ! current_user_can( Permissions::VIEW_DASHBOARD ); $screens = array( new Screen( self::PREFIX . 'dashboard', array( 'title' => __( 'Dashboard', 'google-site-kit' ), 'capability' => Permissions::VIEW_DASHBOARD, 'enqueue_callback' => function ( Assets $assets ) { if ( $this->context->input()->filter( INPUT_GET, 'permaLink' ) ) { $assets->enqueue_asset( 'googlesitekit-entity-dashboard' ); } else { $assets->enqueue_asset( 'googlesitekit-main-dashboard' ); } }, 'render_callback' => function ( Context $context ) { $is_view_only = ! $this->authentication->is_authenticated(); $setup_slug = htmlspecialchars( $context->input()->filter( INPUT_GET, 'slug' ) ?: '' ); $reauth = $context->input()->filter( INPUT_GET, 'reAuth', FILTER_VALIDATE_BOOLEAN ); if ( $context->input()->filter( INPUT_GET, 'permaLink' ) ) { ?> <div id="js-googlesitekit-entity-dashboard" data-view-only="<?php echo esc_attr( $is_view_only ); ?>" class="googlesitekit-page"></div> <?php } else { $setup_module_slug = $setup_slug && $reauth ? $setup_slug : ''; if ( $setup_module_slug ) { $active_modules = $this->modules->get_active_modules(); if ( ! array_key_exists( $setup_module_slug, $active_modules ) ) { try { $module_details = $this->modules->get_module( $setup_module_slug ); /* translators: %s: The module name */ $message = sprintf( __( 'The %s module cannot be set up as it has not been activated yet.', 'google-site-kit' ), $module_details->name ); } catch ( \Exception $e ) { $message = $e->getMessage(); } wp_die( sprintf( '<span class="googlesitekit-notice">%s</span>', esc_html( $message ) ), 403 ); } } ?> <div id="js-googlesitekit-main-dashboard" data-view-only="<?php echo esc_attr( $is_view_only ); ?>" data-setup-module-slug="<?php echo esc_attr( $setup_module_slug ); ?>" class="googlesitekit-page"></div> <?php } }, ) ), new Screen( self::PREFIX . 'splash', array( 'title' => __( 'Dashboard', 'google-site-kit' ), 'capability' => Permissions::VIEW_SPLASH, 'parent_slug' => $show_splash_in_menu ? Screen::MENU_SLUG : self::PARENT_SLUG_NULL, // This callback will redirect to the dashboard on successful authentication. 'initialize_callback' => function ( Context $context ) { // Get the dismissed items for this user. $user_options = new User_Options( $context ); $dismissed_items = new Dismissed_Items( $user_options ); $splash_context = $context->input()->filter( INPUT_GET, 'googlesitekit_context' ); $reset_session = $context->input()->filter( INPUT_GET, 'googlesitekit_reset_session', FILTER_VALIDATE_BOOLEAN ); // If the user is authenticated, redirect them to the disconnect URL and then send them back here. if ( ! $reset_session && 'revoked' === $splash_context && $this->authentication->is_authenticated() ) { $this->authentication->disconnect(); wp_safe_redirect( add_query_arg( array( 'googlesitekit_reset_session' => 1 ) ) ); exit; } // Don't consider redirect if the current user cannot access the dashboard (yet). if ( ! current_user_can( Permissions::VIEW_DASHBOARD ) ) { return; } // Redirect to dashboard if user is authenticated or if // they have already accessed the shared dashboard. if ( $this->authentication->is_authenticated() || ( ! current_user_can( Permissions::AUTHENTICATE ) && $dismissed_items->is_dismissed( 'shared_dashboard_splash' ) && current_user_can( Permissions::VIEW_SHARED_DASHBOARD ) ) ) { wp_safe_redirect( $context->admin_url( 'dashboard', array( // Pass through the notification parameter, or removes it if none. 'notification' => $context->input()->filter( INPUT_GET, 'notification' ), ) ) ); exit; } }, ) ), new Screen( self::PREFIX . 'settings', array( 'title' => __( 'Settings', 'google-site-kit' ), 'capability' => Permissions::MANAGE_OPTIONS, ) ), ); $screens[] = new Screen( self::PREFIX . 'user-input', array( 'title' => __( 'User Input', 'google-site-kit' ), 'capability' => Permissions::MANAGE_OPTIONS, 'parent_slug' => self::PARENT_SLUG_NULL, ) ); $screens[] = new Screen( self::PREFIX . 'ad-blocking-recovery', array( 'title' => __( 'Ad Blocking Recovery', 'google-site-kit' ), 'capability' => Permissions::MANAGE_OPTIONS, 'parent_slug' => self::PARENT_SLUG_NULL, ) ); return $screens; } } Admin/Authorize_Application.php 0000644 00000006352 14720521221 0012604 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin\Authorize_Application * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Assets; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; /** * Class to handle all wp-admin Authorize Application related functionality. * * @since 1.126.0 * @access private * @ignore */ final class Authorize_Application { use Method_Proxy_Trait; /** * Plugin context. * * @since 1.126.0 * @var Context */ private $context; /** * Assets instance. * * @since 1.126.0 * @var Assets */ private $assets; /** * Constructor. * * @since 1.126.0 * * @param Context $context Plugin context. * @param Assets $assets Optional. Assets API instance. Default is a new instance. */ public function __construct( Context $context, Assets $assets = null ) { $this->context = $context; $this->assets = $assets ?: new Assets( $this->context ); } /** * Registers functionality through WordPress hooks. * * @since 1.126.0 */ public function register() { add_action( 'admin_enqueue_scripts', $this->get_method_proxy( 'enqueue_assets' ) ); add_action( 'admin_footer', $this->get_method_proxy( 'render_custom_footer' ) ); } /** * Checks if the current screen is the Authorize Application screen. * * @since 1.126.0 * * @return bool True if the current screen is the Authorize Application screen, false otherwise. */ protected function is_authorize_application_screen() { $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( $current_screen instanceof \WP_Screen && 'authorize-application' === $current_screen->id ) { return true; } return false; } /** * Checks if the current service is a Google service. * * @since 1.126.0 * * @return bool True if the current service is a Google service, false otherwise. */ protected function is_google_service() { $success_url = isset( $_GET['success_url'] ) ? esc_url_raw( wp_unslash( $_GET['success_url'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification $success_url = sanitize_text_field( $success_url ); $parsed_url = wp_parse_url( $success_url ); if ( empty( $parsed_url['host'] ) ) { return false; } // Check if the domain is a '*.google.com' domain. return preg_match( '/\.google\.com$/', $parsed_url['host'] ) === 1; } /** * Enqueues assets for the Authorize Application screen. * * @since 1.126.0 */ private function enqueue_assets() { if ( $this->is_authorize_application_screen() && $this->is_google_service() ) { $this->assets->enqueue_asset( 'googlesitekit-authorize-application-css' ); } } /** * Renders custom footer for the Authorize Application screen if the service is a Google service. * * @since 1.126.0 */ private function render_custom_footer() { if ( $this->is_authorize_application_screen() && $this->is_google_service() ) { echo '<div class="googlesitekit-authorize-application__footer"><p>' . esc_html__( 'Powered by Site Kit', 'google-site-kit' ) . '</p></div>'; } } } User_Input/User_Specific_Answers.php 0000644 00000004506 14720521221 0013600 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User_Input\User_Specific_Answers * * @package Google\Site_Kit\Core\User_Input * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User_Input; use Closure; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class for handling the user specific answers in User Input. * * @since 1.90.0 * @access private * @ignore */ class User_Specific_Answers extends User_Setting { /** * The user option name for this setting. */ const OPTION = 'googlesitekit_user_input_settings'; /** * The scope for which the questions are handled by this class. */ const SCOPE = 'user'; /** * Gets the expected value type. * * @since 1.90.0 * * @return string The type name. */ protected function get_type() { return 'array'; } /** * Gets the default value. * * @since 1.90.0 * * @return array The default value. */ protected function get_default() { return array(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.90.0 * * @return Closure */ protected function get_sanitize_callback() { $questions = array_filter( User_Input::get_questions(), function ( $question ) { return static::SCOPE === $question['scope']; } ); return function ( $settings ) use ( $questions ) { if ( ! is_array( $settings ) ) { return $this->get(); } $results = array(); foreach ( $settings as $setting_key => $setting_values ) { // Ensure all the data is valid. if ( ! in_array( $setting_key, array_keys( $questions ), true ) || ! is_array( $setting_values ) || static::SCOPE !== $setting_values['scope'] || ! is_array( $setting_values['values'] ) ) { continue; } $valid_values = array(); $valid_values['scope'] = $setting_values['scope']; $valid_answers = array(); // Verify that each answer value is a string. foreach ( $setting_values['values'] as $answer ) { if ( is_scalar( $answer ) ) { $valid_answers[] = $answer; } } $valid_values['values'] = $valid_answers; if ( ! empty( $valid_values ) ) { $results[ $setting_key ] = $valid_values; } } return $results; }; } } User_Input/User_Input.php 0000644 00000015012 14720521221 0011442 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User_Input\User_Input * * @package Google\Site_Kit\Core\User_Input * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User_Input; use ArrayAccess; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Key_Metrics\Key_Metrics_Setup_Completed_By; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\User_Surveys\Survey_Queue; use WP_Error; use WP_User; /** * Class for handling User Input settings. * * @since 1.90.0 * @access private * @ignore */ class User_Input { /** * Site_Specific_Answers instance. * * @since 1.90.0 * @var Site_Specific_Answers */ protected $site_specific_answers; /** * User_Options instance. * * @since 1.90.0 * @var User_Options */ protected $user_options; /** * User_Specific_Answers instance. * * @since 1.90.0 * @var User_Specific_Answers */ protected $user_specific_answers; /** * REST_User_Input_Controller instance. * * @since 1.90.0 * @var REST_User_Input_Controller */ protected $rest_controller; /** * User Input questions. * * @since 1.90.0 * @var array|ArrayAccess */ private static $questions = array( 'purpose' => array( 'scope' => 'site', ), 'postFrequency' => array( 'scope' => 'user', ), 'goals' => array( 'scope' => 'user', ), ); /** * Constructor. * * @since 1.90.0 * * @param Context $context Plugin context. * @param Options $options Optional. Options instance. Default a new instance. * @param User_Options $user_options Optional. User_Options instance. Default a new instance. * @param Survey_Queue $survey_queue Optional. Survey_Queue instance. Default a new instance. */ public function __construct( Context $context, Options $options = null, User_Options $user_options = null, Survey_Queue $survey_queue = null ) { $this->site_specific_answers = new Site_Specific_Answers( $options ?: new Options( $context ) ); $this->user_options = $user_options ?: new User_Options( $context ); $this->user_specific_answers = new User_Specific_Answers( $this->user_options ); $this->rest_controller = new REST_User_Input_Controller( $this, $survey_queue ?: new Survey_Queue( $this->user_options ), new Key_Metrics_Setup_Completed_By( $options ?: new Options( $context ) ) ); } /** * Registers functionality. * * @since 1.90.0 */ public function register() { $this->site_specific_answers->register(); $this->user_specific_answers->register(); $this->rest_controller->register(); } /** * Gets the set of user input questions. * * @since 1.90.0 * * @return array The user input questions. */ public static function get_questions() { return static::$questions; } /** * Gets user input answers. * * @since 1.90.0 * * @return array|WP_Error User input answers. */ public function get_answers() { $questions = static::$questions; $site_answers = $this->site_specific_answers->get(); $user_answers = $this->user_specific_answers->get(); $settings = array_merge( is_array( $site_answers ) ? $site_answers : array(), is_array( $user_answers ) ? $user_answers : array() ); // If there are no settings, return default empty values. if ( empty( $settings ) ) { array_walk( $questions, function ( &$question ) { $question['values'] = array(); } ); return $questions; } foreach ( $settings as &$setting ) { if ( ! isset( $setting['answeredBy'] ) ) { continue; } $answered_by = intval( $setting['answeredBy'] ); if ( ! $answered_by || $answered_by === $this->user_options->get_user_id() ) { continue; } $setting['author'] = array( 'photo' => get_avatar_url( $answered_by ), 'login' => ( new WP_User( $answered_by ) )->user_login, ); } // If there are un-answered questions, return default empty values for them. foreach ( $questions as $question_key => $question_value ) { if ( ! isset( $settings[ $question_key ] ) ) { $settings[ $question_key ] = $question_value; $settings[ $question_key ]['values'] = array(); } } return $settings; } /** * Determines whether the current user input settings have empty values or not. * * @since 1.90.0 * * @param array $settings The settings to check. * @return boolean|null TRUE if at least one of the settings has empty values, otherwise FALSE. */ public function are_settings_empty( $settings = array() ) { if ( empty( $settings ) ) { $settings = $this->get_answers(); if ( is_wp_error( $settings ) ) { return null; } } foreach ( $settings as $setting ) { if ( empty( $setting['values'] ) ) { return true; } } return false; } /** * Sets user input answers. * * @since 1.90.0 * * @param array $settings User settings. * @return array|WP_Error User input answers. */ public function set_answers( $settings ) { $site_settings = array(); $user_settings = array(); foreach ( $settings as $setting_key => $answers ) { $setting_data = array(); $setting_data['values'] = $answers; $setting_data['scope'] = static::$questions[ $setting_key ]['scope']; if ( 'site' === $setting_data['scope'] ) { $existing_answers = $this->get_answers(); $answered_by = $this->user_options->get_user_id(); if ( // If the answer to the "purpose" question changed, // attribute the answer to the current user changing the // answer. ( ! empty( $existing_answers['purpose']['values'] ) && ! empty( array_diff( $existing_answers['purpose']['values'], $answers ) ) ) || // If the answer to the "purpose" question was empty, // attribute the answer to the current user. empty( $existing_answers['purpose']['answeredBy'] ) ) { $answered_by = $this->user_options->get_user_id(); } else { // Otherwise, attribute the answer to the user who answered // the question previously. $answered_by = $existing_answers['purpose']['answeredBy']; } $setting_data['answeredBy'] = $answered_by; $site_settings[ $setting_key ] = $setting_data; } elseif ( 'user' === $setting_data['scope'] ) { $user_settings[ $setting_key ] = $setting_data; } } $this->site_specific_answers->set( $site_settings ); $this->user_specific_answers->set( $user_settings ); return $this->get_answers(); } } User_Input/REST_User_Input_Controller.php 0000644 00000012474 14720521221 0014513 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User_Input\REST_User_Input_Controller * * @package Google\Site_Kit\Core\User_Input * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User_Input; use Google\Site_Kit\Core\Key_Metrics\Key_Metrics_Setup_Completed_By; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\User_Surveys\Survey_Queue; use Google\Site_Kit\Core\Util\Feature_Flags; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling User Input settings rest routes. * * @since 1.90.0 * @access private * @ignore */ class REST_User_Input_Controller { /** * User_Input instance. * * @since 1.90.0 * @var User_Input */ protected $user_input; /** * Survey_Queue instance. * * @since 1.104.0 * @var Survey_Queue */ protected $survey_queue; /** * Key_Metrics_Setup_Completed_By instance. * * @since 1.113.0 * @var Key_Metrics_Setup_Completed_By */ protected $key_metrics_setup_completed_by; /** * Constructor. * * @since 1.90.0 * * @param User_Input $user_input User_Input instance. * @param Survey_Queue $survey_queue Survey_Queue instance. * @param Key_Metrics_Setup_Completed_By $key_metrics_setup_completed_by Key_Metrics_Setup_Completed_By instance. */ public function __construct( User_Input $user_input, Survey_Queue $survey_queue, Key_Metrics_Setup_Completed_By $key_metrics_setup_completed_by ) { $this->user_input = $user_input; $this->survey_queue = $survey_queue; $this->key_metrics_setup_completed_by = $key_metrics_setup_completed_by; } /** * Registers functionality. * * @since 1.90.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/user-input-settings', ) ); } ); } /** * Gets related REST routes. * * @since 1.90.0 * * @return array List of REST_Route objects. */ private function get_rest_routes() { return array( new REST_Route( 'core/user/data/user-input-settings', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { $response = rest_ensure_response( $this->user_input->get_answers() ); // Iterating over each setting in the response data to remove the 'author' key. // We use pass-by-reference (&$setting) to directly modify the original $response data. // This is done to ensure that if the current user doesn't have the `list_users` capability, // they won't be able to see the `{setting}.author` key of each answer object. if ( ! current_user_can( 'list_users' ) ) { foreach ( $response->data as &$setting ) { if ( isset( $setting['author'] ) ) { unset( $setting['author'] ); } } } return $response; }, 'permission_callback' => function () { return current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ); }, ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request->get_param( 'data' ); if ( ! isset( $data['settings'] ) || ! is_array( $data['settings'] ) ) { return new WP_Error( 'rest_missing_callback_param', __( 'Missing settings data.', 'google-site-kit' ), array( 'status' => 400 ) ); } $answers = $this->user_input->set_answers( $data['settings'] ); if ( ! empty( $answers['purpose']['values'] ) ) { $key_metrics_setup_already_done_by_user = $this->key_metrics_setup_completed_by->get(); if ( empty( $key_metrics_setup_already_done_by_user ) ) { $current_user_id = get_current_user_id(); $this->key_metrics_setup_completed_by->set( $current_user_id ); } } $response = rest_ensure_response( $answers ); if ( $response instanceof WP_REST_Response ) { $this->survey_queue->dequeue( 'user_input_answered_other_survey' ); } return $response; }, 'permission_callback' => function () { return current_user_can( Permissions::AUTHENTICATE ); }, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'settings' => array( 'type' => 'object', 'required' => true, 'questions' => array_fill_keys( array_keys( User_Input::get_questions() ), array( 'type' => 'array', 'items' => array( 'type' => 'string' ), ) ), ), ), ), ), ), ) ), ); } } User_Input/Site_Specific_Answers.php 0000644 00000004751 14720521221 0013570 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User_Input\Site_Specific_Answers * * @package Google\Site_Kit\Core\User_Input * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User_Input; use Google\Site_Kit\Core\Storage\Setting; /** * Class for handling the site specific answers in User Input. * * @since 1.90.0 * @access private * @ignore */ class Site_Specific_Answers extends Setting { /** * The option_name for this setting. */ const OPTION = 'googlesitekit_user_input_settings'; /** * The scope for which the answers are handled by this class. */ const SCOPE = 'site'; /** * Gets the expected value type. * * @since 1.90.0 * * @return string The type name. */ protected function get_type() { return 'array'; } /** * Gets the default value. * * @since 1.90.0 * * @return array The default value. */ protected function get_default() { return array(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.90.0 * * @return callable Callback method that filters or type casts invalid setting values. */ protected function get_sanitize_callback() { $questions = array_filter( User_Input::get_questions(), function ( $question ) { return static::SCOPE === $question['scope']; } ); return function ( $settings ) use ( $questions ) { if ( ! is_array( $settings ) ) { return $this->get(); } $results = array(); foreach ( $settings as $setting_key => $setting_values ) { // Ensure all the data is valid. if ( ! in_array( $setting_key, array_keys( $questions ), true ) || ! is_array( $setting_values ) || static::SCOPE !== $setting_values['scope'] || ! is_array( $setting_values['values'] ) || ! is_int( $setting_values['answeredBy'] ) ) { continue; } $valid_values = array(); $valid_values['scope'] = $setting_values['scope']; $valid_values['answeredBy'] = $setting_values['answeredBy']; $valid_answers = array(); // Verify that each answer value is a string. foreach ( $setting_values['values'] as $answer ) { if ( is_scalar( $answer ) ) { $valid_answers[] = $answer; } } $valid_values['values'] = $valid_answers; if ( ! empty( $valid_values ) ) { $results[ $setting_key ] = $valid_values; } } return $results; }; } } Authentication/Authentication.php 0000644 00000111513 14720521221 0013211 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Authentication * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\Encrypted_Options; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Storage\Transients; use Google\Site_Kit\Core\Admin\Notice; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit\Core\User_Input\User_Input; use Google\Site_Kit\Plugin; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Util\BC_Functions; use Google\Site_Kit\Core\Util\URL; use Google\Site_Kit\Core\Util\Auto_Updates; use Google\Site_Kit\Core\Authentication\REST_Authentication_Controller; /** * Authentication Class. * * @since 1.0.0 * @access private * @ignore */ final class Authentication { use Method_Proxy_Trait; const ACTION_CONNECT = 'googlesitekit_connect'; const ACTION_DISCONNECT = 'googlesitekit_disconnect'; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Options object. * * @since 1.0.0 * * @var Options */ private $options = null; /** * User_Options object. * * @since 1.0.0 * * @var User_Options */ private $user_options = null; /** * User_Input * * @since 1.90.0 * * @var User_Input */ private $user_input = null; /** * Transients object. * * @since 1.0.0 * * @var Transients */ private $transients = null; /** * Modules object. * * @since 1.70.0 * * @var Modules */ private $modules = null; /** * OAuth client object. * * @since 1.0.0 * * @var Clients\OAuth_Client */ private $auth_client = null; /** * OAuth credentials instance. * * @since 1.0.0 * @var Credentials */ protected $credentials; /** * Verification instance. * * @since 1.0.0 * @var Verification */ protected $verification; /** * Verification meta instance. * * @since 1.1.0 * @var Verification_Meta */ protected $verification_meta; /** * Verification file instance. * * @since 1.1.0 * @var Verification_File */ protected $verification_file; /** * Profile instance. * * @since 1.0.0 * @var Profile */ protected $profile; /** * Token instance. * * @since 1.39.0 * @var Token */ protected $token; /** * Owner_ID instance. * * @since 1.16.0 * @var Owner_ID */ protected $owner_id; /** * Has_Connected_Admins instance. * * @since 1.14.0 * @var Has_Connected_Admins */ protected $has_connected_admins; /** * Has_Multiple_Admins instance. * * @since 1.29.0 * @var Has_Multiple_Admins */ protected $has_multiple_admins; /** * Connected_Proxy_URL instance. * * @since 1.17.0 * @var Connected_Proxy_URL */ protected $connected_proxy_url; /** * Disconnected_Reason instance. * * @since 1.17.0 * @var Disconnected_Reason */ protected $disconnected_reason; /** * Google_Proxy instance. * * @since 1.1.2 * @var Google_Proxy */ protected $google_proxy; /** * Initial_Version instance. * * @since 1.25.0 * @var Initial_Version */ protected $initial_version; /** * Flag set when site fields are synchronized during the current request. * * @var bool */ private $did_sync_fields; /** * REST_Authentication_controller instance. * * @since 1.131.0 * @var REST_Authentication_Controller */ protected $rest_authentication_controller; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. * @param Options $options Optional. Option API instance. Default is a new instance. * @param User_Options $user_options Optional. User Option API instance. Default is a new instance. * @param Transients $transients Optional. Transient API instance. Default is a new instance. * @param User_Input $user_input Optional. User_Input instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null, User_Options $user_options = null, Transients $transients = null, User_Input $user_input = null ) { $this->context = $context; $this->options = $options ?: new Options( $this->context ); $this->user_options = $user_options ?: new User_Options( $this->context ); $this->transients = $transients ?: new Transients( $this->context ); $this->modules = new Modules( $this->context, $this->options, $this->user_options, $this ); $this->user_input = $user_input ?: new User_Input( $context, $this->options, $this->user_options ); $this->google_proxy = new Google_Proxy( $this->context ); $this->credentials = new Credentials( new Encrypted_Options( $this->options ) ); $this->verification = new Verification( $this->user_options ); $this->verification_meta = new Verification_Meta( $this->user_options ); $this->verification_file = new Verification_File( $this->user_options ); $this->profile = new Profile( $this->user_options ); $this->token = new Token( $this->user_options ); $this->owner_id = new Owner_ID( $this->options ); $this->has_connected_admins = new Has_Connected_Admins( $this->options, $this->user_options ); $this->has_multiple_admins = new Has_Multiple_Admins( $this->transients ); $this->connected_proxy_url = new Connected_Proxy_URL( $this->options ); $this->disconnected_reason = new Disconnected_Reason( $this->user_options ); $this->initial_version = new Initial_Version( $this->user_options ); $this->rest_authentication_controller = new REST_Authentication_Controller( $this ); } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ public function register() { $this->credentials()->register(); $this->verification()->register(); $this->verification_file()->register(); $this->verification_meta()->register(); $this->has_connected_admins->register(); $this->owner_id->register(); $this->connected_proxy_url->register(); $this->disconnected_reason->register(); $this->initial_version->register(); $this->rest_authentication_controller->register(); add_filter( 'allowed_redirect_hosts', $this->get_method_proxy( 'allowed_redirect_hosts' ) ); add_filter( 'googlesitekit_admin_data', $this->get_method_proxy( 'inline_js_admin_data' ) ); add_filter( 'googlesitekit_admin_notices', $this->get_method_proxy( 'authentication_admin_notices' ) ); add_filter( 'googlesitekit_inline_base_data', $this->get_method_proxy( 'inline_js_base_data' ) ); add_filter( 'googlesitekit_setup_data', $this->get_method_proxy( 'inline_js_setup_data' ) ); add_action( 'admin_init', $this->get_method_proxy( 'handle_oauth' ) ); add_action( 'admin_init', $this->get_method_proxy( 'check_connected_proxy_url' ) ); add_action( 'admin_action_' . self::ACTION_CONNECT, $this->get_method_proxy( 'handle_connect' ) ); add_action( 'admin_action_' . self::ACTION_DISCONNECT, $this->get_method_proxy( 'handle_disconnect' ) ); add_action( 'admin_action_' . Google_Proxy::ACTION_PERMISSIONS, function () { $this->handle_proxy_permissions(); } ); add_action( 'googlesitekit_authorize_user', function () { if ( ! $this->credentials->using_proxy() ) { return; } $this->set_connected_proxy_url(); }, 10, 3 ); add_filter( 'googlesitekit_user_data', function ( $user ) { if ( $this->profile->has() ) { $profile_data = $this->profile->get(); $user['user']['email'] = $profile_data['email']; $user['user']['picture'] = $profile_data['photo']; // Older versions of Site Kit (before 1.86.0) did not // fetch the user's full name, so we need to check for // that attribute before using it. $user['user']['full_name'] = isset( $profile_data['full_name'] ) ? $profile_data['full_name'] : null; } $user['connectURL'] = esc_url_raw( $this->get_connect_url() ); $user['hasMultipleAdmins'] = $this->has_multiple_admins->get(); $user['initialVersion'] = $this->initial_version->get(); $user['isUserInputCompleted'] = ! $this->user_input->are_settings_empty(); $user['verified'] = $this->verification->has(); return $user; } ); add_filter( 'googlesitekit_inline_tracking_data', $this->get_method_proxy( 'inline_js_tracking_data' ) ); // Synchronize site fields on shutdown when select options change. $option_updated = function () { $sync_site_fields = function () { if ( $this->did_sync_fields ) { return; } // This method should run no more than once per request. $this->did_sync_fields = true; if ( $this->credentials->using_proxy() ) { $this->google_proxy->sync_site_fields( $this->credentials() ); } }; add_action( 'shutdown', $sync_site_fields ); }; add_action( 'update_option_blogname', $option_updated ); add_action( 'update_option_googlesitekit_db_version', $option_updated ); add_action( OAuth_Client::CRON_REFRESH_PROFILE_DATA, function ( $user_id ) { $this->cron_refresh_profile_data( $user_id ); } ); // If no initial version set for the current user, set it when getting a new access token. if ( ! $this->initial_version->get() ) { $set_initial_version = function () { $this->initial_version->set( GOOGLESITEKIT_VERSION ); }; add_action( 'googlesitekit_authorize_user', $set_initial_version ); add_action( 'googlesitekit_reauthorize_user', $set_initial_version ); } add_action( 'current_screen', function ( $current_screen ) { $this->maybe_refresh_token_for_screen( $current_screen->id ); } ); add_action( 'heartbeat_tick', function ( $response, $screen_id ) { $this->maybe_refresh_token_for_screen( $screen_id ); }, 10, 2 ); // Regularly synchronize Google profile data. add_action( 'googlesitekit_reauthorize_user', function () { if ( ! $this->profile->has() ) { return; } $profile_data = $this->profile->get(); if ( ! isset( $profile_data['last_updated'] ) || time() - $profile_data['last_updated'] > DAY_IN_SECONDS ) { $this->get_oauth_client()->refresh_profile_data( 30 * MINUTE_IN_SECONDS ); } } ); } /** * Gets the OAuth credentials object. * * @since 1.0.0 * * @return Credentials Credentials instance. */ public function credentials() { return $this->credentials; } /** * Gets the verification instance. * * @since 1.0.0 * * @return Verification Verification instance. */ public function verification() { return $this->verification; } /** * Gets the verification tag instance. * * @since 1.0.0 * @deprecated 1.1.0 * * @return Verification_Meta Verification tag instance. */ public function verification_tag() { _deprecated_function( __METHOD__, '1.1.0', __CLASS__ . '::verification_meta()' ); return $this->verification_meta; } /** * Gets the verification meta instance. * * @since 1.1.0 * * @return Verification_Meta Verification tag instance. */ public function verification_meta() { return $this->verification_meta; } /** * Gets the verification file instance. * * @since 1.1.0 * * @return Verification_File Verification file instance. */ public function verification_file() { return $this->verification_file; } /** * Gets the Profile instance. * * @since 1.0.0 * * @return Profile Profile instance. */ public function profile() { return $this->profile; } /** * Gets the Token instance. * * @since 1.39.0 * * @return Token Token instance. */ public function token() { return $this->token; } /** * Gets the OAuth client instance. * * @since 1.0.0 * * @return Clients\OAuth_Client OAuth client instance. */ public function get_oauth_client() { if ( ! $this->auth_client instanceof OAuth_Client ) { $this->auth_client = new OAuth_Client( $this->context, $this->options, $this->user_options, $this->credentials, $this->google_proxy, $this->profile, $this->token ); } return $this->auth_client; } /** * Gets the Google Proxy instance. * * @since 1.19.0 * * @return Google_Proxy An instance of Google Proxy. */ public function get_google_proxy() { return $this->google_proxy; } /** * Revokes authentication along with user options settings. * * @since 1.0.0 */ public function disconnect() { global $wpdb; // Revoke token via API call. $this->get_oauth_client()->revoke_token(); // Delete all user data. $user_id = $this->user_options->get_user_id(); $prefix = $this->user_options->get_meta_key( 'googlesitekit\_%' ); // Reset Has_Connected_Admins setting. $this->has_connected_admins->delete(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key LIKE %s", $user_id, $prefix ) ); wp_cache_delete( $user_id, 'user_meta' ); } /** * Gets the URL for connecting to Site Kit. * * @since 1.0.0 * @since 1.32.0 Updated to use dedicated action URL. * * @return string Connect URL. */ public function get_connect_url() { return add_query_arg( array( 'action' => self::ACTION_CONNECT, 'nonce' => wp_create_nonce( self::ACTION_CONNECT ), ), admin_url( 'index.php' ) ); } /** * Gets the URL for disconnecting from Site Kit. * * @since 1.0.0 * @since 1.32.0 Updated to use dedicated action URL. * * @return string Disconnect URL. */ public function get_disconnect_url() { return add_query_arg( array( 'action' => self::ACTION_DISCONNECT, 'nonce' => wp_create_nonce( self::ACTION_DISCONNECT ), ), admin_url( 'index.php' ) ); } /** * Check if the current user is authenticated. * * @since 1.0.0 * * @return boolean True if the user is authenticated, false otherwise. */ public function is_authenticated() { return $this->token->has(); } /** * Checks whether the Site Kit setup is considered complete. * * If this is not the case, most permissions will be force-prevented to ensure that only permissions required for * initial setup are granted. * * @since 1.0.0 * @since 1.7.0 Moved from `Permissions` class. * * @return bool True if setup is completed, false otherwise. */ public function is_setup_completed() { if ( ! $this->credentials->has() ) { return false; } /** * Filters whether the Site Kit plugin should consider its setup to be completed. * * This can be used by essential auto-activated modules to amend the result of this check. * * @since 1.0.0 * * @param bool $complete Whether the setup is completed. */ return (bool) apply_filters( 'googlesitekit_setup_complete', true ); } /** * Refreshes user profile data in the background. * * @since 1.13.0 * * @param int $user_id User ID to refresh profile data for. */ private function cron_refresh_profile_data( $user_id ) { $original_user_id = $this->user_options->get_user_id(); $this->user_options->switch_user( $user_id ); if ( $this->is_authenticated() ) { $this->get_oauth_client()->refresh_profile_data( 30 * MINUTE_IN_SECONDS ); } $this->user_options->switch_user( $original_user_id ); } /** * Proactively refreshes the current user's OAuth token when on the * Site Kit Plugin Dashboard screen. * * Also refreshes the module owner's OAuth token for all shareable modules * the current user can read shared data for. * * @since 1.42.0 * @since 1.70.0 Moved the closure within regiser() to this method. * * @param string $screen_id The unique ID of the current WP_Screen. * * @return void */ private function maybe_refresh_token_for_screen( $screen_id ) { if ( 'dashboard' !== $screen_id && 'toplevel_page_googlesitekit-dashboard' !== $screen_id ) { return; } $this->refresh_shared_module_owner_tokens(); if ( ! current_user_can( Permissions::AUTHENTICATE ) || ! $this->credentials()->has() ) { return; } $this->refresh_user_token(); } /** * Proactively refreshes the module owner's OAuth token for all shareable * modules the current user can read shared data for. * * @since 1.70.0 * * @return void */ private function refresh_shared_module_owner_tokens() { $shareable_modules = $this->modules->get_shareable_modules(); foreach ( $shareable_modules as $module_slug => $module ) { if ( ! current_user_can( Permissions::READ_SHARED_MODULE_DATA, $module_slug ) ) { continue; } $owner_id = $module->get_owner_id(); if ( ! $owner_id ) { continue; } $restore_user = $this->user_options->switch_user( $owner_id ); $this->refresh_user_token(); $restore_user(); } } /** * Proactively refreshes the current user's OAuth token. * * @since 1.70.0 * * @return void */ private function refresh_user_token() { $token = $this->token->get(); // Do nothing if the token is not set. if ( empty( $token['created'] ) || empty( $token['expires_in'] ) ) { return; } // Do nothing if the token expires in more than 5 minutes. if ( $token['created'] + $token['expires_in'] > time() + 5 * MINUTE_IN_SECONDS ) { return; } $this->get_oauth_client()->refresh_token(); } /** * Accessible method to call refresh_user_token() for classes using Authentication. * * @since 1.131.0 * * @return void */ public function do_refresh_user_token() { $this->refresh_user_token(); } /** * Handles receiving a temporary OAuth code. * * @since 1.0.0 * @since 1.32.0 Moved connect and disconnect actions to dedicated handlers. */ private function handle_oauth() { if ( defined( 'WP_CLI' ) && WP_CLI ) { return; } // Handles Direct OAuth client request. if ( $this->context->input()->filter( INPUT_GET, 'oauth2callback' ) ) { if ( ! current_user_can( Permissions::AUTHENTICATE ) ) { wp_die( esc_html__( 'You don’t have permissions to authenticate with Site Kit.', 'google-site-kit' ), 403 ); } $this->get_oauth_client()->authorize_user(); } } /** * Handles request to connect via oAuth. * * @since 1.32.0 */ private function handle_connect() { $input = $this->context->input(); $nonce = $input->filter( INPUT_GET, 'nonce' ); if ( ! wp_verify_nonce( $nonce, self::ACTION_CONNECT ) ) { $this->invalid_nonce_error( self::ACTION_CONNECT ); } if ( ! current_user_can( Permissions::AUTHENTICATE ) ) { wp_die( esc_html__( 'You don’t have permissions to authenticate with Site Kit.', 'google-site-kit' ), 403 ); } $redirect_url = $input->filter( INPUT_GET, 'redirect', FILTER_DEFAULT ); if ( $redirect_url ) { $redirect_url = esc_url_raw( wp_unslash( $redirect_url ) ); } $error_redirect_url = $input->filter( INPUT_GET, 'errorRedirect', FILTER_DEFAULT ); if ( $error_redirect_url ) { $error_redirect_url = esc_url_raw( wp_unslash( $error_redirect_url ) ); } // User is trying to authenticate, but access token hasn't been set. $additional_scopes = $input->filter( INPUT_GET, 'additional_scopes', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); wp_safe_redirect( $this->get_oauth_client()->get_authentication_url( $redirect_url, $error_redirect_url, $additional_scopes ) ); exit(); } /** * Handles request to disconnect via oAuth. * * @since 1.32.0 */ private function handle_disconnect() { $nonce = $this->context->input()->filter( INPUT_GET, 'nonce' ); if ( ! wp_verify_nonce( $nonce, self::ACTION_DISCONNECT ) ) { $this->invalid_nonce_error( self::ACTION_DISCONNECT ); } if ( ! current_user_can( Permissions::AUTHENTICATE ) ) { wp_die( esc_html__( 'You don’t have permissions to authenticate with Site Kit.', 'google-site-kit' ), 403 ); } $this->disconnect(); $redirect_url = $this->context->admin_url( 'splash', array( 'googlesitekit_reset_session' => 1, ) ); wp_safe_redirect( $redirect_url ); exit(); } /** * Gets the update core URL if the user can update the WordPress core version. * * If the site is multisite, it gets the update core URL for the network admin. * * @since 1.85.0 * * @return string The update core URL. */ private function get_update_core_url() { if ( ! current_user_can( 'update_core' ) ) { return ''; } if ( is_multisite() ) { return admin_url( 'network/update-core.php' ); } return admin_url( 'update-core.php' ); } /** * Modifies the base data to pass to JS. * * @since 1.2.0 * * @param array $data Inline JS data. * @return array Filtered $data. */ private function inline_js_base_data( $data ) { $data['isOwner'] = $this->owner_id->get() === get_current_user_id(); $data['splashURL'] = esc_url_raw( $this->context->admin_url( 'splash' ) ); $data['proxySetupURL'] = ''; $data['proxyPermissionsURL'] = ''; $data['usingProxy'] = false; $data['isAuthenticated'] = $this->is_authenticated(); $data['setupErrorCode'] = null; $data['setupErrorMessage'] = null; $data['setupErrorRedoURL'] = null; $data['proxySupportLinkURL'] = null; $data['updateCoreURL'] = null; if ( $this->credentials->using_proxy() ) { $auth_client = $this->get_oauth_client(); $data['proxySetupURL'] = esc_url_raw( $this->get_proxy_setup_url() ); $data['proxyPermissionsURL'] = esc_url_raw( $this->get_proxy_permissions_url() ); $data['usingProxy'] = true; $data['proxySupportLinkURL'] = esc_url_raw( $this->get_proxy_support_link_url() ); $data['updateCoreURL'] = esc_url_raw( $this->get_update_core_url() ); // Check for an error in the proxy setup. $error_code = $this->user_options->get( OAuth_Client::OPTION_ERROR_CODE ); // If an error is found, add it to the data we send to the client. // // We'll also remove the existing access code in the user options, // because it isn't valid (given there was a setup error). if ( ! empty( $error_code ) ) { $data['setupErrorCode'] = $error_code; $data['setupErrorMessage'] = $auth_client->get_error_message( $error_code ); // Get credentials needed to authenticate with the proxy // so we can build a new setup URL. $credentials = $this->credentials->get(); $access_code = $this->user_options->get( OAuth_Client::OPTION_PROXY_ACCESS_CODE ); // Both the access code and site ID are needed to generate // a setup URL. if ( $access_code && ! empty( $credentials['oauth2_client_id'] ) ) { $setup_url = $this->google_proxy->setup_url( array( 'code' => $access_code, 'site_id' => $credentials['oauth2_client_id'], ) ); $this->user_options->delete( OAuth_Client::OPTION_PROXY_ACCESS_CODE ); } elseif ( $this->is_authenticated() ) { $setup_url = $this->get_connect_url(); } else { $setup_url = $data['proxySetupURL']; } // Add the setup URL to the data sent to the client. $data['setupErrorRedoURL'] = $setup_url; // Remove the error code from the user options so it doesn't // appear again. $this->user_options->delete( OAuth_Client::OPTION_ERROR_CODE ); } } $version = get_bloginfo( 'version' ); $data['wpVersion'] = $this->inline_js_wp_version( $version ); if ( version_compare( $version, '5.5', '>=' ) && function_exists( 'wp_is_auto_update_enabled_for_type' ) ) { $data['changePluginAutoUpdatesCapacity'] = Auto_Updates::is_plugin_autoupdates_enabled() && Auto_Updates::AUTO_UPDATE_NOT_FORCED === Auto_Updates::sitekit_forced_autoupdates_status(); $data['siteKitAutoUpdatesEnabled'] = Auto_Updates::is_sitekit_autoupdates_enabled(); } $data['pluginBasename'] = GOOGLESITEKIT_PLUGIN_BASENAME; $current_user = wp_get_current_user(); $data['userRoles'] = $current_user->roles; return $data; } /** * Gets the WP version to pass to JS. * * @since 1.93.0 * * @param string $version The WP version. * @return array The WP version to pass to JS. */ private function inline_js_wp_version( $version ) { // The trailing '.0' is added to the $version to ensure there are always at least 2 segments in the version. // This is necessary in case the minor version is stripped from the version string by a plugin. // See https://github.com/google/site-kit-wp/issues/4963 for more details. list( $major, $minor ) = explode( '.', $version . '.0' ); return array( 'version' => $version, 'major' => (int) $major, 'minor' => (int) $minor, ); } /** * Modifies the admin data to pass to JS. * * @since 1.0.0 * * @param array $data Inline JS data. * @return array Filtered $data. */ private function inline_js_admin_data( $data ) { $data['connectURL'] = esc_url_raw( $this->get_connect_url() ); $data['disconnectURL'] = esc_url_raw( $this->get_disconnect_url() ); return $data; } /** * Modifies the setup data to pass to JS. * * @since 1.0.0 * * @param array $data Inline JS data. * @return array Filtered $data. */ private function inline_js_setup_data( $data ) { $auth_client = $this->get_oauth_client(); $is_authenticated = $this->is_authenticated(); $data['isSiteKitConnected'] = $this->credentials->has(); $data['isResettable'] = $this->options->has( Credentials::OPTION ); $data['isAuthenticated'] = $is_authenticated; $data['requiredScopes'] = $auth_client->get_required_scopes(); $data['grantedScopes'] = $is_authenticated ? $auth_client->get_granted_scopes() : array(); $data['unsatisfiedScopes'] = $is_authenticated ? $auth_client->get_unsatisfied_scopes() : array(); $data['needReauthenticate'] = $auth_client->needs_reauthentication(); // All admins need to go through site verification process. if ( current_user_can( Permissions::MANAGE_OPTIONS ) ) { $data['isVerified'] = $this->verification->has(); } else { $data['isVerified'] = false; } // The actual data for this is passed in from the Search Console module. if ( ! isset( $data['hasSearchConsoleProperty'] ) ) { $data['hasSearchConsoleProperty'] = false; } return $data; } /** * Adds / modifies tracking relevant data to pass to JS. * * @since 1.78.0 * * @param array $data Inline JS data. * @return array Filtered $data. */ private function inline_js_tracking_data( $data ) { $data['isAuthenticated'] = $this->is_authenticated(); $data['userRoles'] = wp_get_current_user()->roles; return $data; } /** * Add allowed redirect host to safe wp_safe_redirect * * @since 1.0.0 * * @param array $hosts Array of safe hosts to redirect to. * * @return array */ private function allowed_redirect_hosts( $hosts ) { $hosts[] = 'accounts.google.com'; $hosts[] = URL::parse( $this->google_proxy->url(), PHP_URL_HOST ); // In the case of IDNs, ensure the ASCII and non-ASCII domains // are treated as allowable origins. $admin_hostname = URL::parse( admin_url(), PHP_URL_HOST ); // See \Requests_IDNAEncoder::is_ascii. $is_ascii = preg_match( '/(?:[^\x00-\x7F])/', $admin_hostname ) !== 1; // If this host is already an ASCII-only string, it's either // not an IDN or it's an ASCII-formatted IDN. // We only need to intervene if it is non-ASCII. if ( ! $is_ascii ) { // If this host is an IDN in Unicode format, we need to add the // urlencoded versions of the domain to the `$hosts` array, // because this is what will be used for redirects. $hosts[] = rawurlencode( $admin_hostname ); } return $hosts; } /** * Shows admin notification for authentication related issues. * * @since 1.0.0 * * @param array $notices Array of admin notices. * * @return array Array of admin notices. */ private function authentication_admin_notices( $notices ) { // Only include notices if in the correct admin panel. if ( $this->context->is_network_mode() !== is_network_admin() ) { return $notices; } $notices[] = $this->get_reauthentication_needed_notice(); $notices[] = $this->get_reconnect_after_url_mismatch_notice(); return $notices; } /** * Gets reconnect notice. * * @since 1.17.0 * * @return Notice Notice object. */ private function get_reconnect_after_url_mismatch_notice() { return new Notice( 'reconnect_after_url_mismatch', array( 'content' => function () { $connected_url = $this->connected_proxy_url->get(); $current_url = $this->context->get_canonical_home_url(); $content = '<p>' . sprintf( /* translators: 1: Plugin name. 2: URL change message. 3: Proxy setup URL. 4: Reconnect string. 5: Proxy support link for the url-has-changed help page. 6: Help link message. */ __( '%1$s: %2$s <a href="%3$s">%4$s</a>. <a target="_blank" href="%5$s">%6$s</a>', 'google-site-kit' ), esc_html__( 'Site Kit by Google', 'google-site-kit' ), esc_html__( 'Looks like the URL of your site has changed. In order to continue using Site Kit, you’ll need to reconnect, so that your plugin settings are updated with the new URL.', 'google-site-kit' ), esc_url( $this->get_proxy_setup_url() ), esc_html__( 'Reconnect', 'google-site-kit' ), esc_url( $this->get_proxy_support_link_url() . '/?doc=url-has-changed' ), esc_html__( 'Get help', 'google-site-kit' ) ) . '</p>'; // Only show the comparison if URLs don't match as it is possible // they could already match again at this point, although they most likely won't. if ( ! $this->connected_proxy_url->matches_url( $current_url ) ) { $content .= sprintf( '<ul><li>%s</li><li>%s</li></ul>', sprintf( /* translators: %s: Previous URL */ esc_html__( 'Old URL: %s', 'google-site-kit' ), $connected_url ), sprintf( /* translators: %s: Current URL */ esc_html__( 'New URL: %s', 'google-site-kit' ), $current_url ) ); } return $content; }, 'type' => Notice::TYPE_INFO, 'active_callback' => function () { return $this->disconnected_reason->get() === Disconnected_Reason::REASON_CONNECTED_URL_MISMATCH && $this->credentials->has(); }, ) ); } /** * Gets re-authentication notice. * * @since 1.0.0 * * @return Notice Notice object. */ private function get_reauthentication_needed_notice() { return new Notice( 'needs_reauthentication', array( 'content' => function () { ob_start(); ?> <p> <?php echo esc_html( sprintf( /* translators: 1: Plugin name. 2: Message. */ __( '%1$s: %2$s', 'google-site-kit' ), __( 'Site Kit by Google', 'google-site-kit' ), __( 'You need to reauthenticate your Google account.', 'google-site-kit' ) ) ); ?> <a href="#" onclick="clearSiteKitAppStorage()" ><?php esc_html_e( 'Click here', 'google-site-kit' ); ?></a> </p> <?php BC_Functions::wp_print_inline_script_tag( sprintf( " function clearSiteKitAppStorage() { if ( localStorage ) { localStorage.clear(); } if ( sessionStorage ) { sessionStorage.clear(); } document.location = '%s'; } ", esc_url_raw( $this->get_connect_url() ) ) ); return ob_get_clean(); }, 'type' => Notice::TYPE_SUCCESS, 'active_callback' => function () { if ( ! empty( $this->user_options->get( OAuth_Client::OPTION_ERROR_CODE ) ) ) { return false; } $unsatisfied_scopes = $this->get_oauth_client()->get_unsatisfied_scopes(); if ( count( $unsatisfied_scopes ) === 1 && 'https://www.googleapis.com/auth/tagmanager.readonly' === $unsatisfied_scopes[0] ) { return false; } return $this->get_oauth_client()->needs_reauthentication(); }, ) ); } /** * Sets the current connected proxy URL. * * @since 1.17.0 */ private function set_connected_proxy_url() { $this->connected_proxy_url->set( $this->context->get_canonical_home_url() ); } /** * Checks whether the current site URL has changed or not. If the URL has been changed, * it disconnects the Site Kit and sets the disconnected reason to "connected_url_mismatch". * * @since 1.17.0 */ private function check_connected_proxy_url() { if ( $this->connected_proxy_url->matches_url( $this->context->get_canonical_home_url() ) ) { return; } if ( ! current_user_can( Permissions::SETUP ) ) { return; } if ( ! $this->credentials->has() ) { return; } if ( ! $this->credentials->using_proxy() ) { return; } if ( ! $this->is_authenticated() ) { return; } if ( ! $this->connected_proxy_url->has() ) { $this->set_connected_proxy_url(); return; } $this->disconnect(); $this->disconnected_reason->set( Disconnected_Reason::REASON_CONNECTED_URL_MISMATCH ); } /** * Gets the publicly visible URL to set up the plugin with the authentication proxy. * * @since 1.17.0 * * @return string An URL for googlesitekit_proxy_connect_user action protected with a nonce. */ private function get_proxy_setup_url() { return add_query_arg( array( 'action' => Google_Proxy::ACTION_SETUP_START, 'nonce' => wp_create_nonce( Google_Proxy::ACTION_SETUP_START ), ), admin_url( 'index.php' ) ); } /** * Handles proxy permissions. * * @since 1.18.0 */ private function handle_proxy_permissions() { $nonce = $this->context->input()->filter( INPUT_GET, 'nonce' ); if ( ! wp_verify_nonce( $nonce, Google_Proxy::ACTION_PERMISSIONS ) ) { $this->invalid_nonce_error( Google_Proxy::ACTION_PERMISSIONS ); } if ( ! current_user_can( Permissions::AUTHENTICATE ) ) { wp_die( esc_html__( 'You have insufficient permissions to manage Site Kit permissions.', 'google-site-kit' ) ); } if ( ! $this->credentials->using_proxy() ) { wp_die( esc_html__( 'Site Kit is not configured to use the authentication proxy.', 'google-site-kit' ) ); } wp_safe_redirect( $this->get_oauth_client()->get_proxy_permissions_url() ); exit; } /** * Gets the proxy permission URL. * * @since 1.18.0 * * @return string Proxy permission URL. */ private function get_proxy_permissions_url() { return add_query_arg( array( 'action' => Google_Proxy::ACTION_PERMISSIONS, 'nonce' => wp_create_nonce( Google_Proxy::ACTION_PERMISSIONS ), ), admin_url( 'index.php' ) ); } /** * Gets the proxy support URL. * * @since 1.80.0 * * @return string|null Support URL. */ public function get_proxy_support_link_url() { return $this->google_proxy->url( Google_Proxy::SUPPORT_LINK_URI ); } /** * Invalid nonce error handler. * * @since 1.42.0 * * @param string $action Action name. */ public function invalid_nonce_error( $action ) { if ( strpos( $action, 'googlesitekit_proxy_' ) !== 0 ) { wp_nonce_ays( $action ); return; } // Copied from wp_nonce_ays() with tweak to the url. $html = __( 'The link you followed has expired.', 'google-site-kit' ); $html .= '</p><p>'; $html .= sprintf( /* translators: 1: Admin splash URL. 2: Support link URL. */ __( '<a href="%1$s">Please try again</a>. Retry didn’t work? <a href="%2$s" target="_blank">Get help</a>.', 'google-site-kit' ), esc_url( Plugin::instance()->context()->admin_url( 'splash' ) ), esc_url( $this->get_proxy_support_link_url() . '?error_id=nonce_expired' ) ); wp_die( $html, __( 'Something went wrong.', 'google-site-kit' ), 403 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Helper method to return options property. * * @since 1.131.0 * * @return Options */ public function get_options_instance() { return $this->options; } /** * Helper method to return has_connected_admins property. * * @since 1.131.0 * * @return Has_Connected_Admins */ public function get_has_connected_admins_instance() { return $this->has_connected_admins; } /** * Helper method to return has_multiple_admins property. * * @since 1.131.0 * * @return Has_Multiple_Admins */ public function get_has_multiple_admins_instance() { return $this->has_multiple_admins; } /** * Helper method to return owner_id property. * * @since 1.131.0 * * @return Owner_ID */ public function get_owner_id_instance() { return $this->owner_id; } /** * Helper method to return disconnected_reason property. * * @since 1.131.0 * * @return Disconnected_Reason */ public function get_disconnected_reason_instance() { return $this->disconnected_reason; } /** * Helper method to return connected_proxy_url property. * * @since 1.131.0 * * @return Connected_Proxy_URL */ public function get_connected_proxy_url_instance() { return $this->connected_proxy_url; } } Authentication/Verification_File.php 0000644 00000001162 14720521221 0013611 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Verification_File * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class representing the site verification file token for a user. * * @since 1.1.0 * @access private * @ignore */ final class Verification_File extends User_Setting { /** * User option key. */ const OPTION = 'googlesitekit_site_verification_file'; } Authentication/Connected_Proxy_URL.php 0000644 00000002354 14720521221 0014061 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Connected_Proxy_URL * * @package Google\Site_Kit\Core\Authentication * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\Setting; /** * Connected_Proxy_URL class. * * @since 1.17.0 * @access private * @ignore */ class Connected_Proxy_URL extends Setting { /** * The option_name for this setting. */ const OPTION = 'googlesitekit_connected_proxy_url'; /** * Matches provided URL with the current proxy URL in the settings. * * @since 1.17.0 * * @param string $url URL to match against the current one in the settings. * @return bool TRUE if URL matches the current one, otherwise FALSE. */ public function matches_url( $url ) { $sanitize = $this->get_sanitize_callback(); $normalized = $sanitize( $url ); return $normalized === $this->get(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.17.0 * * @return callable A sanitizing function. */ protected function get_sanitize_callback() { return 'trailingslashit'; } } Authentication/Google_Proxy.php 0000644 00000044625 14720521221 0012660 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Google_Proxy * * @package Google\Site_Kit\Core\Authentication * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Util\Feature_Flags; use Exception; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Util\URL; use WP_Error; /** * Class for authentication service. * * @since 1.1.2 * @access private * @ignore */ class Google_Proxy { const PRODUCTION_BASE_URL = 'https://sitekit.withgoogle.com'; const STAGING_BASE_URL = 'https://site-kit-dev.appspot.com'; const DEVELOPMENT_BASE_URL = 'https://site-kit-local.appspot.com'; const OAUTH2_SITE_URI = '/o/oauth2/site/'; const OAUTH2_REVOKE_URI = '/o/oauth2/revoke/'; const OAUTH2_TOKEN_URI = '/o/oauth2/token/'; const OAUTH2_AUTH_URI = '/o/oauth2/auth/'; const OAUTH2_DELETE_SITE_URI = '/o/oauth2/delete-site/'; const SETUP_URI = '/v2/site-management/setup/'; const PERMISSIONS_URI = '/site-management/permissions/'; const FEATURES_URI = '/site-management/features/'; const SURVEY_TRIGGER_URI = '/survey/trigger/'; const SURVEY_EVENT_URI = '/survey/event/'; const SUPPORT_LINK_URI = '/support'; const ACTION_EXCHANGE_SITE_CODE = 'googlesitekit_proxy_exchange_site_code'; const ACTION_SETUP = 'googlesitekit_proxy_setup'; const ACTION_SETUP_START = 'googlesitekit_proxy_setup_start'; const ACTION_PERMISSIONS = 'googlesitekit_proxy_permissions'; const ACTION_VERIFY = 'googlesitekit_proxy_verify'; const NONCE_ACTION = 'googlesitekit_proxy_nonce'; const HEADER_REDIRECT_TO = 'Redirect-To'; /** * Plugin context. * * @since 1.1.2 * @var Context */ private $context; /** * Required scopes list. * * @since 1.68.0 * @var array */ private $required_scopes = array(); /** * Google_Proxy constructor. * * @since 1.1.2 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Sets required scopes to use when the site is registering at proxy. * * @since 1.68.0 * * @param array $scopes List of scopes. */ public function with_scopes( array $scopes ) { $this->required_scopes = $scopes; } /** * Returns the application name: a combination of the namespace and version. * * @since 1.27.0 * * @return string The application name. */ public static function get_application_name() { $platform = self::get_platform(); return $platform . '/google-site-kit/' . GOOGLESITEKIT_VERSION; } /** * Gets the list of features to declare support for when setting up with the proxy. * * @since 1.27.0 * * @return array Array of supported features. */ private function get_supports() { $supports = array( 'credentials_retrieval', 'short_verification_token', ); $home_path = URL::parse( $this->context->get_canonical_home_url(), PHP_URL_PATH ); if ( ! $home_path || '/' === $home_path ) { $supports[] = 'file_verification'; } return $supports; } /** * Returns the setup URL to the authentication proxy. * * @since 1.49.0 * @since 1.71.0 Uses the V2 setup flow by default. * * @param array $query_params Query parameters to include in the URL. * @return string URL to the setup page on the authentication proxy. * * @throws Exception Thrown if called without the required query parameters. */ public function setup_url( array $query_params = array() ) { if ( empty( $query_params['code'] ) ) { throw new Exception( __( 'Missing code parameter for setup URL.', 'google-site-kit' ) ); } if ( empty( $query_params['site_id'] ) && empty( $query_params['site_code'] ) ) { throw new Exception( __( 'Missing site_id or site_code parameter for setup URL.', 'google-site-kit' ) ); } return add_query_arg( $query_params, $this->url( self::SETUP_URI ) ); } /** * Conditionally adds the `step` parameter to the passed query parameters, depending on the given error code. * * @since 1.49.0 * * @param array $query_params Query parameters. * @param string $error_code Error code. * @return array Query parameters with `step` included, depending on the error code. */ public function add_setup_step_from_error_code( $query_params, $error_code ) { switch ( $error_code ) { case 'missing_verification': $query_params['step'] = 'verification'; break; case 'missing_delegation_consent': $query_params['step'] = 'delegation_consent'; break; case 'missing_search_console_property': $query_params['step'] = 'search_console_property'; break; } return $query_params; } /** * Returns the permissions URL to the authentication proxy. * * This only returns a URL if the user already has an access token set. * * @since 1.27.0 * * @param Credentials $credentials Credentials instance. * @param array $query_args Optional. Additional query parameters. * @return string URL to the permissions page on the authentication proxy on success, or an empty string on failure. */ public function permissions_url( Credentials $credentials, array $query_args = array() ) { if ( $credentials->has() ) { $creds = $credentials->get(); $query_args['site_id'] = $creds['oauth2_client_id']; } $query_args['application_name'] = rawurlencode( self::get_application_name() ); $query_args['hl'] = $this->context->get_locale( 'user' ); return add_query_arg( $query_args, $this->url( self::PERMISSIONS_URI ) ); } /** * Gets a URL to the proxy with optional path. * * @since 1.1.2 * * @param string $path Optional. Path to append to the base URL. * @return string Complete proxy URL. */ public function url( $path = '' ) { $url = self::PRODUCTION_BASE_URL; $allowed_urls = array( self::PRODUCTION_BASE_URL, self::STAGING_BASE_URL, self::DEVELOPMENT_BASE_URL, ); if ( defined( 'GOOGLESITEKIT_PROXY_URL' ) && in_array( GOOGLESITEKIT_PROXY_URL, $allowed_urls, true ) ) { $url = GOOGLESITEKIT_PROXY_URL; } $url = untrailingslashit( $url ); if ( $path && is_string( $path ) ) { $url .= '/' . ltrim( $path, '/' ); } return $url; } /** * Sends a POST request to the Google Proxy server. * * @since 1.27.0 * * @param string $uri Endpoint to send the request to. * @param Credentials $credentials Credentials instance. * @param array $args Array of request arguments. * @return array|WP_Error The response as an associative array or WP_Error on failure. */ private function request( $uri, $credentials, array $args = array() ) { $request_args = array( 'headers' => ! empty( $args['headers'] ) && is_array( $args['headers'] ) ? $args['headers'] : array(), 'body' => ! empty( $args['body'] ) && is_array( $args['body'] ) ? $args['body'] : array(), 'timeout' => isset( $args['timeout'] ) ? $args['timeout'] : 15, ); if ( $credentials && $credentials instanceof Credentials ) { if ( ! $credentials->has() ) { return new WP_Error( 'oauth_credentials_not_exist', __( 'OAuth credentials haven\'t been found.', 'google-site-kit' ), array( 'status' => 401 ) ); } $creds = $credentials->get(); $request_args['body']['site_id'] = $creds['oauth2_client_id']; $request_args['body']['site_secret'] = $creds['oauth2_client_secret']; } if ( ! empty( $args['access_token'] ) && is_string( $args['access_token'] ) ) { $request_args['headers']['Authorization'] = 'Bearer ' . $args['access_token']; } if ( isset( $args['mode'] ) && 'async' === $args['mode'] ) { $request_args['timeout'] = 0.01; $request_args['blocking'] = false; } if ( ! empty( $args['json_request'] ) ) { $request_args['headers']['Content-Type'] = 'application/json'; $request_args['body'] = wp_json_encode( $request_args['body'] ); } $url = $this->url( $uri ); $response = wp_remote_post( $url, $request_args ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); $body = json_decode( $body, true ); if ( $code < 200 || 299 < $code ) { $message = is_array( $body ) && ! empty( $body['error'] ) ? $body['error'] : ''; return new WP_Error( 'request_failed', $message, array( 'status' => $code ) ); } if ( ! empty( $args['return'] ) && 'response' === $args['return'] ) { return $response; } if ( is_null( $body ) ) { return new WP_Error( 'failed_to_parse_response', __( 'Failed to parse response.', 'google-site-kit' ), array( 'status' => 500 ) ); } return $body; } /** * Gets site fields. * * @since 1.5.0 * * @return array Associative array of $query_arg => $value pairs. */ public function get_site_fields() { return array( 'name' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), 'url' => $this->context->get_canonical_home_url(), 'redirect_uri' => add_query_arg( 'oauth2callback', 1, admin_url( 'index.php' ) ), 'action_uri' => admin_url( 'index.php' ), 'return_uri' => $this->context->admin_url( 'splash' ), 'analytics_redirect_uri' => add_query_arg( 'gatoscallback', 1, admin_url( 'index.php' ) ), ); } /** * Gets metadata fields. * * @since 1.68.0 * * @return array Metadata fields array. */ public function get_metadata_fields() { $metadata = array( 'supports' => implode( ' ', $this->get_supports() ), 'nonce' => wp_create_nonce( self::NONCE_ACTION ), 'mode' => '', 'hl' => $this->context->get_locale( 'user' ), 'application_name' => self::get_application_name(), 'service_version' => 'v2', ); /** * Filters the setup mode. * * @since 1.68.0 * * @param string $mode An initial setup mode. */ $metadata['mode'] = apply_filters( 'googlesitekit_proxy_setup_mode', $metadata['mode'] ); return $metadata; } /** * Fetch site fields * * @since 1.22.0 * * @param Credentials $credentials Credentials instance. * @return array|WP_Error The response as an associative array or WP_Error on failure. */ public function fetch_site_fields( Credentials $credentials ) { return $this->request( self::OAUTH2_SITE_URI, $credentials ); } /** * Are site fields synced * * @since 1.22.0 * * @param Credentials $credentials Credentials instance. * * @return boolean|WP_Error Boolean do the site fields match or WP_Error on failure. */ public function are_site_fields_synced( Credentials $credentials ) { $site_fields = $this->fetch_site_fields( $credentials ); if ( is_wp_error( $site_fields ) ) { return $site_fields; } $get_site_fields = $this->get_site_fields(); foreach ( $get_site_fields as $key => $site_field ) { if ( ! array_key_exists( $key, $site_fields ) || $site_fields[ $key ] !== $site_field ) { return false; } } return true; } /** * Gets user fields. * * @since 1.10.0 * * @return array Associative array of $query_arg => $value pairs. */ public function get_user_fields() { $user_roles = wp_get_current_user()->roles; // If multisite, also consider network administrators. if ( is_multisite() && current_user_can( 'manage_network' ) ) { $user_roles[] = 'network_administrator'; } $user_roles = array_unique( $user_roles ); return array( 'user_roles' => implode( ',', $user_roles ), ); } /** * Unregisters the site on the proxy. * * @since 1.20.0 * * @param Credentials $credentials Credentials instance. * @return array|WP_Error Response data on success, otherwise WP_Error object. */ public function unregister_site( Credentials $credentials ) { return $this->request( self::OAUTH2_DELETE_SITE_URI, $credentials ); } /** * Registers the site on the proxy. * * @since 1.68.0 * * @param string $mode Sync mode. * @return string|WP_Error Redirect URL on success, otherwise an error. */ public function register_site( $mode = 'async' ) { return $this->send_site_fields( null, $mode ); } /** * Synchronizes site fields with the proxy. * * @since 1.5.0 * @since 1.68.0 Updated the function to return redirect URL. * * @param Credentials $credentials Credentials instance. * @param string $mode Sync mode. * @return string|WP_Error Redirect URL on success, otherwise an error. */ public function sync_site_fields( Credentials $credentials, $mode = 'async' ) { return $this->send_site_fields( $credentials, $mode ); } /** * Sends site fields to the proxy. * * @since 1.68.0 * * @param Credentials $credentials Credentials instance. * @param string $mode Sync mode. * @return string|WP_Error Redirect URL on success, otherwise an error. */ private function send_site_fields( Credentials $credentials = null, $mode = 'async' ) { $response = $this->request( self::OAUTH2_SITE_URI, $credentials, array( 'return' => 'response', 'mode' => $mode, 'body' => array_merge( $this->get_site_fields(), $this->get_user_fields(), $this->get_metadata_fields(), array( 'scope' => implode( ' ', $this->required_scopes ), ) ), ) ); if ( is_wp_error( $response ) ) { return $response; } $redirect_to = wp_remote_retrieve_header( $response, self::HEADER_REDIRECT_TO ); if ( empty( $redirect_to ) ) { return new WP_Error( 'failed_to_retrive_redirect', __( 'Failed to retrieve redirect URL.', 'google-site-kit' ), array( 'status' => 500 ) ); } return $redirect_to; } /** * Exchanges a site code for client credentials from the proxy. * * @since 1.1.2 * * @param string $site_code Site code identifying the site. * @param string $undelegated_code Undelegated code identifying the undelegated token. * @return array|WP_Error Response data containing site_id and site_secret on success, WP_Error object on failure. */ public function exchange_site_code( $site_code, $undelegated_code ) { $response_data = $this->request( self::OAUTH2_SITE_URI, null, array( 'body' => array( 'code' => $undelegated_code, 'site_code' => $site_code, ), ) ); if ( is_wp_error( $response_data ) ) { return $response_data; } if ( ! isset( $response_data['site_id'], $response_data['site_secret'] ) ) { return new WP_Error( 'oauth_credentials_not_exist', __( 'OAuth credentials haven\'t been found.', 'google-site-kit' ), array( 'status' => 401 ) ); } return $response_data; } /** * Gets remote features. * * @since 1.27.0 * @since 1.104.0 Added `php_version` to request. * * @param Credentials $credentials Credentials instance. * @return array|WP_Error Response of the wp_remote_post request. */ public function get_features( Credentials $credentials ) { global $wp_version; $platform = self::get_platform(); $user_count = count_users(); $connectable_user_count = isset( $user_count['avail_roles']['administrator'] ) ? $user_count['avail_roles']['administrator'] : 0; $body = array( 'platform' => $platform . '/google-site-kit', 'version' => GOOGLESITEKIT_VERSION, 'platform_version' => $wp_version, 'php_version' => phpversion(), 'user_count' => $user_count['total_users'], 'connectable_user_count' => $connectable_user_count, 'connected_user_count' => $this->count_connected_users(), ); /** * Filters additional context data sent with the body of a remote-controlled features request. * * @since 1.71.0 * * @param array $body Context data to be sent with the features request. */ $body = apply_filters( 'googlesitekit_features_request_data', $body ); return $this->request( self::FEATURES_URI, $credentials, array( 'body' => $body ) ); } /** * Gets the number of users who are connected (i.e. authenticated / * have an access token). * * @since 1.71.0 * * @return int Number of WordPress user accounts connected to SiteKit. */ public function count_connected_users() { $user_options = new User_Options( $this->context ); $connected_users = get_users( array( 'meta_key' => $user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_compare' => 'EXISTS', 'role' => 'administrator', 'fields' => 'ID', ) ); return count( $connected_users ); } /** * Gets the platform. * * @since 1.37.0 * * @return string WordPress multisite or WordPress. */ public static function get_platform() { if ( is_multisite() ) { return 'wordpress-multisite'; } return 'wordpress'; // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText } /** * Sends survey trigger ID to the proxy. * * @since 1.35.0 * * @param Credentials $credentials Credentials instance. * @param string $access_token Access token. * @param string $trigger_id Token ID. * @return array|WP_Error Response of the wp_remote_post request. */ public function send_survey_trigger( Credentials $credentials, $access_token, $trigger_id ) { return $this->request( self::SURVEY_TRIGGER_URI, $credentials, array( 'access_token' => $access_token, 'json_request' => true, 'body' => array( 'trigger_context' => array( 'trigger_id' => $trigger_id, 'language' => get_user_locale(), ), ), ) ); } /** * Sends survey event to the proxy. * * @since 1.35.0 * * @param Credentials $credentials Credentials instance. * @param string $access_token Access token. * @param array|\stdClass $session Session object. * @param array|\stdClass $event Event object. * @return array|WP_Error Response of the wp_remote_post request. */ public function send_survey_event( Credentials $credentials, $access_token, $session, $event ) { return $this->request( self::SURVEY_EVENT_URI, $credentials, array( 'access_token' => $access_token, 'json_request' => true, 'body' => array( 'session' => $session, 'event' => $event, ), ) ); } } Authentication/Exception/Insufficient_Scopes_Exception.php 0000644 00000004040 14720521221 0020144 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Exception\Insufficient_Scopes_Exception * * @package Google\Site_Kit\Core\Authentication\Exception * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Exception; use Exception; use Google\Site_Kit\Core\Contracts\WP_Errorable; use WP_Error; /** * Exception thrown when authentication scopes are insufficient for a request. * * @since 1.9.0 * @access private * @ignore */ class Insufficient_Scopes_Exception extends Exception implements WP_Errorable { const WP_ERROR_CODE = 'missing_required_scopes'; /** * OAuth scopes that are required but not yet granted. * * @since 1.9.0 * * @var array */ protected $scopes = array(); /** * Constructor. * * @since 1.9.0 * * @param string $message Optional. Exception message. * @param int $code Optional. Exception code. * @param Throwable $previous Optional. Previous exception used for chaining. * @param array $scopes Optional. Scopes that are missing. */ public function __construct( $message = '', $code = 0, $previous = null, $scopes = array() ) { parent::__construct( $message, $code, $previous ); $this->set_scopes( $scopes ); } /** * Sets the missing scopes that raised this exception. * * @since 1.9.0 * * @param array $scopes OAuth scopes that are required but not yet granted. */ public function set_scopes( array $scopes ) { $this->scopes = $scopes; } /** * Gets the missing scopes that raised this exception. * * @since 1.9.0 * * @return array */ public function get_scopes() { return $this->scopes; } /** * Gets the WP_Error representation of this exception. * * @since 1.9.0 * * @return WP_Error */ public function to_wp_error() { return new WP_Error( static::WP_ERROR_CODE, $this->getMessage(), array( 'status' => 403, // Forbidden. 'scopes' => $this->scopes, ) ); } } Authentication/Exception/Missing_Verification_Exception.php 0000644 00000001111 14720521221 0020311 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Exception\Missing_Verification_Exception * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Exception; /** * Exception thrown when the a missing verification error is encountered when exchanging the site code. * * @since 1.48.0 * @access private * @ignore */ class Missing_Verification_Exception extends Exchange_Site_Code_Exception { } Authentication/Exception/Exchange_Site_Code_Exception.php 0000644 00000001023 14720521221 0017640 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Exception\Exchange_Site_Code_Exception * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Exception; use Exception; /** * Exception thrown when exchanging the site code fails. * * @since 1.48.0 * @access private * @ignore */ class Exchange_Site_Code_Exception extends Exception { } Authentication/Exception/Google_OAuth_Exception.php 0000644 00000001027 14720521221 0016520 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Exception\Google_OAuth_Exception * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Exception; use Exception; /** * Exception thrown when a Google OAuth response contains an OAuth error. * * @since 1.2.0 * @access private * @ignore */ class Google_OAuth_Exception extends Exception { } Authentication/Exception/Google_Proxy_Code_Exception.php 0000644 00000002751 14720521221 0017560 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Exception; use Exception; /** * Exception thrown when Google proxy returns an error accompanied with a temporary access code. * * @since 1.0.0 * @since 1.2.0 Renamed to Google_Proxy_Code_Exception. * @access private * @ignore */ class Google_Proxy_Code_Exception extends Exception { /** * Temporary code for an undelegated proxy token. * * @since 1.109.0 Explicitly declared; previously, it was dynamically declared. * * @var string */ protected $access_code; /** * Constructor. * * @since 1.0.0 * * @param string $message Optional. The exception message. Default empty string. * @param integer $code Optional. The numeric exception code. Default 0. * @param string $access_code Optional. Temporary code for an undelegated proxy token. Default empty string. */ public function __construct( $message = '', $code = 0, $access_code = '' ) { parent::__construct( $message, $code ); $this->access_code = $access_code; } /** * Gets the temporary access code for an undelegated proxy token. * * @since 1.0.0 * * @return string Temporary code. */ public function getAccessCode() { return $this->access_code; } } Authentication/Credentials.php 0000644 00000007456 14720521221 0012501 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Credentials * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\Setting; /** * Class representing the OAuth client ID and secret credentials. * * @since 1.0.0 * @access private * @ignore */ final class Credentials extends Setting { /** * Option key in options table. */ const OPTION = 'googlesitekit_credentials'; /** * Retrieves Site Kit credentials. * * @since 1.0.0 * * @return array|bool Value set for the credentials, or false if not set. */ public function get() { /** * Site Kit oAuth Secret is a JSON string of the Google Cloud Platform web application used for Site Kit * that will be associated with this account. This is meant to be a temporary way to specify the client secret * until the authentication proxy has been completed. This filter can be specified from a separate theme or plugin. * * To retrieve the JSON secret, use the following instructions: * - Go to the Google Cloud Platform and create a new project or use an existing one * - In the APIs & Services section, enable the APIs that are used within Site Kit * - Under 'credentials' either create new oAuth Client ID credentials or use an existing set of credentials * - Set the authorizes redirect URIs to be the URL to the oAuth callback for Site Kit, eg. https://<domainname>?oauth2callback=1 (this must be public) * - Click the 'Download JSON' button to download the JSON file that can be copied and pasted into the filter */ $credentials = apply_filters( 'googlesitekit_oauth_secret', '' ); if ( is_string( $credentials ) && trim( $credentials ) ) { $credentials = json_decode( $credentials, true ); } if ( isset( $credentials['web']['client_id'], $credentials['web']['client_secret'] ) ) { return $this->parse_defaults( array( 'oauth2_client_id' => $credentials['web']['client_id'], 'oauth2_client_secret' => $credentials['web']['client_secret'], ) ); } return $this->parse_defaults( $this->options->get( self::OPTION ) ); } /** * Checks whether Site Kit has been setup with client ID and secret. * * @since 1.0.0 * * @return bool True if credentials are set, false otherwise. */ public function has() { $credentials = (array) $this->get(); if ( ! empty( $credentials ) && ! empty( $credentials['oauth2_client_id'] ) && ! empty( $credentials['oauth2_client_secret'] ) ) { return true; } return false; } /** * Parses Credentials data and merges with its defaults. * * @since 1.0.0 * * @param mixed $data Credentials data. * @return array Parsed $data. */ private function parse_defaults( $data ) { $defaults = $this->get_default(); if ( ! is_array( $data ) ) { return $defaults; } return wp_parse_args( $data, $defaults ); } /** * Gets the default value. * * @since 1.2.0 * * @return array */ protected function get_default() { return array( 'oauth2_client_id' => '', 'oauth2_client_secret' => '', ); } /** * Determines whether the authentication proxy is used. * * In order to streamline the setup and authentication flow, the plugin uses a proxy mechanism based on an external * service. This can be overridden by providing actual GCP credentials with the {@see 'googlesitekit_oauth_secret'} * filter. * * @since 1.9.0 * * @return bool True if proxy authentication is used, false otherwise. */ public function using_proxy() { $creds = $this->get(); if ( ! $this->has() ) { return true; } return (bool) preg_match( '/\.apps\.sitekit\.withgoogle\.com$/', $creds['oauth2_client_id'] ); } } Authentication/REST_Authentication_Controller.php 0000644 00000012011 14720521221 0016242 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\REST_Authentication_Controller * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_REST_Server; use WP_REST_Request; use WP_REST_Response; /** * REST Authentication Controller Class. * * @since 1.131.0 * @access private * @ignore */ final class REST_Authentication_Controller { /** * Authentication instance. * * @since 1.131.0 * @var Authentication */ protected $authentication; /** * Constructor. * * @since 1.131.0 * * @param Authentication $authentication Authentication instance. */ public function __construct( Authentication $authentication ) { $this->authentication = $authentication; } /** * Registers functionality through WordPress hooks. * * @since 1.131.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $routes ) { $authentication_routes = array( '/' . REST_Routes::REST_ROOT . '/core/site/data/connection', '/' . REST_Routes::REST_ROOT . '/core/user/data/authentication', ); return array_merge( $routes, $authentication_routes ); } ); } /** * Gets related REST routes. * * @since 1.3.0 * @since 1.131.0 Moved to REST_Authentication_Controller class. * * @return array List of REST_Route objects. */ private function get_rest_routes() { $can_setup = function () { return current_user_can( Permissions::SETUP ); }; $can_access_authentication = function () { return current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ); }; $can_disconnect = function () { return current_user_can( Permissions::AUTHENTICATE ); }; $can_view_authenticated_dashboard = function () { return current_user_can( Permissions::VIEW_AUTHENTICATED_DASHBOARD ); }; return array( new REST_Route( 'core/site/data/connection', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { $data = array( 'connected' => $this->authentication->credentials()->has(), 'resettable' => $this->authentication->get_options_instance()->has( Credentials::OPTION ), 'setupCompleted' => $this->authentication->is_setup_completed(), 'hasConnectedAdmins' => $this->authentication->get_has_connected_admins_instance()->get(), 'hasMultipleAdmins' => $this->authentication->get_has_multiple_admins_instance()->get(), 'ownerID' => $this->authentication->get_owner_id_instance()->get(), ); return new WP_REST_Response( $data ); }, 'permission_callback' => $can_setup, ), ) ), new REST_Route( 'core/user/data/authentication', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { $oauth_client = $this->authentication->get_oauth_client(); $is_authenticated = $this->authentication->is_authenticated(); $data = array( 'authenticated' => $is_authenticated, 'requiredScopes' => $oauth_client->get_required_scopes(), 'grantedScopes' => $is_authenticated ? $oauth_client->get_granted_scopes() : array(), 'unsatisfiedScopes' => $is_authenticated ? $oauth_client->get_unsatisfied_scopes() : array(), 'needsReauthentication' => $oauth_client->needs_reauthentication(), 'disconnectedReason' => $this->authentication->get_disconnected_reason_instance()->get(), 'connectedProxyURL' => $this->authentication->get_connected_proxy_url_instance()->get(), ); return new WP_REST_Response( $data ); }, 'permission_callback' => $can_access_authentication, ), ) ), new REST_Route( 'core/user/data/disconnect', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function () { $this->authentication->disconnect(); return new WP_REST_Response( true ); }, 'permission_callback' => $can_disconnect, ), ) ), new REST_Route( 'core/user/data/get-token', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function () { $this->authentication->do_refresh_user_token(); return new WP_REST_Response( array( 'token' => $this->authentication->get_oauth_client()->get_access_token(), ) ); }, 'permission_callback' => $can_view_authenticated_dashboard, ), ) ), ); } } Authentication/Token.php 0000644 00000007440 14720521221 0011315 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Token * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Storage\Encrypted_User_Options; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; /** * Class representing the OAuth token for a user. * * This includes the access token, its creation and expiration data, and the refresh token. * This class is compatible with `Google\Site_Kit\Core\Storage\User_Setting`, as it should in the future be adjusted * so that the four pieces of data become a single user setting. * * @since 1.39.0 * @access private * @ignore */ final class Token { /** * User_Options instance. * * @since 1.39.0 * @var User_Options */ protected $user_options; /** * Encrypted_User_Options instance. * * @since 1.39.0 * @var Encrypted_User_Options */ private $encrypted_user_options; /** * Constructor. * * @since 1.39.0 * * @param User_Options $user_options User_Options instance. */ public function __construct( User_Options $user_options ) { $this->user_options = $user_options; $this->encrypted_user_options = new Encrypted_User_Options( $this->user_options ); } /** * Checks whether or not the setting exists. * * @since 1.39.0 * * @return bool True on success, false on failure. */ public function has() { if ( ! $this->get() ) { return false; } return true; } /** * Gets the value of the setting. * * @since 1.39.0 * * @return mixed Value set for the option, or default if not set. */ public function get() { $access_token = $this->encrypted_user_options->get( OAuth_Client::OPTION_ACCESS_TOKEN ); if ( empty( $access_token ) ) { return array(); } $token = array( 'access_token' => $access_token, 'expires_in' => (int) $this->user_options->get( OAuth_Client::OPTION_ACCESS_TOKEN_EXPIRES_IN ), 'created' => (int) $this->user_options->get( OAuth_Client::OPTION_ACCESS_TOKEN_CREATED ), ); $refresh_token = $this->encrypted_user_options->get( OAuth_Client::OPTION_REFRESH_TOKEN ); if ( ! empty( $refresh_token ) ) { $token['refresh_token'] = $refresh_token; } return $token; } /** * Sets the value of the setting with the given value. * * @since 1.39.0 * * @param mixed $value Setting value. Must be serializable if non-scalar. * * @return bool True on success, false on failure. */ public function set( $value ) { if ( empty( $value['access_token'] ) ) { return false; } // Use reasonable defaults for these fields. if ( empty( $value['expires_in'] ) ) { $value['expires_in'] = HOUR_IN_SECONDS; } if ( empty( $value['created'] ) ) { $value['created'] = time(); } $this->encrypted_user_options->set( OAuth_Client::OPTION_ACCESS_TOKEN, $value['access_token'] ); $this->user_options->set( OAuth_Client::OPTION_ACCESS_TOKEN_EXPIRES_IN, $value['expires_in'] ); $this->user_options->set( OAuth_Client::OPTION_ACCESS_TOKEN_CREATED, $value['created'] ); if ( ! empty( $value['refresh_token'] ) ) { $this->encrypted_user_options->set( OAuth_Client::OPTION_REFRESH_TOKEN, $value['refresh_token'] ); } return true; } /** * Deletes the setting. * * @since 1.39.0 * * @return bool True on success, false on failure. */ public function delete() { $this->user_options->delete( OAuth_Client::OPTION_ACCESS_TOKEN ); $this->user_options->delete( OAuth_Client::OPTION_ACCESS_TOKEN_EXPIRES_IN ); $this->user_options->delete( OAuth_Client::OPTION_ACCESS_TOKEN_CREATED ); $this->user_options->delete( OAuth_Client::OPTION_REFRESH_TOKEN ); return true; } } Authentication/Has_Connected_Admins.php 0000644 00000006462 14720521221 0014230 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Has_Connected_Admins * * @package Google\Site_Kit\Core\Authentication * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Storage\Options_Interface; use Google\Site_Kit\Core\Storage\Setting; use Google\Site_Kit\Core\Storage\User_Options_Interface; use WP_User; /** * Has_Connected_Admins class. * * @since 1.14.0 * @access private * @ignore */ class Has_Connected_Admins extends Setting { /** * The option_name for this setting. */ const OPTION = 'googlesitekit_has_connected_admins'; /** * User options instance implementing User_Options_Interface. * * @since 1.14.0 * @var User_Options_Interface */ protected $user_options; /** * Constructor. * * @since 1.14.0 * * @param Options_Interface $options Options instance. * @param User_Options_Interface $user_options User options instance. */ public function __construct( Options_Interface $options, User_Options_Interface $user_options ) { parent::__construct( $options ); $this->user_options = $user_options; } /** * Registers the setting in WordPress. * * @since 1.14.0 */ public function register() { parent::register(); $access_token_meta_key = $this->user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN ); add_action( 'added_user_meta', function ( $mid, $uid, $meta_key ) use ( $access_token_meta_key ) { // phpcs:ignore WordPress.WP.Capabilities.RoleFound if ( $meta_key === $access_token_meta_key && user_can( $uid, 'administrator' ) ) { $this->set( true ); } }, 10, 3 ); add_action( 'deleted_user_meta', function ( $mid, $uid, $meta_key ) use ( $access_token_meta_key ) { if ( $meta_key === $access_token_meta_key ) { $this->delete(); } }, 10, 3 ); } /** * Gets the value of the setting. If the option is not set yet, it pulls connected * admins from the database and sets the option. * * @since 1.14.0 * * @return boolean TRUE if the site kit already has connected admins, otherwise FALSE. */ public function get() { // If the option doesn't exist, query the fresh value, set it and return it. if ( ! $this->has() ) { $users = $this->query_connected_admins(); $has_connected_admins = count( $users ) > 0; $this->set( (int) $has_connected_admins ); return $has_connected_admins; } return (bool) parent::get(); } /** * Queries connected admins and returns an array of connected admin IDs. * * @since 1.14.0 * * @return array The array of connected admin IDs. */ protected function query_connected_admins() { return get_users( array( 'meta_key' => $this->user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_compare' => 'EXISTS', 'role' => 'administrator', 'number' => 1, 'fields' => 'ID', ) ); } /** * Gets the expected value type. * * @since 1.14.0 * * @return string The type name. */ protected function get_type() { return 'boolean'; } } Authentication/Owner_ID.php 0000644 00000003026 14720521221 0011677 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Owner_ID * * @package Google\Site_Kit\Core\Authentication * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\Setting; /** * Owner_ID class. * * @since 1.16.0 * @access private * @ignore */ class Owner_ID extends Setting { /** * The option_name for this setting. */ const OPTION = 'googlesitekit_owner_id'; /** * Gets the value of the setting. * * @since 1.16.0 * * @return mixed Value set for the option, or registered default if not set. */ public function get() { return (int) parent::get(); } /** * Gets the expected value type. * * @since 1.16.0 * * @return string The type name. */ protected function get_type() { return 'integer'; } /** * Gets the default value. * * We use the old "googlesitekit_first_admin" option here as it used to store the ID * of the first admin user to use the plugin. If this option doesn't exist, it will return 0. * * @since 1.16.0 * * @return int The default value. */ protected function get_default() { return (int) $this->options->get( 'googlesitekit_first_admin' ); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.16.0 * * @return callable The callable sanitize callback. */ protected function get_sanitize_callback() { return 'intval'; } } Authentication/Initial_Version.php 0000644 00000001171 14720521221 0013326 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Initial_Version * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class representing the initial Site Kit version the user started with. * * @since 1.25.0 * @access private * @ignore */ final class Initial_Version extends User_Setting { /** * User option key. */ const OPTION = 'googlesitekitpersistent_initial_version'; } Authentication/Clients/OAuth_Client_Base.php 0000644 00000026300 14720521221 0015102 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Clients\OAuth_Client_Base * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Clients; use Exception; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Credentials; use Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit\Core\Authentication\Profile; use Google\Site_Kit\Core\Authentication\Token; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\Encrypted_Options; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; /** * Base class for connecting to Google APIs via OAuth. * * @since 1.39.0 * @access private * @ignore */ abstract class OAuth_Client_Base { const OPTION_ACCESS_TOKEN = 'googlesitekit_access_token'; const OPTION_ACCESS_TOKEN_EXPIRES_IN = 'googlesitekit_access_token_expires_in'; const OPTION_ACCESS_TOKEN_CREATED = 'googlesitekit_access_token_created_at'; const OPTION_REFRESH_TOKEN = 'googlesitekit_refresh_token'; const OPTION_AUTH_SCOPES = 'googlesitekit_auth_scopes'; const OPTION_ERROR_CODE = 'googlesitekit_error_code'; const OPTION_PROXY_ACCESS_CODE = 'googlesitekit_proxy_access_code'; /** * Plugin context. * * @since 1.39.0 * @var Context */ protected $context; /** * Options instance * * @since 1.39.0 * @var Options */ protected $options; /** * User_Options instance * * @since 1.39.0 * @var User_Options */ protected $user_options; /** * OAuth credentials instance. * * @since 1.39.0 * @var Credentials */ protected $credentials; /** * Google_Proxy instance. * * @since 1.39.0 * @var Google_Proxy */ protected $google_proxy; /** * Google Client object. * * @since 1.39.0 * @var Google_Site_Kit_Client */ protected $google_client; /** * Profile instance. * * @since 1.39.0 * @var Profile */ protected $profile; /** * Token instance. * * @since 1.39.0 * @var Token */ protected $token; /** * Constructor. * * @since 1.39.0 * * @param Context $context Plugin context. * @param Options $options Optional. Option API instance. Default is a new instance. * @param User_Options $user_options Optional. User Option API instance. Default is a new instance. * @param Credentials $credentials Optional. Credentials instance. Default is a new instance from $options. * @param Google_Proxy $google_proxy Optional. Google proxy instance. Default is a new instance. * @param Profile $profile Optional. Profile instance. Default is a new instance. * @param Token $token Optional. Token instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null, User_Options $user_options = null, Credentials $credentials = null, Google_Proxy $google_proxy = null, Profile $profile = null, Token $token = null ) { $this->context = $context; $this->options = $options ?: new Options( $this->context ); $this->user_options = $user_options ?: new User_Options( $this->context ); $this->credentials = $credentials ?: new Credentials( new Encrypted_Options( $this->options ) ); $this->google_proxy = $google_proxy ?: new Google_Proxy( $this->context ); $this->profile = $profile ?: new Profile( $this->user_options ); $this->token = $token ?: new Token( $this->user_options ); } /** * Gets the Google client object. * * @since 1.39.0 * @since 1.2.0 Now always returns a Google_Site_Kit_Client. * * @return Google_Site_Kit_Client Google client object. */ public function get_client() { if ( ! $this->google_client instanceof Google_Site_Kit_Client ) { $credentials = $this->credentials->get(); $this->google_client = Client_Factory::create_client( array( 'client_id' => $credentials['oauth2_client_id'], 'client_secret' => $credentials['oauth2_client_secret'], 'redirect_uri' => $this->get_redirect_uri(), 'token' => $this->get_token(), 'token_callback' => array( $this, 'set_token' ), 'token_exception_callback' => function ( Exception $e ) { $this->handle_fetch_token_exception( $e ); }, 'required_scopes' => $this->get_required_scopes(), 'login_hint_email' => $this->profile->has() ? $this->profile->get()['email'] : '', 'using_proxy' => $this->credentials->using_proxy(), 'proxy_url' => $this->google_proxy->url(), ) ); } return $this->google_client; } /** * Gets the list of currently required Google OAuth scopes. * * @since 1.39.0 * @see https://developers.google.com/identity/protocols/googlescopes * * @return array List of Google OAuth scopes. */ public function get_required_scopes() { /** * Filters the list of required Google OAuth scopes. * * See all Google oauth scopes here: https://developers.google.com/identity/protocols/googlescopes * * @since 1.39.0 * * @param array $scopes List of scopes. */ $scopes = (array) apply_filters( 'googlesitekit_auth_scopes', array() ); return array_unique( array_merge( // Default scopes that are always required. array( 'openid', 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email', ), $scopes ) ); } /** * Gets the list of currently granted Google OAuth scopes for the current user. * * @since 1.39.0 * @see https://developers.google.com/identity/protocols/googlescopes * * @return string[] List of Google OAuth scopes. */ public function get_granted_scopes() { return $this->user_options->get( self::OPTION_AUTH_SCOPES ) ?: array(); } /** * Sets the list of currently granted Google OAuth scopes for the current user. * * @since 1.39.0 * @see https://developers.google.com/identity/protocols/googlescopes * * @param string[] $scopes List of Google OAuth scopes. */ public function set_granted_scopes( $scopes ) { $required_scopes = $this->get_required_scopes(); $scopes = array_values( array_unique( array_intersect( $scopes, $required_scopes ) ) ); $this->user_options->set( self::OPTION_AUTH_SCOPES, $scopes ); } /** * Gets the current user's full OAuth token data, including access token and optional refresh token. * * @since 1.39.0 * * @return array Associative array with 'access_token', 'expires_in', 'created', and 'refresh_token' keys, or empty * array if no token available. */ public function get_token() { return $this->token->get(); } /** * Sets the current user's full OAuth token data, including access token and optional refresh token. * * @since 1.39.0 * * @param array $token { * Full token data, optionally including the refresh token. * * @type string $access_token Required. The access token. * @type int $expires_in Number of seconds in which the token expires. Default 3600 (1 hour). * @type int $created Timestamp in seconds when the token was created. Default is the current time. * @type string $refresh_token The refresh token, if relevant. If passed, it is set as well. * } * @return bool True on success, false on failure. */ public function set_token( array $token ) { // Remove the error code from the user options so it doesn't // appear again. $this->user_options->delete( OAuth_Client::OPTION_ERROR_CODE ); return $this->token->set( $token ); } /** * Deletes the current user's token and all associated data. * * @since 1.0.3 */ protected function delete_token() { $this->token->delete(); $this->user_options->delete( self::OPTION_AUTH_SCOPES ); } /** * Converts the given error code to a user-facing message. * * @since 1.39.0 * * @param string $error_code Error code. * @return string Error message. */ public function get_error_message( $error_code ) { switch ( $error_code ) { case 'access_denied': return __( 'Setup was interrupted because you did not grant the necessary permissions.', 'google-site-kit' ); case 'access_token_not_received': return __( 'Unable to receive access token because of an unknown error.', 'google-site-kit' ); case 'cannot_log_in': return __( 'Internal error that the Google login redirect failed.', 'google-site-kit' ); case 'invalid_client': return __( 'Unable to receive access token because of an invalid client.', 'google-site-kit' ); case 'invalid_code': return __( 'Unable to receive access token because of an empty authorization code.', 'google-site-kit' ); case 'invalid_grant': return __( 'Unable to receive access token because of an invalid authorization code or refresh token.', 'google-site-kit' ); case 'invalid_request': return __( 'Unable to receive access token because of an invalid OAuth request.', 'google-site-kit' ); case 'missing_delegation_consent': return __( 'Looks like your site is not allowed access to Google account data and can’t display stats in the dashboard.', 'google-site-kit' ); case 'missing_search_console_property': return __( 'Looks like there is no Search Console property for your site.', 'google-site-kit' ); case 'missing_verification': return __( 'Looks like the verification token for your site is missing.', 'google-site-kit' ); case 'oauth_credentials_not_exist': return __( 'Unable to authenticate Site Kit, as no client credentials exist.', 'google-site-kit' ); case 'refresh_token_not_exist': return __( 'Unable to refresh access token, as no refresh token exists.', 'google-site-kit' ); case 'unauthorized_client': return __( 'Unable to receive access token because of an unauthorized client.', 'google-site-kit' ); case 'unsupported_grant_type': return __( 'Unable to receive access token because of an unsupported grant type.', 'google-site-kit' ); default: /* translators: %s: error code from API */ return sprintf( __( 'Unknown Error (code: %s).', 'google-site-kit' ), $error_code ); } } /** * Handles an exception thrown when fetching an access token. * * @since 1.2.0 * * @param Exception $e Exception thrown. */ protected function handle_fetch_token_exception( Exception $e ) { $error_code = $e->getMessage(); // Revoke and delete user connection data on 'invalid_grant'. // This typically happens during refresh if the refresh token is invalid or expired. if ( 'invalid_grant' === $error_code ) { $this->delete_token(); } $this->user_options->set( self::OPTION_ERROR_CODE, $error_code ); if ( $e instanceof Google_Proxy_Code_Exception ) { $this->user_options->set( self::OPTION_PROXY_ACCESS_CODE, $e->getAccessCode() ); } } /** * Gets the OAuth redirect URI that listens to the callback request. * * @since 1.39.0 * * @return string OAuth redirect URI. */ protected function get_redirect_uri() { return add_query_arg( 'oauth2callback', '1', admin_url( 'index.php' ) ); } } Authentication/Clients/Google_Site_Kit_Proxy_Client.php 0000644 00000007614 14720521221 0017347 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Clients\Google_Site_Kit_Proxy_Client * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Clients; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit\Core\Authentication\Clients\OAuth2; use Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception; use Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpHandlerFactory; use Google\Site_Kit_Dependencies\GuzzleHttp\Psr7; use Google\Site_Kit_Dependencies\GuzzleHttp\Psr7\Request; use Exception; /** * Modified Google Site Kit API client relying on the authentication proxy. * * @since 1.0.0 * @since 1.2.0 Renamed to Google_Site_Kit_Proxy_Client. * @access private * @ignore */ class Google_Site_Kit_Proxy_Client extends Google_Site_Kit_Client { /** * Base URL to the proxy. * * @since 1.1.2 * @var string */ protected $proxy_base_path = Google_Proxy::PRODUCTION_BASE_URL; /** * Construct the Google client. * * @since 1.1.2 * * @param array $config Proxy client configuration. */ public function __construct( array $config = array() ) { if ( ! empty( $config['proxy_base_path'] ) ) { $this->setProxyBasePath( $config['proxy_base_path'] ); } unset( $config['proxy_base_path'] ); parent::__construct( $config ); $this->setApplicationName( Google_Proxy::get_application_name() ); } /** * Sets the base URL to the proxy. * * @since 1.2.0 * * @param string $base_path Proxy base URL. */ public function setProxyBasePath( $base_path ) { $this->proxy_base_path = untrailingslashit( $base_path ); } /** * Revokes an OAuth2 access token using the authentication proxy. * * @since 1.0.0 * * @param string|array|null $token Optional. Access token. Default is the current one. * @return bool True on success, false on failure. */ public function revokeToken( $token = null ) { if ( ! $token ) { $token = $this->getAccessToken(); } if ( is_array( $token ) ) { $token = $token['access_token']; } $body = Psr7\stream_for( http_build_query( array( 'client_id' => $this->getClientId(), 'token' => $token, ) ) ); $request = new Request( 'POST', $this->proxy_base_path . Google_Proxy::OAUTH2_REVOKE_URI, array( 'Cache-Control' => 'no-store', 'Content-Type' => 'application/x-www-form-urlencoded', ), $body ); $http_handler = HttpHandlerFactory::build( $this->getHttpClient() ); $response = $http_handler( $request ); return 200 === (int) $response->getStatusCode(); } /** * Creates a Google auth object for the authentication proxy. * * @since 1.0.0 */ protected function createOAuth2Service() { return new OAuth2( array( 'clientId' => $this->getClientId(), 'clientSecret' => $this->getClientSecret(), 'authorizationUri' => $this->proxy_base_path . Google_Proxy::OAUTH2_AUTH_URI, 'tokenCredentialUri' => $this->proxy_base_path . Google_Proxy::OAUTH2_TOKEN_URI, 'redirectUri' => $this->getRedirectUri(), 'issuer' => $this->getClientId(), 'signingKey' => null, 'signingAlgorithm' => null, ) ); } /** * Handles an erroneous response from a request to fetch an auth token. * * @since 1.2.0 * * @param string $error Error code / error message. * @param array $data Associative array of full response data. * * @throws Google_Proxy_Code_Exception Thrown when proxy returns an error accompanied by a temporary access code. */ protected function handleAuthTokenErrorResponse( $error, array $data ) { if ( ! empty( $data['code'] ) ) { throw new Google_Proxy_Code_Exception( $error, 0, $data['code'] ); } parent::handleAuthTokenErrorResponse( $error, $data ); } } Authentication/Clients/OAuth2.php 0000644 00000002660 14720521221 0012737 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Clients\OAuth2 * * @package Google\Site_Kit * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Clients; use Google\Site_Kit_Dependencies\Google\Auth\OAuth2 as Google_Service_OAuth2; use Google\Site_Kit_Dependencies\GuzzleHttp\Psr7\Utils; use Google\Site_Kit_Dependencies\GuzzleHttp\Psr7\Query; use Google\Site_Kit_Dependencies\Psr\Http\Message\RequestInterface; /** * Class for connecting to Google APIs via OAuth2. * * @since 1.87.0 * @access private * @ignore */ class OAuth2 extends Google_Service_OAuth2 { /** * Overrides generateCredentialsRequest with additional parameters. * * @since 1.87.0 * * @param array $extra_params Optional. Array of extra parameters to fetch with. * @return RequestInterface Token credentials request. */ public function generateCredentialsRequest( $extra_params = array() ) { $request = parent::generateCredentialsRequest(); $grant_type = $this->getGrantType(); if ( empty( $extra_params ) || 'refresh_token' !== $grant_type ) { return $request; } $params = array( 'body' => Query::build( array_merge( Query::parse( Utils::copyToString( $request->getBody() ) ), $extra_params ) ), ); return Utils::modifyRequest( $request, $params ); } } Authentication/Clients/OAuth_Client.php 0000644 00000052356 14720521221 0014162 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Clients\OAuth_Client * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Clients; use Exception; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Credentials; use Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit\Core\Authentication\Owner_ID; use Google\Site_Kit\Core\Authentication\Profile; use Google\Site_Kit\Core\Authentication\Token; use Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics\Activity_Metrics; use Google\Site_Kit\Core\Dashboard_Sharing\Activity_Metrics\Active_Consumers; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Util\Scopes; use Google\Site_Kit\Core\Util\URL; use Google\Site_Kit_Dependencies\Google\Service\PeopleService as Google_Service_PeopleService; use WP_User; /** * Class for connecting to Google APIs via OAuth. * * @since 1.0.0 * @since 1.39.0 Now extends `OAuth_Client_Base`. * @access private * @ignore */ final class OAuth_Client extends OAuth_Client_Base { const OPTION_ADDITIONAL_AUTH_SCOPES = 'googlesitekit_additional_auth_scopes'; const OPTION_REDIRECT_URL = 'googlesitekit_redirect_url'; const OPTION_ERROR_REDIRECT_URL = 'googlesitekit_error_redirect_url'; const CRON_REFRESH_PROFILE_DATA = 'googlesitekit_cron_refresh_profile_data'; /** * Owner_ID instance. * * @since 1.16.0 * @var Owner_ID */ private $owner_id; /** * Activity_Metrics instance. * * @since 1.87.0 * @var Activity_Metrics */ private $activity_metrics; /** * Active_Consumers instance. * * @since 1.87.0 * @var Active_Consumers */ private $active_consumers; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. * @param Options $options Optional. Option API instance. Default is a new instance. * @param User_Options $user_options Optional. User Option API instance. Default is a new instance. * @param Credentials $credentials Optional. Credentials instance. Default is a new instance from $options. * @param Google_Proxy $google_proxy Optional. Google proxy instance. Default is a new instance. * @param Profile $profile Optional. Profile instance. Default is a new instance. * @param Token $token Optional. Token instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null, User_Options $user_options = null, Credentials $credentials = null, Google_Proxy $google_proxy = null, Profile $profile = null, Token $token = null ) { parent::__construct( $context, $options, $user_options, $credentials, $google_proxy, $profile, $token ); $this->owner_id = new Owner_ID( $this->options ); $this->activity_metrics = new Activity_Metrics( $this->context, $this->user_options ); $this->active_consumers = new Active_Consumers( $this->user_options ); } /** * Refreshes the access token. * * While this method can be used to explicitly refresh the current access token, the preferred way * should be to rely on the Google_Site_Kit_Client to do that automatically whenever the current access token * has expired. * * @since 1.0.0 */ public function refresh_token() { $token = $this->get_token(); if ( empty( $token['refresh_token'] ) ) { $this->delete_token(); $this->user_options->set( self::OPTION_ERROR_CODE, 'refresh_token_not_exist' ); return; } $active_consumers = $this->activity_metrics->get_for_refresh_token(); try { $token_response = $this->get_client()->fetchAccessTokenWithRefreshToken( $token['refresh_token'], $active_consumers ); } catch ( \Exception $e ) { $this->handle_fetch_token_exception( $e ); return; } if ( ! isset( $token_response['access_token'] ) ) { $this->user_options->set( self::OPTION_ERROR_CODE, 'access_token_not_received' ); return; } $this->active_consumers->delete(); $this->set_token( $token_response ); } /** * Revokes the access token. * * @since 1.0.0 */ public function revoke_token() { try { $this->get_client()->revokeToken(); } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement // No special handling, we just need to make sure this goes through. } $this->delete_token(); } /** * Gets the list of currently granted Google OAuth scopes for the current user. * * @since 1.0.0 * @see https://developers.google.com/identity/protocols/googlescopes * * @return string[] List of Google OAuth scopes. */ public function get_granted_scopes() { $base_scopes = parent::get_granted_scopes(); $extra_scopes = $this->get_granted_additional_scopes(); return array_unique( array_merge( $base_scopes, $extra_scopes ) ); } /** * Gets the list of currently granted additional Google OAuth scopes for the current user. * * Scopes are considered "additional scopes" if they were granted to perform a specific action, * rather than being granted as an overall required scope. * * @since 1.9.0 * @see https://developers.google.com/identity/protocols/googlescopes * * @return string[] List of Google OAuth scopes. */ public function get_granted_additional_scopes() { return array_values( $this->user_options->get( self::OPTION_ADDITIONAL_AUTH_SCOPES ) ?: array() ); } /** * Checks if new scopes are required that are not yet granted for the current user. * * @since 1.9.0 * * @return bool true if any required scopes are not satisfied, otherwise false. */ public function needs_reauthentication() { if ( ! $this->token->has() ) { return false; } return ! $this->has_sufficient_scopes(); } /** * Gets the list of scopes which are not satisfied by the currently granted scopes. * * @since 1.9.0 * * @param string[] $scopes Optional. List of scopes to test against granted scopes. * Default is the list of required scopes. * @return string[] Filtered $scopes list, only including scopes that are not satisfied. */ public function get_unsatisfied_scopes( array $scopes = null ) { if ( null === $scopes ) { $scopes = $this->get_required_scopes(); } $granted_scopes = $this->get_granted_scopes(); $unsatisfied_scopes = array_filter( $scopes, function ( $scope ) use ( $granted_scopes ) { return ! Scopes::is_satisfied_by( $scope, $granted_scopes ); } ); return array_values( $unsatisfied_scopes ); } /** * Checks whether or not currently granted scopes are sufficient for the given list. * * @since 1.9.0 * * @param string[] $scopes Optional. List of scopes to test against granted scopes. * Default is the list of required scopes. * @return bool True if all $scopes are satisfied, false otherwise. */ public function has_sufficient_scopes( array $scopes = null ) { if ( null === $scopes ) { $scopes = $this->get_required_scopes(); } return Scopes::are_satisfied_by( $scopes, $this->get_granted_scopes() ); } /** * Sets the list of currently granted Google OAuth scopes for the current user. * * @since 1.0.0 * @see https://developers.google.com/identity/protocols/googlescopes * * @param string[] $scopes List of Google OAuth scopes. */ public function set_granted_scopes( $scopes ) { $required_scopes = $this->get_required_scopes(); $base_scopes = array(); $extra_scopes = array(); foreach ( $scopes as $scope ) { if ( in_array( $scope, $required_scopes, true ) ) { $base_scopes[] = $scope; } else { $extra_scopes[] = $scope; } } parent::set_granted_scopes( $base_scopes ); $this->user_options->set( self::OPTION_ADDITIONAL_AUTH_SCOPES, $extra_scopes ); } /** * Gets the current user's OAuth access token. * * @since 1.0.0 * * @return string|bool Access token if it exists, false otherwise. */ public function get_access_token() { $token = $this->get_token(); if ( empty( $token['access_token'] ) ) { return false; } return $token['access_token']; } /** * Sets the current user's OAuth access token. * * @since 1.0.0 * @deprecated 1.39.0 Use `OAuth_Client::set_token` instead. * * @param string $access_token New access token. * @param int $expires_in TTL of the access token in seconds. * @param int $created Optional. Timestamp when the token was created, in GMT. Default is the current time. * @return bool True on success, false on failure. */ public function set_access_token( $access_token, $expires_in, $created = 0 ) { _deprecated_function( __METHOD__, '1.39.0', self::class . '::set_token' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped return $this->set_token( array( 'access_token' => $access_token, 'expires_in' => $expires_in, 'created' => $created, ) ); } /** * Gets the current user's OAuth refresh token. * * @since 1.0.0 * @deprecated 1.39.0 Use `OAuth_Client::get_token` instead. * * @return string|bool Refresh token if it exists, false otherwise. */ public function get_refresh_token() { _deprecated_function( __METHOD__, '1.39.0', self::class . '::get_token' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $token = $this->get_token(); if ( empty( $token['refresh_token'] ) ) { return false; } return $token['refresh_token']; } /** * Sets the current user's OAuth refresh token. * * @since 1.0.0 * @deprecated 1.39.0 Use `OAuth_Client::set_token` instead. * * @param string $refresh_token New refresh token. * @return bool True on success, false on failure. */ public function set_refresh_token( $refresh_token ) { _deprecated_function( __METHOD__, '1.39.0', self::class . '::set_token' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $token = $this->get_token(); $token['refresh_token'] = $refresh_token; return $this->set_token( $token ); } /** * Gets the authentication URL. * * @since 1.0.0 * @since 1.9.0 Added $additional_scopes parameter. * @since 1.34.1 Updated handling of $additional_scopes to restore rewritten scope. * * @param string $redirect_url Redirect URL after authentication. * @param string $error_redirect_url Redirect URL after authentication error. * @param string[] $additional_scopes List of additional scopes to request. * @return string Authentication URL. */ public function get_authentication_url( $redirect_url = '', $error_redirect_url = '', $additional_scopes = array() ) { if ( empty( $redirect_url ) ) { $redirect_url = $this->context->admin_url( 'splash' ); } if ( is_array( $additional_scopes ) ) { // Rewrite each scope to convert `gttp` -> `http`, if it starts with this placeholder scheme. // This restores the original scope rewritten by getConnectURL. $additional_scopes = array_map( function ( $scope ) { return preg_replace( '/^gttp(s)?:/', 'http$1:', $scope ); }, $additional_scopes ); } else { $additional_scopes = array(); } $url_query = URL::parse( $redirect_url, PHP_URL_QUERY ); if ( $url_query ) { parse_str( $url_query, $query_args ); } if ( empty( $query_args['notification'] ) ) { $redirect_url = add_query_arg( array( 'notification' => 'authentication_success' ), $redirect_url ); } // Ensure we remove error query string. $redirect_url = remove_query_arg( 'error', $redirect_url ); $this->user_options->set( self::OPTION_REDIRECT_URL, $redirect_url ); $this->user_options->set( self::OPTION_ERROR_REDIRECT_URL, $error_redirect_url ); // Ensure the latest required scopes are requested. $scopes = array_merge( $this->get_required_scopes(), $additional_scopes ); $this->get_client()->setScopes( array_unique( $scopes ) ); return add_query_arg( $this->google_proxy->get_metadata_fields(), $this->get_client()->createAuthUrl() ); } /** * Redirects the current user to the Google OAuth consent screen, or processes a response from that consent * screen if present. * * @since 1.0.0 * @since 1.49.0 Uses the new `Google_Proxy::setup_url_v2` method when the `serviceSetupV2` feature flag is enabled. */ public function authorize_user() { $code = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'code' ) ?? '' ); $error_code = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'error' ) ?? '' ); // If the OAuth redirects with an error code, handle it. if ( ! empty( $error_code ) ) { $this->user_options->set( self::OPTION_ERROR_CODE, $error_code ); wp_safe_redirect( $this->authorize_user_redirect_url() ); exit(); } if ( ! $this->credentials->has() ) { $this->user_options->set( self::OPTION_ERROR_CODE, 'oauth_credentials_not_exist' ); wp_safe_redirect( $this->authorize_user_redirect_url() ); exit(); } try { $token_response = $this->get_client()->fetchAccessTokenWithAuthCode( $code ); } catch ( Google_Proxy_Code_Exception $e ) { // Redirect back to proxy immediately with the access code. $credentials = $this->credentials->get(); $params = array( 'code' => $e->getAccessCode(), 'site_id' => ! empty( $credentials['oauth2_client_id'] ) ? $credentials['oauth2_client_id'] : '', ); $params = $this->google_proxy->add_setup_step_from_error_code( $params, $e->getMessage() ); $url = $this->google_proxy->setup_url( $params ); wp_safe_redirect( $url ); exit(); } catch ( Exception $e ) { $this->handle_fetch_token_exception( $e ); wp_safe_redirect( $this->authorize_user_redirect_url() ); exit(); } if ( ! isset( $token_response['access_token'] ) ) { $this->user_options->set( self::OPTION_ERROR_CODE, 'access_token_not_received' ); wp_safe_redirect( $this->authorize_user_redirect_url() ); exit(); } // Update the access token and refresh token. $this->set_token( $token_response ); // Store the previously granted scopes for use in the action below before they're updated. $previous_scopes = $this->get_granted_scopes(); // Update granted scopes. if ( isset( $token_response['scope'] ) ) { $scopes = explode( ' ', sanitize_text_field( $token_response['scope'] ) ); } elseif ( $this->context->input()->filter( INPUT_GET, 'scope' ) ) { $scope = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'scope' ) ); $scopes = explode( ' ', $scope ); } else { $scopes = $this->get_required_scopes(); } $scopes = array_filter( $scopes, function ( $scope ) { if ( ! is_string( $scope ) ) { return false; } if ( in_array( $scope, array( 'openid', 'profile', 'email' ), true ) ) { return true; } return 0 === strpos( $scope, 'https://www.googleapis.com/auth/' ); } ); $this->set_granted_scopes( $scopes ); $this->refresh_profile_data( 2 * MINUTE_IN_SECONDS ); /** * Fires when the current user has just been authorized to access Google APIs. * * In other words, this action fires whenever Site Kit has just obtained a new set of access token and * refresh token for the current user, which may happen to set up the initial connection or to request * access to further scopes. * * @since 1.3.0 * @since 1.6.0 The $token_response parameter was added. * @since 1.30.0 The $scopes and $previous_scopes parameters were added. * * @param array $token_response Token response data. * @param string[] $scopes List of scopes. * @param string[] $previous_scopes List of previous scopes. */ do_action( 'googlesitekit_authorize_user', $token_response, $scopes, $previous_scopes ); // This must happen after googlesitekit_authorize_user as the permissions checks depend on // values set which affect the meta capability mapping. $current_user_id = get_current_user_id(); if ( $this->should_update_owner_id( $current_user_id ) ) { $this->owner_id->set( $current_user_id ); } $redirect_url = $this->user_options->get( self::OPTION_REDIRECT_URL ); if ( $redirect_url ) { $url_query = URL::parse( $redirect_url, PHP_URL_QUERY ); if ( $url_query ) { parse_str( $url_query, $query_args ); } $reauth = isset( $query_args['reAuth'] ) && 'true' === $query_args['reAuth']; if ( false === $reauth && empty( $query_args['notification'] ) ) { $redirect_url = add_query_arg( array( 'notification' => 'authentication_success' ), $redirect_url ); } $this->user_options->delete( self::OPTION_REDIRECT_URL ); $this->user_options->delete( self::OPTION_ERROR_REDIRECT_URL ); } else { // No redirect_url is set, use default page. $redirect_url = $this->context->admin_url( 'splash', array( 'notification' => 'authentication_success' ) ); } wp_safe_redirect( $redirect_url ); exit(); } /** * Fetches and updates the user profile data for the currently authenticated Google account. * * @since 1.1.4 * @since 1.13.0 Added $retry_after param, also made public. * * @param int $retry_after Optional. Number of seconds to retry data fetch if unsuccessful. */ public function refresh_profile_data( $retry_after = 0 ) { $client = $this->get_client(); $restore_defer = $client->withDefer( false ); try { $people_service = new Google_Service_PeopleService( $client ); $response = $people_service->people->get( 'people/me', array( 'personFields' => 'emailAddresses,photos,names' ) ); if ( isset( $response['emailAddresses'][0]['value'], $response['photos'][0]['url'], $response['names'][0]['displayName'] ) ) { $this->profile->set( array( 'email' => $response['emailAddresses'][0]['value'], 'photo' => $response['photos'][0]['url'], 'full_name' => $response['names'][0]['displayName'], 'last_updated' => time(), ) ); } // Clear any scheduled job to refresh this data later, if any. wp_clear_scheduled_hook( self::CRON_REFRESH_PROFILE_DATA, array( $this->user_options->get_user_id() ) ); } catch ( Exception $e ) { $retry_after = absint( $retry_after ); if ( $retry_after < 1 ) { return; } wp_schedule_single_event( time() + $retry_after, self::CRON_REFRESH_PROFILE_DATA, array( $this->user_options->get_user_id() ) ); } finally { $restore_defer(); } } /** * Determines whether the authentication proxy is used. * * In order to streamline the setup and authentication flow, the plugin uses a proxy mechanism based on an external * service. This can be overridden by providing actual GCP credentials with the {@see 'googlesitekit_oauth_secret'} * filter. * * @since 1.0.0 * @deprecated 1.9.0 * * @return bool True if proxy authentication is used, false otherwise. */ public function using_proxy() { _deprecated_function( __METHOD__, '1.9.0', Credentials::class . '::using_proxy' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped return $this->credentials->using_proxy(); } /** * Determines whether the current owner ID must be changed or not. * * @since 1.16.0 * * @param int $user_id Current user ID. * @return bool TRUE if owner needs to be changed, otherwise FALSE. */ private function should_update_owner_id( $user_id ) { $current_owner_id = $this->owner_id->get(); if ( $current_owner_id === $user_id ) { return false; } if ( ! empty( $current_owner_id ) && user_can( $current_owner_id, Permissions::MANAGE_OPTIONS ) ) { return false; } if ( ! user_can( $user_id, Permissions::MANAGE_OPTIONS ) ) { return false; } return true; } /** * Returns the permissions URL to the authentication proxy. * * This only returns a URL if the user already has an access token set. * * @since 1.0.0 * * @return string URL to the permissions page on the authentication proxy on success, * or empty string on failure. */ public function get_proxy_permissions_url() { $access_token = $this->get_access_token(); if ( empty( $access_token ) ) { return ''; } return $this->google_proxy->permissions_url( $this->credentials, array( 'token' => $access_token ) ); } /** * Deletes the current user's token and all associated data. * * @since 1.0.3 */ protected function delete_token() { parent::delete_token(); $this->user_options->delete( self::OPTION_REDIRECT_URL ); $this->user_options->delete( self::OPTION_ERROR_REDIRECT_URL ); $this->user_options->delete( self::OPTION_ADDITIONAL_AUTH_SCOPES ); } /** * Return the URL for the user to view the dashboard/splash * page based on their permissions. * * @since 1.77.0 */ private function authorize_user_redirect_url() { $error_redirect_url = $this->user_options->get( self::OPTION_ERROR_REDIRECT_URL ); if ( $error_redirect_url ) { $this->user_options->delete( self::OPTION_ERROR_REDIRECT_URL ); return $error_redirect_url; } return current_user_can( Permissions::VIEW_DASHBOARD ) ? $this->context->admin_url( 'dashboard' ) : $this->context->admin_url( 'splash' ); } /** * Adds a user to the active consumers list. * * @since 1.87.0 * * @param WP_User $user User object. */ public function add_active_consumer( WP_User $user ) { $this->active_consumers->add( $user->ID, $user->roles ); } } Authentication/Clients/Client_Factory.php 0000644 00000015012 14720521221 0014535 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Clients\Client_Factory * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Clients; use Exception; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit_Dependencies\GuzzleHttp\Client; use WP_HTTP_Proxy; /** * Class for creating Site Kit-specific Google_Client instances. * * @since 1.39.0 * @access private * @ignore */ final class Client_Factory { /** * Creates a new Google client instance for the given arguments. * * @since 1.39.0 * * @param array $args Associative array of arguments. * @return Google_Site_Kit_Client|Google_Site_Kit_Proxy_Client The created Google client instance. */ public static function create_client( array $args ) { $args = array_merge( array( 'client_id' => '', 'client_secret' => '', 'redirect_uri' => '', 'token' => array(), 'token_callback' => null, 'token_exception_callback' => null, 'required_scopes' => array(), 'login_hint_email' => '', 'using_proxy' => true, 'proxy_url' => Google_Proxy::PRODUCTION_BASE_URL, ), $args ); if ( $args['using_proxy'] ) { $client = new Google_Site_Kit_Proxy_Client( array( 'proxy_base_path' => $args['proxy_url'] ) ); } else { $client = new Google_Site_Kit_Client(); } // Enable exponential retries, try up to three times. $client->setConfig( 'retry', array( 'retries' => 3 ) ); $http_client = $client->getHttpClient(); $http_client_config = self::get_http_client_config( $http_client->getConfig() ); // In Guzzle 6+, the HTTP client is immutable, so only a new instance can be set. $client->setHttpClient( new Client( $http_client_config ) ); $auth_config = self::get_auth_config( $args['client_id'], $args['client_secret'], $args['redirect_uri'] ); if ( ! empty( $auth_config ) ) { try { $client->setAuthConfig( $auth_config ); } catch ( Exception $e ) { return $client; } } // Offline access so we can access the refresh token even when the user is logged out. $client->setAccessType( 'offline' ); $client->setPrompt( 'consent' ); $client->setRedirectUri( $args['redirect_uri'] ); $client->setScopes( (array) $args['required_scopes'] ); // Set the full token data. if ( ! empty( $args['token'] ) ) { $client->setAccessToken( $args['token'] ); } // Set the callback which is called when the client refreshes the access token on-the-fly. $token_callback = $args['token_callback']; if ( $token_callback ) { $client->setTokenCallback( function ( $cache_key, $access_token ) use ( $client, $token_callback ) { // The same token from this callback should also already be set in the client object, which is useful // to get the full token data, all of which needs to be saved. Just in case, if that is not the same, // we save the passed token only, relying on defaults for the other values. $token = $client->getAccessToken(); if ( $access_token !== $token['access_token'] ) { $token = array( 'access_token' => $access_token ); } $token_callback( $token ); } ); } // Set the callback which is called when refreshing the access token on-the-fly fails. $token_exception_callback = $args['token_exception_callback']; if ( ! empty( $token_exception_callback ) ) { $client->setTokenExceptionCallback( $token_exception_callback ); } if ( ! empty( $args['login_hint_email'] ) ) { $client->setLoginHint( $args['login_hint_email'] ); } return $client; } /** * Get HTTP client configuration. * * @since 1.115.0 * * @param array $config Initial configuration. * @return array The new HTTP client configuration. */ private static function get_http_client_config( $config ) { // Override the default user-agent for the Guzzle client. This is used for oauth/token requests. // By default this header uses the generic Guzzle client's user-agent and includes // Guzzle, cURL, and PHP versions as it is normally shared. // In our case however, the client is namespaced to be used by Site Kit only. $config['headers']['User-Agent'] = Google_Proxy::get_application_name(); /** This filter is documented in wp-includes/class-http.php */ $ssl_verify = apply_filters( 'https_ssl_verify', true, null ); // If SSL verification is enabled (default) use the SSL certificate bundle included with WP. if ( $ssl_verify ) { $config['verify'] = ABSPATH . WPINC . '/certificates/ca-bundle.crt'; } else { $config['verify'] = false; } // Configure the Google_Client's HTTP client to use the same HTTP proxy as WordPress HTTP, if set. $http_proxy = new WP_HTTP_Proxy(); if ( $http_proxy->is_enabled() ) { // See https://docs.guzzlephp.org/en/6.5/request-options.html#proxy for reference. $auth = $http_proxy->use_authentication() ? "{$http_proxy->authentication()}@" : ''; $config['proxy'] = "{$auth}{$http_proxy->host()}:{$http_proxy->port()}"; } /** * Filters the IP version to force hostname resolution with. * * @since 1.115.0 * * @param $force_ip_resolve null|string IP version to force. Default: null. */ $force_ip_resolve = apply_filters( 'googlesitekit_force_ip_resolve', null ); if ( in_array( $force_ip_resolve, array( null, 'v4', 'v6' ), true ) ) { $config['force_ip_resolve'] = $force_ip_resolve; } return $config; } /** * Returns the full OAuth credentials configuration data based on the given client ID and secret. * * @since 1.39.0 * * @param string $client_id OAuth client ID. * @param string $client_secret OAuth client secret. * @param string $redirect_uri OAuth redirect URI. * @return array Credentials data, or empty array if any of the given values is empty. */ private static function get_auth_config( $client_id, $client_secret, $redirect_uri ) { if ( ! $client_id || ! $client_secret || ! $redirect_uri ) { return array(); } return array( 'client_id' => $client_id, 'client_secret' => $client_secret, 'auth_uri' => 'https://accounts.google.com/o/oauth2/v2/auth', 'token_uri' => 'https://oauth2.googleapis.com/token', 'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs', 'redirect_uris' => array( $redirect_uri ), ); } } Authentication/Clients/Google_Site_Kit_Client.php 0000644 00000023120 14720521221 0016134 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Clients\Google_Site_Kit_Client * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Clients; use Google\Site_Kit\Core\Authentication\Clients\OAuth2; use Google\Site_Kit\Core\Authentication\Exception\Google_OAuth_Exception; use Google\Site_Kit_Dependencies\Google_Client; use Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpHandlerFactory; use Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpClientCache; use Google\Site_Kit_Dependencies\GuzzleHttp\ClientInterface; use Google\Site_Kit_Dependencies\Psr\Http\Message\RequestInterface; use Google\Site_Kit\Core\Util\URL; use Exception; use InvalidArgumentException; use LogicException; use WP_User; /** * Extended Google API client with custom functionality for Site Kit. * * @since 1.2.0 * @access private * @ignore */ class Google_Site_Kit_Client extends Google_Client { /** * Callback to pass a potential exception to while refreshing an access token. * * @since 1.2.0 * @var callable|null */ protected $token_exception_callback; /** * Construct the Google client. * * @since 1.2.0 * * @param array $config Client configuration. */ public function __construct( array $config = array() ) { if ( isset( $config['token_exception_callback'] ) ) { $this->setTokenExceptionCallback( $config['token_exception_callback'] ); } unset( $config['token_exception_callback'] ); parent::__construct( $config ); } /** * Sets the function to be called when fetching an access token results in an exception. * * @since 1.2.0 * * @param callable $exception_callback Function accepting an exception as single parameter. */ public function setTokenExceptionCallback( callable $exception_callback ) { $this->token_exception_callback = $exception_callback; } /** * Sets whether or not to return raw requests and returns a callback to reset to the previous value. * * @since 1.2.0 * * @param bool $defer Whether or not to return raw requests. * @return callable Callback function that resets to the original $defer value. */ public function withDefer( $defer ) { $orig_defer = $this->shouldDefer(); $this->setDefer( $defer ); // Return a function to restore the original refer value. return function () use ( $orig_defer ) { $this->setDefer( $orig_defer ); }; } /** * Adds auth listeners to the HTTP client based on the credentials set in the Google API Client object. * * @since 1.2.0 * * @param ClientInterface $http The HTTP client object. * @return ClientInterface The HTTP client object. * * @throws Exception Thrown when fetching a new access token via refresh token on-the-fly fails. */ public function authorize( ClientInterface $http = null ) { if ( $this->isUsingApplicationDefaultCredentials() ) { return parent::authorize( $http ); } $token = $this->getAccessToken(); if ( isset( $token['refresh_token'] ) && $this->isAccessTokenExpired() ) { $callback = $this->getConfig( 'token_callback' ); try { $token_response = $this->fetchAccessTokenWithRefreshToken( $token['refresh_token'] ); if ( $callback ) { // Due to original callback signature this can only accept the token itself. call_user_func( $callback, '', $token_response['access_token'] ); } } catch ( Exception $e ) { // Pass exception to special callback if provided. if ( $this->token_exception_callback ) { call_user_func( $this->token_exception_callback, $e ); } throw $e; } } return parent::authorize( $http ); } /** * Fetches an OAuth 2.0 access token by using a temporary code. * * @since 1.0.0 * @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client. * * @param string $code Temporary authorization code, or undelegated token code. * @return array Access token. * * @throws InvalidArgumentException Thrown when the passed code is empty. */ public function fetchAccessTokenWithAuthCode( $code ) { if ( strlen( $code ) === 0 ) { throw new InvalidArgumentException( 'Invalid code' ); } $auth = $this->getOAuth2Service(); $auth->setCode( $code ); $auth->setRedirectUri( $this->getRedirectUri() ); $http_handler = HttpHandlerFactory::build( $this->getHttpClient() ); $token_response = $this->fetchAuthToken( $auth, $http_handler ); if ( $token_response && isset( $token_response['access_token'] ) ) { $token_response['created'] = time(); $this->setAccessToken( $token_response ); } return $token_response; } /** * Fetches a fresh OAuth 2.0 access token by using a refresh token. * * @since 1.0.0 * @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client. * * @param string $refresh_token Optional. Refresh token. Unused here. * @param array $extra_params Optional. Array of extra parameters to fetch with. * @return array Access token. * * @throws LogicException Thrown when no refresh token is available. */ public function fetchAccessTokenWithRefreshToken( $refresh_token = null, $extra_params = array() ) { if ( null === $refresh_token ) { $refresh_token = $this->getRefreshToken(); if ( ! $refresh_token ) { throw new LogicException( 'refresh token must be passed in or set as part of setAccessToken' ); } } $this->getLogger()->info( 'OAuth2 access token refresh' ); $auth = $this->getOAuth2Service(); $auth->setRefreshToken( $refresh_token ); $http_handler = HttpHandlerFactory::build( $this->getHttpClient() ); $token_response = $this->fetchAuthToken( $auth, $http_handler, $extra_params ); if ( $token_response && isset( $token_response['access_token'] ) ) { $token_response['created'] = time(); if ( ! isset( $token_response['refresh_token'] ) ) { $token_response['refresh_token'] = $refresh_token; } $this->setAccessToken( $token_response ); /** * Fires when the current user has just been reauthorized to access Google APIs with a refreshed access token. * * In other words, this action fires whenever Site Kit has just obtained a new access token based on * the refresh token for the current user, which typically happens once every hour when using Site Kit, * since that is the lifetime of every access token. * * @since 1.25.0 * * @param array $token_response Token response data. */ do_action( 'googlesitekit_reauthorize_user', $token_response ); } return $token_response; } /** * Executes deferred HTTP requests. * * @since 1.38.0 * * @param RequestInterface $request Request object to execute. * @param string $expected_class Expected class to return. * @return object An object of the type of the expected class or Psr\Http\Message\ResponseInterface. */ public function execute( RequestInterface $request, $expected_class = null ) { $request = $request->withHeader( 'X-Goog-Quota-User', self::getQuotaUser() ); return parent::execute( $request, $expected_class ); } /** * Returns a string that uniquely identifies a user of the application. * * @since 1.38.0 * * @return string Unique user identifier. */ public static function getQuotaUser() { $user_id = get_current_user_id(); $url = get_home_url(); $scheme = URL::parse( $url, PHP_URL_SCHEME ); $host = URL::parse( $url, PHP_URL_HOST ); $path = URL::parse( $url, PHP_URL_PATH ); return "{$scheme}://{$user_id}@{$host}{$path}"; } /** * Fetches an OAuth 2.0 access token using a given auth object and HTTP handler. * * This method is used in place of {@see OAuth2::fetchAuthToken()}. * * @since 1.0.0 * @since 1.2.0 Ported from Google_Site_Kit_Proxy_Client. * * @param OAuth2 $auth OAuth2 instance. * @param callable|null $http_handler Optional. HTTP handler callback. Default null. * @param array $extra_params Optional. Array of extra parameters to fetch with. * @return array Access token. */ protected function fetchAuthToken( OAuth2 $auth, callable $http_handler = null, $extra_params = array() ) { if ( is_null( $http_handler ) ) { $http_handler = HttpHandlerFactory::build( HttpClientCache::getHttpClient() ); } $request = $auth->generateCredentialsRequest( $extra_params ); $response = $http_handler( $request ); $credentials = $auth->parseTokenResponse( $response ); if ( ! empty( $credentials['error'] ) ) { $this->handleAuthTokenErrorResponse( $credentials['error'], $credentials ); } $auth->updateToken( $credentials ); return $credentials; } /** * Handles an erroneous response from a request to fetch an auth token. * * @since 1.2.0 * * @param string $error Error code / error message. * @param array $data Associative array of full response data. * * @throws Google_OAuth_Exception Thrown with the given $error as message. */ protected function handleAuthTokenErrorResponse( $error, array $data ) { throw new Google_OAuth_Exception( $error ); } /** * Create a default Google OAuth2 object. * * @return OAuth2 Created OAuth2 instance. */ protected function createOAuth2Service() { $auth = new OAuth2( array( 'clientId' => $this->getClientId(), 'clientSecret' => $this->getClientSecret(), 'authorizationUri' => self::OAUTH2_AUTH_URL, 'tokenCredentialUri' => self::OAUTH2_TOKEN_URI, 'redirectUri' => $this->getRedirectUri(), 'issuer' => $this->getConfig( 'client_id' ), 'signingKey' => $this->getConfig( 'signing_key' ), 'signingAlgorithm' => $this->getConfig( 'signing_algorithm' ), ) ); return $auth; } } Authentication/Profile.php 0000644 00000003205 14720521221 0011630 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Profile * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\User_Options; /** * Class controlling the user's Google profile. * * @since 0.1.0 */ final class Profile { /** * Option key in options table. */ const OPTION = 'googlesitekit_profile'; /** * User_Options instance. * * @since 1.0.0 * @var User_Options */ private $user_options; /** * Constructor. * * @since 1.0.0 * * @param User_Options $user_options User_Options instance. */ public function __construct( User_Options $user_options ) { $this->user_options = $user_options; } /** * Retrieves user profile data. * * @since 1.0.0 * * @return array|bool Value set for the profile, or false if not set. */ public function get() { return $this->user_options->get( self::OPTION ); } /** * Saves user profile data. * * @since 1.0.0 * * @param array $data User profile data: email and photo. * @return bool True on success, false on failure. */ public function set( $data ) { return $this->user_options->set( self::OPTION, $data ); } /** * Verifies if user has their profile information stored. * * @since 1.0.0 * * @return bool True if profile is set, false otherwise. */ public function has() { $profile = (array) $this->get(); if ( ! empty( $profile['email'] ) && ! empty( $profile['photo'] ) ) { return true; } return false; } } Authentication/Has_Multiple_Admins.php 0000644 00000003060 14720521221 0014110 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Has_Multiple_Admins * * @package Google\Site_Kit\Core\Authentication * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\Transients; use WP_User_Query; /** * Has_Multiple_Admins class. * * @since 1.29.0 * @access private * @ignore */ class Has_Multiple_Admins { /** * The option_name for this transient. */ const OPTION = 'googlesitekit_has_multiple_admins'; /** * Transients instance. * * @since 1.29.0 * @var Transients */ protected $transients; /** * Constructor. * * @since 1.29.0 * * @param Transients $transients Transients instance. */ public function __construct( Transients $transients ) { $this->transients = $transients; } /** * Returns a flag indicating whether the current site has multiple users. * * @since 1.29.0 * * @return boolean TRUE if the site kit has multiple admins, otherwise FALSE. */ public function get() { $admins_count = $this->transients->get( self::OPTION ); if ( false === $admins_count ) { $user_query_args = array( 'number' => 1, 'role__in' => array( 'Administrator' ), 'count_total' => true, ); $user_query = new WP_User_Query( $user_query_args ); $admins_count = $user_query->get_total(); $this->transients->get( self::OPTION, $admins_count, HOUR_IN_SECONDS ); } return $admins_count > 1; } } Authentication/Verification_Meta.php 0000644 00000001160 14720521221 0013616 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Verification_Meta * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class representing the site verification meta tag for a user. * * @since 1.1.0 * @access private * @ignore */ final class Verification_Meta extends User_Setting { /** * User option key. */ const OPTION = 'googlesitekit_site_verification_meta'; } Authentication/Setup.php 0000644 00000030623 14720521221 0011334 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Setup * * @package Google\Site_Kit\Core\Authentication * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Authentication\Exception\Exchange_Site_Code_Exception; use Google\Site_Kit\Core\Authentication\Exception\Missing_Verification_Exception; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Util\Remote_Features; /** * Base class for authentication setup. * * @since 1.48.0 * @access private * @ignore */ class Setup { /** * Context instance. * * @since 1.48.0 * * @var Context */ protected $context; /** * User_Options instance. * * @since 1.48.0 * * @var User_Options */ protected $user_options; /** * Authentication instance. * * @since 1.48.0 * * @var Authentication */ protected $authentication; /** * Google_Proxy instance. * * @since 1.48.0 * * @var Google_Proxy */ protected $google_proxy; /** * Proxy support URL. * * @since 1.109.0 Explicitly declared; previously, it was dynamically declared. * * @var string */ protected $proxy_support_link_url; /** * Credentials instance. * * @since 1.48.0 * * @var Credentials */ protected $credentials; /** * Constructor. * * @since 1.48.0 * * @param Context $context Context instance. * @param User_Options $user_options User_Options instance. * @param Authentication $authentication Authentication instance. */ public function __construct( Context $context, User_Options $user_options, Authentication $authentication ) { $this->context = $context; $this->user_options = $user_options; $this->authentication = $authentication; $this->credentials = $authentication->credentials(); $this->google_proxy = $authentication->get_google_proxy(); $this->proxy_support_link_url = $authentication->get_proxy_support_link_url(); } /** * Registers functionality through WordPress hooks. * * @since 1.48.0 */ public function register() { add_action( 'admin_action_' . Google_Proxy::ACTION_SETUP_START, array( $this, 'handle_action_setup_start' ) ); add_action( 'admin_action_' . Google_Proxy::ACTION_VERIFY, array( $this, 'handle_action_verify' ) ); add_action( 'admin_action_' . Google_Proxy::ACTION_EXCHANGE_SITE_CODE, array( $this, 'handle_action_exchange_site_code' ) ); } /** * Composes the oAuth proxy get help link. * * @since 1.81.0 * * @return string The get help link. */ private function get_oauth_proxy_failed_help_link() { return sprintf( /* translators: 1: Support link URL. 2: Get help string. */ __( '<a href="%1$s" target="_blank">%2$s</a>', 'google-site-kit' ), esc_url( add_query_arg( 'error_id', 'request_to_auth_proxy_failed', $this->proxy_support_link_url ) ), esc_html__( 'Get help', 'google-site-kit' ) ); } /** * Handles the setup start action, taking the user to the proxy setup screen. * * @since 1.48.0 */ public function handle_action_setup_start() { $nonce = htmlspecialchars( $this->context->input()->filter( INPUT_GET, 'nonce' ) ?? '' ); $redirect_url = $this->context->input()->filter( INPUT_GET, 'redirect', FILTER_DEFAULT ); $this->verify_nonce( $nonce, Google_Proxy::ACTION_SETUP_START ); if ( ! current_user_can( Permissions::SETUP ) ) { wp_die( esc_html__( 'You have insufficient permissions to connect Site Kit.', 'google-site-kit' ) ); } if ( ! $this->credentials->using_proxy() ) { wp_die( esc_html__( 'Site Kit is not configured to use the authentication proxy.', 'google-site-kit' ) ); } $required_scopes = $this->authentication->get_oauth_client()->get_required_scopes(); $this->google_proxy->with_scopes( $required_scopes ); $oauth_setup_redirect = $this->credentials->has() ? $this->google_proxy->sync_site_fields( $this->credentials, 'sync' ) : $this->google_proxy->register_site( 'sync' ); $oauth_proxy_failed_help_link = $this->get_oauth_proxy_failed_help_link(); if ( is_wp_error( $oauth_setup_redirect ) ) { $error_message = $oauth_setup_redirect->get_error_message(); if ( empty( $error_message ) ) { $error_message = $oauth_setup_redirect->get_error_code(); } wp_die( sprintf( /* translators: 1: Error message or error code. 2: Get help link. */ esc_html__( 'The request to the authentication proxy has failed with an error: %1$s %2$s.', 'google-site-kit' ), esc_html( $error_message ), wp_kses( $oauth_proxy_failed_help_link, array( 'a' => array( 'href' => array(), 'target' => array(), ), ) ) ) ); } if ( ! filter_var( $oauth_setup_redirect, FILTER_VALIDATE_URL ) ) { wp_die( sprintf( /* translators: %s: Get help link. */ esc_html__( 'The request to the authentication proxy has failed. Please, try again later. %s.', 'google-site-kit' ), wp_kses( $oauth_proxy_failed_help_link, array( 'a' => array( 'href' => array(), 'target' => array(), ), ) ) ) ); } if ( $redirect_url ) { $this->user_options->set( OAuth_Client::OPTION_REDIRECT_URL, $redirect_url ); } wp_safe_redirect( $oauth_setup_redirect ); exit; } /** * Handles the action for verifying site ownership. * * @since 1.48.0 * @since 1.49.0 Sets the `verify` and `verification_method` and `site_id` query params. */ public function handle_action_verify() { $input = $this->context->input(); $step = htmlspecialchars( $input->filter( INPUT_GET, 'step' ) ?? '' ); $nonce = htmlspecialchars( $input->filter( INPUT_GET, 'nonce' ) ?? '' ); $code = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_code' ) ?? '' ); $site_code = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_site_code' ) ?? '' ); $verification_token = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_verification_token' ) ?? '' ); $verification_method = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_verification_token_type' ) ?? '' ); $this->verify_nonce( $nonce ); if ( ! current_user_can( Permissions::SETUP ) ) { wp_die( esc_html__( 'You don’t have permissions to set up Site Kit.', 'google-site-kit' ), 403 ); } if ( ! $code ) { wp_die( esc_html__( 'Invalid request.', 'google-site-kit' ), 400 ); } if ( ! $verification_token || ! $verification_method ) { wp_die( esc_html__( 'Verifying site ownership requires a token and verification method.', 'google-site-kit' ), 400 ); } $this->handle_verification( $verification_token, $verification_method ); $proxy_query_params = array( 'step' => $step, 'verify' => 'true', 'verification_method' => $verification_method, ); // If the site does not have a site ID yet, a site code will be passed. // Handling the site code here will save the extra redirect from the proxy if successful. if ( $site_code ) { try { $this->handle_site_code( $code, $site_code ); } catch ( Missing_Verification_Exception $exception ) { $proxy_query_params['site_code'] = $site_code; $this->redirect_to_proxy( $code, $proxy_query_params ); } catch ( Exchange_Site_Code_Exception $exception ) { $this->redirect_to_splash(); } } $credentials = $this->credentials->get(); $proxy_query_params['site_id'] = ! empty( $credentials['oauth2_client_id'] ) ? $credentials['oauth2_client_id'] : ''; $this->redirect_to_proxy( $code, $proxy_query_params ); } /** * Handles the action for exchanging the site code for site credentials. * * This action will only be called if the site code failed to be handled * during the verification step. * * @since 1.48.0 */ public function handle_action_exchange_site_code() { $input = $this->context->input(); $step = htmlspecialchars( $input->filter( INPUT_GET, 'step' ) ?? '' ); $nonce = htmlspecialchars( $input->filter( INPUT_GET, 'nonce' ) ?? '' ); $code = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_code' ) ?? '' ); $site_code = htmlspecialchars( $input->filter( INPUT_GET, 'googlesitekit_site_code' ) ?? '' ); $this->verify_nonce( $nonce ); if ( ! current_user_can( Permissions::SETUP ) ) { wp_die( esc_html__( 'You don’t have permissions to set up Site Kit.', 'google-site-kit' ), 403 ); } if ( ! $code || ! $site_code ) { wp_die( esc_html__( 'Invalid request.', 'google-site-kit' ), 400 ); } try { $this->handle_site_code( $code, $site_code ); } catch ( Missing_Verification_Exception $exception ) { $this->redirect_to_proxy( $code, compact( 'site_code', 'step' ) ); } catch ( Exchange_Site_Code_Exception $exception ) { $this->redirect_to_splash(); } $credentials = $this->credentials->get(); $site_id = ! empty( $credentials['oauth2_client_id'] ) ? $credentials['oauth2_client_id'] : ''; $this->redirect_to_proxy( $code, compact( 'site_id', 'step' ) ); } /** * Verifies the given nonce for a setup action. * * The nonce passed from the proxy will always be the one initially provided to it. * {@see Google_Proxy::setup_url()} * * @since 1.48.0 * * @param string $nonce Action nonce. * @param string $action Action name. Optional. Defaults to the action for the nonce given to the proxy. */ protected function verify_nonce( $nonce, $action = Google_Proxy::NONCE_ACTION ) { if ( ! wp_verify_nonce( $nonce, $action ) ) { $this->authentication->invalid_nonce_error( $action ); } } /** * Handles site verification. * * @since 1.48.0 * * @param string $token Verification token. * @param string $method Verification method. */ protected function handle_verification( $token, $method ) { /** * Verifies site ownership using the given token and verification method. * * @since 1.48.0 * * @param string $token Verification token. * @param string $method Verification method. */ do_action( 'googlesitekit_verify_site_ownership', $token, $method ); } /** * Handles the exchange of a code and site code for client credentials from the proxy. * * @since 1.48.0 * * @param string $code Code ('googlesitekit_code') provided by proxy. * @param string $site_code Site code ('googlesitekit_site_code') provided by proxy. * * @throws Missing_Verification_Exception Thrown if exchanging the site code fails due to missing site verification. * @throws Exchange_Site_Code_Exception Thrown if exchanging the site code fails for any other reason. */ protected function handle_site_code( $code, $site_code ) { $data = $this->google_proxy->exchange_site_code( $site_code, $code ); if ( is_wp_error( $data ) ) { $error_code = $data->get_error_message() ?: $data->get_error_code(); $error_code = $error_code ?: 'unknown_error'; if ( 'missing_verification' === $error_code ) { throw new Missing_Verification_Exception(); } $this->user_options->set( OAuth_Client::OPTION_ERROR_CODE, $error_code ); throw new Exchange_Site_Code_Exception( $error_code ); } $this->credentials->set( array( 'oauth2_client_id' => $data['site_id'], 'oauth2_client_secret' => $data['site_secret'], ) ); } /** * Redirects back to the authentication service with any added parameters. * * For v2 of the proxy, this method now has to ensure that the user is redirected back to the correct step on the * proxy, based on which action was received. * * @since 1.48.0 * @since 1.49.0 Uses the new `Google_Proxy::setup_url_v2` method when the `serviceSetupV2` feature flag is enabled. * * @param string $code Code ('googlesitekit_code') provided by proxy. * @param array $params Additional query parameters to include in the proxy redirect URL. */ protected function redirect_to_proxy( $code = '', $params = array() ) { $params['code'] = $code; $url = $this->authentication->get_google_proxy()->setup_url( $params ); wp_safe_redirect( $url ); exit; } /** * Redirects to the Site Kit splash page. * * @since 1.48.0 */ protected function redirect_to_splash() { wp_safe_redirect( $this->context->admin_url( 'splash' ) ); exit; } } Authentication/Verification.php 0000644 00000003030 14720521221 0012646 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Verification * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class representing the status of whether a user is verified as an owner of the site. * * @since 1.0.0 * @access private * @ignore */ final class Verification extends User_Setting { /** * User option key. */ const OPTION = 'googlesitekit_site_verified_meta'; /** * Gets the value of the setting. * * @since 1.4.0 * * @return mixed Value set for the option, or default if not set. */ public function get() { return (bool) parent::get(); } /** * Flags the user as verified or unverified. * * @since 1.0.0 * * @param bool $verified Whether to flag the user as verified or unverified. * @return bool True on success, false on failure. */ public function set( $verified ) { if ( ! $verified ) { return $this->delete(); } return parent::set( '1' ); } /** * Gets the expected value type. * * @since 1.4.0 * * @return string The type name. */ protected function get_type() { return 'boolean'; } /** * Gets the default value. * * Returns an empty string by default for consistency with get_user_meta. * * @since 1.4.0 * * @return mixed The default value. */ protected function get_default() { return false; } } Authentication/Guards/Using_Proxy_Connection_Guard.php 0000644 00000002160 14720521221 0017243 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Guards\Using_Proxy_Connection_Guard * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Guards; use Google\Site_Kit\Core\Authentication\Credentials; use Google\Site_Kit\Core\Guards\Guard_Interface; /** * Class providing guard logic based on proxy connection. * * @since 1.133.0 * @access private * @ignore */ class Using_Proxy_Connection_Guard implements Guard_Interface { /** * Credentials instance. * * @var Credentials */ private Credentials $credentials; /** * Constructor. * * @since 1.133.0 * @param Credentials $credentials Credentials instance. */ public function __construct( Credentials $credentials ) { $this->credentials = $credentials; } /** * Determines whether the guarded entity can be activated or not. * * @since 1.133.0 * @return bool|\WP_Error */ public function can_activate() { return $this->credentials->using_proxy(); } } Authentication/Guards/Site_Connected_Guard.php 0000644 00000002122 14720521221 0015462 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Guards\Site_Connected_Guard * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication\Guards; use Google\Site_Kit\Core\Authentication\Credentials; use Google\Site_Kit\Core\Guards\Guard_Interface; /** * Class providing guard logic for site connection. * * @since 1.133.0 * @access private * @ignore */ class Site_Connected_Guard implements Guard_Interface { /** * Credentials instance. * * @var Credentials */ private Credentials $credentials; /** * Constructor. * * @since 1.133.0 * @param Credentials $credentials Credentials instance. */ public function __construct( Credentials $credentials ) { $this->credentials = $credentials; } /** * Determines whether the guarded entity can be activated or not. * * @since 1.133.0 * @return bool|\WP_Error */ public function can_activate() { return $this->credentials->has(); } } Authentication/Disconnected_Reason.php 0000644 00000001633 14720521221 0014144 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Authentication\Disconnected_Reason * * @package Google\Site_Kit\Core\Authentication * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Authentication; use Google\Site_Kit\Core\Storage\User_Setting; /** * Disconnected_Reason class. * * @since 1.17.0 * @access private * @ignore */ class Disconnected_Reason extends User_Setting { /** * The option_name for this setting. */ const OPTION = 'googlesitekit_disconnected_reason'; /** * Available reasons. */ const REASON_CONNECTED_URL_MISMATCH = 'connected_url_mismatch'; /** * Registers the setting in WordPress. * * @since 1.17.0 */ public function register() { parent::register(); add_action( 'googlesitekit_authorize_user', array( $this, 'delete' ) ); } } Conversion_Tracking/Conversion_Tracking.php 0000644 00000014410 14720521221 0015167 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking * * @package Google\Site_Kit\Core\Modules * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Contact_Form_7; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Easy_Digital_Downloads; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Mailchimp; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Ninja_Forms; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\OptinMonster; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\PopupMaker; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\WooCommerce; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\WPForms; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Tags\GTag; use LogicException; /** * Class for managing conversion tracking. * * @since 1.126.0 * @access private * @ignore */ class Conversion_Tracking { /** * Context object. * * @var Context */ private $context; /** * Conversion_Tracking_Settings instance. * * @since 1.127.0 * @var Conversion_Tracking_Settings */ protected $conversion_tracking_settings; /** * REST_Conversion_Tracking_Controller instance. * * @since 1.127.0 * @var REST_Conversion_Tracking_Controller */ protected $rest_conversion_tracking_controller; /** * Supported conversion event providers. * * @since 1.126.0 * @since 1.130.0 Added Ninja Forms class. * @var array */ public static $providers = array( Contact_Form_7::CONVERSION_EVENT_PROVIDER_SLUG => Contact_Form_7::class, Easy_Digital_Downloads::CONVERSION_EVENT_PROVIDER_SLUG => Easy_Digital_Downloads::class, Mailchimp::CONVERSION_EVENT_PROVIDER_SLUG => Mailchimp::class, Ninja_Forms::CONVERSION_EVENT_PROVIDER_SLUG => Ninja_Forms::class, OptinMonster::CONVERSION_EVENT_PROVIDER_SLUG => OptinMonster::class, PopupMaker::CONVERSION_EVENT_PROVIDER_SLUG => PopupMaker::class, WooCommerce::CONVERSION_EVENT_PROVIDER_SLUG => WooCommerce::class, WPForms::CONVERSION_EVENT_PROVIDER_SLUG => WPForms::class, ); /** * Constructor. * * @since 1.126.0 * * @param Context $context Plugin context. * @param Options $options Optional. Option API instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null ) { $this->context = $context; $options = $options ?: new Options( $context ); $this->conversion_tracking_settings = new Conversion_Tracking_Settings( $options ); $this->rest_conversion_tracking_controller = new REST_Conversion_Tracking_Controller( $this->conversion_tracking_settings ); } /** * Registers the class functionality. * * @since 1.126.0 */ public function register() { $this->conversion_tracking_settings->register(); $this->rest_conversion_tracking_controller->register(); add_action( 'wp_enqueue_scripts', fn () => $this->maybe_enqueue_scripts(), 30 ); $active_providers = $this->get_active_providers(); array_walk( $active_providers, function ( Conversion_Events_Provider $active_provider ) { $active_provider->register_hooks(); } ); } /** * Enqueues conversion tracking scripts if conditions are satisfied. */ protected function maybe_enqueue_scripts() { if ( // Do nothing if neither Ads nor Analytics *web* snippet has been inserted. ! ( did_action( 'googlesitekit_ads_init_tag' ) || did_action( 'googlesitekit_analytics-4_init_tag' ) ) || ! $this->conversion_tracking_settings->is_conversion_tracking_enabled() ) { return; } $active_providers = $this->get_active_providers(); array_walk( $active_providers, function ( Conversion_Events_Provider $active_provider ) { $script_asset = $active_provider->register_script(); $script_asset->enqueue(); } ); $gtag_event = ' window._googlesitekit = window._googlesitekit || {}; window._googlesitekit.throttledEvents = []; window._googlesitekit.gtagEvent = (name, data) => { var key = JSON.stringify( { name, data } ); if ( !! window._googlesitekit.throttledEvents[ key ] ) { return; } window._googlesitekit.throttledEvents[ key ] = true; setTimeout( () => { delete window._googlesitekit.throttledEvents[ key ]; }, 5 ); gtag( "event", name, { ...data, event_source: "site-kit" } ); } '; wp_add_inline_script( GTag::HANDLE, preg_replace( '/\s+/', ' ', $gtag_event ) ); } /** * Gets the instances of active conversion event providers. * * @since 1.126.0 * * @return array List of active Conversion_Events_Provider instances. * @throws LogicException Thrown if an invalid conversion event provider class name is provided. */ public function get_active_providers() { $active_providers = array(); foreach ( self::$providers as $provider_slug => $provider_class ) { if ( ! is_string( $provider_class ) || ! $provider_class ) { throw new LogicException( sprintf( /* translators: %s: provider slug */ __( 'A conversion event provider class name is required to instantiate a provider: %s', 'google-site-kit' ), $provider_slug ) ); } if ( ! class_exists( $provider_class ) ) { throw new LogicException( sprintf( /* translators: %s: provider classname */ __( "The '%s' class does not exist", 'google-site-kit' ), $provider_class ) ); } if ( ! is_subclass_of( $provider_class, Conversion_Events_Provider::class ) ) { throw new LogicException( sprintf( /* translators: 1: provider classname 2: Conversion_Events_Provider classname */ __( "The '%1\$s' class must extend the base conversion event provider class: %2\$s", 'google-site-kit' ), $provider_class, Conversion_Events_Provider::class ) ); } $instance = new $provider_class( $this->context ); if ( $instance->is_active() ) { $active_providers[ $provider_slug ] = $instance; } } return $active_providers; } } Conversion_Tracking/Conversion_Event_Providers/Contact_Form_7.php 0000644 00000003231 14720521221 0021346 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Contact_Form_7 * * @package Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider; /** * Class for handling Contact Form 7 conversion events. * * @since 1.127.0 * @access private * @ignore */ class Contact_Form_7 extends Conversion_Events_Provider { const CONVERSION_EVENT_PROVIDER_SLUG = 'contact-form-7'; /** * Checks if the Contact Form 7 plugin is active. * * @since 1.127.0 * * @return bool True if Contact Form 7 is active, false otherwise. */ public function is_active() { return defined( 'WPCF7_VERSION' ); } /** * Gets the conversion event names that are tracked by this provider. * * @since 1.127.0 * * @return array List of event names. */ public function get_event_names() { return array( 'contact' ); } /** * Registers the script for the provider. * * @since 1.127.0 * * @return Script Script instance. */ public function register_script() { $script = new Script( 'googlesitekit-events-provider-' . self::CONVERSION_EVENT_PROVIDER_SLUG, array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-events-provider-contact-form-7.js' ), 'execution' => 'defer', ) ); $script->register( $this->context ); return $script; } } Conversion_Tracking/Conversion_Event_Providers/PopupMaker.php 0000644 00000003303 14720521221 0020625 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\PopupMaker * * @package Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider; /** * Class for handling PopupMaker conversion events. * * @since 1.127.0 * @access private * @ignore */ class PopupMaker extends Conversion_Events_Provider { const CONVERSION_EVENT_PROVIDER_SLUG = 'popup-maker'; /** * Checks if the PopupMaker plugin is active. * * @since 1.127.0 * * @return bool True if PopupMaker is active, false otherwise. */ public function is_active() { return defined( 'POPMAKE_VERSION' ); } /** * Gets the conversion event names that are tracked by this provider. * * @since 1.127.0 * * @return array List of event names. */ public function get_event_names() { return array( 'submit_lead_form' ); } /** * Registers the script for the provider. * * @since 1.127.0 * * @return Script Script instance. */ public function register_script() { $script = new Script( 'googlesitekit-events-provider-' . self::CONVERSION_EVENT_PROVIDER_SLUG, array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-events-provider-popup-maker.js' ), 'dependencies' => array( 'popup-maker-site' ), 'execution' => 'defer', ) ); $script->register( $this->context ); return $script; } } Conversion_Tracking/Conversion_Event_Providers/WooCommerce.php 0000644 00000005675 14720521221 0020777 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\WooCommerce * * @package Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider; use Google\Site_Kit\Core\Util\BC_Functions; /** * Class for handling WooCommerce conversion events. * * @since 1.127.0 * @access private * @ignore */ class WooCommerce extends Conversion_Events_Provider { const CONVERSION_EVENT_PROVIDER_SLUG = 'woocommerce'; /** * Checks if the WooCommerce plugin is active. * * @since 1.127.0 * * @return bool True if WooCommerce is active, false otherwise. */ public function is_active() { return did_action( 'woocommerce_loaded' ) > 0; } /** * Gets the conversion event names that are tracked by this provider. * * @since 1.127.0 * * @return array List of event names. */ public function get_event_names() { return array( 'add_to_cart', 'purchase' ); } /** * Registers the script for the provider. * * @since 1.127.0 * * @return Script Script instance. */ public function register_script() { $script = new Script( 'googlesitekit-events-provider-' . self::CONVERSION_EVENT_PROVIDER_SLUG, array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-events-provider-woocommerce.js' ), 'execution' => 'defer', 'dependencies' => array( 'woocommerce' ), ) ); $script->register( $this->context ); return $script; } /** * Adds a hook for a purchase event. * * @since 1.129.0 */ public function register_hooks() { $input = $this->context->input(); add_action( 'woocommerce_thankyou', function ( $order_id ) use ( $input ) { $order = wc_get_order( $order_id ); // If there isn't a valid order for this ID, or if this order // already has a purchase event tracked for it, return early // and don't output the script tag to track the purchase event. if ( ! $order || $order->get_meta( '_googlesitekit_ga_purchase_event_tracked' ) === '1' ) { return; } // Ensure the order key in the query param is valid for this // order. $order_key = $input->filter( INPUT_GET, 'key' ); // Don't output the script tag if the order key is invalid. if ( ! $order->key_is_valid( (string) $order_key ) ) { return; } // Mark the order as tracked by Site Kit. $order->update_meta_data( '_googlesitekit_ga_purchase_event_tracked', 1 ); $order->save(); // Output the script tag to track the purchase event in // Analytics. BC_Functions::wp_print_inline_script_tag( "window?._googlesitekit?.gtagEvent?.( 'purchase' );" ); }, 10, 1 ); } } Conversion_Tracking/Conversion_Event_Providers/Ninja_Forms.php 0000644 00000003307 14720521221 0020753 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Ninja_Forms * * @package Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider; /** * Class for handling Ninja Forms conversion events. * * @since 1.130.0 * @access private * @ignore */ class Ninja_Forms extends Conversion_Events_Provider { const CONVERSION_EVENT_PROVIDER_SLUG = 'ninja-forms'; /** * Checks if the Ninja Forms plugin is active. * * @since 1.130.0 * * @return bool True if Ninja Forms is active, false otherwise. */ public function is_active() { return defined( 'NF_PLUGIN_URL' ); } /** * Gets the conversion event names that are tracked by this provider. * * @since 1.130.0 * * @return array List of event names. */ public function get_event_names() { return array( 'submit_lead_form' ); } /** * Registers the script for the provider. * * @since 1.130.0 * * @return Script Script instance. */ public function register_script() { $script = new Script( 'googlesitekit-events-provider-' . self::CONVERSION_EVENT_PROVIDER_SLUG, array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-events-provider-ninja-forms.js' ), 'execution' => 'defer', 'dependencies' => array( 'nf-front-end-deps' ), ) ); $script->register( $this->context ); return $script; } } Conversion_Tracking/Conversion_Event_Providers/Mailchimp.php 0000644 00000003267 14720521221 0020456 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Mailchimp * * @package Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider; /** * Class for handling Mailchimp conversion events. * * @since 1.127.0 * @access private * @ignore */ class Mailchimp extends Conversion_Events_Provider { const CONVERSION_EVENT_PROVIDER_SLUG = 'mailchimp'; /** * Checks if the Mailchimp plugin is active. * * @since 1.127.0 * * @return bool True if Mailchimp is active, false otherwise. */ public function is_active() { return defined( 'MC4WP_VERSION' ); } /** * Gets the conversion event names that are tracked by this provider. * * @since 1.127.0 * * @return array List of event names. */ public function get_event_names() { return array( 'submit_lead_form' ); } /** * Registers the script for the provider. * * @since 1.127.0 * * @return Script Script instance. */ public function register_script() { $script = new Script( 'googlesitekit-events-provider-' . self::CONVERSION_EVENT_PROVIDER_SLUG, array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-events-provider-mailchimp.js' ), 'execution' => 'defer', 'dependencies' => array( 'mc4wp-forms-api' ), ) ); $script->register( $this->context ); return $script; } } Conversion_Tracking/Conversion_Event_Providers/Easy_Digital_Downloads.php 0000644 00000003404 14720521221 0023114 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\Easy_Digital_Downloads * * @package Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider; /** * Class for handling Easy Digital Downloads conversion events. * * @since 1.130.0 * @access private * @ignore */ class Easy_Digital_Downloads extends Conversion_Events_Provider { const CONVERSION_EVENT_PROVIDER_SLUG = 'easy-digital-downloads'; /** * Checks if the Easy Digital Downloads plugin is active. * * @since 1.130.0 * * @return bool True if Easy Digital Downloads is active, false otherwise. */ public function is_active() { return defined( 'EDD_VERSION' ); } /** * Gets the conversion event names that are tracked by this provider. * * @since 1.130.0 * * @return array List of event names. */ public function get_event_names() { return array( 'add_to_cart' ); } /** * Registers the script for the provider. * * @since 1.130.0 * * @return Script Script instance. */ public function register_script() { $script = new Script( 'googlesitekit-events-provider-' . self::CONVERSION_EVENT_PROVIDER_SLUG, array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-events-provider-easy-digital-downloads.js' ), 'execution' => 'defer', 'dependencies' => array( 'edd-ajax' ), ) ); $script->register( $this->context ); return $script; } } Conversion_Tracking/Conversion_Event_Providers/WPForms.php 0000644 00000003163 14720521221 0020103 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\WPForms * * @package Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider; /** * Class for handling WPForms conversion events. * * @since 1.127.0 * @access private * @ignore */ class WPForms extends Conversion_Events_Provider { const CONVERSION_EVENT_PROVIDER_SLUG = 'wpforms'; /** * Checks if the WPForms plugin is active. * * @since 1.127.0 * * @return bool True if WPForms is active, false otherwise. */ public function is_active() { return defined( 'WPFORMS_VERSION' ); } /** * Gets the conversion event names that are tracked by this provider. * * @since 1.127.0 * * @return array List of event names. */ public function get_event_names() { return array( 'submit_lead_form' ); } /** * Registers the script for the provider. * * @since 1.127.0 * * @return Script Script instance. */ public function register_script() { $script = new Script( 'googlesitekit-events-provider-' . self::CONVERSION_EVENT_PROVIDER_SLUG, array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-events-provider-wpforms.js' ), 'execution' => 'defer', ) ); $script->register( $this->context ); return $script; } } Conversion_Tracking/Conversion_Event_Providers/OptinMonster.php 0000644 00000003223 14720521221 0021204 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers\OptinMonster * * @package Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking\Conversion_Event_Providers; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider; /** * Class for handling OptinMonster conversion events. * * @since 1.127.0 * @access private * @ignore */ class OptinMonster extends Conversion_Events_Provider { const CONVERSION_EVENT_PROVIDER_SLUG = 'optin-monster'; /** * Checks if the OptinMonster plugin is active. * * @since 1.127.0 * * @return bool True if OptinMonster is active, false otherwise. */ public function is_active() { return defined( 'OMAPI_FILE' ); } /** * Gets the conversion event names that are tracked by this provider. * * @since 1.127.0 * * @return array List of event names. */ public function get_event_names() { return array( 'submit_lead_form' ); } /** * Registers the script for the provider. * * @since 1.127.0 * * @return Script Script instance. */ public function register_script() { $script = new Script( 'googlesitekit-events-provider-' . self::CONVERSION_EVENT_PROVIDER_SLUG, array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-events-provider-optin-monster.js' ), 'execution' => 'defer', ) ); $script->register( $this->context ); return $script; } } Conversion_Tracking/REST_Conversion_Tracking_Controller.php 0000644 00000005750 14720521221 0020236 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\REST_Conversion_Tracking_Controller * * @package Google\Site_Kit\Core\Conversion_Tracking * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling rest routes for Conversion Tracking settings. * * @since 1.127.0 * @access private * @ignore */ class REST_Conversion_Tracking_Controller { /** * Conversion_Tracking_Settings instance. * * @since 1.127.0 * @var Conversion_Tracking_Settings */ protected $settings; /** * Constructor. * * @since 1.127.0 * * @param Conversion_Tracking_Settings $settings Conversion Tracking settings. */ public function __construct( Conversion_Tracking_Settings $settings ) { $this->settings = $settings; } /** * Registers functionality through WordPress hooks. * * @since 1.127.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/site/data/conversion-tracking', ) ); } ); } /** * Gets REST route instances. * * @since 1.127.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $has_capabilities = function () { return current_user_can( Permissions::MANAGE_OPTIONS ); }; return array( new REST_Route( 'core/site/data/conversion-tracking', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->settings->get() ); }, 'permission_callback' => $has_capabilities, ) ), new REST_Route( 'core/site/data/conversion-tracking', array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $this->settings->set( $request['data']['settings'] ); return new WP_REST_Response( $this->settings->get() ); }, 'permission_callback' => $has_capabilities, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'settings' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'enabled' => array( 'type' => 'boolean', 'required' => true, ), ), ), ), ), ), ) ), ); } } Conversion_Tracking/Conversion_Events_Provider.php 0000644 00000003151 14720521221 0016543 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Events_Provider * * @package Google\Site_Kit\Core\Conversion_Tracking * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Script; /** * Base class for conversion events provider. * * @since 1.125.0 * @since 1.126.0 Changed from interface to abstract class. * @access private * @ignore */ abstract class Conversion_Events_Provider { /** * Plugin context. * * @since 1.126.0 * @var Context */ protected $context; /** * Constructor. * * @since 1.126.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Checks if the provider is active. * * @since 1.125.0 * * @return bool True if the provider is active, false otherwise. */ public function is_active() { return false; } /** * Gets the event names. * * @since 1.125.0 * * @return array List of event names. */ abstract public function get_event_names(); /** * Registers any actions/hooks for this provider. * * @since 1.129.0 */ public function register_hooks() { // No-op by default, but left here so subclasses can implement // their own `add_action`/hook calls. } /** * Registers the script for the provider. * * @since 1.125.0 * * @return Script Script instance. */ abstract public function register_script(); } Conversion_Tracking/Conversion_Tracking_Settings.php 0000644 00000003202 14720521221 0017044 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Conversion_Tracking\Conversion_Tracking_Settings * * @package Google\Site_Kit\Core\Conversion_Tracking * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Conversion_Tracking; use Google\Site_Kit\Core\Storage\Setting; /** * Class to store conversion tracking settings. * * @since 1.127.0 * @access private * @ignore */ class Conversion_Tracking_Settings extends Setting { /** * The option name for this setting. */ const OPTION = 'googlesitekit_conversion_tracking'; /** * Gets the expected value type. * * @since 1.127.0 * * @return string The expected type of the setting option. */ protected function get_type() { return 'object'; } /** * Gets the default value. * * @since 1.127.0 * * @return array The default value. */ protected function get_default() { return array( 'enabled' => false, ); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.127.0 * * @return callable Sanitize callback. */ protected function get_sanitize_callback() { return function ( $value ) { $new_value = $this->get(); if ( isset( $value['enabled'] ) ) { $new_value['enabled'] = (bool) $value['enabled']; } return $new_value; }; } /** * Accessor for the `enabled` setting. * * @since 1.127.0 * * @return bool TRUE if conversion tracking is enabled, otherwise FALSE. */ public function is_conversion_tracking_enabled() { return $this->get()['enabled']; } } Storage/Setting_With_Owned_Keys_Trait.php 0000644 00000005634 14720521221 0014567 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Storage\Setting_With_Owned_Keys_Trait * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\Setting; /** * Trait for a Setting that has owner ID option key. * * @since 1.16.0 * @access private * @ignore */ trait Setting_With_Owned_Keys_Trait { /** * Returns keys for owned settings. * * @since 1.16.0 * * @return array An array of keys for owned settings. */ abstract public function get_owned_keys(); /** * Registers hooks to determine an owner ID for a module. * * @since 1.16.0 */ protected function register_owned_keys() { add_action( 'add_option_' . static::OPTION, function ( $option, $settings ) { if ( ! current_user_can( Permissions::MANAGE_OPTIONS ) ) { return; } if ( ! is_array( $settings ) || ! $this instanceof Setting ) { return; } $defaults = $this->get_default(); if ( ! is_array( $defaults ) ) { return; } if ( $this->have_owned_settings_changed( $settings, $defaults ) ) { $this->merge_initial_owner_id(); } }, 10, 2 ); add_filter( 'pre_update_option_' . static::OPTION, function ( $settings, $old_settings ) { if ( current_user_can( Permissions::MANAGE_OPTIONS ) && is_array( $settings ) && is_array( $old_settings ) && $this->have_owned_settings_changed( $settings, $old_settings ) ) { return $this->update_owner_id_in_settings( $settings ); } return $settings; }, 10, 2 ); } /** * Merges the current user ID into the module settings as the initial owner ID. * * @since 1.99.0 */ protected function merge_initial_owner_id() { $this->merge( array( 'ownerID' => get_current_user_id() ) ); } /** * Adds the current user ID as the module owner ID to the current module settings. * * @since 1.99.0 * * @param array $settings The new module settings. * @return array Updated module settings with the current user ID as the ownerID setting. */ protected function update_owner_id_in_settings( $settings ) { $settings['ownerID'] = get_current_user_id(); return $settings; } /** * Determines whether the owned settings have changed. * * @since 1.99.0 * * @param array $settings The new settings. * @param array $old_settings The old settings. * @return bool TRUE if owned settings have changed, otherwise FALSE. */ protected function have_owned_settings_changed( $settings, $old_settings ) { $keys = $this->get_owned_keys(); foreach ( $keys as $key ) { if ( isset( $settings[ $key ], $old_settings[ $key ] ) && $settings[ $key ] !== $old_settings[ $key ] ) { return true; } } return false; } } Storage/Encrypted_Options.php 0000644 00000004577 14720521221 0012342 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\Encrypted_Options * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Class providing access to encrypted options. * * @since 1.0.0 * @access private * @ignore */ final class Encrypted_Options implements Options_Interface { /** * Data Encryption API instance. * * @since 1.0.0 * @var Data_Encryption */ private $encryption; /** * Option API instance. * * @since 1.0.0 * @var Options */ private $options; /** * Constructor. * * @since 1.0.0 * * @param Options $options Option API instance. */ public function __construct( Options $options ) { $this->encryption = new Data_Encryption(); $this->options = $options; } /** * Checks whether or not a value is set for the given option. * * @since 1.3.0 * * @param string $option Option name. * @return bool True if value set, false otherwise. */ public function has( $option ) { return $this->options->has( $option ); } /** * Gets the value of the given option. * * @since 1.0.0 * * @param string $option Option name. * @return mixed Value set for the option, or false if not set. */ public function get( $option ) { $raw_value = $this->options->get( $option ); // If there is no value stored, return the default which will not be encrypted. if ( ! $this->options->has( $option ) ) { return $raw_value; } $data = $this->encryption->decrypt( $raw_value ); return maybe_unserialize( $data ); } /** * Sets the value for a option. * * @since 1.0.0 * * @param string $option Option name. * @param mixed $value Option value. Must be serializable if non-scalar. * @return bool True on success, false on failure. */ public function set( $option, $value ) { if ( ! is_scalar( $value ) ) { $value = maybe_serialize( $value ); } $raw_value = $this->encryption->encrypt( $value ); if ( ! $raw_value ) { return false; } return $this->options->set( $option, $raw_value ); } /** * Deletes the given option. * * @since 1.0.0 * * @param string $option Option name. * @return bool True on success, false on failure. */ public function delete( $option ) { return $this->options->delete( $option ); } } Storage/Transients.php 0000644 00000004113 14720521221 0011006 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\Transients * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; use Google\Site_Kit\Context; /** * Class providing access to transients. * * It uses regular transients or network transients, depending on in which mode the plugin is running. * * @since 1.0.0 * @access private * @ignore */ final class Transients { /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Gets the value of the given transient. * * @since 1.0.0 * * @param string $transient Transient name. * @return mixed Value set for the transient, or false if not set. */ public function get( $transient ) { if ( $this->context->is_network_mode() ) { return get_site_transient( $transient ); } return get_transient( $transient ); } /** * Sets the value for a transient. * * @since 1.0.0 * * @param string $transient Transient name. * @param mixed $value Transient value. Must be serializable if non-scalar. * @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration). * @return bool True on success, false on failure. */ public function set( $transient, $value, $expiration = 0 ) { if ( $this->context->is_network_mode() ) { return set_site_transient( $transient, $value, $expiration ); } return set_transient( $transient, $value, $expiration ); } /** * Deletes the given transient. * * @since 1.0.0 * * @param string $transient Transient name. * @return bool True on success, false on failure. */ public function delete( $transient ) { if ( $this->context->is_network_mode() ) { return delete_site_transient( $transient ); } return delete_transient( $transient ); } } Storage/Options.php 0000644 00000005573 14720521221 0010322 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\Options * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; use Google\Site_Kit\Context; /** * Class providing access to options. * * It uses regular options or network options, depending on in which mode the plugin is running. * * @since 1.0.0 * @access private * @ignore */ final class Options implements Options_Interface { /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Checks whether or not a value is set for the given option. * * @since 1.3.0 * * @param string $option Option name. * @return bool True if value set, false otherwise. */ public function has( $option ) { // Call for option to ensure 'notoptions' cache is fresh for the option. $value = $this->get( $option ); if ( $this->context->is_network_mode() ) { $network_id = get_current_network_id(); $notoptions = wp_cache_get( "$network_id:notoptions", 'site-options' ); } else { $notoptions = wp_cache_get( 'notoptions', 'options' ); } // Check for `notoptions` cache. If unavailable, query the database. // This is particularly happening when `WP_INSTALLING` is true, // which includes `wp-activate.php` in multisite setups and certain // other multisite-related functions. // See: https://github.com/google/site-kit-wp/issues/7653. if ( false === $notoptions ) { return (bool) $value; } return ! isset( $notoptions[ $option ] ); } /** * Gets the value of the given option. * * @since 1.0.0 * * @param string $option Option name. * @return mixed Value set for the option, or false if not set. */ public function get( $option ) { if ( $this->context->is_network_mode() ) { return get_network_option( null, $option ); } return get_option( $option ); } /** * Sets the value for a option. * * @since 1.0.0 * * @param string $option Option name. * @param mixed $value Option value. Must be serializable if non-scalar. * @return bool True on success, false on failure. */ public function set( $option, $value ) { if ( $this->context->is_network_mode() ) { return update_network_option( null, $option, $value ); } return update_option( $option, $value ); } /** * Deletes the given option. * * @since 1.0.0 * * @param string $option Option name. * @return bool True on success, false on failure. */ public function delete( $option ) { if ( $this->context->is_network_mode() ) { return delete_network_option( null, $option ); } return delete_option( $option ); } } Storage/Setting/List_Setting.php 0000644 00000002715 14720521221 0012707 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Storage\Setting\List_Setting * * @package Google\Site_Kit\Core\Storage\Setting * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage\Setting; /** * A trait for a single setting of the array type. * * @since 1.98.0 * @access private * @ignore */ trait List_Setting { /** * Gets the expected value type. * * @since 1.98.0 * * @return string The type name. */ protected function get_type() { return 'array'; } /** * Gets the default value. * * @since 1.98.0 * * @return array The default value. */ protected function get_default() { return array(); } /** * Gets the value of the setting. * * @since 1.98.0 * * @return array Value set for the option, or default if not set. */ public function get() { $value = parent::get(); return is_array( $value ) ? $value : $this->get_default(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.98.0 * * @return callable Sanitize callback. */ protected function get_sanitize_callback() { return array( $this, 'sanitize_list_items' ); } /** * Filters array items. * * @since 1.98.0 * * @param array $items The original array items. * @return array Filtered items. */ abstract protected function sanitize_list_items( $items ); } Storage/User_Aware_Interface.php 0000644 00000001463 14720521221 0012676 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Storage\User_Aware_Interface * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Interface for Options implementations. * * @since 1.18.0 * @access private * @ignore */ interface User_Aware_Interface { /** * Gets the associated user ID. * * @since 1.18.0 * * @return int User ID. */ public function get_user_id(); /** * Switches the current user to the one with the given ID. * * @since 1.18.0 * * @param int $user_id User ID. * @return callable A closure to switch back to the original user. */ public function switch_user( $user_id ); } Storage/User_Options_Interface.php 0000644 00000002535 14720521221 0013273 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Storage\User_Options_Interface * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Interface for Options implementations. * * @since 1.4.0 * @access private * @ignore */ interface User_Options_Interface extends User_Aware_Interface { /** * Gets the value of the given option. * * @since 1.4.0 * * @param string $option Option name. * @return mixed Value set for the option, or false if not set. */ public function get( $option ); /** * Sets the value for a option. * * @since 1.4.0 * * @param string $option Option name. * @param mixed $value Option value. Must be serializable if non-scalar. * @return bool True on success, false on failure. */ public function set( $option, $value ); /** * Deletes the given option. * * @since 1.4.0 * * @param string $option Option name. * @return bool True on success, false on failure. */ public function delete( $option ); /** * Gets the underlying meta key for the given option. * * @since 1.4.0 * * @param string $option Option name. * @return string Meta key name. */ public function get_meta_key( $option ); } Storage/Setting.php 0000644 00000010005 14720521221 0010266 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Base class for a single setting. * * @since 1.2.0 * @access private * @ignore */ abstract class Setting { /** * The option_name for this setting. * Override in a sub-class. */ const OPTION = ''; /** * Options instance implementing Options_Interface. * * @since 1.2.0 * @var Options_Interface */ protected $options; /** * Setting constructor. * * @since 1.2.0 * * @param Options_Interface $options Options_Interface instance. */ public function __construct( Options_Interface $options ) { $this->options = $options; } /** * Registers the setting in WordPress. * * @since 1.2.0 */ public function register() { register_setting( static::OPTION, static::OPTION, array( 'type' => $this->get_type(), 'sanitize_callback' => $this->get_sanitize_callback(), 'default' => $this->get_default(), ) ); } /** * Subscribes to updates for this setting. * * @since 1.118.0 * * @param callable $callback Function taking $old_value & $new_value parameters that gets called when option value updates. * @return \Closure Function to remove added listeners. */ public function on_change( callable $callback ) { $option = static::OPTION; $on_add_option = function ( $_, $value ) use ( $callback ) { $callback( $this->get_default(), $value ); }; add_action( "add_option_{$option}", $on_add_option, 10, 2 ); $on_update_option = function ( $old_value, $value ) use ( $callback ) { $callback( $old_value, $value ); }; add_action( "update_option_{$option}", $on_update_option, 10, 2 ); return function () use ( $option, $on_add_option, $on_update_option ) { remove_action( "add_option_{$option}", $on_add_option ); remove_action( "update_option_{$option}", $on_update_option ); }; } /** * Checks whether or not the option is set with a valid value. * * @since 1.2.0 * @since 1.3.0 Now relies on {@see Options_Interface::has()}. * * @return bool True on success, false on failure. */ public function has() { return $this->options->has( static::OPTION ); } /** * Gets the value of the setting. * * @since 1.2.0 * * @return mixed Value set for the option, or registered default if not set. */ public function get() { return $this->options->get( static::OPTION ); } /** * Sets the value of the setting with the given value. * * @since 1.2.0 * * @param mixed $value Setting value. Must be serializable if non-scalar. * * @return bool True on success, false on failure. */ public function set( $value ) { return $this->options->set( static::OPTION, $value ); } /** * Deletes the setting. * * @since 1.2.0 * * @return bool True on success, false on failure. */ public function delete() { return $this->options->delete( static::OPTION ); } /** * Gets the expected value type. * * Returns 'string' by default for consistency with register_setting. * Override in a sub-class if different. * * @since 1.2.0 * * @return string The type name. */ protected function get_type() { return 'string'; } /** * Gets the default value. * * For use with register_setting and fetching the default directly. * Returns false by default for consistency with get_option. * Override in a sub-class if different. * * @since 1.2.0 * * @return mixed The default value. */ protected function get_default() { return false; } /** * Gets the callback for sanitizing the setting's value before saving. * * For use internally with register_setting. * Returns `null` for consistency with the default in register_setting. * Override in a sub-class. * * @since 1.2.0 * * @return callable|null */ protected function get_sanitize_callback() { return null; } } Storage/Data_Encryption.php 0000644 00000006610 14720521221 0011743 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\Data_Encryption * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Class responsible for encrypting and decrypting data. * * @since 1.0.0 * @access private * @ignore */ final class Data_Encryption { /** * Key to use for encryption. * * @since 1.0.0 * @var string */ private $key; /** * Salt to use for encryption. * * @since 1.0.0 * @var string */ private $salt; /** * Constructor. * * @since 1.0.0 */ public function __construct() { $this->key = $this->get_default_key(); $this->salt = $this->get_default_salt(); } /** * Encrypts a value. * * If a user-based key is set, that key is used. Otherwise the default key is used. * * @since 1.0.0 * * @param string $value Value to encrypt. * @return string|bool Encrypted value, or false on failure. */ public function encrypt( $value ) { if ( ! extension_loaded( 'openssl' ) ) { return $value; } $method = 'aes-256-ctr'; $ivlen = openssl_cipher_iv_length( $method ); $iv = openssl_random_pseudo_bytes( $ivlen ); $raw_value = openssl_encrypt( $value . $this->salt, $method, $this->key, 0, $iv ); if ( ! $raw_value ) { return false; } return base64_encode( $iv . $raw_value ); } /** * Decrypts a value. * * If a user-based key is set, that key is used. Otherwise the default key is used. * * @since 1.0.0 * * @param string $raw_value Value to decrypt. * @return string|bool Decrypted value, or false on failure. */ public function decrypt( $raw_value ) { if ( ! extension_loaded( 'openssl' ) || ! is_string( $raw_value ) ) { return $raw_value; } $decoded_value = base64_decode( $raw_value, true ); if ( false === $decoded_value ) { return $raw_value; } $method = 'aes-256-ctr'; $ivlen = openssl_cipher_iv_length( $method ); $iv = substr( $decoded_value, 0, $ivlen ); $decoded_value = substr( $decoded_value, $ivlen ); $value = openssl_decrypt( $decoded_value, $method, $this->key, 0, $iv ); if ( ! $value || substr( $value, - strlen( $this->salt ) ) !== $this->salt ) { return false; } return substr( $value, 0, - strlen( $this->salt ) ); } /** * Gets the default encryption key to use. * * @since 1.0.0 * * @return string Default (not user-based) encryption key. */ private function get_default_key() { if ( defined( 'GOOGLESITEKIT_ENCRYPTION_KEY' ) && '' !== GOOGLESITEKIT_ENCRYPTION_KEY ) { return GOOGLESITEKIT_ENCRYPTION_KEY; } if ( defined( 'LOGGED_IN_KEY' ) && '' !== LOGGED_IN_KEY ) { return LOGGED_IN_KEY; } // If this is reached, you're either not on a live site or have a serious security issue. return 'das-ist-kein-geheimer-schluessel'; } /** * Gets the default encryption salt to use. * * @since 1.0.0 * * @return string Encryption salt. */ private function get_default_salt() { if ( defined( 'GOOGLESITEKIT_ENCRYPTION_SALT' ) && '' !== GOOGLESITEKIT_ENCRYPTION_SALT ) { return GOOGLESITEKIT_ENCRYPTION_SALT; } if ( defined( 'LOGGED_IN_SALT' ) && '' !== LOGGED_IN_SALT ) { return LOGGED_IN_SALT; } // If this is reached, you're either not on a live site or have a serious security issue. return 'das-ist-kein-geheimes-salz'; } } Storage/User_Aware_Trait.php 0000644 00000002660 14720521221 0012061 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Storage\User_Aware * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Trait for user aware entities. * * @since 1.18.0 * @access private * @ignore */ trait User_Aware_Trait { /** * User ID. * * @since 1.18.0 * @var int */ private $user_id; /** * Gets the associated user ID. * * @since 1.18.0 * * @return int User ID. */ public function get_user_id() { return (int) $this->user_id; } /** * Switches the current user to the one with the given ID. * * This method exists to exchange the user that is set as the current user in WordPress on the fly. In most cases * it is preferred to create a new instance of the class when dealing with multiple users. This method should only * be applied when the entire chain of class main instances need to be updated to rely on another user, i.e. when * the current WordPress user has changed. * * @since 1.18.0 * * @param int $user_id User ID. * @return callable A closure to switch back to the original user. */ public function switch_user( $user_id ) { $prev_user_id = $this->user_id; $this->user_id = (int) $user_id; return function () use ( $prev_user_id ) { $this->user_id = $prev_user_id; }; } } Storage/User_Transients.php 0000644 00000016723 14720521221 0012016 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\User_Transients * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; use Google\Site_Kit\Context; /** * Class providing access to per-user transients. * * @since 1.18.0 * @access private * @ignore */ class User_Transients implements User_Aware_Interface { /** * Plugin context. * * @since 1.18.0 * @var Context */ private $context; /** * User_Options object. * * @since 1.18.0 * @var User_Options */ private $user_options; /** * External cache group. * * @since 1.18.0 * @var string */ private $ext_cache_group; /** * Constructor. * * @since 1.18.0 * * @param Context $context Plugin context. * @param int $user_id Optional. User ID for whom transients should be managed. Default is the current user. */ public function __construct( Context $context, $user_id = 0 ) { $this->context = $context; $this->user_options = new User_Options( $context, $user_id ); $this->ext_cache_group = $context->is_network_mode() ? 'site-transient' : 'transient'; } /** * Gets the associated user ID. * * @since 1.18.0 * * @return int User ID. */ public function get_user_id() { return $this->user_options->get_user_id(); } /** * Switches the current user to the one with the given ID. * * This method exists to exchange the user that is set as the current user in WordPress on the fly. In most cases * it is preferred to create a new instance of the class when dealing with multiple users. This method should only * be applied when the entire chain of class main instances need to be updated to rely on another user, i.e. when * the current WordPress user has changed. * * @since 1.18.0 * * @param int $user_id User ID. * @return callable A closure to switch back to the original user. */ public function switch_user( $user_id ) { return $this->user_options->switch_user( $user_id ); } /** * Gets the value of the given transient. * * @since 1.18.0 * * @param string $transient Transient name. * @return mixed Value set for the transient, or false if not set. */ public function get( $transient ) { return wp_using_ext_object_cache() ? $this->get_from_cache( $transient ) : $this->get_from_user_options( $transient ); } /** * Sets the value for a transient. * * @since 1.18.0 * * @param string $transient Transient name. * @param mixed $value Transient value. * @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration). * @return bool True on success, false on failure. */ public function set( $transient, $value, $expiration = 0 ) { return wp_using_ext_object_cache() ? $this->set_in_cache( $transient, $value, $expiration ) : $this->set_in_user_options( $transient, $value, $expiration ); } /** * Deletes the given transient. * * @since 1.18.0 * * @param string $transient Transient name. * @return bool True on success, false on failure. */ public function delete( $transient ) { return wp_using_ext_object_cache() ? $this->delete_from_cache( $transient ) : $this->delete_from_user_options( $transient ); } /** * Gets prefixed transient name for an external cache. * * @since 1.18.0 * * @param string $transient Transient name. * @return string Prefixed transient name. */ private function get_transient_name_for_cache( $transient ) { $user_id = $this->get_user_id(); return $this->user_options->get_meta_key( "user_{$user_id}_{$transient}" ); } /** * Gets the value of the given transient from an external cache. * * @since 1.18.0 * * @param string $transient Transient name. * @return mixed Value set for the transient, or false if not set. */ private function get_from_cache( $transient ) { return wp_cache_get( $this->get_transient_name_for_cache( $transient ), $this->ext_cache_group ); } /** * Sets the value for a transient in an external cache. * * @since 1.18.0 * * @param string $transient Transient name. * @param mixed $value Transient value. * @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration). * @return bool True on success, false on failure. */ private function set_in_cache( $transient, $value, $expiration ) { return wp_cache_set( $this->get_transient_name_for_cache( $transient ), $value, $this->ext_cache_group, // phpcs:ignore WordPressVIPMinimum.Performance.LowExpiryCacheTime.CacheTimeUndetermined (int) $expiration ); } /** * Deletes the given transient in an external cache. * * @since 1.18.0 * * @param string $transient Transient name. * @return bool True on success, false on failure. */ private function delete_from_cache( $transient ) { return wp_cache_delete( $this->get_transient_name_for_cache( $transient ), $this->ext_cache_group ); } /** * Gets prefixed transient name. * * @since 1.18.0 * * @param string $transient Transient name. * @return string Prefixed transient name. */ private function get_transient_name_for_user_options( $transient ) { return 'googlesitekit_transient_' . $transient; } /** * Gets prefixed transient timeout name. * * @since 1.18.0 * * @param string $transient Transient name. * @return string Prefixed transient timeout name. */ private function get_transient_timeout_for_user_options( $transient ) { return 'googlesitekit_transient_timeout_' . $transient; } /** * Gets the value of the given transient. * * @since 1.18.0 * * @param string $transient Transient name. * @return mixed Value set for the transient, or false if not set. */ private function get_from_user_options( $transient ) { $prefixed_transient_timeout = $this->get_transient_timeout_for_user_options( $transient ); $timeout = $this->user_options->get( $prefixed_transient_timeout ); if ( false === $timeout || $timeout < time() ) { $this->delete( $transient ); return false; } $prefixed_transient = $this->get_transient_name_for_user_options( $transient ); return $this->user_options->get( $prefixed_transient ); } /** * Sets the value for a transient. * * @since 1.18.0 * * @param string $transient Transient name. * @param mixed $value Transient value. * @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration). * @return bool True on success, false on failure. */ private function set_in_user_options( $transient, $value, $expiration ) { $prefixed_transient_timeout = $this->get_transient_timeout_for_user_options( $transient ); $this->user_options->set( $prefixed_transient_timeout, time() + $expiration ); $prefixed_transient = $this->get_transient_name_for_user_options( $transient ); return $this->user_options->set( $prefixed_transient, $value ); } /** * Deletes the given transient. * * @since 1.18.0 * * @param string $transient Transient name. * @return bool True on success, false on failure. */ private function delete_from_user_options( $transient ) { $prefixed_transient_timeout = $this->get_transient_timeout_for_user_options( $transient ); $this->user_options->delete( $prefixed_transient_timeout ); $prefixed_transient = $this->get_transient_name_for_user_options( $transient ); return $this->user_options->delete( $prefixed_transient ); } } Storage/Setting_With_Owned_Keys_Interface.php 0000644 00000001230 14720521221 0015370 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Storage\Setting_With_Owned_Keys_Interface * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Interface for a settings class that includes owned settings. * * @since 1.16.0 * @access private * @ignore */ interface Setting_With_Owned_Keys_Interface { /** * Returns keys for owned settings. * * @since 1.16.0 * * @return array An array of keys for owned settings. */ public function get_owned_keys(); } Storage/Setting_With_Legacy_Keys_Trait.php 0000644 00000002044 14720521221 0014707 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Storage\Setting_With_Legacy_Keys_Trait * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; use Google\Site_Kit\Core\Util\Migrate_Legacy_Keys; /** * Trait for a Setting that has legacy option keys to migrate. * * @since 1.2.0 * @access private * @ignore */ trait Setting_With_Legacy_Keys_Trait { use Migrate_Legacy_Keys; /** * Registers an option filter for the setting to migrate legacy keys. * * @param array $legacy_key_map Mapping of legacy keys to current key. * * @since 1.2.0 */ protected function register_legacy_keys_migration( array $legacy_key_map ) { add_filter( 'option_' . static::OPTION, function ( $option ) use ( $legacy_key_map ) { if ( is_array( $option ) ) { return $this->migrate_legacy_keys( $option, $legacy_key_map ); } return $option; }, 0 ); } } Storage/Encrypted_User_Options.php 0000644 00000005521 14720521221 0013326 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\Encrypted_User_Options * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Class providing access to encrypted per-user options. * * @since 1.0.0 * @access private * @ignore */ final class Encrypted_User_Options implements User_Options_Interface { /** * Data Encryption API instance. * * @since 1.0.0 * @var Data_Encryption */ private $encryption; /** * User Option API instance. * * @since 1.0.0 * @var User_Options */ private $user_options; /** * Constructor. * * @since 1.0.0 * * @param User_Options $user_options User Option API instance. */ public function __construct( User_Options $user_options ) { $this->encryption = new Data_Encryption(); $this->user_options = $user_options; } /** * Gets the value of the given user option. * * @since 1.0.0 * * @param string $option User option name. * @return mixed Value set for the user option, or false if not set. */ public function get( $option ) { $raw_value = $this->user_options->get( $option ); if ( ! $raw_value ) { return false; } $data = $this->encryption->decrypt( $raw_value ); return maybe_unserialize( $data ); } /** * Sets the value for a user option. * * @since 1.0.0 * * @param string $option User option name. * @param mixed $value User option value. Must be serializable if non-scalar. * @return bool True on success, false on failure. */ public function set( $option, $value ) { if ( ! is_scalar( $value ) ) { $value = maybe_serialize( $value ); } $raw_value = $this->encryption->encrypt( $value ); if ( ! $raw_value ) { return false; } return $this->user_options->set( $option, $raw_value ); } /** * Deletes the given user option. * * @since 1.0.0 * * @param string $option User option name. * @return bool True on success, false on failure. */ public function delete( $option ) { return $this->user_options->delete( $option ); } /** * Gets the underlying meta key for the given option. * * @since 1.4.0 * * @param string $option Option name. * @return string Meta key name. */ public function get_meta_key( $option ) { return $this->user_options->get_meta_key( $option ); } /** * Gets the ID of the user that options are controlled for. * * @since 1.4.0 * * @return int User ID. */ public function get_user_id() { return $this->user_options->get_user_id(); } /** * Switches the user that options are controlled for to the one with the given ID. * * @since 1.4.0 * * @param int $user_id User ID. */ public function switch_user( $user_id ) { $this->user_options->switch_user( $user_id ); } } Storage/Post_Meta.php 0000644 00000004330 14720521221 0010550 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\Post_Meta * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Post metadata storage class. * * @since 1.33.0 * @access private * @ignore */ final class Post_Meta implements Post_Meta_Interface { /** * Gets post meta. * * @since 1.33.0 * * @param int $post_id Post ID. * @param string $key Metadata key. * @param bool $single Whether to return a single value. * @return mixed Post meta value. */ public function get( $post_id, $key, $single = false ) { return get_post_meta( $post_id, $key, $single ); } /** * Updates a post meta field based on the given post ID. * * @since 1.33.0 * * @param int $post_id Post ID. * @param string $key Metadata key. * @param mixed $value Metadata value. * @param mixed $prev_value Previous value to check before updating. If specified, only update existing metadata entries with this value. Otherwise, update all entries. * @return bool TRUE on success, otherwise FALSE. */ public function update( $post_id, $key, $value, $prev_value = '' ) { return update_post_meta( $post_id, $key, $value, $prev_value ); } /** * Adds a meta field to the given post. * * @since 1.33.0 * * @param int $post_id Post ID. * @param string $key Metadata key. * @param mixed $value Metadata value. * @param bool $unique Whether the same key should not be added. * @return int|bool Meta id on success, otherwise FALSE. */ public function add( $post_id, $key, $value, $unique = false ) { return add_post_meta( $post_id, $key, $value, $unique ); } /** * Deletes a post meta field for the given post ID. * * @since 1.33.0 * * @param int $post_id Post ID. * @param string $key Metadata key. * @param mixed $value Metadata value. If provided, rows will only be removed that match the value. * @return bool TRUE on success, otherwise FALSE. */ public function delete( $post_id, $key, $value = '' ) { return delete_post_meta( $post_id, $key, $value ); } } Storage/Cache.php 0000644 00000006513 14720521221 0007665 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\Cache * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; use Google\Site_Kit_Dependencies\Google\Service\Exception as Google_Service_Exception; /** * Class providing a server side caching framework. * * @since 1.0.0 * @access private * @ignore */ final class Cache { /** * The key for saving the global cache keys. * * @var string $global_cache_keys_key The key. */ private static $global_cache_keys_key = 'googlesitekit_global_cache_keys'; /** * The global record of cache keys used on the site. * * @var array */ private $global_cache_keys; /** * Construct the Cache class. */ public function __construct() { $this->global_cache_keys = get_option( self::$global_cache_keys_key ) ?: array(); } /** * Helper function to get the cache data. */ public function get_current_cache_data() { $cache_data = array(); // Add the global cache data. $keys = $this->get_global_cache_keys(); foreach ( $keys as $key ) { // This only retrieves fresh data because transients expire. $cache = get_transient( 'googlesitekit_' . $key ); if ( $cache ) { $cache_data[ $key ] = $cache; } else { // Remove the expired key from the global cache. $this->remove_global_cache_key( $key ); } } return $cache_data; } /** * Remove a cache key to the global record of cache keys. * * @param string $key The key to add. */ private function remove_global_cache_key( $key ) { $key_index = array_search( $key, $this->global_cache_keys, true ); if ( $key_index ) { unset( $this->global_cache_keys[ $key_index ] ); update_option( self::$global_cache_keys_key, $this->global_cache_keys, false ); } } /** * Add a cache key to the global record of cache keys. * * @param string $key The key to add. */ private function add_global_cache_key( $key ) { // Only add the key if it isn't already present. if ( ! in_array( $key, $this->global_cache_keys, true ) ) { $this->global_cache_keys[] = $key; update_option( self::$global_cache_keys_key, $this->global_cache_keys, false ); } } /** * Retrieve the global record of cache keys. * * @return array The array of cache keys used on the site. */ private function get_global_cache_keys() { return $this->global_cache_keys; } /** * Cache some data. * * @param Object $key The original data key. * @param Object $data The data to cache. */ public function set_cache_data( $key, $data ) { set_transient( 'googlesitekit_' . $key, $data, HOUR_IN_SECONDS ); $this->add_global_cache_key( $key ); } /** * Cache the results of a batch operation. * * @param array $batch_requests The original requests. * @param array $results The results to cache. */ public function cache_batch_results( $batch_requests, $results ) { $request_keys = wp_list_pluck( $batch_requests, 'key' ); foreach ( $results as $key => $result ) { if ( $result instanceof \Exception || $result instanceof Google_Service_Exception ) { continue; } $key = str_replace( 'response-', '', $key ); if ( in_array( $key, $request_keys, true ) ) { $this->set_cache_data( $key, $result ); } } } } Storage/User_Setting.php 0000644 00000006437 14720521221 0011302 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\User_Setting * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Base class for a single user setting. * * @since 1.4.0 * @access private * @ignore */ abstract class User_Setting { /** * The user option name for this setting. * Override in a sub-class. */ const OPTION = ''; /** * User_Options_Interface implementation. * * @since 1.4.0 * @var User_Options_Interface */ protected $user_options; /** * User_Setting constructor. * * @since 1.4.0 * * @param User_Options_Interface $user_options User_Options_Interface instance. */ public function __construct( User_Options_Interface $user_options ) { $this->user_options = $user_options; } /** * Registers the setting in WordPress. * * @since 1.4.0 */ public function register() { register_meta( 'user', $this->user_options->get_meta_key( static::OPTION ), array( 'type' => $this->get_type(), 'sanitize_callback' => $this->get_sanitize_callback(), 'single' => true, ) ); } /** * Checks whether or not the setting exists. * * @since 1.4.0 * * @return bool True on success, false on failure. */ public function has() { return metadata_exists( 'user', $this->user_options->get_user_id(), $this->user_options->get_meta_key( static::OPTION ) ); } /** * Gets the value of the setting. * * @since 1.4.0 * * @return mixed Value set for the option, or default if not set. */ public function get() { if ( ! $this->has() ) { return $this->get_default(); } return $this->user_options->get( static::OPTION ); } /** * Sets the value of the setting with the given value. * * @since 1.4.0 * * @param mixed $value Setting value. Must be serializable if non-scalar. * * @return bool True on success, false on failure. */ public function set( $value ) { return $this->user_options->set( static::OPTION, $value ); } /** * Deletes the setting. * * @since 1.4.0 * * @return bool True on success, false on failure. */ public function delete() { return $this->user_options->delete( static::OPTION ); } /** * Gets the expected value type. * * Returns 'string' by default for consistency with register_meta. * Override in a sub-class if different. * * Valid values are 'string', 'boolean', 'integer', 'number', 'array', and 'object'. * * @since 1.4.0 * * @return string The type name. */ protected function get_type() { return 'string'; } /** * Gets the default value. * * Returns an empty string by default for consistency with get_user_meta. * Override in a sub-class if different. * * @since 1.4.0 * * @return mixed The default value. */ protected function get_default() { return ''; } /** * Gets the callback for sanitizing the setting's value before saving. * * For use internally with register_meta. * Returns `null` for consistency with the default in register_meta. * Override in a sub-class. * * @since 1.4.0 * * @return callable|null */ protected function get_sanitize_callback() { return null; } } Storage/Post_Meta_Setting.php 0000644 00000007242 14720521221 0012252 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\Post_Meta_Setting * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Base class for a single post meta setting. * * @since 1.33.0 * @access private * @ignore */ abstract class Post_Meta_Setting { /** * The post meta key for this setting. * Override in a sub-class. */ const META_KEY = ''; /** * Post_Meta_Interface implementation. * * @since 1.33.0 * @var Post_Meta_Interface */ protected $post_meta; /** * Post_Meta_Setting constructor. * * @since 1.33.0 * * @param Post_Meta_Interface $post_meta Post_Meta_Interface instance. */ public function __construct( Post_Meta_Interface $post_meta ) { $this->post_meta = $post_meta; } /** * Registers the post setting in WordPress. * * @since 1.33.0 */ public function register() { register_meta( 'post', static::META_KEY, array( 'type' => $this->get_type(), 'sanitize_callback' => $this->get_sanitize_callback(), 'single' => true, 'show_in_rest' => $this->get_show_in_rest(), ) ); } /** * Gets the expected value type. * * Returns 'string' by default for consistency with register_meta. * Override in a sub-class if different. * * Valid values are 'string', 'boolean', 'integer', 'number', 'array', and 'object'. * * @since 1.33.0 * * @return string The type name. */ protected function get_type() { return 'string'; } /** * Gets the default value. * * Returns an empty string by default. * Override in a sub-class if different. * * @since 1.33.0 * * @return mixed The default value. */ protected function get_default() { return ''; } /** * Gets the callback for sanitizing the setting's value before saving. * * For use internally with register_meta. * Returns `null` for consistency with the default in register_meta. * Override in a sub-class. * * @since 1.33.0 * * @return callable|null Sanitize callback function. */ protected function get_sanitize_callback() { return null; } /** * Gets the `show_in_rest` value for this postmeta setting value. * * @since 1.37.0 * * @return bool|Array Any valid value for the `show_in_rest` */ protected function get_show_in_rest() { return false; } /** * Checks whether a post meta exists or not. * * @since 1.33.0 * * @param int $post_id Post ID. * @return bool True if the meta key exists, otherwise false. */ public function has( $post_id ) { return metadata_exists( 'post', $post_id, static::META_KEY ); } /** * Gets the value of the setting. * * @since 1.33.0 * * @param int $post_id Post ID. * @return mixed Value set for the setting, or default if not set. */ public function get( $post_id ) { if ( ! $this->has( $post_id ) ) { return $this->get_default(); } return $this->post_meta->get( $post_id, static::META_KEY, true ); } /** * Updates the post setting for the given post ID. * * @since 1.33.0 * * @param int $post_id Post ID. * @param mixed $value Metadata value. * @return bool TRUE on success, otherwise FALSE. */ public function set( $post_id, $value ) { return $this->post_meta->update( $post_id, static::META_KEY, $value ); } /** * Deletes the post setting for the given post ID. * * @since 1.33.0 * * @param int $post_id Post ID. * @return bool TRUE on success, otherwise FALSE. */ public function delete( $post_id ) { return $this->post_meta->delete( $post_id, static::META_KEY ); } } Storage/Setting_With_ViewOnly_Keys_Interface.php 0000644 00000001260 14720521221 0016073 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Storage\Setting_With_ViewOnly_Keys_Interface * * @package Google\Site_Kit\Core\Storage * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Interface for a settings class that includes view-only settings. * * @since 1.111.0 * @access private * @ignore */ interface Setting_With_ViewOnly_Keys_Interface { /** * Returns keys for view-only settings. * * @since 1.111.0 * * @return array An array of keys for view-only settings. */ public function get_view_only_keys(); } Storage/User_Options.php 0000644 00000005664 14720521221 0011321 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Storage\User_Options * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; use Google\Site_Kit\Context; /** * Class providing access to per-user options. * * It uses user options (which are per site) or user meta, depending on in which mode the plugin is running. * * @since 1.0.0 * @access private * @ignore */ final class User_Options implements User_Options_Interface { use User_Aware_Trait; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. * @param int $user_id Optional. User ID for whom options should be managed. Default is the current user. */ public function __construct( Context $context, $user_id = 0 ) { $this->context = $context; if ( empty( $user_id ) ) { $user_id = get_current_user_id(); } $this->user_id = (int) $user_id; } /** * Gets the value of the given user option. * * @since 1.0.0 * * @param string $option User option name. * @return mixed Value set for the user option, or false if not set. */ public function get( $option ) { $user_id = $this->get_user_id(); if ( ! $user_id ) { return false; } if ( $this->context->is_network_mode() ) { $value = get_user_meta( $user_id, $option ); if ( empty( $value ) ) { return false; } return $value[0]; } return get_user_option( $option, $user_id ); } /** * Sets the value for a user option. * * @since 1.0.0 * * @param string $option User option name. * @param mixed $value User option value. Must be serializable if non-scalar. * @return bool True on success, false on failure. */ public function set( $option, $value ) { $user_id = $this->get_user_id(); if ( ! $user_id ) { return false; } if ( $this->context->is_network_mode() ) { return (bool) update_user_meta( $user_id, $option, $value ); } return (bool) update_user_option( $user_id, $option, $value ); } /** * Deletes the given user option. * * @since 1.0.0 * * @param string $option User option name. * @return bool True on success, false on failure. */ public function delete( $option ) { $user_id = $this->get_user_id(); if ( ! $user_id ) { return false; } if ( $this->context->is_network_mode() ) { return (bool) delete_user_meta( $user_id, $option ); } return (bool) delete_user_option( $user_id, $option ); } /** * Gets the underlying meta key for the given option. * * @since 1.4.0 * * @param string $option Option name. * @return string Meta key name. */ public function get_meta_key( $option ) { global $wpdb; if ( $this->context->is_network_mode() ) { return $option; } return $wpdb->get_blog_prefix() . $option; } } Storage/Options_Interface.php 0000644 00000002516 14720521221 0012274 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Storage\Options_Interface * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Interface for Options implementations. * * @since 1.2.0 * @access private * @ignore */ interface Options_Interface { /** * Checks whether or not a value is set for the given option. * * @since 1.3.0 * * @param string $option Option name. * @return bool True if value set, false otherwise. */ public function has( $option ); /** * Gets the value of the given option. * * @since 1.2.0 * * @param string $option Option name. * @return mixed Value set for the option, or false if not set. */ public function get( $option ); /** * Sets the value for a option. * * @since 1.2.0 * * @param string $option Option name. * @param mixed $value Option value. Must be serializable if non-scalar. * @return bool True on success, false on failure. */ public function set( $option, $value ); /** * Deletes the given option. * * @since 1.2.0 * * @param string $option Option name. * @return bool True on success, false on failure. */ public function delete( $option ); } Storage/Post_Meta_Interface.php 0000644 00000003746 14720521221 0012542 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Storage\Post_Meta_Interface * * @package Google\Site_Kit\Core\Storage * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Storage; /** * Interface for Post_Meta implementations. * * @since 1.33.0 * @access private * @ignore */ interface Post_Meta_Interface { /** * Gets post meta. * * @since 1.33.0 * * @param int $post_id Post ID. * @param string $key Metadata key. * @param bool $single Whether to return a single value. * @return mixed Post meta value. */ public function get( $post_id, $key, $single = false ); /** * Updates a post meta field based on the given post ID. * * @since 1.33.0 * * @param int $post_id Post ID. * @param string $key Metadata key. * @param mixed $value Metadata value. * @param mixed $prev_value Previous value to check before updating. If specified, only update existing metadata entries with this value. Otherwise, update all entries. * @return bool TRUE on success, otherwise FALSE. */ public function update( $post_id, $key, $value, $prev_value = '' ); /** * Adds a meta field to the given post. * * @since 1.33.0 * * @param int $post_id Post ID. * @param string $key Metadata key. * @param mixed $value Metadata value. * @param bool $unique Whether the same key should not be added. * @return int|bool Meta id on success, otherwise FALSE. */ public function add( $post_id, $key, $value, $unique = false ); /** * Deletes a post meta field for the given post ID. * * @since 1.33.0 * * @param int $post_id Post ID. * @param string $key Metadata key. * @param mixed $value Metadata value. If provided, rows will only be removed that match the value. * @return bool TRUE on success, otherwise FALSE. */ public function delete( $post_id, $key, $value = '' ); } CLI/CLI_Commands.php 0000644 00000001754 14720521221 0010117 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\CLI\CLI_Commands * * @package Google\Site_Kit\Core\CLI * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\CLI; use Google\Site_Kit\Context; use WP_CLI; /** * CLI commands hub class. * * @since 1.11.0 * @access private * @ignore */ class CLI_Commands { /** * Plugin context. * * @since 1.11.0 * * @var Context */ private $context; /** * Constructor. * * @since 1.11.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Registers WP CLI commands. * * @since 1.11.0 */ public function register() { WP_CLI::add_command( 'google-site-kit auth', new Authentication_CLI_Command( $this->context ) ); WP_CLI::add_command( 'google-site-kit reset', new Reset_CLI_Command( $this->context ) ); } } CLI/Reset_CLI_Command.php 0000644 00000002513 14720521221 0011070 0 ustar 00 <?php /** * Site Kit Cache CLI Commands * * @package Google\Site_Kit\Core\CLI * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\CLI; use Google\Site_Kit\Core\Util\Reset; use Google\Site_Kit\Core\Util\Reset_Persistent; use WP_CLI; /** * Resets Site Kit Settings and Data. * * @since 1.11.0 * @access private * @ignore */ class Reset_CLI_Command extends CLI_Command { /** * Deletes options, user stored options, transients and clears object cache for stored options. * * ## OPTIONS * * [--persistent] * : Additionally deletes persistent options. * * ## EXAMPLES * * wp google-site-kit reset * wp google-site-kit reset --persistent * * @since 1.11.0 * @since 1.27.0 Added --persistent flag to delete persistent options. * * @param array $args Positional args. * @param array $assoc_args Additional flags. */ public function __invoke( $args, $assoc_args ) { $reset = new Reset( $this->context ); $reset->all(); if ( isset( $assoc_args['persistent'] ) && true === $assoc_args['persistent'] ) { $reset_persistent = new Reset_Persistent( $this->context ); $reset_persistent->all(); } WP_CLI::success( 'Settings successfully reset.' ); } } CLI/Authentication_CLI_Command.php 0000644 00000002674 14720521221 0012775 0 ustar 00 <?php /** * Site Kit Authentication CLI Commands * * @package Google\Site_Kit\Core\CLI * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\CLI; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Storage\Transients; use Google\Site_Kit\Core\Authentication\Authentication; use WP_CLI; /** * Manages Site Kit user authentication for Google APIs. * * @since 1.11.0 * @access private * @ignore */ class Authentication_CLI_Command extends CLI_Command { /** * Disconnects a user from Site Kit, removing their relevant user options and revoking their token. * * ## OPTIONS * * --id=<id> * : User ID to disconnect. * * ## EXAMPLES * * wp google-site-kit auth disconnect --id=11 * * @alias revoke * * @since 1.11.0 * * @param array $args Array of arguments. * @param array $assoc_args Array of associated arguments. */ public function disconnect( $args, $assoc_args ) { $user_id = absint( $assoc_args['id'] ); $authentication = new Authentication( $this->context, new Options( $this->context ), new User_Options( $this->context, $user_id ), new Transients( $this->context ) ); $authentication->disconnect(); WP_CLI::success( sprintf( 'User with ID %d successfully disconnected.', $user_id ) ); } } CLI/CLI_Command.php 0000644 00000001326 14720521221 0007727 0 ustar 00 <?php /** * Site Kit CLI Command * * @package Google\Site_Kit\Core\CLI * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\CLI; use Google\Site_Kit\Context; use WP_CLI_Command; /** * Base CLI Command class. * * @since 1.11.0 * @access private * @ignore */ class CLI_Command extends WP_CLI_Command { /** * Plugin context. * * @since 1.11.0 * * @var Context */ protected $context; /** * Constructor. * * @since 1.11.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } } Feature_Tours/Feature_Tours.php 0000644 00000002646 14720521221 0012637 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Feature_Tours\Feature_Tours * * @package Google\Site_Kit\Core\Feature_Tours * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Feature_Tours; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\User_Options; /** * Class for handling feature tours. * * @since 1.27.0 * @access private * @ignore */ class Feature_Tours { /** * Dismissed_Tours instance. * * @since 1.27.0 * @var Dismissed_Tours */ protected $dismissed_tours; /** * REST_Feature_Tours_Controller instance. * * @since 1.27.0 * @var REST_Feature_Tours_Controller */ protected $rest_controller; /** * Constructor. * * @since 1.27.0 * * @param Context $context Plugin context. * @param User_Options $user_options Optional. User option API. Default is a new instance. */ public function __construct( Context $context, User_Options $user_options = null ) { $this->dismissed_tours = new Dismissed_Tours( $user_options ?: new User_Options( $context ) ); $this->rest_controller = new REST_Feature_Tours_Controller( $this->dismissed_tours ); } /** * Registers functionality through WordPress hooks. * * @since 1.27.0 */ public function register() { $this->dismissed_tours->register(); $this->rest_controller->register(); } } Feature_Tours/Dismissed_Tours.php 0000644 00000003574 14720521221 0013171 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Feature_Tours\Dismissed_Tours * * @package Google\Site_Kit\Core\Feature_Tours * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Feature_Tours; use Closure; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class for representing a user's dismissed feature tours. * * @since 1.27.0 * @access private * @ignore */ class Dismissed_Tours extends User_Setting { /** * The user option name for this setting. * * @note This option is prefixed differently * so that it will persist across disconnect/reset. */ const OPTION = 'googlesitekitpersistent_dismissed_tours'; /** * Adds one or more tours to the list of dismissed tours. * * @since 1.27.0 * * @param string ...$tour_slug The tour identifier to dismiss. */ public function add( ...$tour_slug ) { $value = array_merge( $this->get(), $tour_slug ); $this->set( $value ); } /** * Gets the value of the setting. * * @since 1.27.0 * * @return array Value set for the option, or default if not set. */ public function get() { $value = parent::get(); return is_array( $value ) ? $value : array(); } /** * Gets the expected value type. * * @since 1.27.0 * * @return string The type name. */ protected function get_type() { return 'array'; } /** * Gets the default value. * * @since 1.27.0 * * @return array The default value. */ protected function get_default() { return array(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.27.0 * * @return Closure */ protected function get_sanitize_callback() { return function ( $value ) { return is_array( $value ) ? array_values( array_unique( $value ) ) : $this->get(); }; } } Feature_Tours/REST_Feature_Tours_Controller.php 0000644 00000006005 14720521221 0015670 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Feature_Tours\REST_Feature_Tours_Controller * * @package Google\Site_Kit\Core\Feature_Tours * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Feature_Tours; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling feature tour rest routes. * * @since 1.27.0 * @access private * @ignore */ class REST_Feature_Tours_Controller { /** * Dismissed_Tours instance. * * @since 1.27.0 * @var Dismissed_Tours */ protected $dismissed_tours; /** * Constructor. * * @since 1.27.0 * * @param Dismissed_Tours $dismissed_tours Dismissed tours instance. */ public function __construct( Dismissed_Tours $dismissed_tours ) { $this->dismissed_tours = $dismissed_tours; } /** * Registers functionality through WordPress hooks. * * @since 1.27.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { $feature_tour_routes = array( '/' . REST_Routes::REST_ROOT . '/core/user/data/dismissed-tours', ); return array_merge( $paths, $feature_tour_routes ); } ); } /** * Gets REST route instances. * * @since 1.27.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_dismiss_tour = function () { return current_user_can( Permissions::AUTHENTICATE ) || current_user_can( Permissions::VIEW_SHARED_DASHBOARD ); }; return array( new REST_Route( 'core/user/data/dismissed-tours', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->dismissed_tours->get() ); }, 'permission_callback' => $can_dismiss_tour, ) ), new REST_Route( 'core/user/data/dismiss-tour', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; if ( empty( $data['slug'] ) ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'slug' ), array( 'status' => 400 ) ); } $this->dismissed_tours->add( $data['slug'] ); return new WP_REST_Response( $this->dismissed_tours->get() ); }, 'permission_callback' => $can_dismiss_tour, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, ), ), ) ), ); } } Admin_Bar/Admin_Bar_Enabled.php 0000644 00000002514 14720521221 0012315 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin_Bar\Admin_Bar_Enabled * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin_Bar; use Google\Site_Kit\Core\Storage\Setting; /** * Class handling the admin bar menu settings. * * @since 1.39.0 * @access private * @ignore */ class Admin_Bar_Enabled extends Setting { /** * The option_name for this setting. */ const OPTION = 'googlesitekit_admin_bar_menu_enabled'; /** * Gets the value of the setting. * * @since 1.39.0 * * @return bool Value set for the option, or registered default if not set. */ public function get() { return (bool) parent::get(); } /** * Gets the expected value type. * * @since 1.39.0 * * @return string The type name. */ protected function get_type() { return 'boolean'; } /** * Gets the default value. * * @since 1.39.0 * * @return boolean The default value. */ protected function get_default() { return true; } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.39.0 * * @return callable The callable sanitize callback. */ protected function get_sanitize_callback() { return 'boolval'; } } Admin_Bar/Admin_Bar.php 0000644 00000024135 14720521221 0010706 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Admin_Bar\Admin_Bar * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Admin_Bar; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Assets\Assets; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; use Google\Site_Kit\Core\Util\Requires_Javascript_Trait; use WP_REST_Server; use WP_REST_Request; /** * Class handling the plugin's admin bar menu. * * @since 1.0.0 * @access private * @ignore */ final class Admin_Bar { use Requires_Javascript_Trait; use Method_Proxy_Trait; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Assets Instance. * * @since 1.0.0 * @var Assets */ private $assets; /** * Modules instance. * * @since 1.4.0 * @var Modules */ private $modules; /** * Admin_Bar_Enabled instance. * * @since 1.39.0 * @var Admin_Bar_Enabled */ private $admin_bar_enabled; /** * Authentication instance. * * @since 1.120.0 * @var Authentication */ private $authentication; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. * @param Assets $assets Optional. Assets API instance. Default is a new instance. * @param Modules $modules Optional. Modules instance. Default is a new instance. */ public function __construct( Context $context, Assets $assets = null, Modules $modules = null ) { $this->context = $context; $this->assets = $assets ?: new Assets( $this->context ); $this->modules = $modules ?: new Modules( $this->context ); $options = new Options( $this->context ); $this->admin_bar_enabled = new Admin_Bar_Enabled( $options ); $this->authentication = new Authentication( $this->context ); } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ public function register() { add_action( 'admin_bar_menu', $this->get_method_proxy( 'add_menu_button' ), 99 ); add_action( 'admin_enqueue_scripts', $this->get_method_proxy( 'enqueue_assets' ), 40 ); add_action( 'wp_enqueue_scripts', $this->get_method_proxy( 'enqueue_assets' ), 40 ); // TODO: This can be removed at some point, see https://github.com/ampproject/amp-wp/pull/4001. add_filter( 'amp_dev_mode_element_xpaths', array( $this, 'add_amp_dev_mode' ) ); add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $routes ) { return array_merge( $routes, array( '/' . REST_Routes::REST_ROOT . '/core/site/data/admin-bar-settings', ) ); } ); $this->admin_bar_enabled->register(); } /** * Add data-ampdevmode attributes to the elements that need it. * * @see \Google\Site_Kit\Core\Assets\Assets::get_assets() The 'googlesitekit' string is added to all inline scripts. * @see \Google\Site_Kit\Core\Assets\Assets::add_amp_dev_mode_attributes() The data-ampdevmode attribute is added to registered scripts/styles here. * * @param string[] $xpath_queries XPath queries for elements that should get the data-ampdevmode attribute. * @return string[] XPath queries. */ public function add_amp_dev_mode( $xpath_queries ) { $xpath_queries[] = '//script[ contains( text(), "googlesitekit" ) ]'; return $xpath_queries; } /** * Render the Adminbar button. * * @since 1.0.0 * * @param object $wp_admin_bar The WP AdminBar object. */ private function add_menu_button( $wp_admin_bar ) { if ( ! $this->is_active() ) { return; } $args = array( 'id' => 'google-site-kit', 'title' => '<span class="googlesitekit-wp-adminbar__icon"></span> <span class="googlesitekit-wp-adminbar__label">Site Kit</span>', 'href' => '#', 'meta' => array( 'class' => 'menupop googlesitekit-wp-adminbar', ), ); if ( $this->context->is_amp() && ! $this->is_amp_dev_mode() ) { $post = get_post(); if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) { return; } $args['href'] = add_query_arg( 'googlesitekit_adminbar_open', 'true', get_edit_post_link( $post->ID ) ); } else { $args['meta']['html'] = $this->menu_markup(); } $wp_admin_bar->add_node( $args ); } /** * Checks if admin bar menu is active and displaying. * * @since 1.0.0 * * @return bool True if Admin bar should display, False when it's not. */ public function is_active() { // Only active if the admin bar is showing. if ( ! is_admin_bar_showing() ) { return false; } // In the admin, never show the admin bar except for the post editing screen. if ( is_admin() && ! $this->is_admin_post_screen() ) { return false; } if ( ! current_user_can( Permissions::VIEW_ADMIN_BAR_MENU ) ) { return false; } $enabled = $this->admin_bar_enabled->get(); if ( ! $enabled ) { return false; } // No entity was identified - don't display the admin bar menu. $entity = $this->context->get_reference_entity(); if ( ! $entity ) { return false; } // Check permissions for viewing post data. if ( in_array( $entity->get_type(), array( 'post', 'blog' ), true ) && $entity->get_id() ) { // If a post entity, check permissions for that post. if ( ! current_user_can( Permissions::VIEW_POST_INSIGHTS, $entity->get_id() ) ) { return false; } } $current_url = $entity->get_url(); /** * Filters whether the Site Kit admin bar menu should be displayed. * * The admin bar menu is only shown when there is data for the current URL and the current * user has the correct capability to view the data. Modules use this filter to indicate the * presence of valid data. * * @since 1.0.0 * * @param bool $display Whether to display the admin bar menu. * @param string $current_url The URL of the current request. */ return apply_filters( 'googlesitekit_show_admin_bar_menu', true, $current_url ); } /** * Checks if current screen is an admin edit post screen. * * @since 1.0.0 */ private function is_admin_post_screen() { $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false; // No screen context available. if ( ! $current_screen instanceof \WP_Screen ) { return false; } // Only show for post screens. if ( 'post' !== $current_screen->base ) { return false; } // Don't show for new post screen. if ( 'add' === $current_screen->action ) { return false; } return true; } /** * Checks whether AMP dev mode is enabled. * * This is only relevant if the current context is AMP. * * @since 1.1.0 * @since 1.120.0 Added the `data-view-only` attribute. * * @return bool True if AMP dev mode is enabled, false otherwise. */ private function is_amp_dev_mode() { return function_exists( 'amp_is_dev_mode' ) && amp_is_dev_mode(); } /** * Return the Adminbar content markup. * * @since 1.0.0 */ private function menu_markup() { // Start buffer output. ob_start(); $is_view_only = ! $this->authentication->is_authenticated(); ?> <div class="googlesitekit-plugin ab-sub-wrapper"> <?php $this->render_noscript_html(); ?> <div id="js-googlesitekit-adminbar" data-view-only="<?php echo esc_attr( $is_view_only ); ?>" class="googlesitekit-adminbar"> <?php /** * Display server rendered content before JS-based adminbar modules. * * @since 1.0.0 */ do_action( 'googlesitekit_adminbar_modules_before' ); ?> <section id="js-googlesitekit-adminbar-modules" class="googlesitekit-adminbar-modules"></section> <?php /** * Display server rendered content after JS-based adminbar modules. * * @since 1.0.0 */ do_action( 'googlesitekit_adminbar_modules_after' ); ?> </div> </div> <?php // Get the buffer output. $markup = ob_get_clean(); return $markup; } /** * Enqueues assets. * * @since 1.39.0 */ private function enqueue_assets() { if ( ! $this->is_active() ) { return; } // Enqueue styles. $this->assets->enqueue_asset( 'googlesitekit-adminbar-css' ); if ( $this->context->is_amp() && ! $this->is_amp_dev_mode() ) { // AMP Dev Mode support was added in v1.4, and if it is not enabled then short-circuit since scripts will be invalid. return; } // Enqueue scripts. $this->assets->enqueue_asset( 'googlesitekit-adminbar' ); $this->modules->enqueue_assets(); } /** * Gets related REST routes. * * @since 1.39.0 * * @return array List of REST_Route objects. */ private function get_rest_routes() { $can_authenticate = function () { return current_user_can( Permissions::AUTHENTICATE ); }; $settings_callback = function () { return array( 'enabled' => $this->admin_bar_enabled->get(), ); }; return array( new REST_Route( 'core/site/data/admin-bar-settings', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => $settings_callback, 'permission_callback' => $can_authenticate, ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) use ( $settings_callback ) { $data = $request->get_param( 'data' ); if ( isset( $data['enabled'] ) ) { $this->admin_bar_enabled->set( ! empty( $data['enabled'] ) ); } return $settings_callback( $request ); }, 'permission_callback' => $can_authenticate, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'enabled' => array( 'type' => 'boolean', 'required' => false, ), ), ), ), ), ) ), ); } } Util/URL.php 0000644 00000012627 14720521221 0006640 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\URL * * @package Google\Site_Kit\Core\Util * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Class for custom URL parsing methods. * * @since 1.84.0 * @access private * @ignore */ class URL { /** * Prefix for Punycode-encoded hostnames. */ const PUNYCODE_PREFIX = 'xn--'; /** * Parses URLs with UTF-8 multi-byte characters, * otherwise similar to `wp_parse_url()`. * * @since 1.84.0 * * @param string $url The URL to parse. * @param int $component The specific component to retrieve. Use one of the PHP * predefined constants to specify which one. * Defaults to -1 (= return all parts as an array). * @return mixed False on parse failure; Array of URL components on success; * When a specific component has been requested: null if the component * doesn't exist in the given URL; a string or - in the case of * PHP_URL_PORT - integer when it does. See parse_url()'s return values. */ public static function parse( $url, $component = -1 ) { $url = (string) $url; if ( mb_strlen( $url, 'UTF-8' ) === strlen( $url ) ) { return wp_parse_url( $url, $component ); } $to_unset = array(); if ( '//' === mb_substr( $url, 0, 2 ) ) { $to_unset[] = 'scheme'; $url = 'placeholder:' . $url; } elseif ( '/' === mb_substr( $url, 0, 1 ) ) { $to_unset[] = 'scheme'; $to_unset[] = 'host'; $url = 'placeholder://placeholder' . $url; } $parts = self::mb_parse_url( $url ); if ( false === $parts ) { // Parsing failure. return $parts; } // Remove the placeholder values. foreach ( $to_unset as $key ) { unset( $parts[ $key ] ); } return _get_component_from_parsed_url_array( $parts, $component ); } /** * Replacement for parse_url which is UTF-8 multi-byte character aware. * * @since 1.84.0 * * @param string $url The URL to parse. * @return mixed False on parse failure; Array of URL components on success */ private static function mb_parse_url( $url ) { $enc_url = preg_replace_callback( '%[^:/@?&=#]+%usD', function ( $matches ) { return rawurlencode( $matches[0] ); }, $url ); $parts = parse_url( $enc_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url if ( false === $parts ) { return $parts; } foreach ( $parts as $name => $value ) { $parts[ $name ] = urldecode( $value ); } return $parts; } /** * Permutes site URL to cover all different variants of it (not considering the path). * * @since 1.99.0 * * @param string $site_url Site URL to get permutations for. * @return array List of permutations. */ public static function permute_site_url( $site_url ) { $hostname = self::parse( $site_url, PHP_URL_HOST ); $path = self::parse( $site_url, PHP_URL_PATH ); return array_reduce( self::permute_site_hosts( $hostname ), function ( $urls, $host ) use ( $path ) { $host_with_path = $host . $path; array_push( $urls, "https://$host_with_path", "http://$host_with_path" ); return $urls; }, array() ); } /** * Generates common variations of the given hostname. * * Returns a list of hostnames that includes: * - (if IDN) in Punycode encoding * - (if IDN) in Unicode encoding * - with and without www. subdomain (including IDNs) * * @since 1.99.0 * * @param string $hostname Hostname to generate variations of. * @return string[] Hostname variations. */ public static function permute_site_hosts( $hostname ) { if ( ! $hostname || ! is_string( $hostname ) ) { return array(); } // See \Requests_IDNAEncoder::is_ascii. $is_ascii = preg_match( '/(?:[^\x00-\x7F])/', $hostname ) !== 1; $is_www = 0 === strpos( $hostname, 'www.' ); // Normalize hostname without www. $hostname = $is_www ? substr( $hostname, strlen( 'www.' ) ) : $hostname; $hosts = array( $hostname, "www.$hostname" ); try { // An ASCII hostname can only be non-IDN or punycode-encoded. if ( $is_ascii ) { // If the hostname is in punycode encoding, add the decoded version to the list of hosts. if ( 0 === strpos( $hostname, self::PUNYCODE_PREFIX ) || false !== strpos( $hostname, '.' . self::PUNYCODE_PREFIX ) ) { // Ignoring phpcs here, and not passing the variant so that the correct default can be selected by PHP based on the // version. INTL_IDNA_VARIANT_UTS46 for PHP>=7.4, INTL_IDNA_VARIANT_2003 for PHP<7.4. // phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet $host_decoded = idn_to_utf8( $hostname ); array_push( $hosts, $host_decoded, "www.$host_decoded" ); } } else { // If it's not ASCII, then add the punycode encoded version. // Ignoring phpcs here, and not passing the variant so that the correct default can be selected by PHP based on the // version. INTL_IDNA_VARIANT_UTS46 for PHP>=7.4, INTL_IDNA_VARIANT_2003 for PHP<7.4. // phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet $host_encoded = idn_to_ascii( $hostname ); array_push( $hosts, $host_encoded, "www.$host_encoded" ); } } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // Do nothing. } return $hosts; } } Util/WP_Context_Switcher_Trait.php 0000644 00000004200 14720521221 0013227 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Util\WP_Context_Switcher_Trait * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Trait for temporarily switching WordPress context, e.g. from admin to frontend. * * @since 1.16.0 * @access private * @ignore */ trait WP_Context_Switcher_Trait { /** * Switches to WordPress frontend context if necessary. * * Context is only switched if WordPress is not already in frontend context. Context should only ever be switched * temporarily. Call the returned closure as soon as possible after to restore the original context. * * @since 1.16.0 * * @return callable Closure that restores context. */ protected static function with_frontend_context() { $restore = self::get_restore_current_screen_closure(); if ( ! is_admin() ) { return $restore; } self::switch_current_screen( 'front' ); return $restore; } /** * Switches the current WordPress screen via the given screen ID or hook name. * * @since 1.16.0 * * @param string $screen_id WordPress screen ID. */ private static function switch_current_screen( $screen_id ) { global $current_screen; require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php'; require_once ABSPATH . 'wp-admin/includes/screen.php'; $current_screen = \WP_Screen::get( $screen_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } /** * Returns the closure to restore the current screen. * * Calling the closure will restore the `$current_screen` global to what it was set to at the time of calling * this method. * * @since 1.16.0 * * @return callable Closure that restores context. */ private static function get_restore_current_screen_closure() { global $current_screen; $original_screen = $current_screen; return static function () use ( $original_screen ) { global $current_screen; $current_screen = $original_screen; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited }; } } Util/Sanitize.php 0000644 00000002054 14720521221 0007755 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Sanitize * * @package Google\Site_Kit\Core\Util * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Utility class for sanitizing data. * * @since 1.93.0 * @access private * @ignore */ class Sanitize { /** * Filters empty or non-string elements from a given array. * * @since 1.93.0 * * @param array $elements Array to check. * @return array Empty array or a filtered array containing only non-empty strings. */ public static function sanitize_string_list( $elements = array() ) { if ( ! is_array( $elements ) ) { $elements = array( $elements ); } if ( empty( $elements ) ) { return array(); } $filtered_elements = array_filter( $elements, function ( $element ) { return is_string( $element ) && ! empty( $element ); } ); // Avoid index gaps for filtered values. return array_values( $filtered_elements ); } } Util/Google_URL_Matcher_Trait.php 0000644 00000006030 14720521221 0012731 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Util\Google_URL_Matcher_Trait * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Trait for matching URLs and domains for Google Site Verification and Search Console. * * @since 1.6.0 * @access private * @ignore */ trait Google_URL_Matcher_Trait { /** * Compares two URLs for whether they qualify for a Site Verification or Search Console URL match. * * In order for the URLs to be considered a match, they have to be fully equal, except for a potential * trailing slash in one of them, which will be ignored. * * @since 1.6.0 * * @param string $url The URL. * @param string $compare The URL to compare. * @return bool True if the URLs are considered a match, false otherwise. */ protected function is_url_match( $url, $compare ) { $url = untrailingslashit( $url ); $compare = untrailingslashit( $compare ); $url_normalizer = new Google_URL_Normalizer(); $url = $url_normalizer->normalize_url( $url ); $compare = $url_normalizer->normalize_url( $compare ); return $url === $compare; } /** * Compares two domains for whether they qualify for a Site Verification or Search Console domain match. * * The value to compare may be either a domain or a full URL. If the latter, its scheme and a potential trailing * slash will be stripped out before the comparison. * * In order for the comparison to be considered a match then, the domains have to fully match, except for a * potential "www." prefix, which will be ignored. If the value to compare is a full URL and includes a path other * than just a trailing slash, it will not be a match. * * @since 1.6.0 * * @param string $domain A domain. * @param string $compare The domain or URL to compare. * @return bool True if the URLs/domains are considered a match, false otherwise. */ protected function is_domain_match( $domain, $compare ) { $domain = $this->strip_domain_www( $domain ); $compare = $this->strip_domain_www( $this->strip_url_scheme( untrailingslashit( $compare ) ) ); $url_normalizer = new Google_URL_Normalizer(); $domain = $url_normalizer->normalize_url( $domain ); $compare = $url_normalizer->normalize_url( $compare ); return $domain === $compare; } /** * Strips the scheme from a URL. * * @since 1.6.0 * * @param string $url URL with or without scheme. * @return string The passed $url without its scheme. */ protected function strip_url_scheme( $url ) { return preg_replace( '#^(\w+:)?//#', '', $url ); } /** * Strips the "www." prefix from a domain. * * @since 1.6.0 * * @param string $domain Domain with or without "www." prefix. * @return string The passed $domain without "www." prefix. */ protected function strip_domain_www( $domain ) { return preg_replace( '/^www\./', '', $domain ); } } Util/Activation_Notice.php 0000644 00000010155 14720521221 0011572 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Activation_Notice * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Admin\Notice; use Google\Site_Kit\Core\Assets\Assets; use Google\Site_Kit\Core\Util\Requires_Javascript_Trait; /** * Class handling plugin activation. * * @since 1.10.0 Renamed from Activation. * @access private * @ignore */ final class Activation_Notice { use Requires_Javascript_Trait; /** * Plugin context. * * @since 1.10.0 * @var Context */ private $context; /** * Activation flag instance. * * @since 1.10.0 * @var Activation_Flag */ protected $activation_flag; /** * Assets API instance. * * @since 1.10.0 * @var Assets */ protected $assets; /** * Constructor. * * @since 1.10.0 * * @param Context $context Plugin context. * @param Activation_Flag $activation_flag Activation flag instance. * @param Assets $assets Optional. The Assets API instance. Default is a new instance. */ public function __construct( Context $context, Activation_Flag $activation_flag, Assets $assets = null ) { $this->context = $context; $this->activation_flag = $activation_flag; $this->assets = $assets ?: new Assets( $this->context ); } /** * Registers functionality through WordPress hooks. * * @since 1.10.0 */ public function register() { add_filter( 'googlesitekit_admin_notices', function ( $notices ) { $notices[] = $this->get_activation_notice(); return $notices; } ); add_action( 'admin_enqueue_scripts', function ( $hook_suffix ) { if ( 'plugins.php' !== $hook_suffix || ! $this->activation_flag->get_activation_flag( is_network_admin() ) ) { return; } /** * Prevent the default WordPress "Plugin Activated" notice from rendering. * * @link https://github.com/WordPress/WordPress/blob/e1996633228749cdc2d92bc04cc535d45367bfa4/wp-admin/plugins.php#L569-L570 */ unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.VIP.SuperGlobalInputUsage $this->assets->enqueue_asset( 'googlesitekit-admin-css' ); $this->assets->enqueue_asset( 'googlesitekit-activation' ); } ); } /** * Gets the admin notice indicating that the plugin has just been activated. * * @since 1.10.0 * * @return Notice Admin notice instance. */ private function get_activation_notice() { return new Notice( 'activated', array( 'content' => function () { ob_start(); ?> <div class="googlesitekit-plugin"> <?php $this->render_noscript_html(); ?> <div id="js-googlesitekit-activation" class="googlesitekit-activation googlesitekit-activation--loading"> <div class="googlesitekit-activation__loading"> <div role="progressbar" class="mdc-linear-progress mdc-linear-progress--indeterminate"> <div class="mdc-linear-progress__buffering-dots"></div> <div class="mdc-linear-progress__buffer"></div> <div class="mdc-linear-progress__bar mdc-linear-progress__primary-bar"> <span class="mdc-linear-progress__bar-inner"></span> </div> <div class="mdc-linear-progress__bar mdc-linear-progress__secondary-bar"> <span class="mdc-linear-progress__bar-inner"></span> </div> </div> </div> </div> </div> <?php return ob_get_clean(); }, 'type' => Notice::TYPE_SUCCESS, 'active_callback' => function ( $hook_suffix ) { if ( 'plugins.php' !== $hook_suffix ) { return false; } $network_wide = is_network_admin(); $flag = $this->activation_flag->get_activation_flag( $network_wide ); if ( $flag ) { // Unset the flag so that the notice only shows once. $this->activation_flag->delete_activation_flag( $network_wide ); } return $flag; }, 'dismissible' => true, ) ); } } Util/Entity.php 0000644 00000006316 14720521221 0007450 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Entity * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Class representing an entity. * * An entity in Site Kit terminology is based on a canonical URL, i.e. every * canonical frontend URL has an associated entity. * * An entity may also have a type, if it can be determined. * Possible types are e.g. 'post' for a WordPress post (of any post type!), * 'term' for a WordPress term (of any taxonomy!), 'blog' for the blog archive, * 'date' for a date-based archive etc. * * For specific entity types, the entity will also have a title, and it may * even have an ID. For example: * * For a type of 'post', the entity ID will be the post ID and the entity * title will be the post title. * * For a type of 'term', the entity ID will be the term ID and the entity * title will be the term title. * * For a type of 'date', there will be no entity ID, but the entity title * will be the title of the date-based archive. * * @since 1.7.0 * @access private * @ignore */ final class Entity { /** * The entity URL. * * @since 1.7.0 * @var string */ private $url; /** * The entity type. * * @since 1.7.0 * @var string */ private $type; /** * The entity title. * * @since 1.7.0 * @var string */ private $title; /** * The entity ID. * * @since 1.7.0 * @var int */ private $id; /** * Entity URL sub-variant. * * @since 1.42.0 * @var string */ private $mode; /** * Constructor. * * @since 1.7.0 * * @param string $url The entity URL. * @param array $args { * Optional. Additional entity arguments. * * @type string $type The entity type. * @type string $title The entity title. * @type int $id The entity ID. * @type string $mode Entity URL sub-variant. * } */ public function __construct( $url, array $args = array() ) { $args = array_merge( array( 'type' => '', 'title' => '', 'id' => 0, 'mode' => '', ), $args ); $this->url = $url; $this->type = (string) $args['type']; $this->title = (string) $args['title']; $this->id = (int) $args['id']; $this->mode = (string) $args['mode']; } /** * Gets the entity URL. * * @since 1.7.0 * * @return string The entity URL. */ public function get_url() { return $this->url; } /** * Gets the entity type. * * @since 1.7.0 * * @return string The entity type, or empty string if unknown. */ public function get_type() { return $this->type; } /** * Gets the entity title. * * @since 1.7.0 * * @return string The entity title, or empty string if unknown. */ public function get_title() { return $this->title; } /** * Gets the entity ID. * * @since 1.7.0 * * @return int The entity ID, or 0 if unknown. */ public function get_id() { return $this->id; } /** * Gets the entity URL sub-variant. * * @since 1.42.0 * * @return string The entity title, or empty string if unknown. */ public function get_mode() { return $this->mode; } } Util/Exit_Handler.php 0000644 00000001504 14720521221 0010534 0 ustar 00 <?php /** * Exit_Handler * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Exit_Handler class. * * @since 1.1.0 * @access private * @ignore */ class Exit_Handler { /** * Invokes the handler. * * @since 1.1.0 */ public function invoke() { $callback = static function () { exit; }; if ( defined( 'GOOGLESITEKIT_TESTS' ) ) { /** * Allows the callback to be filtered during tests. * * @since 1.1.0 * @param \Closure $callback Exit handler callback. */ $callback = apply_filters( 'googlesitekit_exit_handler', $callback ); } if ( $callback instanceof \Closure ) { $callback(); } } } Util/Scopes.php 0000644 00000005137 14720521221 0007430 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Scopes * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Utility class for handling generic OAuth scope functions. * * @since 1.9.0 * @access private * @ignore */ class Scopes { /** * Mapping of requested scope to satisfying scopes. * * @since 1.9.0 * * @var array */ protected static $map = array( 'https://www.googleapis.com/auth/adsense.readonly' => array( 'https://www.googleapis.com/auth/adsense', ), 'https://www.googleapis.com/auth/analytics.readonly' => array( 'requires_all' => true, 'https://www.googleapis.com/auth/analytics', 'https://www.googleapis.com/auth/analytics.edit', ), 'https://www.googleapis.com/auth/tagmanager.readonly' => array( 'https://www.googleapis.com/auth/tagmanager.edit.containers', ), 'https://www.googleapis.com/auth/webmasters.readonly' => array( 'https://www.googleapis.com/auth/webmasters', ), ); /** * Tests if the given scope is satisfied by the given list of granted scopes. * * @since 1.9.0 * * @param string $scope OAuth scope to test for. * @param string[] $granted_scopes Available OAuth scopes to test the individual scope against. * @return bool True if the given scope is satisfied, otherwise false. */ public static function is_satisfied_by( $scope, array $granted_scopes ) { if ( in_array( $scope, $granted_scopes, true ) ) { return true; } if ( empty( self::$map[ $scope ] ) ) { return false; } $satisfying_scopes = array_filter( self::$map[ $scope ], 'is_string' ); if ( ! empty( self::$map[ $scope ]['requires_all'] ) ) { // Return true if all satisfying scopes are present, otherwise false. return ! array_diff( $satisfying_scopes, $granted_scopes ); } // Return true if any of the scopes are present, otherwise false. return (bool) array_intersect( $satisfying_scopes, $granted_scopes ); } /** * Tests if all the given scopes are satisfied by the list of granted scopes. * * @since 1.9.0 * * @param string[] $scopes OAuth scopes to test. * @param string[] $granted_scopes OAuth scopes to test $scopes against. * @return bool True if all given scopes are satisfied, otherwise false. */ public static function are_satisfied_by( array $scopes, array $granted_scopes ) { foreach ( $scopes as $scope ) { if ( ! self::is_satisfied_by( $scope, $granted_scopes ) ) { return false; } } return true; } } Util/Migration_1_8_1.php 0000644 00000015544 14720521221 0011017 0 ustar 00 <?php /** * Migration for 1.8.1 * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit\Core\Authentication\Profile; use Google\Site_Kit\Core\Authentication\Verification_File; use Google\Site_Kit\Core\Authentication\Verification_Meta; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use WP_User; use WP_Error; /** * Class Migration_1_8_1 * * @since 1.8.1 * @access private * @ignore */ class Migration_1_8_1 { /** * Target DB version. */ const DB_VERSION = '1.8.1'; /** * Context instance. * * @since 1.8.1 * @var Context */ protected $context; /** * Options instance. * * @since 1.8.1 * @var Options */ protected $options; /** * User_Options instance. * * @since 1.8.1 * @var User_Options */ protected $user_options; /** * Authentication instance. * * @since 1.8.1 * @var Authentication */ protected $authentication; /** * Constructor. * * @since 1.8.1 * * @param Context $context Plugin context instance. * @param Options $options Optional. Options instance. * @param User_Options $user_options Optional. User_Options instance. * @param Authentication $authentication Optional. Authentication instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null, User_Options $user_options = null, Authentication $authentication = null ) { $this->context = $context; $this->options = $options ?: new Options( $this->context ); $this->user_options = $user_options ?: new User_Options( $this->context ); $this->authentication = $authentication ?: new Authentication( $this->context, $this->options, $this->user_options ); } /** * Registers hooks. * * @since 1.8.1 */ public function register() { add_action( 'admin_init', array( $this, 'migrate' ) ); } /** * Migrates the DB. * * @since 1.8.1 */ public function migrate() { $db_version = $this->options->get( 'googlesitekit_db_version' ); // Do not run if database version already updated. if ( $db_version && version_compare( $db_version, self::DB_VERSION, '>=' ) ) { return; } // Only run routine if using the authentication service, otherwise it // is irrelevant. if ( ! $this->authentication->credentials()->using_proxy() ) { return; } // Only run routine once site credentials present, otherwise it is not // possible to connect to the authentication service. if ( ! $this->authentication->credentials()->has() ) { return; } $this->clear_and_flag_unauthorized_verified_users(); // Update database version. $this->options->set( 'googlesitekit_db_version', self::DB_VERSION ); } /** * Checks whether there are any users that are verified without proper * authorization, clear their Site Kit data, and flag them on the * authentication service. * * @since 1.8.1 * * @return boolean|WP_Error True on success, WP_Error on failure. */ private function clear_and_flag_unauthorized_verified_users() { // Detect all unauthorized verified users and clean their Site Kit data. $unauthorized_identifiers = $this->clear_unauthorized_verified_users(); // If no unauthorized verified users found, all is well, no need to // show a notification. if ( empty( $unauthorized_identifiers ) ) { return true; } // Flag site as affected so that the notification to inform and explain // steps to resolve will be shown. $credentials = $this->authentication->credentials()->get(); $google_proxy = new Google_Proxy( $this->context ); $response = wp_remote_post( $google_proxy->url( '/notifications/mark/' ), array( 'body' => array( 'site_id' => $credentials['oauth2_client_id'], 'site_secret' => $credentials['oauth2_client_secret'], 'notification_id' => 'verification_leak', 'notification_state' => 'required', // This is a special parameter only supported for this // particular notification. 'identifiers' => implode( ',', $unauthorized_identifiers ), ), ) ); if ( is_wp_error( $response ) ) { return $response; } $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $response_code ) { $body = wp_remote_retrieve_body( $response ); $decoded = json_decode( $body, true ); return new WP_Error( $response_code, ! empty( $decoded['error'] ) ? $decoded['error'] : $body ); } return true; } /** * Checks for any users that are verified without proper authorization and * clears all their Site Kit data. * * @since 1.8.1 * * @return array List of email addresses for the unauthorized users. */ private function clear_unauthorized_verified_users() { global $wpdb; $unauthorized_identifiers = array(); $profile = new Profile( $this->user_options ); // Store original user ID to switch back later. $backup_user_id = $this->user_options->get_user_id(); // Iterate through all users verified via Site Kit. foreach ( $this->get_verified_user_ids() as $user_id ) { $this->user_options->switch_user( $user_id ); // If the user has setup access, there is no problem. if ( user_can( $user_id, Permissions::SETUP ) ) { continue; } // Try to get profile email, otherwise fall back to WP email. if ( $this->authentication->profile()->has() ) { $unauthorized_identifiers[] = $this->authentication->profile()->get()['email']; } else { $user = get_user_by( 'id', $user_id ); $unauthorized_identifiers[] = $user->user_email; } $prefix = $this->user_options->get_meta_key( 'googlesitekit\_%' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key LIKE %s", $user_id, $prefix ) ); wp_cache_delete( $user_id, 'user_meta' ); } // Restore original user ID. $this->user_options->switch_user( $backup_user_id ); return $unauthorized_identifiers; } /** * Gets all user IDs that are verified via Site Kit. * * @since @1.31.0 * * @return array List of user ids of verified users. Maximum of 20. */ private function get_verified_user_ids() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery return $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key IN (%s, %s) LIMIT 20", $this->user_options->get_meta_key( Verification_File::OPTION ), $this->user_options->get_meta_key( Verification_Meta::OPTION ) ) ); } } Util/Requires_Javascript_Trait.php 0000644 00000002134 14720521221 0013316 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Util\Requires_Javascript_Trait * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Trait to display no javascript fallback message. * * @since 1.5.0 * @access private * @ignore */ trait Requires_Javascript_Trait { /** * Outputs a fallback message when Javascript is disabled. * * @since 1.5.0 */ protected function render_noscript_html() { ?> <noscript> <div class="googlesitekit-noscript notice notice-warning"> <div class="mdc-layout-grid"> <div class="mdc-layout-grid__inner"> <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12"> <p class="googlesitekit-noscript__text"> <?php esc_html_e( 'The Site Kit by Google plugin requires JavaScript to be enabled in your browser.', 'google-site-kit' ) ?> </p> </div> </div> </div> </div> </noscript> <?php } } Util/Entity_Factory.php 0000644 00000051324 14720521221 0011136 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Entity_Factory * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Plugin; use WP_Query; use WP_Post; use WP_Term; use WP_User; use WP_Post_Type; use WP_Screen; /** * Class providing access to entities. * * This class entirely relies on WordPress core behavior and is technically decoupled from Site Kit. For example, * entities returned by this factory rely on the regular WordPress home URL and ignore Site Kit-specific details, such * as an alternative "reference site URL". * * Instead of relying on this class directly, use {@see Context::get_reference_entity()} or * {@see Context::get_reference_entity_from_url()}. * * @since 1.15.0 * @access private * @ignore */ final class Entity_Factory { /** * Gets the entity for the current WordPress context, if available. * * @since 1.15.0 * * @return Entity|null The entity for the current context, or null if none could be determined. */ public static function from_context() { global $wp_the_query; // If currently in WP admin, run admin-specific checks. if ( is_admin() ) { $screen = get_current_screen(); if ( ! $screen instanceof WP_Screen || 'post' !== $screen->base ) { return null; } $post = get_post(); if ( $post instanceof WP_Post && self::is_post_public( $post ) ) { return self::create_entity_for_post( $post, 1 ); } return null; } // Otherwise, run frontend-specific `WP_Query` logic. if ( $wp_the_query instanceof WP_Query ) { $entity = self::from_wp_query( $wp_the_query ); $request_uri = Plugin::instance()->context()->input()->filter( INPUT_SERVER, 'REQUEST_URI' ); return self::maybe_convert_to_amp_entity( $request_uri, $entity ); } return null; } /** * Gets the entity for the given URL, if available. * * Calling this method is expensive, so it should only be used in certain admin contexts where this is acceptable. * * @since 1.15.0 * * @param string $url URL to determine the entity from. * @return Entity|null The entity for the URL, or null if none could be determined. */ public static function from_url( $url ) { $query = WP_Query_Factory::from_url( $url ); if ( ! $query ) { return null; } $query->get_posts(); $entity = self::from_wp_query( $query ); return self::maybe_convert_to_amp_entity( $url, $entity ); } /** * Gets the entity for the given `WP_Query` object, if available. * * @since 1.15.0 * * @param WP_Query $query WordPress query object. Must already have run the actual database query. * @return Entity|null The entity for the query, or null if none could be determined. */ public static function from_wp_query( WP_Query $query ) { // A singular post (possibly the static front page). if ( $query->is_singular() ) { $post = $query->get_queried_object(); if ( $post instanceof WP_Post && self::is_post_public( $post ) ) { return self::create_entity_for_post( $post, self::get_query_pagenum( $query, 'page' ) ); } return null; } $page = self::get_query_pagenum( $query ); // The blog. if ( $query->is_home() ) { // The blog is either the front page... if ( $query->is_front_page() ) { return self::create_entity_for_front_blog( $page ); } // ...or it is a separate post assigned as 'page_for_posts'. return self::create_entity_for_posts_blog( $page ); } // A taxonomy term archive. if ( $query->is_category() || $query->is_tag() || $query->is_tax() ) { $term = $query->get_queried_object(); if ( $term instanceof WP_Term ) { return self::create_entity_for_term( $term, $page ); } } // An author archive. if ( $query->is_author() ) { $user = $query->get_queried_object(); if ( $user instanceof WP_User ) { return self::create_entity_for_author( $user, $page ); } } // A post type archive. if ( $query->is_post_type_archive() ) { $post_type = $query->get( 'post_type' ); if ( is_array( $post_type ) ) { $post_type = reset( $post_type ); } $post_type_object = get_post_type_object( $post_type ); if ( $post_type_object instanceof WP_Post_Type ) { return self::create_entity_for_post_type( $post_type_object, $page ); } } // A date-based archive. if ( $query->is_date() ) { $queried_post = self::get_first_query_post( $query ); if ( ! $queried_post ) { return null; } if ( $query->is_year() ) { return self::create_entity_for_date( $queried_post, 'year', $page ); } if ( $query->is_month() ) { return self::create_entity_for_date( $queried_post, 'month', $page ); } if ( $query->is_day() ) { return self::create_entity_for_date( $queried_post, 'day', $page ); } // Time archives are not covered for now. While they can theoretically be used in WordPress, they // aren't fully supported, and WordPress does not link to them anywhere. return null; } return null; } /** * Creates the entity for a given post object. * * @since 1.15.0 * @since 1.68.0 Method access modifier changed to public. * * @param WP_Post $post A WordPress post object. * @param int $page Page number. * @return Entity The entity for the post. */ public static function create_entity_for_post( WP_Post $post, $page ) { $url = self::paginate_post_url( get_permalink( $post ), $post, $page ); return new Entity( urldecode( $url ), array( 'type' => 'post', 'title' => $post->post_title, 'id' => $post->ID, ) ); } /** * Creates the entity for the posts page blog archive. * * This method should only be used when the blog is handled via a separate page, i.e. when 'show_on_front' is set * to 'page' and the 'page_for_posts' option is set. In this case the blog is technically a post itself, therefore * its entity also includes an ID. * * @since 1.15.0 * * @param int $page Page number. * @return Entity|null The entity for the posts blog archive, or null if not set. */ private static function create_entity_for_posts_blog( $page ) { $post_id = (int) get_option( 'page_for_posts' ); if ( ! $post_id ) { return null; } $post = get_post( $post_id ); if ( ! $post ) { return null; } return new Entity( self::paginate_entity_url( get_permalink( $post ), $page ), array( 'type' => 'blog', 'title' => $post->post_title, 'id' => $post->ID, ) ); } /** * Creates the entity for the front page blog archive. * * This method should only be used when the front page is set to display the * blog archive, i.e. is not technically a post itself. * * @since 1.15.0 * * @param int $page Page number. * @return Entity The entity for the front blog archive. */ private static function create_entity_for_front_blog( $page ) { // The translation string intentionally omits the 'google-site-kit' text domain since it should use // WordPress core translations. return new Entity( self::paginate_entity_url( user_trailingslashit( home_url() ), $page ), array( 'type' => 'blog', 'title' => __( 'Home', 'default' ), ) ); } /** * Creates the entity for a given term object, i.e. for a taxonomy term archive. * * @since 1.15.0 * * @param WP_Term $term A WordPress term object. * @param int $page Page number. * @return Entity The entity for the term. */ private static function create_entity_for_term( WP_Term $term, $page ) { // See WordPress's `get_the_archive_title()` function for this behavior. The strings here intentionally omit // the 'google-site-kit' text domain since they should use WordPress core translations. switch ( $term->taxonomy ) { case 'category': $title = $term->name; $prefix = _x( 'Category:', 'category archive title prefix', 'default' ); break; case 'post_tag': $title = $term->name; $prefix = _x( 'Tag:', 'tag archive title prefix', 'default' ); break; case 'post_format': $prefix = ''; switch ( $term->slug ) { case 'post-format-aside': $title = _x( 'Asides', 'post format archive title', 'default' ); break; case 'post-format-gallery': $title = _x( 'Galleries', 'post format archive title', 'default' ); break; case 'post-format-image': $title = _x( 'Images', 'post format archive title', 'default' ); break; case 'post-format-video': $title = _x( 'Videos', 'post format archive title', 'default' ); break; case 'post-format-quote': $title = _x( 'Quotes', 'post format archive title', 'default' ); break; case 'post-format-link': $title = _x( 'Links', 'post format archive title', 'default' ); break; case 'post-format-status': $title = _x( 'Statuses', 'post format archive title', 'default' ); break; case 'post-format-audio': $title = _x( 'Audio', 'post format archive title', 'default' ); break; case 'post-format-chat': $title = _x( 'Chats', 'post format archive title', 'default' ); break; } break; default: $tax = get_taxonomy( $term->taxonomy ); $title = $term->name; $prefix = sprintf( /* translators: %s: Taxonomy singular name. */ _x( '%s:', 'taxonomy term archive title prefix', 'default' ), $tax->labels->singular_name ); } return new Entity( self::paginate_entity_url( get_term_link( $term ), $page ), array( 'type' => 'term', 'title' => self::prefix_title( $title, $prefix ), 'id' => $term->term_id, ) ); } /** * Creates the entity for a given user object, i.e. for an author archive. * * @since 1.15.0 * * @param WP_User $user A WordPress user object. * @param int $page Page number. * @return Entity The entity for the user. */ private static function create_entity_for_author( WP_User $user, $page ) { // See WordPress's `get_the_archive_title()` function for this behavior. The string here intentionally omits // the 'google-site-kit' text domain since it should use WordPress core translations. $title = $user->display_name; $prefix = _x( 'Author:', 'author archive title prefix', 'default' ); return new Entity( self::paginate_entity_url( get_author_posts_url( $user->ID, $user->user_nicename ), $page ), array( 'type' => 'user', 'title' => self::prefix_title( $title, $prefix ), 'id' => $user->ID, ) ); } /** * Creates the entity for a given post type object. * * @since 1.15.0 * * @param WP_Post_Type $post_type A WordPress post type object. * @param int $page Page number. * @return Entity The entity for the post type. */ private static function create_entity_for_post_type( WP_Post_Type $post_type, $page ) { // See WordPress's `get_the_archive_title()` function for this behavior. The string here intentionally omits // the 'google-site-kit' text domain since it should use WordPress core translations. $title = $post_type->labels->name; $prefix = _x( 'Archives:', 'post type archive title prefix', 'default' ); return new Entity( self::paginate_entity_url( get_post_type_archive_link( $post_type->name ), $page ), array( 'type' => 'post_type', 'title' => self::prefix_title( $title, $prefix ), ) ); } /** * Creates the entity for a date-based archive. * * The post specified has to any post from the query, in order to extract the relevant date information. * * @since 1.15.0 * * @param WP_Post $queried_post A WordPress post object from the query. * @param string $type Type of the date-based archive. Either 'year', 'month', or 'day'. * @param int $page Page number. * @return Entity|null The entity for the date archive, or null if unable to parse date. */ private static function create_entity_for_date( WP_Post $queried_post, $type, $page ) { // See WordPress's `get_the_archive_title()` function for this behavior. The strings here intentionally omit // the 'google-site-kit' text domain since they should use WordPress core translations. switch ( $type ) { case 'year': $prefix = _x( 'Year:', 'date archive title prefix', 'default' ); $format = _x( 'Y', 'yearly archives date format', 'default' ); $url_func = 'get_year_link'; $url_func_format = 'Y'; break; case 'month': $prefix = _x( 'Month:', 'date archive title prefix', 'default' ); $format = _x( 'F Y', 'monthly archives date format', 'default' ); $url_func = 'get_month_link'; $url_func_format = 'Y/m'; break; default: $type = 'day'; $prefix = _x( 'Day:', 'date archive title prefix', 'default' ); $format = _x( 'F j, Y', 'daily archives date format', 'default' ); $url_func = 'get_day_link'; $url_func_format = 'Y/m/j'; } $title = get_post_time( $format, false, $queried_post, true ); $url_func_args = get_post_time( $url_func_format, false, $queried_post ); if ( ! $url_func_args ) { return null; // Unable to parse date, likely there is none set. } $url_func_args = array_map( 'absint', explode( '/', $url_func_args ) ); return new Entity( self::paginate_entity_url( call_user_func_array( $url_func, $url_func_args ), $page ), array( 'type' => $type, 'title' => self::prefix_title( $title, $prefix ), ) ); } /** * Checks whether a given post is public, i.e. has a public URL. * * @since 1.15.0 * * @param WP_Post $post A WordPress post object. * @return bool True if the post is public, false otherwise. */ private static function is_post_public( WP_Post $post ) { // If post status isn't 'publish', the post is not public. if ( 'publish' !== get_post_status( $post ) ) { return false; } // If the post type overall is not publicly viewable, the post is not public. if ( ! is_post_type_viewable( $post->post_type ) ) { return false; } // Otherwise, the post is public. return true; } /** * Gets the first post from a WordPress query. * * @since 1.15.0 * * @param WP_Query $query WordPress query object. Must already have run the actual database query. * @return WP_Post|null WordPress post object, or null if none found. */ private static function get_first_query_post( WP_Query $query ) { if ( ! $query->posts ) { return null; } $post = reset( $query->posts ); if ( $post instanceof WP_Post ) { return $post; } if ( is_numeric( $post ) ) { return get_post( $post ); } return null; } /** * Combines an entity title and prefix. * * This is based on the WordPress core function `get_the_archive_title()`. * * @since 1.15.0 * * @param string $title The title. * @param string $prefix The prefix to add, should end in a colon. * @return string Resulting entity title. */ private static function prefix_title( $title, $prefix ) { if ( empty( $prefix ) ) { return $title; } // See WordPress's `get_the_archive_title()` function for this behavior. The string here intentionally omits // the 'google-site-kit' text domain since it should use WordPress core translations. return sprintf( /* translators: 1: Title prefix. 2: Title. */ _x( '%1$s %2$s', 'archive title', 'default' ), $prefix, $title ); } /** * Converts given entity to AMP entity if the given URL is an AMP URL. * * @since 1.42.0 * * @param string $url URL to determine the entity from. * @param Entity $entity The initial entity. * @return Entity The initial or new entity for the given URL. */ private static function maybe_convert_to_amp_entity( $url, $entity ) { if ( is_null( $entity ) || ! defined( 'AMP__VERSION' ) ) { return $entity; } $url_parts = URL::parse( $url ); $current_url = $entity->get_url(); if ( ! empty( $url_parts['query'] ) ) { $url_query_params = array(); wp_parse_str( $url_parts['query'], $url_query_params ); // check if the $url has amp query param. if ( array_key_exists( 'amp', $url_query_params ) ) { $new_url = add_query_arg( 'amp', '1', $current_url ); return self::convert_to_amp_entity( $new_url, $entity ); } } if ( ! empty( $url_parts['path'] ) ) { // We need to correctly add trailing slash if the original url had trailing slash. // That's the reason why we need to check for both version. if ( '/amp' === substr( $url_parts['path'], -4 ) ) { // -strlen('/amp') is -4 $new_url = untrailingslashit( $current_url ) . '/amp'; return self::convert_to_amp_entity( $new_url, $entity ); } if ( '/amp/' === substr( $url_parts['path'], -5 ) ) { // -strlen('/amp/') is -5 $new_url = untrailingslashit( $current_url ) . '/amp/'; return self::convert_to_amp_entity( $new_url, $entity ); } } return $entity; } /** * Converts given entity to AMP entity by changing the entity URL and adding correct mode. * * @since 1.42.0 * * @param string $new_url URL of the new entity. * @param Entity $entity The initial entity. * @return Entity The new entity. */ private static function convert_to_amp_entity( $new_url, $entity ) { $new_entity = new Entity( $new_url, array( 'id' => $entity->get_id(), 'type' => $entity->get_type(), 'title' => $entity->get_title(), 'mode' => 'amp_secondary', ) ); return $new_entity; } /** * Gets the page number for a query, via the specified query var. Defaults to 1. * * @since 1.68.0 * * @param WP_Query $query A WordPress query object. * @param string $query_var Optional. Query var to look for, expects 'paged' or 'page'. Default 'paged'. * @return int The page number. */ private static function get_query_pagenum( $query, $query_var = 'paged' ) { return $query->get( $query_var ) ? (int) $query->get( $query_var ) : 1; } /** * Paginates an entity URL. * * Logic extracted from `paginate_links` in WordPress core. * https://github.com/WordPress/WordPress/blob/7f5d7f1b56087c3eb718da4bd81deb06e077bbbb/wp-includes/general-template.php#L4203 * * @since 1.68.0 * * @param string $url The URL to paginate. * @param int $pagenum The page number to add to the URL. * @return string The paginated URL. */ private static function paginate_entity_url( $url, $pagenum ) { global $wp_rewrite; if ( 1 === $pagenum ) { return $url; } // Setting up default values based on the given URL. $url_parts = explode( '?', $url ); // Append the format placeholder to the base URL. $base = trailingslashit( $url_parts[0] ) . '%_%'; // URL base depends on permalink settings. $format = $wp_rewrite->using_index_permalinks() && ! strpos( $base, 'index.php' ) ? 'index.php/' : ''; $format .= $wp_rewrite->using_permalinks() ? user_trailingslashit( $wp_rewrite->pagination_base . '/%#%', 'paged' ) : '?paged=%#%'; // Array of query args to add. $add_args = array(); // Merge additional query vars found in the original URL into 'add_args' array. if ( isset( $url_parts[1] ) ) { // Find the format argument. $format_parts = explode( '?', str_replace( '%_%', $format, $base ) ); $format_query = isset( $format_parts[1] ) ? $format_parts[1] : ''; wp_parse_str( $format_query, $format_args ); // Find the query args of the requested URL. $url_query_args = array(); wp_parse_str( $url_parts[1], $url_query_args ); // Remove the format argument from the array of query arguments, to avoid overwriting custom format. foreach ( $format_args as $format_arg => $format_arg_value ) { unset( $url_query_args[ $format_arg ] ); } $add_args = array_merge( $add_args, urlencode_deep( $url_query_args ) ); } $link = str_replace( '%_%', $format, $base ); $link = str_replace( '%#%', $pagenum, $link ); if ( $add_args ) { $link = add_query_arg( $add_args, $link ); } return $link; } /** * Paginates a post URL. * * Logic extracted from `_wp_link_page` in WordPress core. * https://github.com/WordPress/WordPress/blob/7f5d7f1b56087c3eb718da4bd81deb06e077bbbb/wp-includes/post-template.php#L1031 * * @since 1.68.0 * * @param string $url The URL to paginate. * @param WP_Post $post The WordPress post object. * @param int $pagenum The page number to add to the URL. * @return string The paginated URL. */ private static function paginate_post_url( $url, $post, $pagenum ) { global $wp_rewrite; if ( 1 === $pagenum ) { return $url; } if ( ! get_option( 'permalink_structure' ) || in_array( $post->post_status, array( 'draft', 'pending' ), true ) ) { $url = add_query_arg( 'page', $pagenum, $url ); } elseif ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === (int) $post->ID ) { $url = trailingslashit( $url ) . user_trailingslashit( "$wp_rewrite->pagination_base/" . $pagenum, 'single_paged' ); } else { $url = trailingslashit( $url ) . user_trailingslashit( $pagenum, 'single_paged' ); } return $url; } } Util/Sort.php 0000644 00000002143 14720521221 0007115 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Sort * * @package Google\Site_Kit\Core\Util * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Utility class for sorting lists. * * @since 1.90.0 * @access private * @ignore */ class Sort { /** * Sorts the provided list in a case-insensitive manner. * * @since 1.90.0 * * @param array $list_to_sort The list to sort. * @param string $orderby The field by which the list should be ordered by. * * @return array The sorted list. */ public static function case_insensitive_list_sort( array $list_to_sort, $orderby ) { usort( $list_to_sort, function ( $a, $b ) use ( $orderby ) { if ( is_array( $a ) && is_array( $b ) ) { return strcasecmp( $a[ $orderby ], $b[ $orderby ] ); } if ( is_object( $a ) && is_object( $b ) ) { return strcasecmp( $a->$orderby, $b->$orderby ); } return 0; } ); return $list_to_sort; } } Util/REST_Entity_Search_Controller.php 0000644 00000006530 14720521221 0013773 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\REST_Entity_Search_Controller * * @package Google\Site_Kit\Core\Util * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use WP_REST_Server; use WP_REST_Request; use WP_REST_Response; /** * Class for handling entity search REST routes. * * @since 1.68.0 * @access private * @ignore */ class REST_Entity_Search_Controller { /** * Plugin context. * * @since 1.68.0 * @var Context */ private $context; /** * Constructor. * * @since 1.68.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Registers functionality through WordPress hooks. * * @since 1.68.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); } /** * Gets REST route instances. * * @since 1.68.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_search = function () { return current_user_can( Permissions::AUTHENTICATE ) || current_user_can( Permissions::VIEW_SHARED_DASHBOARD ); }; return array( new REST_Route( 'core/search/data/entity-search', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function ( WP_REST_Request $request ) { $query = rawurldecode( $request['query'] ); $entities = array(); if ( filter_var( $query, FILTER_VALIDATE_URL ) ) { $entity = $this->context->get_reference_entity_from_url( $query ); if ( $entity && $entity->get_id() ) { $entities = array( array( 'id' => $entity->get_id(), 'title' => $entity->get_title(), 'url' => $entity->get_url(), 'type' => $entity->get_type(), ), ); } } else { $args = array( 'posts_per_page' => 10, 'google-site-kit' => 1, 's' => $query, 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'post_status' => array( 'publish' ), ); $posts = ( new \WP_Query( $args ) )->posts; if ( ! empty( $posts ) ) { $entities = array_map( function ( $post ) { $entity = Entity_Factory::create_entity_for_post( $post, 1 ); return array( 'id' => $entity->get_id(), 'title' => $entity->get_title(), 'url' => $entity->get_url(), 'type' => $entity->get_type(), ); }, $posts ); } } return new WP_REST_Response( $entities ); }, 'permission_callback' => $can_search, ), ), array( 'args' => array( 'query' => array( 'type' => 'string', 'description' => __( 'Text content to search for.', 'google-site-kit' ), 'required' => true, ), ), ) ), ); } } Util/Feature_Flags.php 0000644 00000004336 14720521221 0010703 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Feature_Flags * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use ArrayAccess; /** * Class for interacting with feature flag configuration. * * @since 1.22.0 * @access private * @ignore */ class Feature_Flags { /** * Feature flag definitions. * * @since 1.22.0 * @var array|ArrayAccess */ private static $features = array(); /** * Checks if the given feature is enabled. * * @since 1.22.0 * * @param string $feature Feature key path to check. * @return bool */ public static function enabled( $feature ) { if ( ! $feature || ! is_string( $feature ) || empty( static::$features ) ) { return false; } /** * Filters a feature flag's status (on or off). * * Mainly this is used by E2E tests to allow certain features to be disabled or * enabled for testing, but is also useful to switch features on/off on-the-fly. * * @since 1.25.0 * * @param bool $feature_enabled The current status of this feature flag (`true` or `false`). * @param string $feature The feature name. */ return apply_filters( 'googlesitekit_is_feature_enabled', false, $feature ); } /** * Gets all enabled feature flags. * * @since 1.25.0 * * @return string[] An array of all enabled features. */ public static function get_enabled_features() { $enabled_features = array(); foreach ( static::$features as $feature_name ) { if ( static::enabled( $feature_name ) ) { $enabled_features[] = $feature_name; } } return $enabled_features; } /** * Sets the feature configuration. * * @since 1.22.0 * * @param array|ArrayAccess $features Feature configuration. */ public static function set_features( $features ) { if ( is_array( $features ) || $features instanceof ArrayAccess ) { static::$features = $features; } } /** * Gets all available feature flags. * * @since 1.26.0 * * @return array An array of all available features. */ public static function get_available_features() { return static::$features; } } Util/BC_Functions.php 0000644 00000011274 14720521221 0010507 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\BC_Functions * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use BadMethodCallException; use WP_REST_Request; /** * Class for providing backwards compatible core functions, without polyfilling. * * @since 1.7.0 * @access private * @ignore */ class BC_Functions { /** * Proxies calls to global functions, while falling back to the internal method by the same name. * * @since 1.7.0 * * @param string $function_name Function name to call. * @param array $arguments Arguments passed to function. * @return mixed * @throws BadMethodCallException Thrown if no method exists by the same name as the function. */ public static function __callStatic( $function_name, $arguments ) { if ( function_exists( $function_name ) ) { return call_user_func_array( $function_name, $arguments ); } if ( method_exists( __CLASS__, $function_name ) ) { return self::{ $function_name }( ...$arguments ); } throw new BadMethodCallException( "$function_name does not exist." ); } /** * Basic implementation of the wp_sanitize_script_attributes function introduced in the WordPress version 5.7.0. * * @since 1.41.0 * * @param array $attributes Key-value pairs representing `<script>` tag attributes. * @return string String made of sanitized `<script>` tag attributes. */ protected static function wp_sanitize_script_attributes( $attributes ) { $attributes_string = ''; foreach ( $attributes as $attribute_name => $attribute_value ) { if ( is_bool( $attribute_value ) ) { if ( $attribute_value ) { $attributes_string .= ' ' . esc_attr( $attribute_name ); } } else { $attributes_string .= sprintf( ' %1$s="%2$s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) ); } } return $attributes_string; } /** * A fallback for the wp_get_script_tag function introduced in the WordPress version 5.7.0. * * @since 1.41.0 * * @param array $attributes Key-value pairs representing `<script>` tag attributes. * @return string String containing `<script>` opening and closing tags. */ protected static function wp_get_script_tag( $attributes ) { return sprintf( "<script %s></script>\n", self::wp_sanitize_script_attributes( $attributes ) ); } /** * A fallback for the wp_print_script_tag function introduced in the WordPress version 5.7.0. * * @since 1.41.0 * * @param array $attributes Key-value pairs representing `<script>` tag attributes. */ protected static function wp_print_script_tag( $attributes ) { echo self::wp_get_script_tag( $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * A fallback for the wp_get_inline_script_tag function introduced in the WordPress version 5.7.0. * * @since 1.41.0 * * @param string $javascript Inline JavaScript code. * @param array $attributes Optional. Key-value pairs representing `<script>` tag attributes. * @return string String containing inline JavaScript code wrapped around `<script>` tag. */ protected static function wp_get_inline_script_tag( $javascript, $attributes = array() ) { $javascript = "\n" . trim( $javascript, "\n\r " ) . "\n"; return sprintf( "<script%s>%s</script>\n", self::wp_sanitize_script_attributes( $attributes ), $javascript ); } /** * A fallback for the wp_get_inline_script_tag function introduced in the WordPress version 5.7.0. * * @since 1.41.0 * * @param string $javascript Inline JavaScript code. * @param array $attributes Optional. Key-value pairs representing `<script>` tag attributes. */ protected static function wp_print_inline_script_tag( $javascript, $attributes = array() ) { echo self::wp_get_inline_script_tag( $javascript, $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * A fallback for the wp_get_sidebar function introduced in the WordPress version 5.9.0. * * Retrieves the registered sidebar with the given ID. * * @since 1.86.0 * * @global array $wp_registered_sidebars The registered sidebars. * * @param string $id The sidebar ID. * @return array|null The discovered sidebar, or null if it is not registered. */ protected static function wp_get_sidebar( $id ) { global $wp_registered_sidebars; foreach ( (array) $wp_registered_sidebars as $sidebar ) { if ( $sidebar['id'] === $id ) { return $sidebar; } } if ( 'wp_inactive_widgets' === $id ) { return array( 'id' => 'wp_inactive_widgets', 'name' => __( 'Inactive widgets', 'default' ), ); } return null; } } Util/Migration_1_123_0.php 0000644 00000011045 14720521221 0011144 0 ustar 00 <?php /** * Migration for 1.123.0 * * @package Google\Site_Kit\Core\Util * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Modules\Module_Sharing_Settings; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Modules\Analytics_4; use Google\Site_Kit\Modules\Analytics_4\Settings as Analytics_Settings; /** * Class Migration_1_123_0 * * @since 1.123.0 * @access private * @ignore */ class Migration_1_123_0 { /** * Target DB version. */ const DB_VERSION = '1.123.0'; /** * DB version option name. */ const DB_VERSION_OPTION = 'googlesitekit_db_version'; /** * Legacy analytics module slug. */ const LEGACY_ANALYTICS_MODULE_SLUG = 'analytics'; /** * Legacy analytics option name. */ const LEGACY_ANALYTICS_OPTION = 'googlesitekit_analytics_settings'; /** * Context instance. * * @since 1.123.0 * @var Context */ protected $context; /** * Options instance. * * @since 1.123.0 * @var Options */ protected $options; /** * Analytics_Settings instance. * * @since 1.123.0 * @var Analytics_Settings */ protected $analytics_settings; /** * Constructor. * * @since 1.123.0 * * @param Context $context Plugin context instance. * @param Options $options Optional. Options instance. */ public function __construct( Context $context, Options $options = null ) { $this->context = $context; $this->options = $options ?: new Options( $context ); $this->analytics_settings = new Analytics_Settings( $this->options ); } /** * Registers hooks. * * @since 1.123.0 */ public function register() { add_action( 'admin_init', array( $this, 'migrate' ) ); } /** * Migrates the DB. * * @since 1.123.0 */ public function migrate() { $db_version = $this->options->get( self::DB_VERSION_OPTION ); if ( ! $db_version || version_compare( $db_version, self::DB_VERSION, '<' ) ) { $this->migrate_legacy_analytics_settings(); $this->activate_analytics(); $this->migrate_legacy_analytics_sharing_settings(); $this->options->set( self::DB_VERSION_OPTION, self::DB_VERSION ); } } /** * Migrates the legacy analytics settings over to analytics-4. * * @since 1.123.0 */ protected function migrate_legacy_analytics_settings() { if ( ! $this->analytics_settings->has() ) { return; } $legacy_settings = $this->options->get( self::LEGACY_ANALYTICS_OPTION ); if ( empty( $legacy_settings ) ) { return; } $recovered_settings = array(); $options_to_migrate = array( 'accountID', 'adsConversionID', 'trackingDisabled', ); array_walk( $options_to_migrate, function ( $setting ) use ( &$recovered_settings, $legacy_settings ) { $recovered_settings[ $setting ] = $legacy_settings[ $setting ]; } ); if ( ! empty( $recovered_settings ) ) { $this->analytics_settings->merge( $recovered_settings ); } } /** * Activates the analytics-4 module if the legacy analytics module was active. * * @since 1.123.0 */ protected function activate_analytics() { $option = $this->options->get( Modules::OPTION_ACTIVE_MODULES ); // Check legacy option. if ( ! is_array( $option ) ) { $option = $this->options->get( 'googlesitekit-active-modules' ); } if ( ! is_array( $option ) ) { return; } $analytics_active = in_array( Analytics_4::MODULE_SLUG, $option, true ); // If analytics-4 is already active, bail. if ( $analytics_active ) { return; } $legacy_analytics_active = in_array( self::LEGACY_ANALYTICS_MODULE_SLUG, $option, true ); if ( $legacy_analytics_active ) { $option[] = Analytics_4::MODULE_SLUG; $this->options->set( Modules::OPTION_ACTIVE_MODULES, $option ); } } /** * Replicates sharing settings from the legacy analytics module to analytics-4. * * @since 1.123.0 */ protected function migrate_legacy_analytics_sharing_settings() { $option = $this->options->get( Module_Sharing_Settings::OPTION ); if ( ! is_array( $option ) ) { return; } // If sharing settings for analytics-4 already exist, bail. if ( isset( $option[ Analytics_4::MODULE_SLUG ] ) ) { return; } if ( isset( $option[ self::LEGACY_ANALYTICS_MODULE_SLUG ] ) ) { $option[ Analytics_4::MODULE_SLUG ] = $option[ self::LEGACY_ANALYTICS_MODULE_SLUG ]; $this->options->set( Module_Sharing_Settings::OPTION, $option ); } } } Util/Developer_Plugin_Installer.php 0000644 00000012706 14720521221 0013454 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\DeveloperPluginInstaller * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use WP_REST_Server; use WP_REST_Request; use WP_REST_Response; /** * Class responsible for providing the helper plugin via the automatic updater. * * @since 1.3.0 */ class Developer_Plugin_Installer { const SLUG = 'google-site-kit-dev-settings'; /** * Plugin context. * * @since 1.3.0 * @var Context */ private $context; /** * Constructor. * * @since 1.3.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Registers functionality through WordPress hooks. * * @since 1.3.0 */ public function register() { // Only filter plugins API response if the developer plugin is not already active. if ( ! defined( 'GOOGLESITEKITDEVSETTINGS_VERSION' ) ) { add_filter( 'plugins_api', function ( $value, $action, $args ) { return $this->plugin_info( $value, $action, $args ); }, 10, 3 ); } add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); } /** * Gets related REST routes. * * @since 1.3.0 * * @return array List of REST_Route objects. */ private function get_rest_routes() { $can_setup = function () { return current_user_can( Permissions::SETUP ); }; return array( new REST_Route( 'core/site/data/developer-plugin', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { $is_active = defined( 'GOOGLESITEKITDEVSETTINGS_VERSION' ); $installed = $is_active; $slug = self::SLUG; $plugin = "$slug/$slug.php"; if ( ! $is_active ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } foreach ( array_keys( get_plugins() ) as $installed_plugin ) { if ( $installed_plugin === $plugin ) { $installed = true; break; } } } // Alternate wp_nonce_url without esc_html breaking query parameters. $nonce_url = function ( $action_url, $action ) { return add_query_arg( '_wpnonce', wp_create_nonce( $action ), $action_url ); }; $activate_url = $nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=' . $plugin ), 'activate-plugin_' . $plugin ); $install_url = $nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $slug ), 'install-plugin_' . $slug ); return new WP_REST_Response( array( 'active' => $is_active, 'installed' => $installed, 'activateURL' => current_user_can( 'activate_plugin', $plugin ) ? esc_url_raw( $activate_url ) : false, 'installURL' => current_user_can( 'install_plugins' ) ? esc_url_raw( $install_url ) : false, 'configureURL' => $is_active ? esc_url_raw( $this->context->admin_url( 'dev-settings' ) ) : false, ) ); }, 'permission_callback' => $can_setup, ), ) ), ); } /** * Retrieves plugin information data from the Site Kit REST API. * * @since 1.3.0 * * @param false|object|array $value The result object or array. Default false. * @param string $action The type of information being requested from the Plugin Installation API. * @param object $args Plugin API arguments. * @return false|object|array Updated $value, or passed-through $value on failure. */ private function plugin_info( $value, $action, $args ) { if ( 'plugin_information' !== $action || self::SLUG !== $args->slug ) { return $value; } $data = $this->fetch_plugin_data(); if ( ! $data ) { return $value; } $new_data = array( 'slug' => self::SLUG, 'name' => $data['name'], 'version' => $data['version'], 'author' => '<a href="https://opensource.google.com">Google</a>', 'download_link' => $data['download_url'], 'trunk' => $data['download_url'], 'tested' => $data['tested'], 'requires' => $data['requires'], 'requires_php' => $data['requires_php'], 'last_updated' => $data['last_updated'], ); if ( ! empty( $data['icons'] ) ) { $new_data['icons'] = $data['icons']; } if ( ! empty( $data['banners'] ) ) { $new_data['banners'] = $data['banners']; } if ( ! empty( $data['banners_rtl'] ) ) { $new_data['banners_rtl'] = $data['banners_rtl']; } return (object) $new_data; } /** * Gets plugin data from the API. * * @since 1.3.0 * @since 1.99.0 Update plugin data to pull from GCS bucket. * * @return array|null Associative array of plugin data, or null on failure. */ private function fetch_plugin_data() { // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get $response = wp_remote_get( 'https://storage.googleapis.com/site-kit-dev-plugins/google-site-kit-dev-settings/updates.json' ); // Retrieve data from the body and decode json format. return json_decode( wp_remote_retrieve_body( $response ), true ); } } Util/Uninstallation.php 0000644 00000006626 14720521221 0011204 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Uninstallation * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\Encrypted_Options; use Google\Site_Kit\Core\Authentication\Credentials; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Remote_Features\Remote_Features_Cron; use Google\Site_Kit\Modules\Analytics_4\Conversion_Reporting\Conversion_Reporting_Cron; use Google\Site_Kit\Modules\Analytics_4\Synchronize_AdSenseLinked; use Google\Site_Kit\Modules\Analytics_4\Synchronize_AdsLinked; use Google\Site_Kit\Modules\Analytics_4\Synchronize_Property; /** * Utility class for handling uninstallation of the plugin. * * @since 1.20.0 * @access private * @ignore */ class Uninstallation { /** * Plugin context. * * @since 1.20.0 * @var Context */ private $context; /** * Options instance. * * @since 1.20.0 * @var Options */ private $options; /** * List of scheduled events. * * @since 1.136.0 * @var array */ const SCHEDULED_EVENTS = array( Conversion_Reporting_Cron::CRON_ACTION, OAuth_Client::CRON_REFRESH_PROFILE_DATA, Remote_Features_Cron::CRON_ACTION, Synchronize_AdSenseLinked::CRON_SYNCHRONIZE_ADSENSE_LINKED, Synchronize_AdsLinked::CRON_SYNCHRONIZE_ADS_LINKED, Synchronize_Property::CRON_SYNCHRONIZE_PROPERTY, ); /** * Constructor. * * This class and its logic must be instantiated early in the WordPress * bootstrap lifecycle because the 'uninstall.php' script runs decoupled * from regular action hooks like 'init'. * * @since 1.20.0 * * @param Context $context Plugin context. * @param Options $options Optional. Options instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null ) { $this->context = $context; $this->options = $options ?: new Options( $this->context ); } /** * Registers functionality through WordPress hooks. * * @since 1.20.0 */ public function register() { add_action( 'googlesitekit_uninstallation', function () { $this->uninstall(); $this->clear_scheduled_events(); } ); add_action( 'googlesitekit_deactivation', function () { $this->clear_scheduled_events(); } ); add_action( 'googlesitekit_reset', function () { $this->clear_scheduled_events(); } ); } /** * Runs necessary logic for uninstallation of the plugin. * * If connected to the proxy, it will issue a request to unregister the site. * * @since 1.20.0 */ private function uninstall() { $credentials = new Credentials( new Encrypted_Options( $this->options ) ); if ( $credentials->has() && $credentials->using_proxy() ) { $google_proxy = new Google_Proxy( $this->context ); $google_proxy->unregister_site( $credentials ); } } /** * Clears all scheduled events. * * @since 1.136.0 */ private function clear_scheduled_events() { foreach ( self::SCHEDULED_EVENTS as $event ) { // Only clear scheduled events that are set, important in E2E // testing. if ( (bool) wp_next_scheduled( $event ) ) { wp_unschedule_hook( $event ); } } } } Util/Google_URL_Normalizer.php 0000644 00000003145 14720521221 0012331 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Google_URL_Normalizer * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Class handling URL normalization for comparisons and API requests. * * @since 1.18.0 * @access private * @ignore */ final class Google_URL_Normalizer { /** * Normalizes a URL by converting to all lowercase, converting Unicode characters * to punycode, and removing bidirectional control characters. * * @since 1.18.0 * * @param string $url The URL or domain to normalize. * @return string The normalized URL or domain. */ public function normalize_url( $url ) { // Remove bidirectional control characters. $url = preg_replace( array( '/\xe2\x80\xac/', '/\xe2\x80\xab/' ), '', $url ); $url = $this->decode_unicode_url_or_domain( $url ); $url = strtolower( $url ); return $url; } /** * Returns the Punycode version of a Unicode URL or domain name. * * @since 1.18.0 * * @param string $url The URL or domain name to decode. */ protected function decode_unicode_url_or_domain( $url ) { $encoder_class = class_exists( '\WpOrg\Requests\IdnaEncoder' ) ? '\WpOrg\Requests\IdnaEncoder' : '\Requests_IDNAEncoder'; $parts = URL::parse( $url ); if ( ! $parts || ! isset( $parts['host'] ) || '' === $parts['host'] ) { return $encoder_class::encode( $url ); } $decoded_host = $encoder_class::encode( $parts['host'] ); return str_replace( $parts['host'], $decoded_host, $url ); } } Util/Activation_Flag.php 0000644 00000006373 14720521221 0011231 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Activation_Flag * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\Options; /** * Class handling plugin activation. * * @since 1.10.0 * @access private * @ignore */ final class Activation_Flag { const OPTION_SHOW_ACTIVATION_NOTICE = 'googlesitekit_show_activation_notice'; const OPTION_NEW_SITE_POSTS = 'googlesitekit_new_site_posts'; /** * Plugin context. * * @since 1.10.0 * @var Context */ private $context; /** * Option API instance. * * @since 1.10.0 * @var Options */ protected $options; /** * Constructor. * * @since 1.10.0 * * @param Context $context Plugin context. * @param Options $options Optional. The Option API instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null ) { $this->context = $context; $this->options = $options ?: new Options( $this->context ); } /** * Registers functionality through WordPress hooks. * * @since 1.10.0 */ public function register() { add_action( 'googlesitekit_activation', function ( $network_wide ) { // Set activation flag. $this->set_activation_flag( $network_wide ); } ); add_filter( 'googlesitekit_admin_data', function ( $data ) { return $this->inline_js_admin_data( $data ); } ); } /** * Sets the flag that the plugin has just been activated. * * @since 1.10.0 Migrated from Activation class. * * @param bool $network_wide Whether the plugin is being activated network-wide. */ public function set_activation_flag( $network_wide ) { if ( $network_wide ) { update_network_option( null, self::OPTION_SHOW_ACTIVATION_NOTICE, '1' ); return; } update_option( self::OPTION_SHOW_ACTIVATION_NOTICE, '1', false ); } /** * Gets the flag that the plugin has just been activated. * * @since 1.10.0 Migrated from Activation class. * * @param bool $network_wide Whether to check the flag network-wide. * @return bool True if just activated, false otherwise. */ public function get_activation_flag( $network_wide ) { if ( $network_wide ) { return (bool) get_network_option( null, self::OPTION_SHOW_ACTIVATION_NOTICE ); } return (bool) get_option( self::OPTION_SHOW_ACTIVATION_NOTICE ); } /** * Deletes the flag that the plugin has just been activated. * * @since 1.10.0 Migrated from Activation class. * * @param bool $network_wide Whether the plugin is being activated network-wide. */ public function delete_activation_flag( $network_wide ) { if ( $network_wide ) { delete_network_option( null, self::OPTION_SHOW_ACTIVATION_NOTICE ); return; } delete_option( self::OPTION_SHOW_ACTIVATION_NOTICE ); } /** * Modifies the admin data to pass to JS. * * @since 1.10.0 Migrated from Activation class. * * @param array $data Inline JS data. * @return array Filtered $data. */ private function inline_js_admin_data( $data ) { $data['newSitePosts'] = $this->options->get( self::OPTION_NEW_SITE_POSTS ); return $data; } } Util/Migration_1_3_0.php 0000644 00000005734 14720521221 0011011 0 ustar 00 <?php /** * Migration for 1.3.0 * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Tracking\Tracking_Consent; /** * Class Migration_1_3_0 * * @since 1.3.0 * @access private * @ignore */ class Migration_1_3_0 { /** * Target DB version. */ const DB_VERSION = '1.3.0'; /** * Context instance. * * @var Context */ protected $context; /** * Options instance. * * @var Options */ protected $options; /** * User_Options instance. * * @var User_Options */ protected $user_options; /** * Constructor. * * @since 1.3.0 * * @param Context $context Plugin context instance. * @param Options $options Optional. Options instance. * @param User_Options $user_options Optional. User_Options instance. */ public function __construct( Context $context, Options $options = null, User_Options $user_options = null ) { $this->context = $context; $this->options = $options ?: new Options( $context ); $this->user_options = $user_options ?: new User_Options( $context ); } /** * Registers hooks. * * @since 1.3.0 */ public function register() { add_action( 'admin_init', array( $this, 'migrate' ) ); } /** * Migrates the DB. * * @since 1.3.0 */ public function migrate() { $db_version = $this->options->get( 'googlesitekit_db_version' ); if ( ! $db_version || version_compare( $db_version, self::DB_VERSION, '<' ) ) { $this->migrate_tracking_opt_in(); $this->options->set( 'googlesitekit_db_version', self::DB_VERSION ); } } /** * Migrates the global tracking opt-in to a user option. * * @since 1.3.0 * @since 1.4.0 Migrates preference for up to 20 users. */ private function migrate_tracking_opt_in() { // Only migrate if tracking was opted-in. if ( $this->options->get( Tracking_Consent::OPTION ) ) { $backup_user_id = $this->user_options->get_user_id(); foreach ( $this->get_authenticated_users() as $user_id ) { $this->user_options->switch_user( $user_id ); $this->user_options->set( Tracking_Consent::OPTION, 1 ); } $this->user_options->switch_user( $backup_user_id ); } } /** * Gets the authenticated users connected to Site Kit. * * @since 1.4.0 * * @return string[] User IDs of authenticated users. Maximum of 20. */ private function get_authenticated_users() { return get_users( array( 'meta_key' => $this->user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_compare' => 'EXISTS', 'number' => 20, 'fields' => 'ID', ) ); } } Util/Auto_Updates.php 0000644 00000010256 14720521221 0010567 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Auto_Updates * * @package Google\Site_Kit\Core\Util * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use stdClass; /** * Utility class for auto-updates settings. * * @since 1.93.0 * @access private * @ignore */ class Auto_Updates { /** * Auto updated forced enabled. * * @since 1.93.0 * @var true */ const AUTO_UPDATE_FORCED_ENABLED = true; /** * Auto updated forced disabled. * * @since 1.93.0 * @var false */ const AUTO_UPDATE_FORCED_DISABLED = false; /** * Auto updated not forced. * * @since 1.93.0 * @var false */ const AUTO_UPDATE_NOT_FORCED = null; /** * Checks whether plugin auto-updates are enabled for the site. * * @since 1.93.0 * * @return bool `false` if auto-updates are disabled, `true` otherwise. */ public static function is_plugin_autoupdates_enabled() { if ( self::AUTO_UPDATE_FORCED_DISABLED === self::sitekit_forced_autoupdates_status() ) { return false; } if ( function_exists( 'wp_is_auto_update_enabled_for_type' ) ) { return wp_is_auto_update_enabled_for_type( 'plugin' ); } return false; } /** * Check whether the site has auto updates enabled for Site Kit. * * @since 1.93.0 * * @return bool `true` if auto updates are enabled, otherwise `false`. */ public static function is_sitekit_autoupdates_enabled() { if ( self::AUTO_UPDATE_FORCED_ENABLED === self::sitekit_forced_autoupdates_status() ) { return true; } if ( self::AUTO_UPDATE_FORCED_DISABLED === self::sitekit_forced_autoupdates_status() ) { return false; } $enabled_auto_updates = (array) get_site_option( 'auto_update_plugins', array() ); if ( ! $enabled_auto_updates ) { return false; } // Check if the Site Kit is in the list of auto-updated plugins. return in_array( GOOGLESITEKIT_PLUGIN_BASENAME, $enabled_auto_updates, true ); } /** * Checks whether auto-updates are forced for Site Kit. * * @since 1.93.0 * * @return bool|null */ public static function sitekit_forced_autoupdates_status() { if ( ! function_exists( 'wp_is_auto_update_forced_for_item' ) ) { return self::AUTO_UPDATE_NOT_FORCED; } if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $sitekit_plugin_data = get_plugin_data( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); $sitekit_update_data = self::get_sitekit_update_data(); $item = (object) array_merge( $sitekit_plugin_data, $sitekit_update_data ); $is_auto_update_forced_for_sitekit = wp_is_auto_update_forced_for_item( 'plugin', null, $item ); if ( true === $is_auto_update_forced_for_sitekit ) { return self::AUTO_UPDATE_FORCED_ENABLED; } if ( false === $is_auto_update_forced_for_sitekit ) { return self::AUTO_UPDATE_FORCED_DISABLED; } return self::AUTO_UPDATE_NOT_FORCED; } /** * Merges plugin update data in the site transient with some default plugin data. * * @since 1.113.0 * * @return array Site Kit plugin update data. */ protected static function get_sitekit_update_data() { $sitekit_update_data = array( 'id' => 'w.org/plugins/' . dirname( GOOGLESITEKIT_PLUGIN_BASENAME ), 'slug' => dirname( GOOGLESITEKIT_PLUGIN_BASENAME ), 'plugin' => GOOGLESITEKIT_PLUGIN_BASENAME, 'new_version' => '', 'url' => '', 'package' => '', 'icons' => array(), 'banners' => array(), 'banners_rtl' => array(), 'tested' => '', 'requires_php' => GOOGLESITEKIT_PHP_MINIMUM, 'compatibility' => new stdClass(), ); $plugin_updates = get_site_transient( 'update_plugins' ); $transient_data = array(); if ( isset( $plugin_updates->noupdate[ GOOGLESITEKIT_PLUGIN_BASENAME ] ) ) { $transient_data = $plugin_updates->noupdate[ GOOGLESITEKIT_PLUGIN_BASENAME ]; } if ( isset( $plugin_updates->response[ GOOGLESITEKIT_PLUGIN_BASENAME ] ) ) { $transient_data = $plugin_updates->response[ GOOGLESITEKIT_PLUGIN_BASENAME ]; } return array_merge( $sitekit_update_data, (array) $transient_data ); } } Util/Input.php 0000644 00000005412 14720521221 0007267 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Input * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Class for input superglobal access. * * @since 1.1.2 * @access private * @ignore */ class Input { /** * Map of input type to superglobal array. * * For use as fallback only. * * @since 1.1.4 * @var array */ protected $fallback_map; /** * Constructor. * * @since 1.1.4 */ public function __construct() { // Fallback map for environments where filter_input may not work with ENV or SERVER types. $this->fallback_map = array( INPUT_ENV => $_ENV, INPUT_SERVER => $_SERVER, // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage ); } /** * Gets a specific external variable by name and optionally filters it. * * @since 1.1.2 * @since 1.92.0 Changed default value of $options parameter to 0. * * @link https://php.net/manual/en/function.filter-input.php * * @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV. * @param string $variable_name Name of a variable to get. * @param int $filter [optional] The ID of the filter to apply. The manual page lists the available filters. * @param mixed $options [optional] Associative array of options or bitwise disjunction of flags. * If filter accepts options, flags can be provided in "flags" field of array. * @return mixed Value of the requested variable on success, * FALSE if the filter fails, * NULL if the $variable_name variable is not set. * * If the flag FILTER_NULL_ON_FAILURE is used, it returns FALSE if the variable is not set * and NULL if the filter fails. */ public function filter( $type, $variable_name, $filter = FILTER_DEFAULT, $options = 0 ) { $value = filter_input( $type, $variable_name, $filter, $options ); // Fallback for environments where filter_input may not work with specific types. if ( // Only use this fallback for affected input types. isset( $this->fallback_map[ $type ] ) // Only use the fallback if the value is not-set (could be either depending on FILTER_NULL_ON_FAILURE). && in_array( $value, array( null, false ), true ) // Only use the fallback if the key exists in the input map. && array_key_exists( $variable_name, $this->fallback_map[ $type ] ) ) { return filter_var( $this->fallback_map[ $type ][ $variable_name ], $filter, $options ); } return $value; } } Util/Date.php 0000644 00000003672 14720521221 0007053 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\URL * * @package Google\Site_Kit\Core\Util * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Class for custom date parsing methods. * * @since 1.99.0 * @access private * @ignore */ class Date { /** * Parses a date range string into a start date and an end date. * * @since 1.99.0 * * @param string $range Date range string. Either 'last-7-days', 'last-14-days', 'last-90-days', or * 'last-28-days' (default). * @param string $multiplier Optional. How many times the date range to get. This value can be specified if the * range should be request multiple times back. Default 1. * @param int $offset Days the range should be offset by. Default 1. Used by Search Console where * data is delayed by two days. * @param bool $previous Whether to select the previous period. Default false. * @return array List with two elements, the first with the start date and the second with the end date, both as 'Y-m-d'. */ public static function parse_date_range( $range, $multiplier = 1, $offset = 1, $previous = false ) { preg_match( '*-(\d+)-*', $range, $matches ); $number_of_days = $multiplier * ( isset( $matches[1] ) ? $matches[1] : 28 ); // Calculate the end date. For previous period requests, offset period by the number of days in the request. $end_date_offset = $previous ? $offset + $number_of_days : $offset; $date_end = gmdate( 'Y-m-d', strtotime( $end_date_offset . ' days ago' ) ); // Set the start date. $start_date_offset = $end_date_offset + $number_of_days - 1; $date_start = gmdate( 'Y-m-d', strtotime( $start_date_offset . ' days ago' ) ); return array( $date_start, $date_end ); } } Util/Migrate_Legacy_Keys.php 0000644 00000002125 14720521221 0012035 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Util\Migrate_Legacy_Keys * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Trait for a class that migrates array keys from old to new. * * @since 1.2.0 * @access private * @ignore */ trait Migrate_Legacy_Keys { /** * Migrates legacy array keys to the current key. * * @since 1.2.0 * * @param array $legacy_array Input associative array to migrate keys for. * @param array $key_mapping Map of legacy key to current key. * @return array Updated array. */ protected function migrate_legacy_keys( array $legacy_array, array $key_mapping ) { foreach ( $key_mapping as $legacy_key => $current_key ) { if ( ! isset( $legacy_array[ $current_key ] ) && isset( $legacy_array[ $legacy_key ] ) ) { $legacy_array[ $current_key ] = $legacy_array[ $legacy_key ]; } unset( $legacy_array[ $legacy_key ] ); } return $legacy_array; } } Util/Google_Icon.php 0000644 00000003123 14720521221 0010351 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Google_Icon * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Class for the Google SVG Icon * * @since 1.28.0 * @access private * @ignore */ final class Google_Icon { /** * We use fill="white" as a placeholder attribute that we replace in with_fill() * to match the colorscheme that the user has set. * * See the comment in includes/Core/Admin/Screen.php::add() for more information. */ const XML = '<svg width="20" height="20" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill="white" d="M17.6 8.5h-7.5v3h4.4c-.4 2.1-2.3 3.5-4.4 3.4-2.6-.1-4.6-2.1-4.7-4.7-.1-2.7 2-5 4.7-5.1 1.1 0 2.2.4 3.1 1.2l2.3-2.2C14.1 2.7 12.1 2 10.2 2c-4.4 0-8 3.6-8 8s3.6 8 8 8c4.6 0 7.7-3.2 7.7-7.8-.1-.6-.1-1.1-.3-1.7z" fillrule="evenodd" cliprule="evenodd"></path></svg>'; /** * Returns a base64 encoded version of the SVG. * * @since 1.28.0 * * @param string $source SVG icon source. * @return string Base64 representation of SVG */ public static function to_base64( $source = self::XML ) { return base64_encode( $source ); } /** * Returns SVG XML with fill color replaced. * * @since 1.28.0 * * @param string $color Any valid color for css, either word or hex code. * @return string SVG XML with the fill color replaced */ public static function with_fill( $color ) { return str_replace( 'white', esc_attr( $color ), self::XML ); } } Util/Synthetic_WP_Query.php 0000644 00000010126 14720521221 0011733 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Synthetic_WP_Query * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use WP_Query; use WP_Post; /** * Class extending WordPress core's `WP_Query` for more self-contained behavior. * * @since 1.16.0 * @access private * @ignore */ final class Synthetic_WP_Query extends WP_Query { /** * The hash of the `$query` last parsed into `$query_vars`. * * @since 1.16.0 * @var string */ private $parsed_query_hash = ''; /** * Whether automatic 404 detection in `get_posts()` method is enabled. * * @since 1.16.0 * @var bool */ private $enable_404_detection = false; /** * Sets whether 404 detection in `get_posts()` method should be enabled. * * @since 1.16.0 * * @param bool $enable Whether or not to enable 404 detection. */ public function enable_404_detection( $enable ) { $this->enable_404_detection = (bool) $enable; } /** * Initiates object properties and sets default values. * * @since 1.16.0 */ public function init() { parent::init(); $this->parsed_query_hash = ''; } /** * Extends `WP_Query::parse_query()` to ensure it is not unnecessarily run twice. * * @since 1.16.0 * * @param string|array $query Optional. Array or string of query parameters. See `WP_Query::parse_query()`. */ public function parse_query( $query = '' ) { if ( ! empty( $query ) ) { $query_to_hash = wp_parse_args( $query ); } elseif ( ! isset( $this->query ) ) { $query_to_hash = $this->query_vars; } else { $query_to_hash = $this->query; } // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize $query_hash = md5( serialize( $query_to_hash ) ); // If this query was parsed before, bail early. if ( $query_hash === $this->parsed_query_hash ) { return; } parent::parse_query( $query ); // Set query hash for current `$query` and `$query_vars` properties. $this->parsed_query_hash = $query_hash; } /** * Extends `WP_Query::get_posts()` to include supplemental logic such as detecting a 404 state. * * The majority of the code is a copy of `WP::handle_404()`. * * @since 1.16.0 * * @return WP_Post[]|int[] Array of post objects or post IDs. */ public function get_posts() { $results = parent::get_posts(); // If 404 detection is not enabled, just return the results. if ( ! $this->enable_404_detection ) { return $results; } // Check if this is a single paginated post query. if ( $this->posts && $this->is_singular() && $this->post && ! empty( $this->query_vars['page'] ) ) { // If the post is actually paged and the 'page' query var is within bounds, it's all good. $next = '<!--nextpage-->'; if ( false !== strpos( $this->post->post_content, $next ) && (int) trim( $this->query_vars['page'], '/' ) <= ( substr_count( $this->post->post_content, $next ) + 1 ) ) { return $results; } // Otherwise, this query is out of bounds, so set a 404. $this->set_404(); return $results; } // If no posts were found, this is technically a 404. if ( ! $this->posts ) { // If this is a paginated query (i.e. out of bounds), always consider it a 404. if ( $this->is_paged() ) { $this->set_404(); return $results; } // If this is an author archive, don't consider it a 404 if the author exists. if ( $this->is_author() ) { $author = $this->get( 'author' ); if ( is_numeric( $author ) && $author > 0 && is_user_member_of_blog( $author ) ) { return $results; } } // If this is a valid taxonomy or post type archive, don't consider it a 404. if ( ( $this->is_category() || $this->is_tag() || $this->is_tax() || $this->is_post_type_archive() ) && $this->get_queried_object() ) { return $results; } // If this is a search results page or the home index, don't consider it a 404. if ( $this->is_home() || $this->is_search() ) { return $results; } // Otherwise, set a 404. $this->set_404(); } return $results; } } Util/Reset_Persistent.php 0000644 00000001232 14720521221 0011466 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Reset_Persistent * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Class providing functions to reset the persistent plugin settings. * * @since 1.27.0 * @access private * @ignore */ class Reset_Persistent extends Reset { /** * MySQL key pattern for all persistent Site Kit keys. */ const KEY_PATTERN = 'googlesitekitpersistent\_%'; /** * REST API endpoint. */ const REST_ROUTE = 'core/site/data/reset-persistent'; } Util/Migration_1_129_0.php 0000644 00000010015 14720521221 0011146 0 ustar 00 <?php /** * Migration for Conversion ID. * * @package Google\Site_Kit\Core\Util * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Modules\Analytics_4\Settings as Analytics_Settings; use Google\Site_Kit\Modules\Ads; use Google\Site_Kit\Modules\Ads\Settings as Ads_Settings; /** * Class Migration_1_129_0 * * @since 1.129.0 * @access private * @ignore */ class Migration_1_129_0 { /** * Target DB version. */ const DB_VERSION = '1.129.0'; /** * DB version option name. */ const DB_VERSION_OPTION = 'googlesitekit_db_version'; /** * Context instance. * * @since 1.129.0 * @var Context */ protected $context; /** * Options instance. * * @since 1.129.0 * @var Options */ protected $options; /** * Analytics_Settings instance. * * @since 1.129.0 * @var Analytics_Settings */ protected $analytics_settings; /** * Ads_Settings instance. * * @since 1.129.0 * @var Ads_Settings */ protected $ads_settings; /** * Constructor. * * @since 1.129.0 * * @param Context $context Plugin context instance. * @param Options $options Optional. Options instance. */ public function __construct( Context $context, Options $options = null ) { $this->context = $context; $this->options = $options ?: new Options( $context ); $this->analytics_settings = new Analytics_Settings( $this->options ); $this->ads_settings = new Ads_Settings( $this->options ); } /** * Registers hooks. * * @since 1.129.0 */ public function register() { add_action( 'admin_init', array( $this, 'migrate' ) ); } /** * Migrates the DB. * * @since 1.129.0 */ public function migrate() { $db_version = $this->options->get( self::DB_VERSION_OPTION ); if ( ! $db_version || version_compare( $db_version, self::DB_VERSION, '<' ) ) { $this->migrate_analytics_conversion_id_setting(); $this->activate_ads_module(); $this->options->set( self::DB_VERSION_OPTION, self::DB_VERSION ); } } /** * Migrates the Ads Conversion ID to the new Ads module. * * @since 1.129.0 */ protected function migrate_analytics_conversion_id_setting() { if ( ! $this->analytics_settings->has() ) { return; } $analytics_settings = $this->analytics_settings->get(); if ( empty( $analytics_settings ) || ! array_key_exists( 'adsConversionID', $analytics_settings ) || empty( $analytics_settings['adsConversionID'] ) ) { return; } $ads_settings = $this->ads_settings->get(); if ( array_key_exists( 'conversionID', $ads_settings ) && ! empty( $ads_settings['conversionID'] ) ) { // If there is already an adsConversionID set in the Ads module, do not overwrite it, remove it from the Analytics module. unset( $analytics_settings['adsConversionID'] ); $this->analytics_settings->set( $analytics_settings ); return; } $ads_settings['conversionID'] = $analytics_settings['adsConversionID']; $this->ads_settings->set( $ads_settings ); unset( $analytics_settings['adsConversionID'] ); $analytics_settings['adsConversionIDMigratedAtMs'] = time() * 1000; $this->analytics_settings->set( $analytics_settings ); } /** * Activates the ads module if the Ads Conversion ID was previously set. * * @since 1.129.0 */ protected function activate_ads_module() { $active_modules = $this->options->get( Modules::OPTION_ACTIVE_MODULES ); if ( is_array( $active_modules ) && in_array( 'ads', $active_modules, true ) ) { return; } $ads_settings = $this->ads_settings->get(); // Activate the Ads module if the Ads Conversion ID was previously set // and the Ads module is not already active. if ( ! empty( $ads_settings['conversionID'] ) ) { $active_modules[] = Ads::MODULE_SLUG; $this->options->set( Modules::OPTION_ACTIVE_MODULES, $active_modules ); } } } Util/Method_Proxy_Trait.php 0000644 00000002136 14720521221 0011754 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Method_Proxy_Trait * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; trait Method_Proxy_Trait { /** * Gets a proxy function for a class method. * * @since 1.17.0 * * @param string $method Method name. * @return callable A proxy function. */ private function get_method_proxy( $method ) { return function ( ...$args ) use ( $method ) { return $this->{ $method }( ...$args ); }; } /** * Gets a proxy function for a class method which can be executed only once. * * @since 1.24.0 * * @param string $method Method name. * @return callable A proxy function. */ private function get_method_proxy_once( $method ) { return function ( ...$args ) use ( $method ) { static $called; static $return_value; if ( ! $called ) { $called = true; $return_value = $this->{ $method }( ...$args ); } return $return_value; }; } } Util/Health_Checks.php 0000644 00000007702 14720521221 0010661 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Health_Checks * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Exception; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit_Dependencies\Google\Service\SearchConsole as Google_Service_SearchConsole; use Google\Site_Kit_Dependencies\Google_Service_Exception; use WP_REST_Server; /** * Class for performing health checks. * * @since 1.14.0 * @access private * @ignore */ class Health_Checks { /** * Authentication instance. * * @var Authentication */ protected $authentication; /** * Google_Proxy instance. * * @var Google_Proxy */ protected $google_proxy; /** * Constructor. * * @param Authentication $authentication Authentication instance. */ public function __construct( Authentication $authentication ) { $this->authentication = $authentication; $this->google_proxy = $authentication->get_google_proxy(); } /** * Registers functionality through WordPress hooks. * * @since 1.14.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $rest_routes ) { $health_check_routes = $this->get_rest_routes(); return array_merge( $rest_routes, $health_check_routes ); } ); } /** * Gets all health check REST routes. * * @since 1.14.0 * * @return REST_Route[] List of REST_Route objects. */ private function get_rest_routes() { return array( new REST_Route( 'core/site/data/health-checks', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { $checks = array( 'googleAPI' => $this->check_google_api(), 'skService' => $this->check_service_connectivity(), ); return compact( 'checks' ); }, 'permission_callback' => function () { return current_user_can( Permissions::VIEW_SHARED_DASHBOARD ) || current_user_can( Permissions::SETUP ); }, ), ) ), ); } /** * Checks connection to Google APIs. * * @since 1.14.0 * * @return array Results data. */ private function check_google_api() { $client = $this->authentication->get_oauth_client()->get_client(); $restore_defer = $client->withDefer( false ); $error_msg = ''; // Make a request to the Search API. // This request is bound to fail but this is okay as long as the error response comes // from a Google API endpoint (Google_Service_exception). The test is only intended // to check that the server is capable of connecting to the Google API (at all) // regardless of valid authentication, which will likely be missing here. try { ( new Google_Service_SearchConsole( $client ) )->sites->listSites(); $pass = true; } catch ( Google_Service_Exception $e ) { if ( ! empty( $e->getErrors() ) ) { $pass = true; } else { $pass = false; $error_msg = $e->getMessage(); } } catch ( Exception $e ) { $pass = false; $error_msg = $e->getMessage(); } $restore_defer(); return array( 'pass' => $pass, 'errorMsg' => $error_msg, ); } /** * Checks connection to Site Kit service. * * @since 1.85.0 * * @return array Results data. */ private function check_service_connectivity() { $service_url = $this->google_proxy->url(); $response = wp_remote_head( $service_url ); if ( is_wp_error( $response ) ) { return array( 'pass' => false, 'errorMsg' => $response->get_error_message(), ); } $status_code = wp_remote_retrieve_response_code( $response ); $pass = is_int( $status_code ) && $status_code < 400; return array( 'pass' => $pass, 'errorMsg' => $pass ? '' : 'connection_fail', ); } } Util/Reset.php 0000644 00000016530 14720521221 0007255 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Reset * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use WP_REST_Server; use WP_REST_Request; use WP_REST_Response; /** * Class providing functions to reset the plugin. * * @since 1.0.0 * @since 1.1.1 Removed delete_all_plugin_options(), delete_all_user_metas() and delete_all_transients() methods. * @access private * @ignore */ class Reset { /** * MySQL key pattern for all Site Kit keys. */ const KEY_PATTERN = 'googlesitekit\_%'; /** * REST API endpoint. */ const REST_ROUTE = 'core/site/data/reset'; /** * Action for triggering a reset. */ const ACTION = 'googlesitekit_reset'; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Gets the URL to handle a reset action. * * @since 1.30.0 * * @return string */ public static function url() { return add_query_arg( array( 'action' => static::ACTION, 'nonce' => wp_create_nonce( static::ACTION ), ), admin_url( 'index.php' ) ); } /** * Constructor. * * @since 1.0.0 * @since 1.1.1 Removed $options and $transients params. * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Registers functionality through WordPress hooks. * * @since 1.3.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_action( 'admin_action_' . static::ACTION, function () { $this->handle_reset_action( $this->context->input()->filter( INPUT_GET, 'nonce' ) ); } ); } /** * Deletes options, user stored options, transients and clears object cache for stored options. * * @since 1.0.0 */ public function all() { $this->delete_options( 'site' ); $this->delete_user_options( 'site' ); $this->delete_post_meta( 'site' ); if ( $this->context->is_network_mode() ) { $this->delete_options( 'network' ); $this->delete_user_options( 'network' ); $this->delete_post_meta( 'network' ); } wp_cache_flush(); } /** * Deletes all Site Kit options and transients. * * @since 1.3.0 * * @param string $scope Scope of the deletion ('site' or 'network'). */ private function delete_options( $scope ) { global $wpdb; if ( 'site' === $scope ) { list ( $table_name, $column_name, $transient_prefix ) = array( $wpdb->options, 'option_name', '_transient_' ); } elseif ( 'network' === $scope ) { list ( $table_name, $column_name, $transient_prefix ) = array( $wpdb->sitemeta, 'meta_key', '_site_transient_' ); } else { return; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( $wpdb->prepare( /* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ " DELETE FROM {$table_name} WHERE {$column_name} LIKE %s OR {$column_name} LIKE %s OR {$column_name} LIKE %s OR {$column_name} = %s ", /* phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ static::KEY_PATTERN, $transient_prefix . static::KEY_PATTERN, $transient_prefix . 'timeout_' . static::KEY_PATTERN, 'googlesitekit-active-modules' ) ); } /** * Deletes all Site Kit user options. * * @param string $scope Scope of the deletion ('site' or 'network'). */ private function delete_user_options( $scope ) { global $wpdb; if ( 'site' === $scope ) { $meta_prefix = $wpdb->get_blog_prefix(); } elseif ( 'network' === $scope ) { $meta_prefix = ''; } else { return; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", $meta_prefix . static::KEY_PATTERN ) ); } /** * Deletes all Site Kit post meta settings. * * @since 1.33.0 * * @param string $scope Scope of the deletion ('site' or 'network'). */ private function delete_post_meta( $scope ) { global $wpdb; $sites = array(); if ( 'network' === $scope ) { $sites = get_sites( array( 'fields' => 'ids', 'number' => 9999999, ) ); } else { $sites[] = get_current_blog_id(); } foreach ( $sites as $site_id ) { $prefix = $wpdb->get_blog_prefix( $site_id ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( $wpdb->prepare( "DELETE FROM {$prefix}postmeta WHERE `meta_key` LIKE %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared static::KEY_PATTERN ) ); } } /** * Gets related REST routes. * * @since 1.3.0 * * @return array List of REST_Route objects. */ private function get_rest_routes() { $can_setup = function () { return current_user_can( Permissions::SETUP ); }; return array( new REST_Route( static::REST_ROUTE, array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function () { $this->all(); $this->maybe_hard_reset(); // Call hooks on plugin reset. This is used to reset the ad blocking recovery notification. do_action( 'googlesitekit_reset' ); return new WP_REST_Response( true ); }, 'permission_callback' => $can_setup, ), ) ), ); } /** * Handles the reset admin action. * * @since 1.30.0 * * @param string $nonce WP nonce for action. */ private function handle_reset_action( $nonce ) { if ( ! wp_verify_nonce( $nonce, static::ACTION ) ) { $authentication = new Authentication( $this->context ); $authentication->invalid_nonce_error( static::ACTION ); } if ( ! current_user_can( Permissions::SETUP ) ) { wp_die( esc_html__( 'You don’t have permissions to set up Site Kit.', 'google-site-kit' ), 403 ); } // Call hooks on plugin reset. This is used to reset the ad blocking recovery notification. do_action( 'googlesitekit_reset' ); $this->all(); $this->maybe_hard_reset(); wp_safe_redirect( $this->context->admin_url( 'splash', array( // Trigger client-side storage reset. 'googlesitekit_reset_session' => 1, // Show reset-success notification. 'notification' => 'reset_success', ) ) ); exit; } /** * Performs hard reset if it is enabled programmatically. * * @since 1.46.0 */ public function maybe_hard_reset() { /** * Filters the hard reset option, which is `false` by default. * * By default, when Site Kit is reset it does not delete "persistent" data * (options prefixed with `googlesitekitpersistent_`). If this filter returns `true`, * all options belonging to Site Kit, including those with the above "persistent" * prefix, will be deleted. * * @since 1.46.0 * * @param bool $hard_reset_enabled If a hard reset is enabled. `false` by default. */ $hard_reset_enabled = apply_filters( 'googlesitekit_hard_reset_enabled', false ); if ( ! $hard_reset_enabled ) { return; } $reset_persistent = new Reset_Persistent( $this->context ); $reset_persistent->all(); } } Util/Collection_Key_Cap_Filter.php 0000644 00000002603 14720521221 0013162 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\Collection_Key_Cap_Filter * * @package Google\Site_Kit\Core\Util * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; /** * Class for filtering a specific key of a collection based on a capability. * * @since 1.77.0 * @access private * @ignore */ class Collection_Key_Cap_Filter { /** * Collection key. * * @since 1.77.0 * @var string */ private $key; /** * Capability. * * @since 1.77.0 * @var string */ private $cap; /** * Constructor. * * @since 1.77.0. * * @param string $key Target collection key to filter. * @param string $capability Required capability to filter by. */ public function __construct( $key, $capability ) { $this->key = $key; $this->cap = $capability; } /** * Filters the given value of a specific key in each item of the given collection * based on the key and capability. * * @since 1.77.0 * * @param array[] $collection Array of arrays. * @return array[] Filtered collection. */ public function filter_key_by_cap( array $collection ) { foreach ( $collection as $meta_arg => &$value ) { if ( ! current_user_can( $this->cap, $meta_arg ) ) { unset( $value[ $this->key ] ); } } return $collection; } } Util/WP_Query_Factory.php 0000644 00000023420 14720521221 0011371 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Util\WP_Query_Factory * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Util; use WP_Query; /** * Class creating `WP_Query` instances. * * @since 1.15.0 * @access private * @ignore */ final class WP_Query_Factory { use WP_Context_Switcher_Trait; /** * Creates a `WP_Query` instance to use for a given URL. * * The `WP_Query` instance returned is initialized with the correct query arguments, but the actual query will not * have run yet. The `WP_Query::get_posts()` method should be used to do that. * * This is an expensive function that works similarly to WordPress core's `url_to_postid()` function, however also * covering non-post URLs. It follows logic used in `WP::parse_request()` to cover the other kinds of URLs. The * majority of the code is a direct copy of certain parts of these functions. * * @since 1.15.0 * * @param string $url URL to get WordPress query object for. * @return WP_Query|null WordPress query instance, or null if unable to parse query from URL. */ public static function from_url( $url ) { $url = self::normalize_url( $url ); if ( empty( $url ) ) { return null; } $url_path_vars = self::get_url_path_vars( $url ); $url_query_vars = self::get_url_query_vars( $url ); $query_args = self::parse_wp_query_args( $url_path_vars, $url_query_vars ); $restore_context = self::with_frontend_context(); // Return extended version of `WP_Query` with self-contained 404 detection. $query = new Synthetic_WP_Query(); $query->parse_query( $query_args ); $query->enable_404_detection( true ); $restore_context(); return $query; } /** * Normalizes the URL for further processing. * * @since 1.15.0 * * @param string $url URL to normalize. * @return string Normalized URL, or empty string if URL is irrelevant for parsing into `WP_Query` arguments. */ private static function normalize_url( $url ) { global $wp_rewrite; $url_host = str_replace( 'www.', '', URL::parse( $url, PHP_URL_HOST ) ); $home_url_host = str_replace( 'www.', '', URL::parse( home_url(), PHP_URL_HOST ) ); // Bail early if the URL does not belong to this site. if ( $url_host && $url_host !== $home_url_host ) { return ''; } // Strip 'index.php/' if we're not using path info permalinks. if ( ! $wp_rewrite->using_index_permalinks() ) { $url = str_replace( $wp_rewrite->index . '/', '', $url ); } return $url; } /** * Parses the path segment of a URL to get variables based on WordPress rewrite rules. * * The variables returned from this method are not necessarily all relevant for a `WP_Query`, they will still need * to go through sanitization against the available public query vars from WordPress. * * This code is mostly a partial copy of `WP::parse_request()` which is used to parse the current request URL * into variables in a similar way. * * @since 1.15.0 * * @param string $url URL to parse path vars from. * @return array Associative array of path vars. */ private static function get_url_path_vars( $url ) { global $wp_rewrite; $url_path = URL::parse( $url, PHP_URL_PATH ); // Strip potential home URL path segment from URL path. $home_path = untrailingslashit( URL::parse( home_url( '/' ), PHP_URL_PATH ) ); if ( ! empty( $home_path ) ) { $url_path = substr( $url_path, strlen( $home_path ) ); } // Strip leading and trailing slashes. if ( is_string( $url_path ) ) { $url_path = trim( $url_path, '/' ); } // Fetch the rewrite rules. $rewrite = $wp_rewrite->wp_rewrite_rules(); // Match path against rewrite rules. $matched_rule = ''; $query = ''; $matches = array(); if ( empty( $url_path ) || $url_path === $wp_rewrite->index ) { if ( isset( $rewrite['$'] ) ) { $matched_rule = '$'; $query = $rewrite['$']; $matches = array( '' ); } } else { foreach ( (array) $rewrite as $match => $query ) { if ( preg_match( "#^$match#", $url_path, $matches ) ) { if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) { // This is a verbose page match, let's check to be sure about it. // We'll rely 100% on WP core functions here. // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions $page = get_page_by_path( $matches[ $varmatch[1] ] ); if ( ! $page ) { continue; } $post_status_obj = get_post_status_object( $page->post_status ); if ( ! $post_status_obj->public && ! $post_status_obj->protected && ! $post_status_obj->private && $post_status_obj->exclude_from_search ) { continue; } } $matched_rule = $match; break; } } } // If rewrite rules matched, populate $url_path_vars. $url_path_vars = array(); if ( $matched_rule ) { // Trim the query of everything up to the '?'. $query = preg_replace( '!^.+\?!', '', $query ); // Substitute the substring matches into the query. $query = addslashes( \WP_MatchesMapRegex::apply( $query, $matches ) ); parse_str( $query, $url_path_vars ); } return $url_path_vars; } /** * Parses the query segment of a URL to get variables. * * The variables returned from this method are not necessarily all relevant for a `WP_Query`, they will still need * to go through sanitization against the available public query vars from WordPress. * * @since 1.15.0 * * @param string $url URL to parse query vars from. * @return array Associative array of query vars. */ private static function get_url_query_vars( $url ) { $url_query = URL::parse( $url, PHP_URL_QUERY ); $url_query_vars = array(); if ( $url_query ) { parse_str( $url_query, $url_query_vars ); } return $url_query_vars; } /** * Returns arguments for a `WP_Query` instance based on URL path vars and URL query vars. * * This method essentially sanitizes the passed vars, allowing only WordPress public query vars to be used as * actual arguments for `WP_Query`. When combining URL path vars and URL query vars, the latter take precedence. * * This code is mostly a partial copy of `WP::parse_request()` which is used to parse the current request URL * into query arguments in a similar way. * * @since 1.15.0 * * @param array $url_path_vars Associative array as returned from {@see WP_Query_Factory::get_url_path_vars()}. * @param array $url_query_vars Associative array as returned from {@see WP_Query_Factory::get_url_query_vars()}. * @return array Associative array of arguments to pass to a `WP_Query` instance. */ private static function parse_wp_query_args( array $url_path_vars, array $url_query_vars ) { global $wp; // Determine available post type query vars. $post_type_query_vars = array(); foreach ( get_post_types( array(), 'objects' ) as $post_type => $post_type_obj ) { if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->query_var ) { $post_type_query_vars[ $post_type_obj->query_var ] = $post_type; } } // Depending on whether WordPress already parsed the main request (and thus filtered 'query_vars'), we should // either manually trigger the filter or not. if ( did_action( 'parse_request' ) ) { $public_query_vars = $wp->public_query_vars; } else { $public_query_vars = apply_filters( 'query_vars', $wp->public_query_vars ); } // Populate `WP_Query` arguments. $query_args = array(); foreach ( $public_query_vars as $wpvar ) { if ( isset( $url_query_vars[ $wpvar ] ) ) { $query_args[ $wpvar ] = $url_query_vars[ $wpvar ]; } elseif ( isset( $url_path_vars[ $wpvar ] ) ) { $query_args[ $wpvar ] = $url_path_vars[ $wpvar ]; } if ( ! empty( $query_args[ $wpvar ] ) ) { if ( ! is_array( $query_args[ $wpvar ] ) ) { $query_args[ $wpvar ] = (string) $query_args[ $wpvar ]; } else { foreach ( $query_args[ $wpvar ] as $key => $value ) { if ( is_scalar( $value ) ) { $query_args[ $wpvar ][ $key ] = (string) $value; } } } if ( isset( $post_type_query_vars[ $wpvar ] ) ) { $query_args['post_type'] = $post_type_query_vars[ $wpvar ]; $query_args['name'] = $query_args[ $wpvar ]; } } } // Convert urldecoded spaces back into '+'. foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy => $taxonomy_obj ) { if ( $taxonomy_obj->query_var && isset( $query_args[ $taxonomy_obj->query_var ] ) ) { $query_args[ $taxonomy_obj->query_var ] = str_replace( ' ', '+', $query_args[ $taxonomy_obj->query_var ] ); } } // Don't allow non-publicly queryable taxonomies to be queried from the front end. foreach ( get_taxonomies( array( 'publicly_queryable' => false ), 'objects' ) as $taxonomy => $t ) { if ( isset( $query_args['taxonomy'] ) && $taxonomy === $query_args['taxonomy'] ) { unset( $query_args['taxonomy'], $query_args['term'] ); } } // Limit publicly queried post_types to those that are 'publicly_queryable'. if ( isset( $query_args['post_type'] ) ) { $queryable_post_types = get_post_types( array( 'publicly_queryable' => true ) ); if ( ! is_array( $query_args['post_type'] ) ) { if ( ! in_array( $query_args['post_type'], $queryable_post_types, true ) ) { unset( $query_args['post_type'] ); } } else { $query_args['post_type'] = array_intersect( $query_args['post_type'], $queryable_post_types ); } } // Resolve conflicts between posts with numeric slugs and date archive queries. $query_args = wp_resolve_numeric_slug_conflicts( $query_args ); // This is a WordPress core filter applied here to allow for the same modifications (e.g. for post formats). $query_args = apply_filters( 'request', $query_args ); return $query_args; } } Contracts/WP_Errorable.php 0000644 00000001120 14720521221 0011526 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Contracts\WP_Errorable. * * @package Google\Site_Kit\Core\Contracts * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Contracts; use WP_Error; /** * Interface for a class which can be represented as a WP_Error. * * @since 1.9.0 */ interface WP_Errorable { /** * Gets the WP_Error representation of this entity. * * @since 1.9.0 * * @return WP_Error */ public function to_wp_error(); } Prompts/Dismissed_Prompts.php 0000644 00000006204 14720521221 0012367 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Prompts\Dismissed_Prompts * * @package Google\Site_Kit\Core\Prompts * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Prompts; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class for representing a user's dismissed prompts. * * @since 1.121.0 * @access private * @ignore */ class Dismissed_Prompts extends User_Setting { /** * The user option name for this setting. * * @note This option is prefixed differently so that it will persist across disconnect/reset. */ const OPTION = 'googlesitekitpersistent_dismissed_prompts'; const DISMISS_PROMPT_PERMANENTLY = 0; /** * Adds one prompt to the list of dismissed prompts or updates the triggered count. * * @since 1.121.0 * * @param string $prompt Prompt to dismiss. * @param int $expires_in_seconds TTL for the prompt. */ public function add( $prompt, $expires_in_seconds = self::DISMISS_PROMPT_PERMANENTLY ) { $prompts = $this->get(); if ( array_key_exists( $prompt, $prompts ) ) { $prompts[ $prompt ]['expires'] = $expires_in_seconds ? time() + $expires_in_seconds : 0; $prompts[ $prompt ]['count'] = $prompts[ $prompt ]['count'] + 1; } else { $prompts[ $prompt ] = array( 'expires' => $expires_in_seconds ? time() + $expires_in_seconds : 0, 'count' => 1, ); } $this->set( $prompts ); } /** * Removes one or more prompts from the list of dismissed prompts. * * @since 1.121.0 * * @param string $prompt Item to remove. */ public function remove( $prompt ) { $prompts = $this->get(); // If the prompt is not in dismissed prompts, there's nothing to do. if ( ! array_key_exists( $prompt, $prompts ) ) { return; } unset( $prompts[ $prompt ] ); $this->set( $prompts ); } /** * Gets the value of the setting. * * @since 1.121.0 * * @return array Value set for the option, or default if not set. */ public function get() { $value = parent::get(); return is_array( $value ) ? $value : $this->get_default(); } /** * Gets the expected value type. * * @since 1.121.0 * * @return string The type name. */ protected function get_type() { return 'array'; } /** * Gets the default value. * * @since 1.121.0 * * @return array The default value. */ protected function get_default() { return array(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.121.0 * * @return callable Sanitize callback. */ protected function get_sanitize_callback() { return function ( $prompts ) { if ( ! is_array( $prompts ) ) { return $this->get_default(); } $sanitized_prompts = array(); foreach ( $prompts as $prompt => $data ) { if ( is_array( $data ) && isset( $data['expires'], $data['count'] ) && is_numeric( $data['expires'] ) && is_numeric( $data['count'] ) ) { $sanitized_prompts[ $prompt ] = array( 'expires' => $data['expires'], 'count' => $data['count'], ); } } return $sanitized_prompts; }; } } Prompts/Prompts.php 0000644 00000003200 14720521221 0010354 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Prompts\Prompts * * @package Google\Site_Kit\Core\Prompts * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Prompts; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\User_Options; /** * Class for handling prompts. * * @since 1.121.0 * @access private * @ignore */ class Prompts { /** * Dismissed_Prompts instance. * * @since 1.121.0 * @var Dismissed_Prompts */ protected $dismissed_prompts; /** * REST_Prompts_Controller instance. * * @since 1.121.0 * @var REST_Prompts_Controller */ protected $rest_controller; /** * Constructor. * * @since 1.121.0 * * @param Context $context Plugin context. * @param User_Options $user_options Optional. User option API. Default is a new instance. */ public function __construct( Context $context, User_Options $user_options = null ) { $this->dismissed_prompts = new Dismissed_Prompts( $user_options ?: new User_Options( $context ) ); $this->rest_controller = new REST_Prompts_Controller( $this->dismissed_prompts ); } /** * Gets the reference to the Dismissed_Prompts instance. * * @since 1.121.0 * * @return Dismissed_Prompts An instance of the Dismissed_Prompts class. */ public function get_dismissed_prompts() { return $this->dismissed_prompts; } /** * Registers functionality through WordPress hooks. * * @since 1.121.0 */ public function register() { $this->dismissed_prompts->register(); $this->rest_controller->register(); } } Prompts/REST_Prompts_Controller.php 0000644 00000006301 14720521221 0013421 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Prompts\REST_Prompts_Controller * * @package Google\Site_Kit\Core\Prompts * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Prompts; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling dismissed prompts rest routes. * * @since 1.121.0 * @access private * @ignore */ class REST_Prompts_Controller { /** * Dismissed_Prompts instance. * * @since 1.121.0 * @var Dismissed_Prompts */ protected $dismissed_prompts; /** * Constructor. * * @since 1.121.0 * * @param Dismissed_Prompts $dismissed_prompts Dismissed prompts instance. */ public function __construct( Dismissed_Prompts $dismissed_prompts ) { $this->dismissed_prompts = $dismissed_prompts; } /** * Registers functionality through WordPress hooks. * * @since 1.121.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/dismissed-prompts', ) ); } ); } /** * Gets REST route instances. * * @since 1.121.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_dismiss_prompt = function () { return current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ); }; return array( new REST_Route( 'core/user/data/dismissed-prompts', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->dismissed_prompts->get() ); }, 'permission_callback' => $can_dismiss_prompt, ) ), new REST_Route( 'core/user/data/dismiss-prompt', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; if ( empty( $data['slug'] ) ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'slug' ), array( 'status' => 400 ) ); } $expiration = Dismissed_Prompts::DISMISS_PROMPT_PERMANENTLY; if ( isset( $data['expiration'] ) && intval( $data['expiration'] ) > 0 ) { $expiration = $data['expiration']; } $this->dismissed_prompts->add( $data['slug'], $expiration ); return new WP_REST_Response( $this->dismissed_prompts->get() ); }, 'permission_callback' => $can_dismiss_prompt, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, ), ), ) ), ); } } User/Audience_Settings.php 0000644 00000006436 14720521221 0011575 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User\Audience_Settings * * @package Google\Site_Kit\Core\User * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User; use Google\Site_Kit\Core\Storage\User_Setting; use Google\Site_Kit\Core\Util\Sanitize; /** * Class for audience settings. * * @since 1.134.0 * @access private * @ignore */ class Audience_Settings extends User_Setting { /** * The user option name for audience setting. */ const OPTION = 'googlesitekit_audience_settings'; /** * Gets the expected value type. * * @since 1.124.0 * * @return string The type name. */ protected function get_type() { return 'object'; } /** * Gets the default value. * * @since 1.124.0 * @since 1.136.0 Added `didSetAudiences` default value. * * @return array The default value. */ protected function get_default() { return array( 'configuredAudiences' => null, 'isAudienceSegmentationWidgetHidden' => false, 'didSetAudiences' => false, ); } /** * Merges an array of settings to update. * * @since 1.124.0 * @since 1.138.0 Allow setting `null` for `configuredAudiences`. * * @param array $partial Partial settings array to save. * @return bool True on success, false on failure. */ public function merge( array $partial ) { $settings = $this->get(); $partial = array_filter( $partial, function ( $value, $key ) { // Allow setting `null` for `configuredAudiences`. return 'configuredAudiences' === $key ? true : null !== $value; }, ARRAY_FILTER_USE_BOTH ); $allowed_settings = array( 'configuredAudiences' => true, 'isAudienceSegmentationWidgetHidden' => true, 'didSetAudiences' => true, ); $updated = array_intersect_key( $partial, $allowed_settings ); if ( empty( $settings['didSetAudiences'] ) && isset( $updated['configuredAudiences'] ) && is_array( $updated['configuredAudiences'] ) && ! empty( $updated['configuredAudiences'] ) ) { $updated['didSetAudiences'] = true; } return $this->set( array_merge( $settings, $updated ) ); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.124.0 * @since 1.138.0 Allow setting `null` for `configuredAudiences`. * * @return callable Sanitize callback. */ protected function get_sanitize_callback() { return function ( $settings ) { if ( ! is_array( $settings ) ) { return array(); } $sanitized_settings = array(); // Allow setting `null` for `configuredAudiences`. if ( array_key_exists( 'configuredAudiences', $settings ) ) { $sanitized_settings['configuredAudiences'] = is_null( $settings['configuredAudiences'] ) ? null : Sanitize::sanitize_string_list( $settings['configuredAudiences'] ); } if ( isset( $settings['isAudienceSegmentationWidgetHidden'] ) ) { $sanitized_settings['isAudienceSegmentationWidgetHidden'] = false !== $settings['isAudienceSegmentationWidgetHidden']; } if ( isset( $settings['didSetAudiences'] ) ) { $sanitized_settings['didSetAudiences'] = false !== $settings['didSetAudiences']; } return $sanitized_settings; }; } } User/Audience_Segmentation.php 0000644 00000002442 14720521221 0012423 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User\Audience_Segmentation * * @package Google\Site_Kit\Core\User * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User; use Google\Site_Kit\Core\Storage\User_Options; /** * Class for handling audience settings rest routes. * * @since 1.134.0 * @access private * @ignore */ class Audience_Segmentation { /** * Audience_Settings instance. * * @since 1.134.0 * @var Audience_Settings */ private $audience_settings; /** * REST_Audience_Settings_Controller instance. * * @since 1.134.0 * @var REST_Audience_Settings_Controller */ private $rest_controller; /** * Constructor. * * @since 1.134.0 * * @param User_Options $user_options User_Options instance. */ public function __construct( User_Options $user_options ) { $this->audience_settings = new Audience_Settings( $user_options ); $this->rest_controller = new REST_Audience_Settings_Controller( $this->audience_settings ); } /** * Registers functionality through WordPress hooks. * * @since 1.134.0 */ public function register() { $this->audience_settings->register(); $this->rest_controller->register(); } } User/REST_Audience_Settings_Controller.php 0000644 00000007020 14720521221 0014623 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User\REST_Audience_Settings_Controller * * @package Google\Site_Kit\Core\User * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\Util\Feature_Flags; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling audience settings rest routes. * * @since 1.134.0 * @access private * @ignore */ class REST_Audience_Settings_Controller { /** * Audience_Settings instance. * * @since 1.134.0 * @var Audience_Settings */ private $audience_settings; /** * Constructor. * * @since 1.134.0 * * @param Audience_Settings $audience_settings Audience_Settings instance. */ public function __construct( Audience_Settings $audience_settings ) { $this->audience_settings = $audience_settings; } /** * Registers functionality through WordPress hooks. * * @since 1.134.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { if ( Feature_Flags::enabled( 'audienceSegmentation' ) ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/audience-settings', ) ); } return $paths; } ); } /** * Gets REST route instances. * * @since 1.134.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_view_dashboard = function () { return current_user_can( Permissions::VIEW_DASHBOARD ); }; if ( ! Feature_Flags::enabled( 'audienceSegmentation' ) ) { return array(); } return array( new REST_Route( 'core/user/data/audience-settings', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->audience_settings->get() ); }, 'permission_callback' => $can_view_dashboard, ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $settings = $request['data']['settings']; $this->audience_settings->merge( $settings ); return new WP_REST_Response( $this->audience_settings->get() ); }, 'permission_callback' => $can_view_dashboard, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'settings' => array( 'type' => 'object', 'required' => true, 'minProperties' => 1, 'additionalProperties' => false, 'properties' => array( 'configuredAudiences' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), 'isAudienceSegmentationWidgetHidden' => array( 'type' => 'boolean', ), 'didSetAudiences' => array( 'type' => 'boolean', ), ), ), ), ), ), ), ) ), ); } } User/User.php 0000644 00000001772 14720521221 0007114 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User\User * * @package Google\Site_Kit\Core\User * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User; use Google\Site_Kit\Core\Storage\User_Options; /** * Class for handling audience settings rest routes. * * @since 1.134.0 * @access private * @ignore */ class User { /** * Audience_Segmentation instance. * * @since 1.134.0 * @var Audience_Segmentation */ private $audience_segmentation; /** * Constructor. * * @since 1.134.0 * * @param User_Options $user_options User_Options instance. */ public function __construct( User_Options $user_options ) { $this->audience_segmentation = new Audience_Segmentation( $user_options ); } /** * Registers functionality through WordPress hooks. * * @since 1.134.0 */ public function register() { $this->audience_segmentation->register(); } } User_Surveys/Survey_Queue.php 0000644 00000006420 14720521221 0012372 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User_Surveys\Survey_Queue * * @package Google\Site_Kit\Core\User_Surveys * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User_Surveys; use Google\Site_Kit\Core\Storage\User_Setting; use Google\Site_Kit\Core\Storage\Setting\List_Setting; /** * Class for handling surveys queue. * * @since 1.98.0 * @access private * @ignore */ class Survey_Queue extends User_Setting { use List_Setting; const OPTION = 'googlesitekit_survey_queue'; /** * Adds a new survey to the queue. * * @since 1.98.0 * * @param array $survey { * The survey object to add to the queue. * * @type string $survey_id Survey ID. * @type array $survey_payload Survey payload that describe survey questions and available completions. * @type array $session Session object that contains session ID and session token. * } * @return bool TRUE if the survey has been added to the queue, otherwise FALSE. */ public function enqueue( $survey ) { $surveys = $this->get(); // Do not add the survey if it is already in the queue. foreach ( $surveys as $item ) { if ( $item['survey_id'] === $survey['survey_id'] ) { return false; } } $surveys[] = $survey; $this->set( $surveys ); return true; } /** * Dequeues a survey that has the provided survey ID. * * @since 1.98.0 * * @param string $survey_id The survey ID to dequeue. * @return array|null A survey object if it has been found, otherwise NULL. */ public function dequeue( $survey_id ) { $survey = null; // Search for the requested survey_id. $old_surveys = $this->get(); $new_surveys = array(); foreach ( $old_surveys as $item ) { if ( $item['survey_id'] === $survey_id ) { $survey = $item; } else { $new_surveys[] = $item; } } // Update existing surveys list if we have found the survey we need to dequeue. if ( ! is_null( $survey ) ) { $this->set( $new_surveys ); } return $survey; } /** * Gets the first survey in the queue without removing it from the queue. * * @since 1.98.0 * * @return array|null A survey object if at least one survey exists in the queue, otherwise NULL. */ public function front() { $surveys = $this->get(); return reset( $surveys ) ?: null; } /** * Gets the survey for the provided session. * * @since 1.98.0 * * @param array $session { * The current session object. * * @type string $session_id Session ID. * @type string $session_token Session token. * } * @return array|null A survey object if it has been found for the session, otherwise NULL. */ public function find_by_session( $session ) { $surveys = $this->get(); foreach ( $surveys as $survey ) { if ( ! empty( $survey['session']['session_id'] ) && ! empty( $session['session_id'] ) && $survey['session']['session_id'] === $session['session_id'] ) { return $survey; } } return null; } /** * Sanitizes array items. * * @since 1.98.0 * * @param array $items The original array items. * @return array Filtered items. */ protected function sanitize_list_items( $items ) { return $items; } } User_Surveys/REST_User_Surveys_Controller.php 0000644 00000016615 14720521221 0015456 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User_Surveys\REST_User_Surveys_Controller * * @package Google\Site_Kit\Core\User_Surveys * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User_Surveys; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling user survey rest routes. * * @since 1.35.0 * @access private * @ignore */ class REST_User_Surveys_Controller { /** * Authentication instance. * * @since 1.35.0 * @var Authentication */ protected $authentication; /** * Survey_Timeouts instance. * * @since 1.73.0 * @var Survey_Timeouts */ protected $timeouts; /** * Survey_Queue instance. * * @since 1.98.0 * @var Survey_Queue */ protected $queue; /** * Constructor. * * @since 1.35.0 * * @param Authentication $authentication Authentication instance. * @param Survey_Timeouts $timeouts User timeouts setting. * @param Survey_Queue $queue Surveys queue. */ public function __construct( Authentication $authentication, Survey_Timeouts $timeouts, Survey_Queue $queue ) { $this->authentication = $authentication; $this->timeouts = $timeouts; $this->queue = $queue; } /** * Registers functionality through WordPress hooks. * * @since 1.35.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/survey-timeouts', ) ); } ); } /** * Gets REST route instances. * * @since 1.35.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_authenticate = function () { return $this->authentication->is_authenticated() && $this->authentication->credentials()->using_proxy(); }; return array( 'survey-trigger' => new REST_Route( 'core/user/data/survey-trigger', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $proxy = $this->authentication->get_google_proxy(); $creds = $this->authentication->credentials(); $access_token = (string) $this->authentication->get_oauth_client()->get_access_token(); $data = $request->get_param( 'data' ); $response = $proxy->send_survey_trigger( $creds, $access_token, $data['triggerID'] ); if ( ! is_wp_error( $response ) && ! empty( $response['survey_id'] ) ) { $this->queue->enqueue( $response ); } return new WP_REST_Response( array( 'success' => true ) ); }, 'permission_callback' => $can_authenticate, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'triggerID' => array( 'type' => 'string', 'required' => true, ), ), ), ), ) ), 'survey-event' => new REST_Route( 'core/user/data/survey-event', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $proxy = $this->authentication->get_google_proxy(); $creds = $this->authentication->credentials(); $access_token = (string) $this->authentication->get_oauth_client()->get_access_token(); $data = $request->get_param( 'data' ); if ( isset( $data['event']['survey_shown'] ) ) { $this->timeouts->set_global_timeout(); } $response = $proxy->send_survey_event( $creds, $access_token, $data['session'], $data['event'] ); if ( ! is_wp_error( $response ) ) { $is_survey_closed = isset( $data['event']['survey_closed'] ); $is_completion_shown = isset( $data['event']['completion_shown'] ); if ( $is_completion_shown || $is_survey_closed ) { $survey = $this->queue->find_by_session( $data['session'] ); if ( ! empty( $survey ) ) { $this->queue->dequeue( $survey['survey_id'] ); } } } return new WP_REST_Response( $response ); }, 'permission_callback' => $can_authenticate, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'session' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'session_id' => array( 'type' => 'string', 'required' => true, ), 'session_token' => array( 'type' => 'string', 'required' => true, ), ), ), 'event' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'survey_shown' => array( 'type' => 'object', ), 'survey_closed' => array( 'type' => 'object', ), 'question_answered' => array( 'type' => 'object', ), 'completion_shown' => array( 'type' => 'object', ), 'follow_up_link_clicked' => array( 'type' => 'object', ), ), ), ), ), ), ) ), 'survey-timeout' => new REST_Route( 'core/user/data/survey-timeout', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => $can_authenticate, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; if ( empty( $data['slug'] ) ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'slug' ), array( 'status' => 400 ) ); } $timeout = HOUR_IN_SECONDS; if ( isset( $data['timeout'] ) && intval( $data['timeout'] ) > 0 ) { $timeout = $data['timeout']; } $this->timeouts->add( $data['slug'], $timeout ); return new WP_REST_Response( $this->timeouts->get_survey_timeouts() ); }, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, ), ), ) ), 'survey-timeouts' => new REST_Route( 'core/user/data/survey-timeouts', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => $can_authenticate, 'callback' => function () { return new WP_REST_Response( $this->timeouts->get_survey_timeouts() ); }, ) ), 'survey' => new REST_Route( 'core/user/data/survey', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => $can_authenticate, 'callback' => function () { return new WP_REST_Response( array( 'survey' => $this->queue->front(), ) ); }, ) ), ); } } User_Surveys/Survey_Timeouts.php 0000644 00000003473 14720521221 0013124 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User_Surveys\Survey_Timeouts * * @package Google\Site_Kit\Core\User_Surveys * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User_Surveys; use Google\Site_Kit\Core\Storage\User_Setting; use Google\Site_Kit\Core\Storage\Setting\List_Setting; /** * Class for representing user survey timeouts. * * @since 1.73.0 * @access private * @ignore */ class Survey_Timeouts extends User_Setting { use List_Setting; const OPTION = 'googlesitekit_survey_timeouts'; const GLOBAL_KEY = '__global'; /** * Adds a timeout for the provided survey. * * @since 1.73.0 * * @param string $survey Survey name. * @param int $timeout Tiemout for the survey. */ public function add( $survey, $timeout ) { $surveys = $this->get(); $surveys[ $survey ] = time() + $timeout; $this->set( $surveys ); } /** * Gets survey timeouts. * * @since 1.73.0 * * @return array Survey timeouts array. */ public function get_survey_timeouts() { $surveys = $this->get(); $surveys = $this->sanitize_list_items( $surveys ); return array_keys( $surveys ); } /** * Sets the global timeout to twelve hours. * * @since 1.98.0 */ public function set_global_timeout() { $this->add( self::GLOBAL_KEY, 12 * HOUR_IN_SECONDS ); } /** * Sanitizes survey timeouts. * * @since 1.73.0 * * @param array $items Survey timeouts list. * @return array Filtered survey timeouts. */ protected function sanitize_list_items( $items ) { $surveys = array(); if ( is_array( $items ) ) { foreach ( $items as $item => $ttl ) { if ( $ttl > time() ) { $surveys[ $item ] = $ttl; } } } return $surveys; } } User_Surveys/User_Surveys.php 0000644 00000003146 14720521221 0012411 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\User_Surveys\User_Surveys * * @package Google\Site_Kit\Core\User_Surveys * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\User_Surveys; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Storage\User_Options; /** * Class for handling user surveys. * * @since 1.73.0 * @access private * @ignore */ class User_Surveys { /** * Survey_Timeouts instance. * * @since 1.73.0 * @var Survey_Timeouts */ protected $survey_timeouts; /** * REST_User_Surveys_Controller instance. * * @since 1.73.0 * @var REST_User_Surveys_Controller */ protected $rest_controller; /** * Constructor. * * @since 1.73.0 * * @param Authentication $authentication Authentication instance. * @param User_Options $user_options User option API. * @param Survey_Queue $survey_queue Optional. Survey_Queue instance. Default a new instance. */ public function __construct( Authentication $authentication, User_Options $user_options, Survey_Queue $survey_queue ) { $this->survey_timeouts = new Survey_Timeouts( $user_options ); $this->rest_controller = new REST_User_Surveys_Controller( $authentication, $this->survey_timeouts, $survey_queue ?: new Survey_Queue( $user_options ) ); } /** * Registers functionality through WordPress hooks. * * @since 1.73.0 */ public function register() { $this->survey_timeouts->register(); $this->rest_controller->register(); } } Tags/GTag.php 0000644 00000013510 14720521221 0006771 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tags\GTag * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; /** * Class to handle gtag rendering across modules. * * @since 1.124.0 * @access public * @ignore */ class GTag { use Method_Proxy_Trait; const HANDLE = 'google_gtagjs'; /** * Holds an array of gtag ID's and their inline config elements. * * @var array $tags Array of tag ID's and their configs. */ private $tags = array(); /** * Holds an array of gtag commands, their parameters and command positions. * * @var array $commands Array of gtag config commands. */ private $commands = array(); /** * Register method called after class instantiation. * * @since 1.124.0 * @access public * * @return void */ public function register() { add_action( 'wp_enqueue_scripts', $this->get_method_proxy( 'enqueue_gtag_script' ), 20 ); add_filter( 'wp_resource_hints', function ( $urls, $relation_type ) { if ( 'dns-prefetch' === $relation_type ) { $urls[] = '//www.googletagmanager.com'; } return $urls; }, 10, 2 ); } /** * Method to add a gtag ID and config for output rendering. * * @since 1.124.0 * @access public * * @param string $tag_id The gtag ID. * @param array $config Array of inline gtag config values. * * @return void */ public function add_tag( $tag_id, $config = array() ) { $this->tags[] = array( 'tag_id' => $tag_id, 'config' => $config, ); } /** * Method to add a gtag command, associated parameters and output position. * * @since 1.124.0 * @access public * * @param string $command The gtag command to add. * @param array $parameters Array of command parameters. * @param string $position Position of command. "before|after". * * @return void */ public function add_command( $command, $parameters, $position = 'after' ) { $this->commands[] = array( 'command' => $command, // e.g. 'config', 'event', etc. 'parameters' => $parameters, // e.g. array( 'send_to', 'AW-123456789' ). 'position' => $position, // e.g. 'after', 'before'. This determines the position of the inline script relative to the gtag.js script. ); } /** * Method used to enqueue the gtag script along with additional tags, * configs and commands. * * @since 1.124.0 * @access protected * * @return void */ protected function enqueue_gtag_script() { // $this->tags and $this->commands will be populated via this action's handlers. do_action( 'googlesitekit_setup_gtag', $this ); if ( empty( $this->tags ) ) { return; } $gtag_src = $this->get_gtag_src(); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_enqueue_script( self::HANDLE, $gtag_src, false, null, false ); wp_script_add_data( self::HANDLE, 'script_execution', 'async' ); // Note that `gtag()` may already be defined via the `Consent_Mode` output, but this is safe to call multiple times. wp_add_inline_script( self::HANDLE, 'window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}' ); foreach ( $this->commands as $command ) { wp_add_inline_script( self::HANDLE, $this->get_gtag_call_for_command( $command ), $command['position'] ); } wp_add_inline_script( self::HANDLE, 'gtag("js", new Date());' ); wp_add_inline_script( self::HANDLE, 'gtag("set", "developer_id.dZTNiMT", true);' ); // Site Kit developer ID. foreach ( $this->tags as $tag ) { wp_add_inline_script( self::HANDLE, $this->get_gtag_call_for_tag( $tag ) ); } $filter_google_gtagjs = function ( $tag, $handle ) { if ( self::HANDLE !== $handle ) { return $tag; } $snippet_comment_begin = sprintf( "\n<!-- %s -->\n", esc_html__( 'Google tag (gtag.js) snippet added by Site Kit', 'google-site-kit' ) ); $snippet_comment_end = sprintf( "\n<!-- %s -->\n", esc_html__( 'End Google tag (gtag.js) snippet added by Site Kit', 'google-site-kit' ) ); return $snippet_comment_begin . $tag . $snippet_comment_end; }; add_filter( 'script_loader_tag', $filter_google_gtagjs, 20, 2 ); } /** * Method used to return gtag() config call for selected tag. * * @since 1.124.0 * @access protected * * @param array $tag The Gtag tag, along with its config parameters. * * @return string Gtag call for tag in question. */ protected function get_gtag_call_for_tag( $tag ) { return empty( $tag['config'] ) ? sprintf( 'gtag("config", "%s");', esc_js( $tag['tag_id'] ) ) : sprintf( 'gtag("config", "%s", %s);', esc_js( $tag['tag_id'] ), wp_json_encode( $tag['config'] ) ); } /** * Method used to return gtag call for specific command. * * @since 1.124.0 * @access protected * * @param array $command The command array with applicable command and params. * * @return string Gtag function call for specific command. */ protected function get_gtag_call_for_command( $command ) { $gtag_args = array_merge( array( $command['command'] ), $command['parameters'] ); $gtag_args = array_map( function ( $arg ) { return wp_json_encode( $arg ); }, $gtag_args ); return sprintf( 'gtag(%s);', implode( ',', $gtag_args ) ); } /** * Returns the gtag source URL. * * @since 1.124.0 * * @return string|false The gtag source URL. False if no tags are added. */ public function get_gtag_src() { if ( empty( $this->tags ) ) { return false; } // Load the GTag scripts using the first tag ID - it doesn't matter which is used, // all registered tags will be set up with a config command regardless // of which is used to load the source. return 'https://www.googletagmanager.com/gtag/js?id=' . rawurlencode( $this->tags[0]['tag_id'] ); } } Tags/Tag_Interface.php 0000644 00000001262 14720521221 0010643 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Tags\Tag_Interface * * @package Google\Site_Kit\Core\Tags * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags; /** * Interface for a tag. * * @since 1.24.0 * @access private * @ignore */ interface Tag_Interface { /** * Registers tag hooks. * * @since 1.24.0 */ public function register(); /** * Determines whether the tag can be register or not. * * @since 1.24.0 * * @return bool TRUE if the tag can be register, otherwise FALSE. */ public function can_register(); } Tags/Tag_Matchers_Interface.php 0000644 00000001120 14720521221 0012462 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Tags\Tag_Matchers_Interface * * @package Google\Site_Kit\Core\Tags * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags; /** * Interface for tag matchers. * * @since 1.119.0 * @access private * @ignore */ interface Tag_Matchers_Interface { /** * Holds array of regex tag matchers. * * @since 1.119.0 * * @return array Array of regex matchers. */ public function regex_matchers(); } Tags/Blockable_Tag_Interface.php 0000644 00000001663 14720521221 0012606 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Tags\Blockable_Tag_Interface * * @package Google\Site_Kit\Core\Tags * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags; /** * Interface for a blockable tag. * * @since 1.24.0 * @access private * @ignore */ interface Blockable_Tag_Interface { /** * Checks whether or not the tag should be blocked from rendering. * * @since 1.24.0 * * @return bool TRUE if the tag should be blocked, otherwise FALSE. */ public function is_tag_blocked(); /** * Gets the HTML attributes for a script tag that may potentially require user consent before loading. * * @since 1.24.0 * * @return string HTML attributes to add if the tag requires consent to load, or an empty string. */ public function get_tag_blocked_on_consent_attribute(); } Tags/Tag_With_Linker_Interface.php 0000644 00000001134 14720521221 0013140 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tags\Tag_With_Linker_Interface * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags; /** * Interface for a tag implementing linker domain. * * @since 1.125.0 * @access private * @ignore */ interface Tag_With_Linker_Interface { /** * Sets the current home domain. * * @since 1.125.0 * * @param string $domain Domain name. */ public function set_home_domain( $domain ); } Tags/Tag.php 0000644 00000003053 14720521221 0006663 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tags\Tag * * @package Google\Site_Kit\Core\Tags * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags; use Google\Site_Kit\Core\Guards\Guard_Interface; /** * Base class for tags. * * @since 1.24.0 * @access private * @ignore */ abstract class Tag implements Tag_Interface { /** * Tag ID. * * @since 1.24.0 * @var string */ protected $tag_id; /** * Guards array. * * @since 1.24.0 * @var array */ protected $guards = array(); /** * Constructor. * * @since 1.24.0 * * @param string $tag_id Tag ID. */ public function __construct( $tag_id ) { $this->tag_id = $tag_id; } /** * Adds a new guard to the guards list. * * @since 1.24.0 * * @param Guard_Interface $guard A guard instance to add to the guards list. */ public function use_guard( Guard_Interface $guard ) { $this->guards[] = $guard; } /** * Determines whether the tag can be register or not. * * @since 1.24.0 * * @return bool TRUE if the tag can be register, otherwise FALSE. */ public function can_register() { foreach ( $this->guards as $guard ) { if ( $guard instanceof Guard_Interface ) { $can_activate = $guard->can_activate(); if ( is_wp_error( $can_activate ) || ! $can_activate ) { return false; } } } return true; } /** * Registers tag hooks. * * @since 1.24.0 */ abstract public function register(); } Tags/Tag_With_DNS_Prefetch_Trait.php 0000644 00000001661 14720521221 0013350 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Tags\Tag_With_DNS_Prefetch_Trait * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags; /** * Trait for adding the dns-prefetch directive to a url. * * @since 1.35.0 * @access private * @ignore */ trait Tag_With_DNS_Prefetch_Trait { /** * Gets a callback that can be used for the wp_resource_hints filter to set the dns-prefetch directive for a specified URL. * * @since 1.35.0 * * @param string $url URL to which the dns-prefetch directive should be added. * @return array List of urls. */ protected function get_dns_prefetch_hints_callback( $url ) { return function ( $urls, $relation_type ) use ( $url ) { if ( 'dns-prefetch' === $relation_type ) { $urls[] = $url; } return $urls; }; } } Tags/Tag_With_Linker_Trait.php 0000644 00000002354 14720521221 0012330 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Tags\Tag_With_Linker_Trait * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags; /** * Trait for adding the linker property with domain to tag output. * * @since 1.125.0 * @access private * @ignore */ trait Tag_With_Linker_Trait { /** * Holds the value of the domain for the linker config option in the gtag. * * @var string $home_domain The site's domain for linker property. */ private $home_domain; /** * Method to set home domain. * * @param string $home_domain The value to set for home domain. * * @since 1.125.0 * @return void */ public function set_home_domain( $home_domain ) { $this->home_domain = $home_domain; } /** * Method to add linker domain to tag config. * * @param array $tag_config Tag config to add linker entry to. * * @since 1.125.0 * @return array Tag config, with or without linker values. */ protected function add_linker_to_tag_config( $tag_config ) { return array_merge( $tag_config, array( 'linker' => array( 'domains' => array( $this->home_domain ) ) ) ); } } Tags/Guards/Tag_Verify_Guard.php 0000644 00000002210 14720521221 0012550 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tags\Guards\Tag_Verify_Guard * * @package Google\Site_Kit\Core\Tags\Guards * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags\Guards; use Google\Site_Kit\Core\Guards\Guard_Interface; use Google\Site_Kit\Core\Util\Input; /** * Guard that verifies if the "tagverify" query arg is used. * * @since 1.24.0 * @access private * @ignore */ class Tag_Verify_Guard implements Guard_Interface { /** * Input access abstraction. * * @since 1.24.0 * @var Input */ private $input; /** * Constructor. * * @since 1.24.0 * * @param Input $input Input instance. */ public function __construct( Input $input ) { $this->input = $input; } /** * Determines whether the guarded tag can be activated or not. * * @since 1.24.0 * * @return bool|WP_Error TRUE if guarded tag can be activated, otherwise FALSE or an error. */ public function can_activate() { return ! $this->input->filter( INPUT_GET, 'tagverify', FILTER_VALIDATE_BOOLEAN ); } } Tags/Guards/Tag_Environment_Type_Guard.php 0000644 00000002201 14720521221 0014611 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tags\Guards\Tag_Environment_Type_Guard * * @package Google\Site_Kit\Core\Tags\Guards * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags\Guards; use Google\Site_Kit\Core\Guards\Guard_Interface; /** * Guard that verifies if we're in a production environment. * * @since 1.38.0 * @access private * @ignore */ class Tag_Environment_Type_Guard implements Guard_Interface { /** * Determines whether the guarded tag can be activated or not. * * @since 1.38.0 * * @return bool TRUE if guarded tag can be activated, otherwise FALSE. */ public function can_activate() { if ( ! function_exists( 'wp_get_environment_type' ) ) { return true; } $allowed_environments = apply_filters( 'googlesitekit_allowed_tag_environment_types', array( 'production' ) ); if ( ! is_array( $allowed_environments ) ) { $allowed_environments = array( 'production' ); } return in_array( wp_get_environment_type(), $allowed_environments, true ); } } Tags/Guards/WP_Query_404_Guard.php 0000644 00000001374 14720521221 0012625 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tags\Guards\WP_Query_404_Guard * * @package Google\Site_Kit\Core\Tags\Guards * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tags\Guards; use Google\Site_Kit\Core\Guards\Guard_Interface; /** * Class for WP_Query 404 guard. * * @since 1.105.0 * @access private * @ignore */ class WP_Query_404_Guard implements Guard_Interface { /** * Determines whether the guarded tag can be activated or not. * * @since 1.105.0 * * @return bool TRUE if guarded tag can be activated, otherwise FALSE or an error. */ public function can_activate() { return ! is_404(); } } Nonces/Nonces.php 0000644 00000005127 14720521221 0007730 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Nonces\Nonces * * @package Google\Site_Kit\Core\Nonces * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Nonces; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\Util\Feature_Flags; use WP_REST_Response; use WP_REST_Server; /** * Class managing nonces used by Site Kit. * * @since 1.93.0 * @access private * @ignore */ final class Nonces { /* * Nonce actions. * * @since 1.93.0 */ const NONCE_UPDATES = 'updates'; /** * Plugin context. * * @since 1.93.0 * @var Context */ private $context; /** * Array of nonce actions. * * @since 1.93.0 * @var array */ private $nonce_actions; /** * Constructor. * * Sets up the capability mappings. * * @since 1.93.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; $this->nonce_actions = array( self::NONCE_UPDATES, ); } /** * Registers functionality through WordPress hooks. * * @since 1.93.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/nonces', ) ); } ); } /** * Generate nonces for the current user. * * @since 1.93.0 * * @return array List of nonces. */ public function get_nonces() { $nonces = array(); foreach ( $this->nonce_actions as $nonce_action ) { $nonces[ $nonce_action ] = wp_create_nonce( $nonce_action ); } return $nonces; } /** * Gets related REST routes. * * @since 1.93.0 * * @return array List of REST_Route objects. */ private function get_rest_routes() { $can_access_nonces = function () { return is_user_logged_in(); }; return array( new REST_Route( 'core/user/data/nonces', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->get_nonces() ); }, 'permission_callback' => $can_access_nonces, ), ) ), ); } } Remote_Features/Remote_Features_Fallback.php 0000644 00000002660 14720521221 0015216 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Remote_Features\Remote_Features_Fallback * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Remote_Features; /** * Class providing the integration of remote features. * * @since 1.134.0 * @access private * @ignore */ class Remote_Features_Fallback { /** * Remote_Features_Syncer instance. * * @var Remote_Features_Syncer */ private Remote_Features_Syncer $syncer; /** * Remote_Features instance. * * @var Remote_Features */ private Remote_Features $setting; /** * Constructor. * * @since 1.134.0 * * @param Remote_Features $setting Remote_Features instance. * @param Remote_Features_Syncer $syncer Remote_Features_Syncer instance. */ public function __construct( Remote_Features $setting, Remote_Features_Syncer $syncer ) { $this->syncer = $syncer; $this->setting = $setting; } /** * Fallback for syncing the remote features. * * @since 1.134.0 */ public function remote_features_sync_fallback() { $remote_features = $this->setting->get(); $last_sync_at = $remote_features['last_updated_at'] ?? 0; $is_sync_overdue = ( time() - $last_sync_at ) > DAY_IN_SECONDS; if ( $is_sync_overdue || ! $last_sync_at ) { $this->syncer->pull_remote_features(); } } } Remote_Features/Remote_Features_Cron.php 0000644 00000002366 14720521221 0014423 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Remote_Features\Remote_Features_Cron * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Remote_Features; /** * Class providing cron implementation for remote features. * * @since 1.133.0 * @access private * @ignore */ class Remote_Features_Cron { const CRON_ACTION = 'googlesitekit_cron_update_remote_features'; /** * Cron callback reference. * * @var callable */ private $cron_callback; /** * Constructor. * * @since 1.133.0 * * @param callable $callback Function to call on the cron action. */ public function __construct( callable $callback ) { $this->cron_callback = $callback; } /** * Registers functionality through WordPress hooks. * * @since 1.133.0 */ public function register() { add_action( self::CRON_ACTION, $this->cron_callback ); } /** * Schedules cron if not already set. * * @since 1.133.0 */ public function maybe_schedule_cron() { if ( ! wp_next_scheduled( self::CRON_ACTION ) && ! wp_installing() ) { wp_schedule_event( time(), 'twicedaily', self::CRON_ACTION ); } } } Remote_Features/Remote_Features.php 0000644 00000003370 14720521221 0013436 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Remote_Features\Remote_Features * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Remote_Features; use Closure; use Google\Site_Kit\Core\Storage\Setting; /** * Class handling the storage of remote features. * * @since 1.118.0 * @since 1.133.0 Changed to extend Setting * @access private * @ignore */ final class Remote_Features extends Setting { /** * Option key in options table to store remote features. */ const OPTION = 'googlesitekitpersistent_remote_features'; /** * Gets the expected value type. * * @return string */ protected function get_type() { return 'object'; } /** * Gets the default value. * * @return array */ protected function get_default() { return array( 'last_updated_at' => 0, ); } /** * Includes the current timestamp to the setting and updates it. * * @since 1.134.0 * * @param array $features features array. */ public function update( $features ) { $features['last_updated_at'] = time(); return $this->set( $features ); } /** * Gets the callback for sanitizing the setting's value before saving. * * @return Closure */ protected function get_sanitize_callback() { return function ( $value ) { if ( ! is_array( $value ) ) { return array(); } $new_value = array(); foreach ( $value as $feature => $meta ) { if ( 'last_updated_at' === $feature ) { $new_value[ $feature ] = is_int( $meta ) ? $meta : 0; } else { $new_value[ $feature ] = array( 'enabled' => ! empty( $meta['enabled'] ) ); } } return $new_value; }; } } Remote_Features/Remote_Features_Syncer.php 0000644 00000003325 14720521221 0014761 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Remote_Features\Remote_Features_Syncer * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Remote_Features; use Closure; use Google\Site_Kit\Core\Guards\Guard_Interface; /** * Class handling the synchronization of remote features with local storage. * * @since 1.133.0 * @access private * @ignore */ class Remote_Features_Syncer { /** * Remote_Features instance. * * @var Remote_Features */ private $remote_features; /** * Function which fetches features. * * @var Closure */ private $fetch_features; /** * Guard instances. * * @var Guard_Interface[] */ private array $guards; /** * Constructor. * * @since 1.133.0 * * @param Remote_Features $remote_features Remote_Features instance. * @param Closure $fetch_features Function which fetches features. * @param Guard_Interface ...$guards Guard instances. */ public function __construct( Remote_Features $remote_features, Closure $fetch_features, Guard_Interface ...$guards ) { $this->remote_features = $remote_features; $this->fetch_features = $fetch_features; $this->guards = $guards; } /** * Fetches the latest remote features and sets them in storage. * * @since 1.133.0 */ public function pull_remote_features() { foreach ( $this->guards as $guard ) { if ( ! $guard->can_activate() ) { return; } } $features = ( $this->fetch_features )(); if ( ! is_wp_error( $features ) && is_array( $features ) ) { $this->remote_features->update( $features ); } } } Remote_Features/Remote_Features_Activation.php 0000644 00000003746 14720521221 0015626 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Remote_Features\Remote_Features_Activation * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Remote_Features; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; /** * Class handling the application of remote feature activation. * * @since 1.133.0 * @access private * @ignore */ class Remote_Features_Activation { use Method_Proxy_Trait; /** * Remote_Features instance. * * @var Remote_Features */ private $remote_features; /** * Loaded features. * * @var array */ private $features; /** * Constructor. * * @param Remote_Features $remote_features Remote_Features instance. */ public function __construct( Remote_Features $remote_features ) { $this->remote_features = $remote_features; } /** * Registers functionality through WordPress hooks. * * @since 1.133.0 */ public function register() { add_filter( 'googlesitekit_is_feature_enabled', $this->get_method_proxy( 'enable_features' ), 10, 2 ); } /** * Gets the current set of remote features. * * @return array|mixed */ private function get_features() { if ( null === $this->features ) { $this->features = $this->remote_features->get(); } return $this->features; } /** * Filters feature flags using features stored in options. * * @since 1.133.0 * * @param boolean $feature_enabled Original value of the feature. * @param string $feature_name Feature name. * @return boolean State flag from options if it is available, otherwise the original value. */ private function enable_features( $feature_enabled, $feature_name ) { $features = $this->get_features(); if ( isset( $features[ $feature_name ]['enabled'] ) ) { return filter_var( $features[ $feature_name ]['enabled'], FILTER_VALIDATE_BOOLEAN ); } return $feature_enabled; } } Remote_Features/Remote_Features_Provider.php 0000644 00000006636 14720521221 0015320 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Remote_Features\Remote_Features_Provider * * @package Google\Site_Kit * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Remote_Features; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Credentials; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit\Core\Authentication\Guards\Site_Connected_Guard; use Google\Site_Kit\Core\Authentication\Guards\Using_Proxy_Connection_Guard; use Google\Site_Kit\Core\Storage\Encrypted_Options; use Google\Site_Kit\Core\Storage\Options; /** * Class providing the integration of remote features. * * @since 1.133.0 * @access private * @ignore */ class Remote_Features_Provider { /** * Credentials instance. * * @var Credentials */ private Credentials $credentials; /** * Remote_Features instance. * * @var Remote_Features */ private Remote_Features $setting; /** * Remote_Features_Activation instance. * * @var Remote_Features_Activation */ private Remote_Features_Activation $activation; /** * Remote_Features_Syncer instance. * * @var Remote_Features_Syncer */ private Remote_Features_Syncer $syncer; /** * Remote_Features_Cron instance. * * @var Remote_Features_Cron */ private Remote_Features_Cron $cron; /** * Remote_Features_Fallback instance. * * @var Remote_Features_Fallback */ private Remote_Features_Fallback $fallback; /** * Constructor. * * @since 1.133.0 * * @param Context $context Context instance. * @param Options $options Options instance. */ public function __construct( Context $context, Options $options ) { $this->credentials = new Credentials( new Encrypted_Options( $options ) ); $this->setting = new Remote_Features( $options ); $this->activation = new Remote_Features_Activation( $this->setting ); $this->syncer = new Remote_Features_Syncer( $this->setting, fn() => ( new Google_Proxy( $context ) )->get_features( $this->credentials ), new Site_Connected_Guard( $this->credentials ), new Using_Proxy_Connection_Guard( $this->credentials ) ); $this->cron = new Remote_Features_Cron( array( $this->syncer, 'pull_remote_features' ) ); $this->fallback = new Remote_Features_Fallback( $this->setting, $this->syncer ); } /** * Registers functionality through WordPress hooks. * * @since 1.133.0 */ public function register() { $this->setting->register(); $this->activation->register(); $this->cron->register(); add_action( 'admin_init', fn () => $this->on_admin_init() ); add_action( 'heartbeat_tick', fn ( $response, $screen_id ) => $this->on_heartbeat_tick( $screen_id ), 10, 2 ); } /** * Handles the heartbeat AJAX callback. * * @param string $screen_id The screen ID. */ protected function on_heartbeat_tick( $screen_id ) { if ( 'toplevel_page_googlesitekit-dashboard' !== $screen_id ) { return; } $this->fallback->remote_features_sync_fallback(); } /** * Handles delayed registration on admin_init. */ protected function on_admin_init() { if ( ! $this->credentials->using_proxy() ) { return; } $this->cron->maybe_schedule_cron(); // Sync remote features when credentials change (e.g. during setup). $this->credentials->on_change( array( $this->syncer, 'pull_remote_features' ) ); } } REST_API/REST_Route.php 0000644 00000011510 14720521221 0010450 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\REST_API\REST_Route * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\REST_API; use WP_REST_Server; /** * Class representing a single REST API route. * * @since 1.0.0 * @access private * @ignore */ final class REST_Route { /** * Unique route URI. * * @since 1.0.0 * @var string */ private $uri; /** * Route arguments. * * @since 1.0.0 * @var array */ private $args = array(); /** * Constructor. * * @since 1.0.0 * * @param string $uri Unique route URI. * @param array $endpoints { * List of one or more endpoint arrays for a specific method, with the following data. * * @type string|array $methods One or more methods that the endpoint applies to. * @type callable $callback Callback handling a request to the endpoint. * @type callable $permission_callback Callback to check permissions for a request to the endpoint. * @type array $args Associative array of supported parameters and their requirements. * } * @param array $args { * Optional. Route options that typically include the following keys. * * @type array $args Associative array of globally supported parameters, e.g. those that are part of the URI. * Default none. * @type array $schema Public item schema for the route. Default none. */ public function __construct( $uri, array $endpoints, array $args = array() ) { $this->uri = trim( $uri, '/' ); $this->args = $args; if ( isset( $this->args['args'] ) ) { $this->args['args'] = $this->parse_param_args( $this->args['args'] ); } // In case there are string arguments, this is only a single endpoint and needs to be turned into a list. if ( ! wp_is_numeric_array( $endpoints ) ) { $endpoints = array( $endpoints ); } $endpoint_defaults = array( 'methods' => WP_REST_Server::READABLE, 'callback' => null, 'args' => array(), ); foreach ( $endpoints as $endpoint ) { $endpoint = wp_parse_args( $endpoint, $endpoint_defaults ); $endpoint['args'] = $this->parse_param_args( $endpoint['args'] ); if ( ! empty( $this->args['args'] ) ) { $endpoint['args'] = array_merge( $this->args['args'], $endpoint['args'] ); } $this->args[] = $endpoint; } } /** * Registers the REST route. * * @since 1.16.0 */ public function register() { register_rest_route( REST_Routes::REST_ROOT, $this->get_uri(), $this->get_args() ); } /** * Gets the route URI. * * @since 1.0.0 * * @return string Unique route URI. */ public function get_uri() { return $this->uri; } /** * Gets the route arguments, including endpoints and schema. * * @since 1.0.0 * * @return array Route arguments. */ public function get_args() { return $this->args; } /** * Parses all supported request arguments and their data. * * @since 1.0.0 * * @param array $args Associative array of $arg => $data pairs. * @return array Parsed arguments. */ protected function parse_param_args( array $args ) { return array_map( array( $this, 'parse_param_arg' ), $args ); } /** * Parses data for a supported request argument. * * @since 1.0.0 * * @param array $data { * Request argument data. * * @type string $type Data type of the argument. Default 'string'. * @type string $description Public description of the argument. Default empty string. * @†ype callable $validate_callback Callback to validate the argument. Default * {@see rest_validate_rest_arg()}. * @type callable $sanitize_callback Callback to sanitize the argument. Default * {@see rest_sanitize_rest_arg()}. * @type bool $required Whether the argument is required. Default false. * @type mixed $default Default value for the argument, if any. Default none. * @type array $enum Allowlist of possible values to validate against. Default none. * @type array $items Only if $type is 'array': Similar specification that applies to each item. * @type array $properties Only if $type is 'object'. Similar specification per property. * } * @return array Parsed data. */ protected function parse_param_arg( array $data ) { return wp_parse_args( $data, array( 'type' => 'string', 'description' => '', 'validate_callback' => 'rest_validate_request_arg', 'sanitize_callback' => 'rest_sanitize_request_arg', 'required' => false, 'default' => null, ) ); } } REST_API/Exception/Missing_Required_Param_Exception.php 0000644 00000002605 14720521221 0017067 0 ustar 00 <?php /** * Class Missing_Required_Param_Exception * * @package Google\Site_Kit\Core\REST_API\Exception * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\REST_API\Exception; use Exception; use Google\Site_Kit\Core\Contracts\WP_Errorable; use WP_Error; /** * Class for representing a missing required parameter. * * @since 1.98.0 * @access private * @ignore */ class Missing_Required_Param_Exception extends Exception implements WP_Errorable { /** * Status code. * * @var int */ protected $status; /** * Constructor. * * @since 1.98.0 * * @param string $parameter_name Missing request parameter name. * @param int $code Optional. HTTP Status code of resulting error. Defaults to 400. */ public function __construct( $parameter_name, $code = 400 ) { $this->status = (int) $code; parent::__construct( /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), $parameter_name ) ); } /** * Gets the WP_Error representation of this exception. * * @since 1.98.0 * * @return WP_Error */ public function to_wp_error() { return new WP_Error( 'missing_required_param', $this->getMessage(), array( 'status' => $this->status ) ); } } REST_API/Exception/Invalid_Param_Exception.php 0000644 00000002534 14720521221 0015205 0 ustar 00 <?php /** * Class Invalid_Param_Exception * * @package Google\Site_Kit\Core\REST_API\Exception * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\REST_API\Exception; use Exception; use Google\Site_Kit\Core\Contracts\WP_Errorable; use WP_Error; /** * Class for representing an invalid parameter. * * @since 1.124.0 * @access private * @ignore */ class Invalid_Param_Exception extends Exception implements WP_Errorable { /** * Status code. * * @var int */ protected $status; /** * Constructor. * * @since 1.124.0 * * @param string $parameter_name Invalid request parameter name. * @param int $code Optional. HTTP Status code of resulting error. Defaults to 400. */ public function __construct( $parameter_name, $code = 400 ) { $this->status = (int) $code; parent::__construct( /* translators: %s: Invalid parameter */ sprintf( __( 'Invalid parameter: %s.', 'google-site-kit' ), $parameter_name ) ); } /** * Gets the WP_Error representation of this exception. * * @since 1.124.0 * * @return WP_Error */ public function to_wp_error() { return new WP_Error( 'rest_invalid_param', $this->getMessage(), array( 'status' => $this->status ) ); } } REST_API/Exception/Invalid_Datapoint_Exception.php 0000644 00000001733 14720521221 0016070 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\REST_API\Exception\Invalid_Datapoint_Exception * * @package Google\Site_Kit\Core\REST_API\Exception * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\REST_API\Exception; use Google\Site_Kit\Core\Contracts\WP_Errorable; use Exception; use WP_Error; /** * Exception thrown when a request to an invalid datapoint is made. * * @since 1.9.0 * @access private * @ignore */ class Invalid_Datapoint_Exception extends Exception implements WP_Errorable { const WP_ERROR_CODE = 'invalid_datapoint'; /** * Gets the WP_Error representation of this exception. * * @since 1.9.0 * * @return WP_Error */ public function to_wp_error() { return new WP_Error( static::WP_ERROR_CODE, __( 'Invalid datapoint.', 'google-site-kit' ), array( 'status' => 400, // Bad request. ) ); } } REST_API/REST_Routes.php 0000644 00000005302 14720521221 0010635 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\REST_API\REST_Routes * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\REST_API; use Google\Site_Kit\Context; /** * Class managing REST API routes. * * @since 1.0.0 * @access private * @ignore */ final class REST_Routes { const REST_ROOT = 'google-site-kit/v1'; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ public function register() { add_action( 'rest_api_init', function () { $this->register_routes(); } ); add_filter( 'do_parse_request', function ( $do_parse_request, $wp ) { add_filter( 'query_vars', function ( $vars ) use ( $wp ) { // Unsets standard public query vars to escape conflicts between WordPress core // and Google Site Kit APIs which happen when WordPress incorrectly parses request // arguments. $unset_vars = ( $wp->request && stripos( $wp->request, trailingslashit( rest_get_url_prefix() ) . self::REST_ROOT ) !== false ) // Check regular permalinks. || ( empty( $wp->request ) && stripos( $this->context->input()->filter( INPUT_GET, 'rest_route' ) || '', self::REST_ROOT ) !== false ); // Check plain permalinks. if ( $unset_vars ) { // List of variable names to remove from public query variables list. return array_values( array_diff( $vars, array( 'orderby', ) ) ); } return $vars; } ); return $do_parse_request; }, 10, 2 ); } /** * Registers all REST routes. * * @since 1.0.0 * @since 1.16.0 Reworked to use REST_Route::register method to register a route. */ private function register_routes() { $routes = $this->get_routes(); foreach ( $routes as $route ) { $route->register(); } } /** * Gets available REST routes. * * @since 1.0.0 * @since 1.3.0 Moved most routes into individual classes and introduced {@see 'googlesitekit_rest_routes'} filter. * * @return array List of REST_Route instances. */ private function get_routes() { $routes = array(); /** * Filters the list of available REST routes. * * @since 1.3.0 * * @param array $routes List of REST_Route objects. */ return apply_filters( 'googlesitekit_rest_routes', $routes ); } } REST_API/Data_Request.php 0000644 00000006345 14720521221 0011110 0 ustar 00 <?php /** * Data_Request * * @package Google\Site_Kit\Core\REST_API * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\REST_API; /** * Class Data_Request * * @since 1.0.0 * * @property-read string $method Request method. * @property-read string $type Request type. * @property-read string $identifier Request identifier. * @property-read string $datapoint Request datapoint. * @property-read array $data Request data parameters. * @property-read string $key Request key. */ class Data_Request implements \ArrayAccess { /** * Request method. * * @var string */ protected $method; /** * Request type. * * @var string */ protected $type; /** * Request identifier. * * @var string */ protected $identifier; /** * Request datapoint. * * @var string */ protected $datapoint; /** * Request data parameters. * * @var array */ protected $data; /** * Request key. * * @var string */ protected $key; /** * Data_Request constructor. * * @param string $method Request method. * @param string $type Request type. * @param string $identifier Request identifier. * @param string $datapoint Request datapoint. * @param array|self $data Request data parameters. * @param string $key Request cache key. */ public function __construct( $method = null, $type = null, $identifier = null, $datapoint = null, $data = array(), $key = null ) { $this->method = strtoupper( $method ); $this->type = $type; $this->identifier = $identifier; $this->datapoint = $datapoint; $this->data = $data instanceof self ? $data->data : (array) $data; $this->key = $key; } /** * Gets the accessed property by the given name. * * @param string $name Property name. * * @return mixed */ public function __get( $name ) { return isset( $this->$name ) ? $this->$name : null; } /** * Checks whether or not the given magic property is set. * * @param string $name Property name. * * @return bool */ public function __isset( $name ) { return isset( $this->$name ); } /** * Checks whether the given key exists. * * @param string|int $key Key to check. * * @return bool */ #[\ReturnTypeWillChange] public function offsetExists( $key ) { return array_key_exists( $key, $this->data ); } /** * Gets the value at the given key. * * @param string|int $key Key to return the value for. * * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet( $key ) { if ( $this->offsetExists( $key ) ) { return $this->data[ $key ]; } return null; } /** * Sets the given key to the given value. * * @param string|int $key Key to set the value for. * @param mixed $value New value for the given key. */ #[\ReturnTypeWillChange] public function offsetSet( $key, $value ) { // Data is immutable. } /** * Unsets the given key. * * @param string|int $key Key to unset. */ #[\ReturnTypeWillChange] public function offsetUnset( $key ) { // phpcs:ignore Squiz.Commenting.FunctionComment // Data is immutable. } } Permissions/Permissions.php 0000644 00000061405 14720521221 0012105 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Permissions\Permissions * * @package Google\Site_Kit\Core\Permissions * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Permissions; use Exception; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Dismissals\Dismissed_Items; use Google\Site_Kit\Core\Modules\Module_With_Owner; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\Storage\User_Options; use WP_REST_Response; use WP_REST_Server; use WP_User; /** * Class managing plugin permissions. * * @since 1.0.0 * @access private * @ignore */ final class Permissions { /* * Custom base capabilities. */ const AUTHENTICATE = 'googlesitekit_authenticate'; const SETUP = 'googlesitekit_setup'; const VIEW_POSTS_INSIGHTS = 'googlesitekit_view_posts_insights'; const VIEW_DASHBOARD = 'googlesitekit_view_dashboard'; const VIEW_WP_DASHBOARD_WIDGET = 'googlesitekit_view_wp_dashboard_widget'; const VIEW_ADMIN_BAR_MENU = 'googlesitekit_view_admin_bar_menu'; const MANAGE_OPTIONS = 'googlesitekit_manage_options'; const UPDATE_PLUGINS = 'googlesitekit_update_plugins'; /* * Custom meta capabilities. */ const VIEW_SPLASH = 'googlesitekit_view_splash'; const VIEW_SHARED_DASHBOARD = 'googlesitekit_view_shared_dashboard'; const VIEW_AUTHENTICATED_DASHBOARD = 'googlesitekit_view_authenticated_dashboard'; const VIEW_POST_INSIGHTS = 'googlesitekit_view_post_insights'; const READ_SHARED_MODULE_DATA = 'googlesitekit_read_shared_module_data'; const MANAGE_MODULE_SHARING_OPTIONS = 'googlesitekit_manage_module_sharing_options'; const DELEGATE_MODULE_SHARING_MANAGEMENT = 'googlesitekit_delegate_module_sharing_management'; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Authentication instance. * * @since 1.0.0 * @var Authentication */ protected $authentication; /** * Modules instance. * * @since 1.69.0 * @var Modules */ private $modules; /** * User_Options instance. * * @since 1.69.0 * @var User_Options */ private $user_options; /** * Dismissed_Items instance. * * @since 1.69.0 * @var Dismissed_Items */ private $dismissed_items; /** * Mappings for custom base capabilities to WordPress core built-in ones. * * @since 1.30.0 * @var array */ private $base_to_core = array(); /** * Mappings for custom meta capabilities to WordPress core built-in ones. * * @since 1.0.0 * @var array */ private $meta_to_core = array(); /** * Mappings for custom meta capabilities to custom base capabilities. * * @since 1.30.0 * @var array */ private $meta_to_base = array(); /** * List of custom base capabilities that should require network access if the plugin is in network mode. * * @since 1.30.0 * @var array */ private $network_base = array(); /** * Constructor. * * Sets up the capability mappings. * * @since 1.0.0 * * @param Context $context Plugin context. * @param Authentication $authentication Authentication instance. * @param Modules $modules Modules instance. * @param User_Options $user_options User_Options instance. * @param Dismissed_Items $dismissed_items Dismissed_Items instance. */ public function __construct( Context $context, Authentication $authentication, Modules $modules, User_Options $user_options, Dismissed_Items $dismissed_items ) { $this->context = $context; $this->authentication = $authentication; $this->modules = $modules; $this->user_options = $user_options; $this->dismissed_items = $dismissed_items; $this->base_to_core = array( // By default, only allow administrators to authenticate. self::AUTHENTICATE => 'manage_options', // Allow contributors and up to view their own post's insights. self::VIEW_POSTS_INSIGHTS => 'edit_posts', // Allow editors and up to view the dashboard and module details. self::VIEW_DASHBOARD => 'edit_posts', self::VIEW_WP_DASHBOARD_WIDGET => 'edit_posts', self::VIEW_ADMIN_BAR_MENU => 'edit_posts', // Allow administrators and up to manage options and set up the plugin. self::MANAGE_OPTIONS => 'manage_options', self::SETUP => 'manage_options', self::UPDATE_PLUGINS => 'update_plugins', ); $this->meta_to_core = array( // Allow users that can edit a post to view that post's insights. self::VIEW_POST_INSIGHTS => 'edit_post', ); $this->meta_to_base = array( self::VIEW_SPLASH => self::VIEW_DASHBOARD, self::VIEW_AUTHENTICATED_DASHBOARD => array( self::VIEW_DASHBOARD, self::AUTHENTICATE ), // Allow users that can generally view posts insights to view a specific post's insights. self::VIEW_POST_INSIGHTS => self::VIEW_POSTS_INSIGHTS, // Allow users that can generally view dashboard to read shared module data. self::READ_SHARED_MODULE_DATA => self::VIEW_DASHBOARD, // Admins who can manage options for SK can generally manage module sharing options. self::MANAGE_MODULE_SHARING_OPTIONS => self::MANAGE_OPTIONS, self::DELEGATE_MODULE_SHARING_MANAGEMENT => self::MANAGE_OPTIONS, self::VIEW_SHARED_DASHBOARD => self::VIEW_DASHBOARD, ); $this->network_base = array( // Require network admin access to view the dashboard and module details in network mode. self::VIEW_DASHBOARD => 'manage_network', self::VIEW_WP_DASHBOARD_WIDGET => 'manage_network', self::VIEW_ADMIN_BAR_MENU => 'manage_network', // Require network admin access to manage options and set up the plugin in network mode. self::MANAGE_OPTIONS => 'manage_network_options', self::SETUP => 'manage_network_options', ); } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ public function register() { add_filter( 'map_meta_cap', function ( array $caps, $cap, $user_id, $args ) { return $this->map_meta_capabilities( $caps, $cap, $user_id, $args ); }, 10, 4 ); add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/permissions', ) ); } ); // This constant can be set if an alternative mechanism to grant these capabilities is in place. if ( defined( 'GOOGLESITEKIT_DISABLE_DYNAMIC_CAPABILITIES' ) && GOOGLESITEKIT_DISABLE_DYNAMIC_CAPABILITIES ) { return; } add_filter( 'user_has_cap', function ( array $allcaps ) { return $this->grant_additional_caps( $allcaps ); } ); } /** * Get dashboard sharing meta permissions for current user. * * @since 1.70.0 * * @return array List meta capabilities as keys and current user permission as value. */ public function get_dashboard_sharing_meta_permissions() { $dashboard_sharing_meta_capabilities = self::get_dashboard_sharing_meta_capabilities(); $shareable_modules = array_keys( $this->modules->get_shareable_modules() ); $dashboard_sharing_meta_permissions = array(); foreach ( $dashboard_sharing_meta_capabilities as $cap ) { foreach ( $shareable_modules as $module ) { $dashboard_sharing_meta_permissions[ "{$cap}::" . wp_json_encode( array( $module ) ) ] = current_user_can( $cap, $module ); } } return $dashboard_sharing_meta_permissions; } /** * Check permissions for current user. * * @since 1.21.0 * * @return array List of base capabilities and meta capabilities as keys and current user permission as value. */ public function check_all_for_current_user() { $permissions = self::get_capabilities(); return array_merge( array_combine( $permissions, array_map( 'current_user_can', $permissions ) ), self::get_dashboard_sharing_meta_permissions() ); } /** * Resolves meta capabilities to their base capabilities. * * This method first maps plugin meta capabilities to their base capabilities. In addition, if the meta * capability should also map to a core meta capability, that mapping is taken care of as well. * * If in network mode and the custom base capability requires network access, it is checked that the user * has that access, and if not, the method bails early causing in a result of false. * * It also prevents access to Site Kit's custom capabilities based on additional rules. These additional * checks ideally could be done within the `user_has_cap` filter. However, the `user_has_cap` filter is * applied after a check for multi-site admins which could potentially grant the capability without * executing these additional checks. * * @see WP_User::has_cap() To see the order of execution mentioned above. * * @since 1.0.0 * * @param array $caps List of resolved capabilities. * @param string $cap Capability checked. * @param int $user_id Current user ID. * @param array $args Additional arguments passed to the capability check. * @return array Filtered value of $caps. */ private function map_meta_capabilities( array $caps, $cap, $user_id, $args ) { // Bail early under these circumstances as we already know for sure the check will result in false. if ( isset( $this->network_base[ $cap ] ) && $this->context->is_network_mode() && ! is_super_admin( $user_id ) ) { return array( 'do_not_allow' ); } if ( isset( $this->meta_to_base[ $cap ] ) ) { $caps = (array) $this->meta_to_base[ $cap ]; } if ( isset( $this->meta_to_core[ $cap ] ) ) { $required_core_caps = call_user_func_array( 'map_meta_cap', array_merge( array( $this->meta_to_core[ $cap ], $user_id ), $args ) ); $caps = array_merge( $caps, $required_core_caps ); } // Special setup and authentication rules. if ( ( isset( $this->base_to_core[ $cap ] ) || isset( $this->meta_to_core[ $cap ] ) ) ) { // If setup has not yet been completed, require administrator capabilities for everything. if ( self::SETUP !== $cap && ! $this->authentication->is_setup_completed() ) { $caps[] = self::SETUP; } if ( ! in_array( $cap, array( self::AUTHENTICATE, self::SETUP, self::VIEW_DASHBOARD, self::VIEW_POSTS_INSIGHTS, self::VIEW_WP_DASHBOARD_WIDGET, self::VIEW_ADMIN_BAR_MENU ), true ) ) { // For regular users, require being authenticated. if ( ! $this->is_user_authenticated( $user_id ) ) { return array_merge( $caps, array( 'do_not_allow' ) ); } // For admin users, also require being verified. if ( user_can( $user_id, self::SETUP ) && ! $this->is_user_verified( $user_id ) ) { return array_merge( $caps, array( 'do_not_allow' ) ); } // For all users, require setup to have been completed. if ( ! $this->authentication->is_setup_completed() ) { return array_merge( $caps, array( 'do_not_allow' ) ); } } } if ( in_array( $cap, self::get_dashboard_sharing_capabilities(), true ) ) { $caps = array_merge( $caps, $this->check_dashboard_sharing_capability( $cap, $user_id, $args ) ); } switch ( $cap ) { case self::VIEW_SPLASH: $caps = array_merge( $caps, $this->check_view_splash_capability( $user_id ) ); break; // Intentional fallthrough - viewing the dashboard widget and admin bar menu require // a user to be authenticated. case self::VIEW_AUTHENTICATED_DASHBOARD: $caps = array_merge( $caps, $this->check_view_authenticated_dashboard_capability( $user_id ) ); break; // Intentional fallthrough. case self::VIEW_DASHBOARD: case self::VIEW_POSTS_INSIGHTS: $caps = array_merge( $caps, $this->check_view_dashboard_capability( $user_id ) ); break; case self::VIEW_WP_DASHBOARD_WIDGET: case self::VIEW_ADMIN_BAR_MENU: $caps = array_merge( $caps, $this->check_view_wp_dashboard_widget_and_admin_bar_capability( $user_id ) ); break; } return $caps; } /** * Checks a dashboard sharing capability based on rules of dashboard sharing. * * @since 1.69.0 * * @param string $cap Capability to be checked. * @param int $user_id User ID of the user the capability is checked for. * @param array $args Additional arguments passed to check a meta capability. * @return array Array with a 'do_not_allow' element if checks fail, empty array if checks pass. */ private function check_dashboard_sharing_capability( $cap, $user_id, $args ) { if ( isset( $args[0] ) ) { $module_slug = $args[0]; } switch ( $cap ) { case self::VIEW_SHARED_DASHBOARD: return $this->check_view_shared_dashboard_capability( $user_id ); case self::READ_SHARED_MODULE_DATA: return $this->check_read_shared_module_data_capability( $user_id, $module_slug ); case self::MANAGE_MODULE_SHARING_OPTIONS: case self::DELEGATE_MODULE_SHARING_MANAGEMENT: return $this->check_module_sharing_admin_capability( $cap, $user_id, $module_slug ); default: return array(); } } /** * Checks if the VIEW_SPLASH capability is allowed for the user. * * @since 1.73.0 * * @param int $user_id User ID of the user the capability is checked for. * @return array Array with a 'do_not_allow' element if checks fail, empty array if checks pass. */ private function check_view_splash_capability( $user_id ) { if ( $this->is_shared_dashboard_splash_dismissed( $user_id ) ) { return array( self::AUTHENTICATE ); } if ( ! $this->user_has_shared_role( $user_id ) ) { return array( self::AUTHENTICATE ); } return array(); } /** * Checks if the VIEW_DASHBOARD capability is allowed for the user. * * Allows access to the VIEW_DASHBOARD capability if the user can view either * the authenticated or shared dashboard. * * @since 1.73.0 * * @param int $user_id User ID of the user the capability is checked for. * @return array Array with a 'do_not_allow' element if checks fail, empty array if checks pass. */ private function check_view_dashboard_capability( $user_id ) { $view_authenticated_dashboard = $this->check_view_authenticated_dashboard_capability( $user_id ); if ( in_array( 'do_not_allow', $view_authenticated_dashboard, true ) ) { return $this->check_view_shared_dashboard_capability( $user_id ); } return $view_authenticated_dashboard; } /** * Checks if the VIEW_WP_DASHBOARD_WIDGET and VIEW_ADMIN_BAR_MENU capabilities are allowed for the user. * * Allows access to the VIEW_WP_DASHBOARD_WIDGET and VIEW_ADMIN_BAR_MENU capabilities if the user can view * either the authenticated or shared dashboard and if the user has a shared role for either the Analytics * or Search Console module. * * @since 1.120.0 * * @param int $user_id User ID of the user the capability is checked for. * @return array Array with a 'do_not_allow' element if checks fail, empty array if checks pass. */ private function check_view_wp_dashboard_widget_and_admin_bar_capability( $user_id ) { $view_dashboard_capability = $this->check_view_dashboard_capability( $user_id ); if ( in_array( self::AUTHENTICATE, $view_dashboard_capability, true ) || in_array( 'do_not_allow', $view_dashboard_capability, true ) ) { return $view_dashboard_capability; } if ( ! $this->user_has_shared_role_for_module( $user_id, 'analytics-4' ) && ! $this->user_has_shared_role_for_module( $user_id, 'search-console' ) ) { return array( 'do_not_allow' ); } return array(); } /** * Checks if the VIEW_SHARED_DASHBOARD capability should be denied. * * Prevents access to the VIEW_SHARED_DASHBOARD capability if a user does not * have any of the shared roles set for any shareable module or if they have * not dismissed the dashboard sharing splash screen message. * * @since 1.69.0 * * @param int $user_id User ID of the user the capability is checked for. * @return array Array with a 'do_not_allow' element if checks fail, empty array if checks pass. */ private function check_view_shared_dashboard_capability( $user_id ) { if ( ! $this->user_has_shared_role( $user_id ) ) { return array( 'do_not_allow' ); } if ( ! $this->is_shared_dashboard_splash_dismissed( $user_id ) ) { return array( 'do_not_allow' ); } return array(); } /** * Checks if the VIEW_AUTHENTICATED_DASHBOARD capability is allowed for the user. * * Allows access to the VIEW_AUTHENTICATED_DASHBOARD capability if the user is authenticated. * * @since 1.73.0 * * @param int $user_id User ID of the user the capability is checked for. * @return array Array with a 'do_not_allow' element if checks fail, otherise returns AUTHENTICATE capability. */ private function check_view_authenticated_dashboard_capability( $user_id ) { if ( $this->is_user_authenticated( $user_id ) && $this->is_user_verified( $user_id ) && $this->authentication->is_setup_completed() ) { return array( self::AUTHENTICATE ); } return array( 'do_not_allow' ); } /** * Checks if the READ_SHARED_MODULE_DATA capability should be denied. * * Prevents access to the READ_SHARED_MODULE_DATA capability if a user does not * have the shared roles set for the given module slug. * * @since 1.69.0 * * @param int $user_id User ID of the user the capability is checked for. * @param string $module_slug Module for which the meta capability is checked for. * @return array Array with a 'do_not_allow' element if checks fail, empty array if checks pass. */ private function check_read_shared_module_data_capability( $user_id, $module_slug ) { if ( ! $this->user_has_shared_role_for_module( $user_id, $module_slug ) ) { return array( 'do_not_allow' ); } return array(); } /** * Checks if the MANAGE_MODULE_SHARING_OPTIONS or the DELEGATE_MODULE_SHARING_MANAGEMENT * capability should be denied. * * Prevents access to MANAGE_MODULE_SHARING_OPTIONS or the DELEGATE_MODULE_SHARING_MANAGEMENT * capability if a user is not an authenticated admin. * * Furthermore, it prevents access for these capabilities if the user is not the owner * of the given module slug. This check is skipped for MANAGE_MODULE_SHARING_OPTIONS if the * module settings allow all admins to manage sharing options for that module. * * @since 1.69.0 * * @param string $cap Capability to be checked. * @param int $user_id User ID of the user the capability is checked for. * @param string $module_slug Module for which the meta capability is checked for. * @return array Array with a 'do_not_allow' element if checks fail, empty array if checks pass. */ private function check_module_sharing_admin_capability( $cap, $user_id, $module_slug ) { $module_sharing_settings = $this->modules->get_module_sharing_settings(); $sharing_settings = $module_sharing_settings->get(); if ( ! $this->is_user_authenticated( $user_id ) ) { return array( 'do_not_allow' ); } if ( self::MANAGE_MODULE_SHARING_OPTIONS === $cap && isset( $sharing_settings[ $module_slug ]['management'] ) && 'all_admins' === $sharing_settings[ $module_slug ]['management'] ) { return array(); } try { $module = $this->modules->get_module( $module_slug ); if ( ! ( $module instanceof Module_With_Owner ) ) { return array( 'do_not_allow' ); } if ( $module->get_owner_id() !== $user_id ) { return array( 'do_not_allow' ); } } catch ( Exception $e ) { return array( 'do_not_allow' ); } return array(); } /** * Checks if the given user has a role in the list of shared roles. * * @since 1.73.0 * * @param int $user_id User ID. * @param string[]|null $shared_roles Optional. List of shared role IDs to check against the user's. Defaults to all shared module roles. * @return bool */ private function user_has_shared_role( $user_id, array $shared_roles = null ) { if ( ! is_array( $shared_roles ) ) { $shared_roles = $this->modules->get_module_sharing_settings()->get_all_shared_roles(); } $shared_user_roles = array_intersect( $shared_roles, ( new WP_User( $user_id ) )->roles ); return ! empty( $shared_user_roles ); } /** * Checks if the given user has a role in the list of shared roles for the given module. * * @since 1.73.0 * * @param int $user_id User ID. * @param string $module Module slug. * @return bool */ private function user_has_shared_role_for_module( $user_id, $module ) { $settings = $this->modules->get_module_sharing_settings()->get(); if ( empty( $settings[ $module ]['sharedRoles'] ) ) { return false; } return $this->user_has_shared_role( $user_id, $settings[ $module ]['sharedRoles'] ); } /** * Checks if a user is authenticated in Site Kit. * * @since 1.69.0 * * @param int $user_id User ID of the user to be checked. * @return bool True if the user is authenticated, false if not. */ public function is_user_authenticated( $user_id ) { $restore_user = $this->user_options->switch_user( $user_id ); $is_user_authenticated = $this->authentication->is_authenticated(); $restore_user(); return $is_user_authenticated; } /** * Checks if a user is verified in Site Kit. * * @since 1.69.0 * * @param int $user_id User ID of the user to be checked. * @return bool True if the user is verified, false if not. */ public function is_user_verified( $user_id ) { $restore_user = $this->user_options->switch_user( $user_id ); $is_user_verified = $this->authentication->verification()->has(); $restore_user(); return $is_user_verified; } /** * Checks if a user has dimissed the shared dashboard splash screen message. * * @since 1.69.0 * * @param int $user_id User ID of the user to be checked. * @return bool True if the user has dismissed the splash message, false if not. */ private function is_shared_dashboard_splash_dismissed( $user_id ) { $restore_user = $this->user_options->switch_user( $user_id ); $is_splash_dismissed = $this->dismissed_items->is_dismissed( 'shared_dashboard_splash' ); $restore_user(); return $is_splash_dismissed; } /** * Grants custom capabilities on-the-fly, based on core capabilities. * * If you want to instead set up your own custom role or mechanism to grant these capabilities, you can set a * constant flag `GOOGLESITEKIT_DISABLE_DYNAMIC_CAPABILITIES` to ensure this function is not hooked in. * * @since 1.0.0 * * @param array $allcaps Associative array of $capability => $grant pairs. * @return array Filtered value of $allcaps. */ private function grant_additional_caps( array $allcaps ) { foreach ( $this->base_to_core as $custom_cap => $core_cap ) { if ( isset( $allcaps[ $core_cap ] ) ) { $allcaps[ $custom_cap ] = $allcaps[ $core_cap ]; } } return $allcaps; } /** * Gets related REST routes. * * @since 1.82.0 * * @return array List of REST_Route objects. */ private function get_rest_routes() { return array( new REST_Route( 'core/user/data/permissions', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->check_all_for_current_user() ); }, 'permission_callback' => function () { return current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ); }, ), ) ), ); } /** * Gets all the base capabilities used in Google Site Kit. * * @since 1.31.0 * * @return array */ public static function get_capabilities() { return array( self::AUTHENTICATE, self::SETUP, self::VIEW_POSTS_INSIGHTS, self::VIEW_DASHBOARD, self::MANAGE_OPTIONS, self::UPDATE_PLUGINS, self::VIEW_SPLASH, self::VIEW_AUTHENTICATED_DASHBOARD, self::VIEW_WP_DASHBOARD_WIDGET, self::VIEW_ADMIN_BAR_MENU, self::VIEW_SHARED_DASHBOARD, ); } /** * Gets all the capabilities specifically added for dashboard sharing. * * @since 1.69.0 * * @return array List of capabilities specific to dashboard sharing. */ public static function get_dashboard_sharing_capabilities() { return array( self::VIEW_SHARED_DASHBOARD, self::READ_SHARED_MODULE_DATA, self::MANAGE_MODULE_SHARING_OPTIONS, self::DELEGATE_MODULE_SHARING_MANAGEMENT, ); } /** * Gets all the meta capabilities specifically added for dashboard sharing. * * @since 1.70.0 * * @return array List of meta capabilities specific to dashboard sharing. */ public static function get_dashboard_sharing_meta_capabilities() { return array( self::READ_SHARED_MODULE_DATA, self::MANAGE_MODULE_SHARING_OPTIONS, self::DELEGATE_MODULE_SHARING_MANAGEMENT, ); } } Dismissals/REST_Dismissals_Controller.php 0000644 00000010250 14720521221 0014535 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Dismissals\REST_Dismissals_Controller * * @package Google\Site_Kit\Core\Dismissals * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Dismissals; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling dismissed items rest routes. * * @since 1.37.0 * @access private * @ignore */ class REST_Dismissals_Controller { /** * Dismissed_Items instance. * * @since 1.37.0 * @var Dismissed_Items */ protected $dismissed_items; /** * Constructor. * * @since 1.37.0 * * @param Dismissed_Items $dismissed_items Dismissed items instance. */ public function __construct( Dismissed_Items $dismissed_items ) { $this->dismissed_items = $dismissed_items; } /** * Registers functionality through WordPress hooks. * * @since 1.37.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/dismissed-items', ) ); } ); } /** * Gets REST route instances. * * @since 1.37.0 * @since 1.133.0 Added the `DELETE dismissed-items` route. * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_dismiss_item = function () { return current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ); }; return array( new REST_Route( 'core/user/data/dismissed-items', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->dismissed_items->get_dismissed_items() ); }, 'permission_callback' => $can_dismiss_item, ) ), new REST_Route( 'core/user/data/dismissed-items', array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => function ( WP_REST_Request $request ) { $slugs = $request['data']['slugs']; foreach ( $slugs as $slug ) { $this->dismissed_items->remove( $slug ); } return new WP_REST_Response( $this->dismissed_items->get_dismissed_items() ); }, 'permission_callback' => $can_dismiss_item, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'minProperties' => 1, 'additionalProperties' => false, 'properties' => array( 'slugs' => array( 'type' => 'array', 'required' => true, 'items' => array( 'type' => 'string', ), ), ), ), ), ), ), new REST_Route( 'core/user/data/dismiss-item', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; if ( empty( $data['slug'] ) ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'slug' ), array( 'status' => 400 ) ); } $expiration = Dismissed_Items::DISMISS_ITEM_PERMANENTLY; if ( isset( $data['expiration'] ) && intval( $data['expiration'] ) > 0 ) { $expiration = $data['expiration']; } $this->dismissed_items->add( $data['slug'], $expiration ); return new WP_REST_Response( $this->dismissed_items->get_dismissed_items() ); }, 'permission_callback' => $can_dismiss_item, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, ), ), ) ), ); } } Dismissals/Dismissals.php 0000644 00000003173 14720521221 0011503 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Dismissals\Dismissals * * @package Google\Site_Kit\Core\Dismissals * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Dismissals; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\User_Options; /** * Class for handling dismissals. * * @since 1.37.0 * @access private * @ignore */ class Dismissals { /** * Dismissed_Items instance. * * @since 1.37.0 * @var Dismissed_Items */ protected $dismissed_items; /** * REST_Dismissals_Controller instance. * * @since 1.37.0 * @var REST_Dismissals_Controller */ protected $rest_controller; /** * Constructor. * * @since 1.37.0 * * @param Context $context Plugin context. * @param User_Options $user_options Optional. User option API. Default is a new instance. */ public function __construct( Context $context, User_Options $user_options = null ) { $this->dismissed_items = new Dismissed_Items( $user_options ?: new User_Options( $context ) ); $this->rest_controller = new REST_Dismissals_Controller( $this->dismissed_items ); } /** * Gets the reference to the Dismissed_Items instance. * * @since 1.69.0 * * @return Dismissed_Items An instance of the Dismissed_Items class. */ public function get_dismissed_items() { return $this->dismissed_items; } /** * Registers functionality through WordPress hooks. * * @since 1.37.0 */ public function register() { $this->dismissed_items->register(); $this->rest_controller->register(); } } Dismissals/Dismissed_Items.php 0000644 00000007060 14720521221 0012454 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Dismissals\Dismissed_Items * * @package Google\Site_Kit\Core\Dismissals * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Dismissals; use Closure; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class for representing a user's dismissed items. * * @since 1.37.0 * @access private * @ignore */ class Dismissed_Items extends User_Setting { /** * The user option name for this setting. * * @note This option is prefixed differently so that it will persist across disconnect/reset. */ const OPTION = 'googlesitekitpersistent_dismissed_items'; const DISMISS_ITEM_PERMANENTLY = 0; /** * Adds one or more items to the list of dismissed items. * * @since 1.37.0 * * @param string $item Item to dismiss. * @param int $expires_in_seconds TTL for the item. */ public function add( $item, $expires_in_seconds = self::DISMISS_ITEM_PERMANENTLY ) { $items = $this->get(); $items[ $item ] = $expires_in_seconds ? time() + $expires_in_seconds : 0; $this->set( $items ); } /** * Removes one or more items from the list of dismissed items. * * @since 1.107.0 * * @param string $item Item to remove. */ public function remove( $item ) { $items = $this->get(); // If the item is not in dismissed items, there's nothing to do. if ( ! array_key_exists( $item, $items ) ) { return; } unset( $items[ $item ] ); $this->set( $items ); } /** * Gets the value of the setting. * * @since 1.37.0 * * @return array Value set for the option, or default if not set. */ public function get() { $value = parent::get(); return is_array( $value ) ? $value : $this->get_default(); } /** * Gets the expected value type. * * @since 1.37.0 * * @return string The type name. */ protected function get_type() { return 'array'; } /** * Gets the default value. * * @since 1.37.0 * * @return array The default value. */ protected function get_default() { return array(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.37.0 * * @return callable Sanitize callback. */ protected function get_sanitize_callback() { return function ( $items ) { return $this->filter_dismissed_items( $items ); }; } /** * Determines whether the item is dismissed. * * @since 1.37.0 * * @param string $item The item to check. * @return bool TRUE if item is dismissed, otherwise FALSE. */ public function is_dismissed( $item ) { $items = $this->get(); if ( ! array_key_exists( $item, $items ) ) { return false; } $ttl = $items[ $item ]; if ( $ttl > 0 && $ttl < time() ) { return false; } return true; } /** * Gets dismissed items. * * @since 1.37.0 * * @return array Dismissed items array. */ public function get_dismissed_items() { $dismissed_items = $this->get(); $dismissed_items = $this->filter_dismissed_items( $dismissed_items ); return array_keys( $dismissed_items ); } /** * Filters dismissed items. * * @since 1.37.0 * * @param array $items Dismissed items list. * @return array Filtered dismissed items. */ private function filter_dismissed_items( $items ) { $dismissed = array(); if ( is_array( $items ) ) { foreach ( $items as $item => $ttl ) { if ( self::DISMISS_ITEM_PERMANENTLY === $ttl || $ttl > time() ) { $dismissed[ $item ] = $ttl; } } } return $dismissed; } } Assets/Asset.php 0000644 00000006204 14720521221 0007574 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Assets\Asset * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Assets; use Google\Site_Kit\Context; /** * Class representing a single asset. * * @since 1.0.0 * @access private * @ignore */ abstract class Asset { // Various page contexts for Site Kit in the WordPress Admin. const CONTEXT_ADMIN_GLOBAL = 'admin-global'; const CONTEXT_ADMIN_POST_EDITOR = 'admin-post-editor'; const CONTEXT_ADMIN_POSTS = 'admin-posts'; const CONTEXT_ADMIN_SITEKIT = 'admin-sitekit'; /** * Unique asset handle. * * @since 1.0.0 * @var string */ protected $handle; /** * Asset arguments. * * @since 1.0.0 * @var array */ protected $args = array(); /** * Constructor. * * @since 1.0.0 * @since 1.37.0 Add the 'load_contexts' argument. * * @param string $handle Unique asset handle. * @param array $args { * Associative array of asset arguments. * * @type string $src Required asset source URL. * @type array $dependencies List of asset dependencies. Default empty array. * @type string $version Asset version. Default is the version of Site Kit. * @type bool $fallback Whether to only register as a fallback. Default false. * @type callable $before_print Optional callback to execute before printing. Default none. * @type string[] $load_contexts Optional array of page context values to determine on which page types to load this asset (see the `CONTEXT_` variables above). * } */ public function __construct( $handle, array $args ) { $this->handle = $handle; $this->args = wp_parse_args( $args, array( 'src' => '', 'dependencies' => array(), 'version' => GOOGLESITEKIT_VERSION, 'fallback' => false, 'before_print' => null, 'load_contexts' => array( self::CONTEXT_ADMIN_SITEKIT ), ) ); } /** * Gets the notice handle. * * @since 1.0.0 * * @return string Unique notice handle. */ public function get_handle() { return $this->handle; } /** * Checks to see if the specified context exists for the current request. * * @since 1.37.0 * * @param string $context Context value (see the `CONTEXT_` variables above). * @return bool TRUE if context exists; FALSE otherwise. */ public function has_context( $context ) { return in_array( $context, $this->args['load_contexts'], true ); } /** * Registers the asset. * * @since 1.0.0 * @since 1.15.0 Adds $context parameter. * * @param Context $context Plugin context. */ abstract public function register( Context $context ); /** * Enqueues the asset. * * @since 1.0.0 */ abstract public function enqueue(); /** * Executes the extra callback if defined before printing the asset. * * @since 1.2.0 */ final public function before_print() { if ( ! is_callable( $this->args['before_print'] ) ) { return; } call_user_func( $this->args['before_print'], $this->handle ); } } Assets/Manifest.php 0000644 00000002447 14720521221 0010270 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Assets\Manifest * * @package GoogleSite_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Assets; use Google\Site_Kit\Plugin; /** * Assets manifest. * * @since 1.15.0 * @access private * @ignore */ class Manifest { /** * Entries as $handle => [ $filename, $hash ] map. * * @since 1.48.0 * @var array */ private static $data; /** * Gets the manifest entry for the given handle. * * @since 1.48.0 * * @param string $handle Asset handle to get manifest data for. * @return array List of $filename and $hash, or `null` for both if not found. */ public static function get( $handle ) { if ( null === self::$data ) { self::load(); } if ( isset( self::$data[ $handle ] ) ) { return self::$data[ $handle ]; } return array( null, null ); } /** * Loads the generated manifest file. * * @since 1.48.0 */ private static function load() { $path = Plugin::instance()->context()->path( 'dist/manifest.php' ); if ( file_exists( $path ) ) { // If the include fails, $data will be `false` // so this should only be attempted once. self::$data = include $path; } } } Assets/Assets.php 0000644 00000071245 14720521221 0007766 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Assets\Assets * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Assets; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Modules\Module_Sharing_Settings; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Util\Feature_Flags; use Google\Site_Kit\Core\Util\URL; use WP_Dependencies; use WP_Post_Type; /** * Class managing assets. * * @since 1.0.0 * @access private * @ignore */ final class Assets { /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Lazy-loaded assets as $handle => $instance pairs. * * @since 1.0.0 * @var array */ private $assets = array(); /** * Internal flag for whether assets have been registered yet. * * @since 1.2.0 * @var bool */ private $assets_registered = false; /** * Internal list of print callbacks already done. * * @since 1.2.0 * @var array */ private $print_callbacks_done = array(); /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. */ public function __construct( Context $context ) { $this->context = $context; } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 * @since 1.37.0 Enqueues Block Editor assets. */ public function register() { $register_callback = function () { if ( ! is_admin() ) { return; } if ( $this->assets_registered ) { return; } $this->assets_registered = true; $this->register_assets(); }; add_action( 'admin_enqueue_scripts', $register_callback ); add_action( 'wp_enqueue_scripts', $register_callback ); add_filter( 'script_loader_tag', function ( $tag, $handle ) { return $this->add_async_defer_attribute( $tag, $handle ); }, 10, 2 ); // All other asset-related general logic should only be active when the // current user can actually use Site Kit. if ( false === ( current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ) ) ) { return; } $this->add_amp_dev_mode_attributes( $this->get_assets() ); add_action( 'admin_print_scripts-edit.php', function () { global $post_type; if ( 'post' !== $post_type ) { // For CONTEXT_ADMIN_POSTS we only load scripts for the 'post' post type. return; } $assets = $this->get_assets(); array_walk( $assets, function ( Asset $asset ) { if ( $asset->has_context( Asset::CONTEXT_ADMIN_POSTS ) ) { $this->enqueue_asset( $asset->get_handle() ); } } ); } ); add_action( 'enqueue_block_editor_assets', function () { $assets = $this->get_assets(); array_walk( $assets, function ( $asset ) { if ( $asset->has_context( Asset::CONTEXT_ADMIN_POST_EDITOR ) ) { $this->enqueue_asset( $asset->get_handle() ); } } ); } ); $scripts_print_callback = function () { $scripts = wp_scripts(); $this->run_before_print_callbacks( $scripts, $scripts->queue ); }; add_action( 'wp_print_scripts', $scripts_print_callback ); add_action( 'admin_print_scripts', $scripts_print_callback ); $styles_print_callback = function () { $styles = wp_styles(); $this->run_before_print_callbacks( $styles, $styles->queue ); }; add_action( 'wp_print_styles', $styles_print_callback ); add_action( 'admin_print_styles', $styles_print_callback ); } /** * Enqueues the given plugin asset (script or stylesheet). * * The asset must already be registered in order to be enqueued. * * @since 1.0.0 * * @param string $handle Asset handle. */ public function enqueue_asset( $handle ) { // Register assets on-the-fly if necessary (currently the case for admin bar in frontend). if ( ! $this->assets_registered ) { $this->assets_registered = true; $this->register_assets(); } $assets = $this->get_assets(); if ( empty( $assets[ $handle ] ) ) { return; } $assets[ $handle ]->enqueue(); } /** * Enqueues Google fonts. * * @since 1.0.0 * @deprecated 1.41.0 This method is no longer used as fonts are loaded as a normal style dependency now. */ public function enqueue_fonts() { _deprecated_function( __METHOD__, '1.41.0' ); $assets = $this->get_assets(); if ( ! empty( $assets['googlesitekit-fonts'] ) && $assets['googlesitekit-fonts'] instanceof Asset ) { $assets['googlesitekit-fonts']->enqueue(); } } /** * Get Google fonts src for CSS. * * @since 1.41.0 * * @return string String URL src. */ protected function get_fonts_src() { $font_families = array( 'Google+Sans+Text:400,500', 'Google+Sans+Display:400,500,700', ); if ( Feature_Flags::enabled( 'gm3Components' ) ) { $font_families[] = 'Roboto:300,400,500'; } $filtered_font_families = apply_filters( 'googlesitekit_font_families', $font_families ); if ( empty( $filtered_font_families ) ) { return ''; } return add_query_arg( array( 'family' => implode( '|', $filtered_font_families ), 'subset' => 'latin-ext', 'display' => 'fallback', ), 'https://fonts.googleapis.com/css' ); } /** * Registers all plugin assets. * * @since 1.0.0 */ private function register_assets() { $assets = $this->get_assets(); foreach ( $assets as $asset ) { $asset->register( $this->context ); } } /** * Add data-ampdevmode attributes to assets. * * @todo What about dependencies? * * @param Asset[] $assets Assets. */ private function add_amp_dev_mode_attributes( $assets ) { add_filter( 'script_loader_tag', function ( $tag, $handle ) use ( $assets ) { // TODO: 'hoverintent-js' can be removed from here at some point, see https://github.com/ampproject/amp-wp/pull/3928. if ( $this->context->is_amp() && ( isset( $assets[ $handle ] ) && ( $assets[ $handle ] instanceof Script || 'hoverintent-js' === $handle ) ) ) { $tag = preg_replace( '/(?<=<script)(?=\s|>)/i', ' data-ampdevmode', $tag ); } return $tag; }, 10, 2 ); add_filter( 'style_loader_tag', function ( $tag, $handle ) use ( $assets ) { if ( $this->context->is_amp() && isset( $assets[ $handle ] ) && $assets[ $handle ] instanceof Stylesheet ) { $tag = preg_replace( '/(?<=<link)(?=\s|>)/i', ' data-ampdevmode', $tag ); } return $tag; }, 10, 2 ); } /** * Forms an array of dependencies based on the necessary context. * * @since 1.87.0 * * @param string $context The context for which dependencies should be formed. * @return array The array of dependencies. */ private function get_asset_dependencies( $context = '' ) { $dependencies = array( 'googlesitekit-tracking-data', 'googlesitekit-runtime', 'googlesitekit-i18n', 'googlesitekit-vendor', 'googlesitekit-commons', 'googlesitekit-data', 'googlesitekit-datastore-forms', 'googlesitekit-datastore-location', 'googlesitekit-datastore-site', 'googlesitekit-datastore-user', 'googlesitekit-datastore-ui', 'googlesitekit-widgets', 'googlesitekit-notifications', ); if ( 'dashboard' === $context || 'dashboard-sharing' === $context ) { array_push( $dependencies, 'googlesitekit-components' ); } if ( 'dashboard-sharing' === $context ) { array_push( $dependencies, 'googlesitekit-dashboard-sharing-data' ); } return $dependencies; } /** * Gets all plugin assets. * * The method will lazy-load assets in an internal property so that the processing only happens once. * * @since 1.0.0 * * @return Asset[] Associative array of asset $handle => $instance pairs. */ private function get_assets() { if ( $this->assets ) { return $this->assets; } $base_url = $this->context->url( 'dist/assets/' ); $dependencies = $this->get_asset_dependencies(); // Register plugin scripts. $assets = array( new Script_Data( 'googlesitekit-commons', array( 'global' => '_googlesitekitLegacyData', 'data_callback' => function () { return $this->get_inline_data(); }, ) ), new Script_Data( 'googlesitekit-base-data', array( 'global' => '_googlesitekitBaseData', 'data_callback' => function () { return $this->get_inline_base_data(); }, ) ), new Script_Data( 'googlesitekit-entity-data', array( 'global' => '_googlesitekitEntityData', 'data_callback' => function () { return $this->get_inline_entity_data(); }, ) ), new Script_Data( 'googlesitekit-user-data', array( 'global' => '_googlesitekitUserData', 'data_callback' => function () { return $this->get_inline_user_data(); }, ) ), new Script_Data( 'googlesitekit-apifetch-data', array( 'global' => '_googlesitekitAPIFetchData', 'data_callback' => function () { /** * Preload common data by specifying an array of REST API paths that will be preloaded. * * Filters the array of paths that will be preloaded. * * @since 1.7.0 * * @param array $preload_paths Array of paths to preload. */ $preload_paths = apply_filters( 'googlesitekit_apifetch_preload_paths', array() ); $preloaded = array_reduce( array_unique( $preload_paths ), 'rest_preload_api_request', array() ); return array( 'nonce' => ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ), 'nonceEndpoint' => admin_url( 'admin-ajax.php?action=rest-nonce' ), 'preloadedData' => $preloaded, 'rootURL' => esc_url_raw( get_rest_url() ), ); }, ) ), new Script_Data( 'googlesitekit-dashboard-sharing-data', array( 'global' => '_googlesitekitDashboardSharingData', 'data_callback' => function () { return $this->get_inline_dashboard_sharing_data(); }, ) ), new Script_Data( 'googlesitekit-tracking-data', array( 'global' => '_googlesitekitTrackingData', 'data_callback' => function () { return $this->get_inline_tracking_data(); }, ) ), new Script_Data( 'googlesitekit-modules-data', array( 'global' => '_googlesitekitModulesData', 'data_callback' => function () { return $this->get_inline_modules_data(); }, ) ), new Script( 'googlesitekit-runtime', array( 'src' => $base_url . 'js/runtime.js', ) ), new Script( 'googlesitekit-polyfills', array( 'src' => $base_url . 'js/googlesitekit-polyfills.js', 'dependencies' => array( 'googlesitekit-base-data', ), ) ), new Script( 'googlesitekit-i18n', array( 'src' => $base_url . 'js/googlesitekit-i18n.js', ) ), new Script( 'googlesitekit-vendor', array( 'src' => $base_url . 'js/googlesitekit-vendor.js', 'dependencies' => array( 'googlesitekit-i18n', 'googlesitekit-runtime', 'googlesitekit-polyfills', ), ) ), // Admin assets. new Script( 'googlesitekit-components', array( 'src' => $base_url . ( Feature_Flags::enabled( 'gm3Components' ) ? 'js/googlesitekit-components-gm3.js' : 'js/googlesitekit-components-gm2.js' ), ) ), new Script( 'googlesitekit-activation', array( 'src' => $base_url . 'js/googlesitekit-activation.js', 'dependencies' => $this->get_asset_dependencies( 'dashboard' ), ) ), // Begin JSR Assets. new Script( 'googlesitekit-api', array( 'src' => $base_url . 'js/googlesitekit-api.js', 'dependencies' => array( 'googlesitekit-vendor', 'googlesitekit-apifetch-data', ), ) ), new Script( 'googlesitekit-data', array( 'src' => $base_url . 'js/googlesitekit-data.js', 'dependencies' => array( 'googlesitekit-vendor', 'googlesitekit-api', ), ) ), new Script( 'googlesitekit-datastore-user', array( 'src' => $base_url . 'js/googlesitekit-datastore-user.js', 'dependencies' => array( 'googlesitekit-data', 'googlesitekit-api', 'googlesitekit-user-data', ), ) ), new Script( 'googlesitekit-datastore-location', array( 'src' => $base_url . 'js/googlesitekit-datastore-location.js', 'dependencies' => array( 'googlesitekit-vendor', 'googlesitekit-data', ), ) ), new Script( 'googlesitekit-datastore-site', array( 'src' => $base_url . 'js/googlesitekit-datastore-site.js', 'dependencies' => array( 'googlesitekit-vendor', 'googlesitekit-api', 'googlesitekit-data', 'googlesitekit-base-data', 'googlesitekit-entity-data', ), ) ), new Script( 'googlesitekit-datastore-forms', array( 'src' => $base_url . 'js/googlesitekit-datastore-forms.js', 'dependencies' => array( 'googlesitekit-data', ), ) ), new Script( 'googlesitekit-datastore-ui', array( 'src' => $base_url . 'js/googlesitekit-datastore-ui.js', 'dependencies' => array( 'googlesitekit-data', ), ) ), new Script( 'googlesitekit-modules', array( 'src' => $base_url . 'js/googlesitekit-modules.js', 'dependencies' => array( 'googlesitekit-vendor', 'googlesitekit-api', 'googlesitekit-data', 'googlesitekit-datastore-site', 'googlesitekit-datastore-user', ), ) ), new Script( 'googlesitekit-widgets', array( 'src' => $base_url . 'js/googlesitekit-widgets.js', 'dependencies' => array( 'googlesitekit-data', 'googlesitekit-i18n', 'googlesitekit-components', ), ) ), new Script( 'googlesitekit-notifications', array( 'src' => $base_url . 'js/googlesitekit-notifications.js', 'dependencies' => array( 'googlesitekit-data', 'googlesitekit-i18n', 'googlesitekit-components', ), ) ), new Script( 'googlesitekit-user-input', array( 'src' => $base_url . 'js/googlesitekit-user-input.js', 'dependencies' => $this->get_asset_dependencies( 'dashboard' ), ) ), // End JSR Assets. new Script( 'googlesitekit-splash', array( 'src' => $base_url . 'js/googlesitekit-splash.js', 'dependencies' => $this->get_asset_dependencies( 'dashboard' ), ) ), new Script( 'googlesitekit-entity-dashboard', array( 'src' => $base_url . 'js/googlesitekit-entity-dashboard.js', 'dependencies' => $this->get_asset_dependencies( 'dashboard-sharing' ), ) ), new Script( 'googlesitekit-main-dashboard', array( 'src' => $base_url . 'js/googlesitekit-main-dashboard.js', 'dependencies' => $this->get_asset_dependencies( 'dashboard-sharing' ), ) ), new Script( 'googlesitekit-settings', array( 'src' => $base_url . 'js/googlesitekit-settings.js', 'dependencies' => $this->get_asset_dependencies( 'dashboard-sharing' ), ) ), new Script( 'googlesitekit-ad-blocking-recovery', array( 'src' => $base_url . 'js/googlesitekit-ad-blocking-recovery.js', 'dependencies' => $this->get_asset_dependencies( 'dashboard' ), ) ), new Stylesheet( 'googlesitekit-admin-css', array( 'src' => $base_url . 'css/googlesitekit-admin-css.css', 'dependencies' => array( 'googlesitekit-fonts', ), ) ), // WP Dashboard assets. new Script( 'googlesitekit-wp-dashboard', array( 'src' => $base_url . 'js/googlesitekit-wp-dashboard.js', 'dependencies' => $dependencies, 'execution' => 'defer', ) ), new Stylesheet( 'googlesitekit-wp-dashboard-css', array( 'src' => $base_url . 'css/googlesitekit-wp-dashboard-css.css', 'dependencies' => array( 'googlesitekit-fonts', ), ) ), new Stylesheet( 'googlesitekit-authorize-application-css', array( 'src' => $base_url . 'css/googlesitekit-authorize-application-css.css', 'dependencies' => array( 'googlesitekit-fonts', ), ) ), // Admin bar assets. new Script( 'googlesitekit-adminbar', array( 'src' => $base_url . 'js/googlesitekit-adminbar.js', 'dependencies' => $dependencies, 'execution' => 'defer', ) ), new Stylesheet( 'googlesitekit-adminbar-css', array( 'src' => $base_url . 'css/googlesitekit-adminbar-css.css', 'dependencies' => array( 'googlesitekit-fonts', ), ) ), new Stylesheet( 'googlesitekit-fonts', array( 'src' => $this->get_fonts_src(), 'version' => null, ) ), ); /** * Filters the list of assets that Site Kit should register. * * This filter covers both scripts and stylesheets. * * @since 1.7.0 * * @param Asset[] $assets List of Asset objects. */ $assets = apply_filters( 'googlesitekit_assets', $assets ); $this->assets = array(); foreach ( $assets as $asset ) { $this->assets[ $asset->get_handle() ] = $asset; } return $this->assets; } /** * Gets the most basic inline data needed for JS files. * * This should not include anything remotely expensive to compute. * * @since 1.2.0 * * @return array The base inline data to be output. */ private function get_inline_base_data() { global $wpdb; $site_url = $this->context->get_reference_site_url(); $inline_data = array( 'homeURL' => trailingslashit( $this->context->get_canonical_home_url() ), 'referenceSiteURL' => esc_url_raw( trailingslashit( $site_url ) ), 'adminURL' => esc_url_raw( trailingslashit( admin_url() ) ), 'assetsURL' => esc_url_raw( $this->context->url( 'dist/assets/' ) ), 'widgetsAdminURL' => esc_url_raw( $this->get_widgets_admin_url() ), 'blogPrefix' => $wpdb->get_blog_prefix(), 'ampMode' => $this->context->get_amp_mode(), 'isNetworkMode' => $this->context->is_network_mode(), 'timezone' => get_option( 'timezone_string' ), 'siteName' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), 'enabledFeatures' => Feature_Flags::get_enabled_features(), 'webStoriesActive' => defined( 'WEBSTORIES_VERSION' ), 'postTypes' => $this->get_post_types(), 'storagePrefix' => $this->get_storage_prefix(), 'referenceDate' => apply_filters( 'googlesitekit_reference_date', null ), 'productPostType' => $this->get_product_post_type(), ); /** * Filters the most basic inline data to pass to JS. * * This should not include anything remotely expensive to compute. * * @since 1.2.0 * * @param array $data Base data. */ return apply_filters( 'googlesitekit_inline_base_data', $inline_data ); } /** * Gets the available public post type slugs and their labels. * * @since 1.81.0 * * @return array Available post types array with their respective slugs and labels. */ private function get_post_types() { $post_types = array(); $all_post_types = get_post_types( array( 'public' => true ), 'objects' ); foreach ( $all_post_types as $post_type_slug => $post_type_obj ) { $post_types[] = array( 'slug' => $post_type_slug, 'label' => $post_type_obj->label, ); } return $post_types; } /** * Gets the widgets admin edit page or block editor URL depending * on the current theme. * * Themes which have FSE support do not have the old widgets admin screen. Such * themes only have the option to edit widgets directly in the block editor. * * @since 1.81.0 * * @return string The admin widgets page or block editor URL. */ private function get_widgets_admin_url() { $current_theme = wp_get_theme(); if ( method_exists( $current_theme, 'is_block_theme' ) && $current_theme->is_block_theme() ) { return admin_url( 'site-editor.php' ); } if ( count( $GLOBALS['wp_registered_sidebars'] ) > 0 ) { return admin_url( 'widgets.php' ); } return ''; } /** * Gets the inline data specific to the current entity. * * @since 1.7.0 * * @return array The site inline data to be output. */ private function get_inline_entity_data() { $current_entity = $this->context->get_reference_entity(); return array( 'currentEntityURL' => $current_entity ? $current_entity->get_url() : null, 'currentEntityType' => $current_entity ? $current_entity->get_type() : null, 'currentEntityTitle' => $current_entity ? $current_entity->get_title() : null, 'currentEntityID' => $current_entity ? $current_entity->get_id() : null, ); } /** * Gets the inline data specific to the current user * * @since 1.9.0 * * @return array The user inline data to be output. */ private function get_inline_user_data() { $current_user = wp_get_current_user(); $inline_data = array( 'user' => array( 'id' => $current_user->ID, 'email' => $current_user->user_email, 'name' => $current_user->display_name, 'picture' => get_avatar_url( $current_user->user_email ), ), ); /** * Filters the user inline data to pass to JS. * * This should not include anything remotely expensive to compute. * * @since 1.9.0 * * @param array $data User data. */ return apply_filters( 'googlesitekit_user_data', $inline_data ); } /** * Gets the inline dashboard sharing data * * @since 1.49.0 * * @return array The dashboard sharing inline data to be output. */ private function get_inline_dashboard_sharing_data() { $all_roles = wp_roles()->roles; $inline_data = array( 'roles' => array() ); foreach ( $all_roles as $role_slug => $role_details ) { $role = get_role( $role_slug ); // Filter the role that has `edit_posts` capability. if ( $role->has_cap( 'edit_posts' ) ) { $inline_data['roles'][] = array( 'id' => $role_slug, 'displayName' => translate_user_role( $role_details['name'] ), ); } } $settings = new Module_Sharing_Settings( new Options( $this->context ) ); $inline_data['settings'] = $settings->get(); /** * Filters the dashboard sharing inline data to pass to JS. * * @since 1.49.0 * * @param array $data dashboard sharing data. */ return apply_filters( 'googlesitekit_dashboard_sharing_data', $inline_data ); } /** * Gets data relevant for `trackEvent` calls. * * @since 1.78.0 * * @return array The tracking inline data to be output. */ private function get_inline_tracking_data() { $site_url = $this->context->get_reference_site_url(); $current_user = wp_get_current_user(); $inline_data = array( 'referenceSiteURL' => esc_url_raw( trailingslashit( $site_url ) ), 'userIDHash' => md5( $site_url . $current_user->ID ), ); /** * Filters the data relevant to trackEvent calls to pass to JS. * * @since 1.78.0 * * @param array $inline_data Tracking data. */ return apply_filters( 'googlesitekit_inline_tracking_data', $inline_data ); } /** * Gets the inline data needed for core plugin scripts. * * @since 1.0.0 * * @return array The inline data to be output. */ private function get_inline_data() { $site_url = $this->context->get_reference_site_url(); $input = $this->context->input(); $admin_data = array( 'siteURL' => esc_url_raw( $site_url ), 'resetSession' => $input->filter( INPUT_GET, 'googlesitekit_reset_session', FILTER_VALIDATE_BOOLEAN ), ); return array( /** * Filters the admin data to pass to JS. * * @since 1.0.0 * * @param array $data Admin data. */ 'admin' => apply_filters( 'googlesitekit_admin_data', $admin_data ), 'locale' => $this->context->get_locale( 'user' ), /** * Filters the setup data to pass to JS, needed during the dashboard page load. * * Get the setup data from the options table. * * @since 1.0.0 * * @param array $data Authentication Data. */ 'setup' => apply_filters( 'googlesitekit_setup_data', array() ), ); } /** * Gets inline modules data. * * @since 1.96.0 * * @return array The inline modules data to be output. */ private function get_inline_modules_data() { /** * Filters the inline modules data to pass to JS. * * @since 1.96.0 * * @param array $data Modules data. */ return apply_filters( 'googlesitekit_inline_modules_data', array() ); } /** * Adds support for async and defer attributes to enqueued scripts. * * @since 1.0.0 * * @param string $tag The script tag. * @param string $handle The script handle. * @return string Modified script tag. */ private function add_async_defer_attribute( $tag, $handle ) { $script_execution = wp_scripts()->get_data( $handle, 'script_execution' ); if ( ! $script_execution ) { return $tag; } if ( 'async' !== $script_execution && 'defer' !== $script_execution ) { return $tag; } // Abort adding async/defer for scripts that have this script as a dependency. foreach ( wp_scripts()->registered as $script ) { if ( in_array( $handle, $script->deps, true ) ) { return $tag; } } // Add the attribute if it hasn't already been added. if ( ! preg_match( ":\s$script_execution(=|>|\s):", $tag ) ) { $tag = preg_replace( ':(?=></script>):', " $script_execution", $tag, 1 ); } return $tag; } /** * Executes all extra callbacks before printing a list of dependencies. * * This method ensures that such callbacks that run e.g. `wp_add_inline_script()` are executed just-in-time, * only when the asset is actually loaded in the current request. * * This method works recursively, also looking at dependencies, and supports both scripts and stylesheets. * * @since 1.2.0 * * @param WP_Dependencies $dependencies WordPress dependencies class instance. * @param array $handles List of handles to run before print callbacks for. */ private function run_before_print_callbacks( WP_Dependencies $dependencies, array $handles ) { $is_amp = $this->context->is_amp(); foreach ( $handles as $handle ) { if ( isset( $this->print_callbacks_done[ $handle ] ) ) { continue; } $this->print_callbacks_done[ $handle ] = true; if ( isset( $this->assets[ $handle ] ) ) { $this->assets[ $handle ]->before_print(); // TODO: This can be removed at some point, see https://github.com/ampproject/amp-wp/pull/4001. if ( $is_amp && $this->assets[ $handle ] instanceof Script ) { $this->add_extra_script_amp_dev_mode( $handle ); } } if ( isset( $dependencies->registered[ $handle ] ) && is_array( $dependencies->registered[ $handle ]->deps ) ) { $this->run_before_print_callbacks( $dependencies, $dependencies->registered[ $handle ]->deps ); } } } /** * Adds a comment to all extra scripts so that they are considered compatible with AMP dev mode. * * {@see Assets::add_amp_dev_mode_attributes()} makes all registered scripts and stylesheets compatible, including * their potential inline additions. This method does the same for extra scripts, which are registered under the * 'data' key. * * @since 1.4.0 * * @param string $handle The handle of a registered script. */ private function add_extra_script_amp_dev_mode( $handle ) { $data = wp_scripts()->get_data( $handle, 'data' ) ?: ''; if ( ! empty( $data ) && is_string( $data ) ) { wp_scripts()->add_data( $handle, 'data', '/*googlesitekit*/ ' . $data ); } } /** * Gets the prefix for the client side cache key. * * Cache key is scoped to user session and blog_id to isolate the * cache between users and sites (in multisite). * * @since 1.92.0 * * @return string */ private function get_storage_prefix() { $current_user = wp_get_current_user(); $auth_cookie = wp_parse_auth_cookie(); $blog_id = get_current_blog_id(); $session_token = isset( $auth_cookie['token'] ) ? $auth_cookie['token'] : ''; return wp_hash( $current_user->user_login . '|' . $session_token . '|' . $blog_id ); } /** * Gets the product post type. * * @since 1.116.0 * * @return string|null The product post type name or null if not present on the website. */ protected function get_product_post_type() { /** * Filters the product post type. * * @since 1.116.0 * * @param string $product_post_type The product post type name. */ $product_post_type = apply_filters( 'googlesitekit_product_post_type', 'product' ); $product_type = get_post_type_object( $product_post_type ); if ( $product_type instanceof WP_Post_Type && $product_type->public ) { return $product_post_type; } return null; } } Assets/Script_Data.php 0000644 00000004662 14720521221 0010720 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Assets\Script_Data * * @package Google\Site_Kit\Core\Assets * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Assets; /** * Class for virtual "data-only" scripts. * * @since 1.5.0 * @access private * @ignore */ class Script_Data extends Script { /** * Constructor. * * @since 1.5.0 * * @param string $handle Unique script handle. * @param array $args { * Associative array of script arguments. * * @type callable $data_callback Required. Function to return JSON-encodable data. * @type string $global Required. Name of global variable to assign data to in Javascript. * @type array $dependencies Optional. List of script dependencies. Default empty array. * } */ public function __construct( $handle, array $args ) { // Ensure required keys are always set. $args = $args + array( 'data_callback' => null, 'global' => '', ); // SRC will always be false. $args['src'] = false; parent::__construct( $handle, $args ); // Lazy-load script data before handle is to be printed. $this->args['before_print'] = function ( $handle ) { if ( empty( $this->args['global'] ) || ! is_callable( $this->args['data_callback'] ) ) { return; } $data = call_user_func( $this->args['data_callback'], $handle ); $this->add_script_data( $data ); }; } /** * Adds the given data to the script handle's 'data' key. * * 'data' is the key used by `wp_localize_script`, which is output * in older versions of WP even if the handle has no src (such as an alias). * This is done manually instead of using `wp_localize_script` to avoid casting * top-level keys to strings as this function is primarily intended for * providing an array of translations to Javascript rather than arbitrary data. * * @see \WP_Scripts::localize * * @since 1.5.0 * * @param mixed $data Data to be assigned to the defined global. */ private function add_script_data( $data ) { $script_data = wp_scripts()->get_data( $this->handle, 'data' ) ?: ''; $js = sprintf( 'var %s = %s;', preg_replace( '[^\w\d_-]', '', $this->args['global'] ), // Ensure only a-zA-Z0-9_- are allowed. wp_json_encode( $data ) ); wp_scripts()->add_data( $this->handle, 'data', trim( "$script_data\n$js" ) ); } } Assets/Script.php 0000644 00000010673 14720521221 0007766 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Assets\Script * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Assets; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Util\BC_Functions; /** * Class representing a single script. * * @since 1.0.0 * @access private * @ignore */ class Script extends Asset { /** * Constructor. * * @since 1.0.0 * * @param string $handle Unique script handle. * @param array $args { * Associative array of script arguments. * * @type string $src Required script source URL. * @type array $dependencies List of script dependencies. Default empty array. * @type string $version Script version. Default is the version of Site Kit. * @type bool $fallback Whether to only register as a fallback. Default false. * @type callable $before_print Optional callback to execute before printing. Default none. * @type bool $in_footer Whether to load script in footer. Default true. * @type string $execution How to handle script execution, e.g. 'defer'. Default empty string. * } */ public function __construct( $handle, array $args ) { parent::__construct( $handle, $args ); $this->args = wp_parse_args( $this->args, array( 'in_footer' => true, 'execution' => '', ) ); } /** * Registers the script. * * @since 1.0.0 * @since 1.15.0 Adds $context parameter. * * @param Context $context Plugin context. */ public function register( Context $context ) { if ( $this->args['fallback'] && wp_script_is( $this->handle, 'registered' ) ) { return; } $src = $this->args['src']; $version = $this->args['version']; if ( $src ) { $entry = Manifest::get( $this->handle ); if ( is_array( $entry[0] ) ) { // If the first entry item is an array, we can assume `$entry` is an array of entries in the format filename => hash. // In this scenario we want to match the nested entry against the filename provided in `$src`. $src_filename = basename( $src ); foreach ( $entry as $entry_pair ) { if ( $this->is_matching_manifest_entry( $entry_pair, $src_filename ) ) { list( $filename, $hash ) = $entry_pair; break; } } } else { // Otherwise, `$entry` will be a single entry in the format filename => hash. list( $filename, $hash ) = $entry; } if ( $filename ) { $src = $context->url( 'dist/assets/js/' . $filename ); $version = $hash; } } wp_register_script( $this->handle, $src, (array) $this->args['dependencies'], $version, $this->args['in_footer'] ); if ( ! empty( $this->args['execution'] ) ) { wp_script_add_data( $this->handle, 'script_execution', $this->args['execution'] ); } if ( ! empty( $src ) ) { $this->set_locale_data(); } } /** * Enqueues the script. * * @since 1.0.0 */ public function enqueue() { wp_enqueue_script( $this->handle ); } /** * Checks if the provided manifest entry matches the given filename. * * @since 1.89.0 * * @param array $entry Array of filename, hash. * @param string $src_filename Filename to check. * @return bool */ private function is_matching_manifest_entry( array $entry, $src_filename ) { list ( $filename, $hash ) = $entry; if ( ! isset( $hash ) ) { // If the hash is not set, it means the hash is embedded in the entry filename. // Remove the hash then compare to the src filename. $entry_filename_without_hash = preg_replace( '/-[a-f0-9]+\.js$/', '.js', $filename ); if ( $src_filename === $entry_filename_without_hash ) { return true; } } if ( $filename === $src_filename ) { return true; } return false; } /** * Sets locale data for the script, if it has translations. * * @since 1.21.0 */ private function set_locale_data() { $json_translations = load_script_textdomain( $this->handle, 'google-site-kit' ); if ( ! $json_translations ) { return; } $output = <<<JS ( function( domain, translations ) { try { var localeData = translations.locale_data[ domain ] || translations.locale_data.messages; localeData[""].domain = domain; googlesitekit.i18n.setLocaleData( localeData, domain ); } catch { } } )( "google-site-kit", {$json_translations} ); JS; wp_add_inline_script( $this->handle, $output, 'before' ); } } Assets/Stylesheet.php 0000644 00000004204 14720521221 0010644 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Assets\Stylesheet * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Assets; use Google\Site_Kit\Context; /** * Class representing a single stylesheet. * * @since 1.0.0 * @access private * @ignore */ final class Stylesheet extends Asset { /** * Constructor. * * @since 1.0.0 * * @param string $handle Unique stylesheet handle. * @param array $args { * Associative array of stylesheet arguments. * * @type string $src Required stylesheet source URL. * @type array $dependencies List of stylesheet dependencies. Default empty array. * @type string $version Stylesheet version. Default is the version of Site Kit. * @type bool $fallback Whether to only register as a fallback. Default false. * @type callable $before_print Optional callback to execute before printing. Default none. * @type string $media Media for which the stylesheet is defined. Default 'all'. * } */ public function __construct( $handle, array $args ) { parent::__construct( $handle, $args ); $this->args = wp_parse_args( $this->args, array( 'media' => 'all', ) ); } /** * Registers the stylesheet. * * @since 1.0.0 * @since 1.15.0 Adds $context parameter. * * @param Context $context Plugin context. */ public function register( Context $context ) { if ( $this->args['fallback'] && wp_style_is( $this->handle, 'registered' ) ) { return; } $src = $this->args['src']; $version = $this->args['version']; list( $filename, $hash ) = Manifest::get( $this->handle ); if ( $filename ) { $src = $context->url( 'dist/assets/css/' . $filename ); $version = $hash; } wp_register_style( $this->handle, $src, (array) $this->args['dependencies'], $version, $this->args['media'] ); } /** * Enqueues the stylesheet. * * @since 1.0.0 */ public function enqueue() { wp_enqueue_style( $this->handle ); } } Site_Health/Tag_Placement.php 0000644 00000015313 14720521221 0012150 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Site_Health\Tag_Placement * * @package Google\Site_Kit\Core\Site_Health * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Site_Health; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Modules\Module_With_Tag; use Google\Site_Kit\Core\Modules\Tags\Module_Tag_Matchers; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\Tags\Guards\Tag_Environment_Type_Guard; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; use Google\Site_Kit\Modules\Analytics_4; /** * Class for integrating status tab information with Site Health. * * @since 1.119.0 * @access private * @ignore */ class Tag_Placement { use Method_Proxy_Trait; /** * Modules instance. * * @since 1.119.0 * @var Modules */ private $modules; /** * Tag_Environment_Type_Guard instance. * * @since 1.119.0 * @var Tag_Environment_Type_Guard */ private $environment_tag_guard; /** * Constructor. * * @since 1.119.0 * * @param Modules $modules Modules instance. */ public function __construct( Modules $modules ) { $this->modules = $modules; $this->environment_tag_guard = new Tag_Environment_Type_Guard(); } /** * Registers functionality through WordPress hooks. * * @since 1.119.0 */ public function register() { add_filter( 'site_status_tests', function ( $tests ) { global $wp_version; if ( version_compare( $wp_version, '5.6', '<' ) ) { $tests['direct']['tag_placement'] = array( 'label' => __( 'Tag Placement', 'google-site-kit' ), 'test' => $this->get_method_proxy( 'tag_placement_test' ), ); return $tests; } $tests['async']['tag_placement'] = array( 'label' => __( 'Tag Placement', 'google-site-kit' ), 'test' => rest_url( '/' . REST_Routes::REST_ROOT . '/core/site/data/site-health-tag-placement-test' ), 'has_rest' => true, 'async_direct_test' => $this->get_method_proxy( 'tag_placement_test' ), ); return $tests; } ); } /** * Checks if the modules tags are placed on the website. * * @since 1.119.0 * * @return array Site health status results. */ public function tag_placement_test() { global $wp_version; $result = array( 'label' => __( 'Tag Placement', 'google-site-kit' ), 'status' => 'good', 'badge' => array( 'label' => __( 'Site Kit', 'google-site-kit' ), 'color' => 'blue', ), 'actions' => '', 'test' => 'tag_placement', ); if ( version_compare( $wp_version, '5.6', '<' ) ) { $result['description'] = sprintf( '<p>%s</p>', __( 'This feature requires WordPress version 5.6 or higher', 'google-site-kit' ) ); return $result; } if ( ! $this->environment_tag_guard->can_activate() ) { $result['description'] = sprintf( '<p>%s</p>', __( 'Tags are not output in the current environment.', 'google-site-kit' ) ); return $result; } $active_modules = $this->get_active_modules_with_tags(); if ( empty( $active_modules ) ) { $result['description'] = sprintf( '<p>%s</p>', __( 'Tag status not available: no modules that place tags are connected.', 'google-site-kit' ) ); return $result; } $descriptions = array(); foreach ( $active_modules as $module ) { $settings = $module->get_settings()->get(); $module_name = $module->name; // If module has `canUseSnippet` setting, check if it is disabled. if ( isset( $settings['canUseSnippet'] ) && empty( $settings['useSnippet'] ) ) { $descriptions[] = sprintf( '<li><strong>%s</strong>: %s</li>', $module_name, __( 'Tag placement disabled in settings.', 'google-site-kit' ) ); } else { $content_url = $module->get_content_url(); if ( is_string( $content_url ) ) { $content_url = array( $content_url ); } foreach ( $content_url as $label => $c_url ) { $url = add_query_arg( 'timestamp', time(), $c_url ); $response = wp_remote_get( $url ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get $module_label = is_numeric( $label ) ? $module_name : $module_name . ' (' . $label . ')'; if ( is_wp_error( $response ) ) { $descriptions[] = sprintf( '<li><strong>%s</strong>: %s</li>', $module_label, __( 'There was an error while trying to get the status, please try again later.', 'google-site-kit' ) ); continue; } $response = wp_remote_retrieve_body( $response ); $tag_found = $this->check_if_tag_exists( $module, $response, $module_label ); if ( $tag_found ) { $descriptions[] = $tag_found; } } } } if ( ! empty( $descriptions ) ) { $result['description'] = '<ul>' . join( "\n", $descriptions ) . '</ul>'; } return $result; } /** * Filters active modules to only those which are instances of Module_With_Tag. * * @since 1.119.0 * * @return array Filtered active modules instances. */ protected function get_active_modules_with_tags() { $active_modules = $this->modules->get_active_modules(); $active_modules = array_filter( $active_modules, function ( $module ) { return $module instanceof Module_With_Tag; } ); return $active_modules; } /** * Checks if tag exists. * * @since 1.119.0 * * @param Module_With_Tag $module Module instance. * @param string $content Content to search for the tags. * @param string $module_label Content URL page name appended to the module name to identify multiple tags for a module. * * @return bool TRUE if tag is found, FALSE if not. */ protected function check_if_tag_exists( $module, $content, $module_label = null ) { $check_tag = $module->has_placed_tag_in_content( $content ); $module_label = $module_label ? $module_label : $module->name; switch ( $check_tag ) { case Module_Tag_Matchers::TAG_EXISTS_WITH_COMMENTS: return sprintf( '<li><strong>%s</strong>: %s</li>', $module_label, __( 'Tag detected and placed by Site Kit.', 'google-site-kit' ) ); case Module_Tag_Matchers::TAG_EXISTS: return sprintf( '<li><strong>%s</strong>: %s</li>', $module_label, __( 'Tag detected but could not verify that Site Kit placed the tag.', 'google-site-kit' ) ); case Module_Tag_Matchers::NO_TAG_FOUND: return sprintf( '<li><strong>%s</strong>: %s</li>', $module_label, __( 'No tag detected.', 'google-site-kit' ) ); default: return sprintf( '<li><strong>%s</strong>: %s</li>', $module_label, __( 'No tag detected.', 'google-site-kit' ) ); } } } Site_Health/Site_Health.php 0000644 00000004177 14720521221 0011644 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Site_Health\Site_Health * * @package Google\Site_Kit\Core\Util * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Site_Health; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Permissions\Permissions; /** * Class for integrating information with Site Health. * * @since 1.119.0 * @access private * @ignore */ class Site_Health { /** * Debug_Data instance. * * @since 1.119.0 * @var Debug_Data */ private $debug_data; /** * Tag_Placement instance. * * @since 1.119.0 * @var Tag_Placement */ private $tag_placement; /** * REST_Site_Health_Controller instance. * * @since 1.119.0 * @var REST_Site_Health_Controller */ protected $rest_controller; /** * Constructor. * * @since 1.119.0 * * @param Context $context Context instance. * @param Options $options Options instance. * @param User_Options $user_options User_Options instance. * @param Authentication $authentication Authentication instance. * @param Modules $modules Modules instance. * @param Permissions $permissions Permissions instance. */ public function __construct( Context $context, Options $options, User_Options $user_options, Authentication $authentication, Modules $modules, Permissions $permissions ) { $this->debug_data = new Debug_Data( $context, $options, $user_options, $authentication, $modules, $permissions ); $this->tag_placement = new Tag_Placement( $modules ); $this->rest_controller = new REST_Site_Health_Controller( $this->tag_placement ); } /** * Registers functionality through WordPress hooks. * * @since 1.119.0 */ public function register() { $this->debug_data->register(); $this->tag_placement->register(); $this->rest_controller->register(); } } Site_Health/REST_Site_Health_Controller.php 0000644 00000003252 14720521221 0014675 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Site_Health\REST_Site_Health_Controller * * @package Google\Site_Kit\Core\Site_Health * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Site_Health; use Google\Site_Kit\Core\REST_API\REST_Route; use WP_REST_Server; /** * Class for handling dismissed items rest routes. * * @since 1.119.0 * @access private * @ignore */ class REST_Site_Health_Controller { /** * Tag_Placement instance. * * @since 1.119.0 * @var Tag_Placement */ protected $tag_placement; /** * Constructor. * * @since 1.119.0 * * @param Tag_Placement $tag_placement Tags Placement instance. */ public function __construct( Tag_Placement $tag_placement ) { $this->tag_placement = $tag_placement; } /** * Registers functionality through WordPress hooks. * * @since 1.119.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); } /** * Gets REST route instances. * * @since 1.119.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { return array( new REST_Route( 'core/site/data/site-health-tag-placement-test', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this->tag_placement, 'tag_placement_test' ), 'permission_callback' => function () { return current_user_can( 'view_site_health_checks' ); }, ), ) ), ); } } Site_Health/Debug_Data.php 0000644 00000041726 14720521221 0011433 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Site_Health\Debug_Data * * @package Google\Site_Kit\Core\Util * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Site_Health; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Conversion_Tracking\Conversion_Tracking; use Google\Site_Kit\Core\Modules\Module; use Google\Site_Kit\Core\Modules\Module_With_Debug_Fields; use Google\Site_Kit\Core\Modules\Modules; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Util\Feature_Flags; use Google\Site_Kit\Core\Util\Scopes; /** * Class for integrating debug information with Site Health. * * @since 1.5.0 * @access private * @ignore */ class Debug_Data { /** * Context instance. * * @since 1.5.0 * @var Context */ private $context; /** * Options instance. * * @since 1.5.0 * @var Options */ private $options; /** * User_Options instance. * * @since 1.5.0 * @var User_Options */ private $user_options; /** * Authentication instance. * * @since 1.5.0 * @var Authentication */ private $authentication; /** * Modules instance. * * @since 1.5.0 * @var Modules */ private $modules; /** * Permissions instance. * * @since 1.69.0 * @var Permissions */ private $permissions; /** * Constructor. * * @since 1.5.0 * * @param Context $context Context instance. * @param Options $options Options instance. * @param User_Options $user_options User_Options instance. * @param Authentication $authentication Authentication instance. * @param Modules $modules Modules instance. * @param Permissions $permissions Permissions instance. */ public function __construct( Context $context, Options $options, User_Options $user_options, Authentication $authentication, Modules $modules, Permissions $permissions ) { $this->context = $context; $this->options = $options; $this->user_options = $user_options; $this->authentication = $authentication; $this->modules = $modules; $this->permissions = $permissions; } /** * Registers debug information with Site Health. * * @since 1.5.0 */ public function register() { add_filter( 'debug_information', function ( $info ) { $info['google-site-kit'] = array( 'label' => __( 'Site Kit by Google', 'google-site-kit' ), 'fields' => $this->get_fields(), ); return $info; } ); } /** * Redacts the given string by overwriting a portion with a mask character. * * @since 1.5.0 * * @param string $input_string Input string to redact. * @param int $mask_start Starting position of redaction and length of preserved characters. * If positive, characters are redacted from the end, preserving the first X characters. * If negative, characters are redacted from the beginning preserving the last X characters. * @return string */ public static function redact_debug_value( $input_string, $mask_start = 4 ) { if ( ! is_scalar( $input_string ) ) { return ''; } $input_string = (string) $input_string; if ( $mask_start < 0 ) { $redacted = substr( $input_string, 0, $mask_start ); $unmasked = substr( $input_string, $mask_start ); return str_repeat( '•', strlen( $redacted ) ) . $unmasked; } else { $redacted = substr( $input_string, $mask_start ); $unmasked = substr( $input_string, 0, $mask_start ); return $unmasked . str_repeat( '•', strlen( $redacted ) ); } } /** * Gets all fields. * * @since 1.5.0 * * @return array */ protected function get_fields() { $fields = array( 'version' => array( 'label' => __( 'Version', 'google-site-kit' ), 'value' => GOOGLESITEKIT_VERSION, ), 'php_version' => array( 'label' => __( 'PHP Version', 'google-site-kit' ), 'value' => PHP_VERSION, ), 'wp_version' => array( 'label' => __( 'WordPress Version', 'google-site-kit' ), 'value' => get_bloginfo( 'version' ), ), 'reference_url' => array( 'label' => __( 'Reference Site URL', 'google-site-kit' ), 'value' => $this->context->get_reference_site_url(), ), 'amp_mode' => $this->get_amp_mode_field(), 'site_status' => $this->get_site_status_field(), 'user_status' => $this->get_user_status_field(), 'verification_status' => $this->get_verification_status_field(), 'connected_user_count' => $this->get_connected_user_count_field(), 'active_modules' => $this->get_active_modules_field(), 'recoverable_modules' => $this->get_recoverable_modules_field(), 'required_scopes' => $this->get_required_scopes_field(), 'capabilities' => $this->get_capabilities_field(), 'enabled_features' => $this->get_feature_fields(), ); $fields = array_merge( $fields, $this->get_active_conversion_event_provider_fields() ); $fields = array_merge( $fields, $this->get_consent_mode_fields() ); $fields = array_merge( $fields, $this->get_module_sharing_settings_fields() ); $fields = array_filter( array_merge( $fields, $this->get_module_fields() ) ); $none = __( 'None', 'google-site-kit' ); return array_map( function ( $field ) use ( $none ) { if ( empty( $field['value'] ) ) { $field['value'] = $none; $field['debug'] = 'none'; } return $field; }, $fields ); } /** * Gets the field definition for the amp_mode field. * * @since 1.5.0 * * @return array */ private function get_amp_mode_field() { $mode = $this->context->get_amp_mode(); $mode_map = array( 'primary' => __( 'Primary', 'google-site-kit' ), 'secondary' => __( 'Secondary', 'google-site-kit' ), ); return array( 'label' => __( 'AMP Mode', 'google-site-kit' ), 'value' => isset( $mode_map[ $mode ] ) ? $mode_map[ $mode ] : __( 'No', 'google-site-kit' ), 'debug' => isset( $mode_map[ $mode ] ) ? $mode : 'no', ); } /** * Gets the field definition for the site_status field. * * @since 1.5.0 * * @return array */ private function get_site_status_field() { $is_connected = $this->authentication->credentials()->has(); $using_proxy = $this->authentication->credentials()->using_proxy(); $status_map = array( 'connected-site' => __( 'Connected through site credentials', 'google-site-kit' ), 'connected-oauth' => __( 'Connected through OAuth client credentials', 'google-site-kit' ), 'not-connected' => __( 'Not connected', 'google-site-kit' ), ); if ( $is_connected && $using_proxy ) { $status = 'connected-site'; } elseif ( $is_connected && ! $using_proxy ) { $status = 'connected-oauth'; } else { $status = 'not-connected'; } return array( 'label' => __( 'Site Status', 'google-site-kit' ), 'value' => $status_map[ $status ], 'debug' => $status, ); } /** * Gets the field definition for the user_status field. * * @since 1.5.0 * * @return array */ private function get_user_status_field() { $is_connected = $this->authentication->is_authenticated(); return array( 'label' => __( 'User Status', 'google-site-kit' ), 'value' => $is_connected ? __( 'Authenticated', 'google-site-kit' ) : __( 'Not authenticated', 'google-site-kit' ), 'debug' => $is_connected ? 'authenticated' : 'not authenticated', ); } /** * Gets the field definition for the verification_status field. * * @since 1.37.0 * * @return array */ private function get_verification_status_field() { $label = __( 'Verification Status', 'google-site-kit' ); $is_verified = $this->authentication->verification()->get(); $is_verified_by_file_token = $this->authentication->verification_file()->get(); $is_verified_by_meta_tag = $this->authentication->verification_meta()->get(); if ( ! $is_verified ) { return array( 'label' => $label, 'value' => __( 'Not verified', 'google-site-kit' ), 'debug' => 'not-verified', ); } if ( $is_verified_by_file_token ) { return array( 'label' => $label, 'value' => __( 'Verified through file', 'google-site-kit' ), 'debug' => 'verified-file', ); } if ( $is_verified_by_meta_tag ) { return array( 'label' => $label, 'value' => __( 'Verified through meta tag', 'google-site-kit' ), 'debug' => 'verified-meta', ); } return array( 'label' => $label, 'value' => __( 'Verified outside of Site Kit', 'google-site-kit' ), 'debug' => 'verified-non-site-kit', ); } /** * Gets the number of users with a Site Kit token. * * @since 1.16.0 * * @return array */ private function get_connected_user_count_field() { $users = new \WP_User_Query( array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_key' => $this->user_options->get_meta_key( OAuth_Client::OPTION_ACCESS_TOKEN ), 'fields' => 'ID', 'compare' => 'EXISTS', ) ); return array( 'label' => __( 'Connected user count', 'google-site-kit' ), 'value' => $users->get_total(), ); } /** * Gets the field definition for the active_modules field. * * @since 1.5.0 * * @return array */ private function get_active_modules_field() { $active_modules = $this->modules->get_active_modules(); return array( 'label' => __( 'Active Modules', 'google-site-kit' ), 'value' => join( /* translators: used between list items, there is a space after the comma. */ __( ', ', 'google-site-kit' ), wp_list_pluck( $active_modules, 'name' ) ), 'debug' => join( ', ', wp_list_pluck( $active_modules, 'slug' ) ), ); } /** * Gets the field definition for the recoverable_modules field. * * @since 1.78.0 * * @return array */ private function get_recoverable_modules_field() { $recoverable_modules = $this->modules->get_recoverable_modules(); return array( 'label' => __( 'Recoverable Modules', 'google-site-kit' ), 'value' => join( /* translators: used between list items, there is a space after the comma. */ __( ', ', 'google-site-kit' ), wp_list_pluck( $recoverable_modules, 'name' ) ), 'debug' => join( ', ', wp_list_pluck( $recoverable_modules, 'slug' ) ), ); } /** * Gets the field definition for the module_sharing_settings field. * * @since 1.78.0 * * @return array */ private function get_module_sharing_settings_fields() { $sharing_settings = $this->modules->get_module_sharing_settings(); $fields = array(); foreach ( $this->modules->get_shareable_modules() as $module_slug => $module ) { $module_settings = $sharing_settings->get_module( $module_slug ); $fields[ "{$module_slug}_shared_roles" ] = array_merge( array( /* translators: %s: module name */ 'label' => sprintf( __( '%s Shared Roles', 'google-site-kit' ), $module->name ), ), $this->get_module_shared_role_names( $module_settings['sharedRoles'] ) ); $fields[ "{$module_slug}_management" ] = array_merge( array( /* translators: %s: module name */ 'label' => sprintf( __( '%s Management', 'google-site-kit' ), $module->name ), ), $this->get_module_management( $module_settings['management'] ) ); } return $fields; } /** * Gets the comma separated list of shared role names for module_sharing_settings. * * @since 1.78.0 * * @param array $role_slugs List of role slugs. * * @return array $role_names Comma separated list of role names for module_sharing_settings within value and debug keys. */ private function get_module_shared_role_names( $role_slugs ) { if ( ! $role_slugs ) { return array( 'value' => __( 'None', 'google-site-kit' ), 'debug' => 'none', ); } $wp_role_names = wp_roles()->get_names(); $shared_role_names = array_filter( $wp_role_names, function ( $key ) use ( $role_slugs ) { return in_array( $key, $role_slugs, true ); }, ARRAY_FILTER_USE_KEY ); return array( 'value' => join( /* translators: used between list items, there is a space after the comma. */ __( ', ', 'google-site-kit' ), $shared_role_names ), 'debug' => join( ', ', $role_slugs ), ); } /** * Gets the user friendly and debug values for module management used in module_sharing_settings. * * @since 1.78.0 * * @param string $management The module sharing settings management value. Can be either `owner` or `all_admins`. * * @return array User friendly and debug values for module management used in module_sharing_settings within value and debug keys. */ private function get_module_management( $management ) { switch ( $management ) { case 'all_admins': return array( 'value' => __( 'Any admin signed in with Google', 'google-site-kit' ), 'debug' => 'all_admins', ); default: return array( 'value' => __( 'Owner', 'google-site-kit' ), 'debug' => 'owner', ); } } /** * Gets the field definition for the required_scopes field. * * @since 1.5.0 * * @return array */ private function get_required_scopes_field() { $required_scopes = $this->authentication->get_oauth_client()->get_required_scopes(); $granted_scopes = $this->authentication->get_oauth_client()->get_granted_scopes(); $value = array(); foreach ( $required_scopes as $scope ) { $satisfied = Scopes::is_satisfied_by( $scope, $granted_scopes ); $value[ $scope ] = $satisfied ? '✅' : '⭕'; } return array( 'label' => __( 'Required scopes', 'google-site-kit' ), 'value' => $value, ); } /** * Gets capabilities for the current user. * * @since 1.21.0 * * @return array */ private function get_capabilities_field() { $value = array(); foreach ( $this->permissions->check_all_for_current_user() as $permission => $granted ) { $value[ $permission ] = $granted ? '✅' : '⭕'; } return array( 'label' => __( 'User Capabilities', 'google-site-kit' ), 'value' => $value, ); } /** * Gets field definitions for each active module that supports debug fields. * * @since 1.5.0 * * @return array A flat array of all module debug fields. */ private function get_module_fields() { $modules_with_debug_fields = array_filter( $this->modules->get_active_modules(), function ( Module $module ) { return $module instanceof Module_With_Debug_Fields; } ); $fields_by_module = array_map( function ( Module_With_Debug_Fields $module ) { return $module->get_debug_fields(); }, array_values( $modules_with_debug_fields ) ); return array_merge( array(), ...$fields_by_module ); } /** * Gets the available features. * * @since 1.26.0 * * @return array */ private function get_feature_fields() { $value = array(); $available_features = Feature_Flags::get_available_features(); foreach ( $available_features as $available_feature ) { $enabled_feature = Feature_Flags::enabled( $available_feature ); $value[ $available_feature ] = $enabled_feature ? '✅' : '⭕'; } return array( 'label' => __( 'Features', 'google-site-kit' ), 'value' => $value, ); } /** * Gets the consent mode fields. * * @since 1.125.0 * * @return array */ private function get_consent_mode_fields() { /** * Filters the status of consent mode in Site Kit. * * @since 1.125.0 * * @param string $status The consent mode status. Default: 'disabled'. */ $consent_mode_status = apply_filters( 'googlesitekit_consent_mode_status', 'disabled' ); $consent_api_active = function_exists( 'wp_set_consent' ); return array( 'consent_mode' => array( 'label' => __( 'Consent Mode', 'google-site-kit' ), 'value' => 'enabled' === $consent_mode_status ? __( 'Enabled', 'google-site-kit' ) : __( 'Disabled', 'google-site-kit' ), 'debug' => $consent_mode_status, ), 'consent_api' => array( 'label' => __( 'WP Consent API', 'google-site-kit' ), 'value' => $consent_api_active ? __( 'Detected', 'google-site-kit' ) : __( 'Not detected', 'google-site-kit' ), 'debug' => $consent_api_active ? 'detected' : 'not-detected', ), ); } /** * Gets the conversion event names registered by the currently supported * active plugins. * * @since 1.127.0 * * @return array */ private function get_active_conversion_event_provider_fields() { $value = array(); $conversion_tracking = new Conversion_Tracking( $this->context ); $active_providers = $conversion_tracking->get_active_providers(); foreach ( $active_providers as $active_provider_slug => $active_provider ) { $value[ $active_provider_slug ] = implode( ', ', $active_provider->get_event_names() ); } return array( 'active_conversion_event_providers' => array( 'label' => __( 'Active conversion event providers', 'google-site-kit' ), 'value' => $value, ), ); } } Expirables/Expirable_Items.php 0000644 00000005320 14720521221 0012423 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Expirables\Expirable_Items * * @package Google\Site_Kit\Core\Expirables * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Expirables; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class for handling expirables items. * * @since 1.128.0 * @access private * @ignore */ class Expirable_Items extends User_Setting { /** * The user option name for this setting. * * @note This option is prefixed differently so that it will persist across disconnect/reset. */ const OPTION = 'googlesitekitpersistent_expirable_items'; /** * Adds one or more items to the list of expired items. * * @since 1.128.0 * * @param string $item Item to set expiration for. * @param int $expires_in_seconds TTL for the item. */ public function add( $item, $expires_in_seconds ) { $items = $this->get(); $items[ $item ] = time() + $expires_in_seconds; $this->set( $items ); } /** * Removes one or more items from the list of expirable items. * * @since 1.128.0 * * @param string $item Item to remove. */ public function remove( $item ) { $items = $this->get(); // If the item is not in expirable items, there's nothing to do. if ( ! array_key_exists( $item, $items ) ) { return; } unset( $items[ $item ] ); $this->set( $items ); } /** * Gets the value of the setting. * * @since 1.128.0 * * @return array Value set for the option, or default if not set. */ public function get() { $value = parent::get(); return is_array( $value ) ? $value : $this->get_default(); } /** * Gets the expected value type. * * @since 1.128.0 * * @return string The type name. */ protected function get_type() { return 'array'; } /** * Gets the default value. * * @since 1.128.0 * * @return array The default value. */ protected function get_default() { return array(); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.128.0 * * @return callable Sanitize callback. */ protected function get_sanitize_callback() { return function ( $items ) { return $this->filter_expirable_items( $items ); }; } /** * Filters expirable items. * * @since 1.128.0 * * @param array $items Expirable items list. * @return array Filtered expirable items. */ private function filter_expirable_items( $items ) { $expirables = array(); if ( is_array( $items ) ) { foreach ( $items as $item => $ttl ) { if ( is_integer( $ttl ) ) { $expirables[ $item ] = $ttl; } } } return $expirables; } } Expirables/REST_Expirable_Items_Controller.php 0000644 00000010334 14720521221 0015464 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Expirables\REST_Expirable_Items_Controller * * @package Google\Site_Kit\Core\Expirables * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Expirables; use Google\Site_Kit\Core\Expirables\Expirable_Items; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling expirable items rest routes. * * @since 1.128.0 * @access private * @ignore */ class REST_Expirable_Items_Controller { /** * Expirable_Items instance. * * @since 1.128.0 * @var Expirable_Items */ protected $expirable_items; /** * Constructor. * * @since 1.128.0 * * @param Expirable_Items $expirable_items Expirable items instance. */ public function __construct( Expirable_Items $expirable_items ) { $this->expirable_items = $expirable_items; } /** * Registers functionality through WordPress hooks. * * @since 1.128.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/expirable-items', ) ); } ); } /** * Gets REST route instances. * * @since 1.128.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_manage_expirable_item = function () { return current_user_can( Permissions::VIEW_DASHBOARD ); }; return array( new REST_Route( 'core/user/data/expirable-items', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->expirable_items->get() ); }, 'permission_callback' => $can_manage_expirable_item, ) ), new REST_Route( 'core/user/data/set-expirable-item-timers', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; if ( empty( $data ) || ! is_array( $data ) ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'items' ), array( 'status' => 400 ) ); } foreach ( $data as $datum ) { if ( empty( $datum['slug'] ) ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'slug' ), array( 'status' => 400 ) ); } $expiration = null; if ( isset( $datum['expiration'] ) && intval( $datum['expiration'] ) > 0 ) { $expiration = $datum['expiration']; } if ( ! $expiration ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is invalid: %s.', 'google-site-kit' ), 'expiration' ), array( 'status' => 400 ) ); } $this->expirable_items->add( $datum['slug'], $expiration ); } return new WP_REST_Response( $this->expirable_items->get() ); }, 'permission_callback' => $can_manage_expirable_item, 'args' => array( 'data' => array( 'type' => 'array', 'required' => true, 'items' => array( 'type' => 'object', 'additionalProperties' => false, 'properties' => array( 'slug' => array( 'type' => 'string', 'required' => true, ), 'expiration' => array( 'type' => 'integer', 'required' => true, ), ), ), ), ), ) ), ); } } Expirables/Expirables.php 0000644 00000003412 14720521221 0011445 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Expirables\Expirables * * @package Google\Site_Kit\Core\Expirables * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Expirables; use Google\Site_Kit\Core\Expirables\Expirable_Items; use Google\Site_Kit\Core\Expirables\REST_Expirable_Items_Controller; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\User_Options; /** * Class for handling expirables. * * @since 1.128.0 * @access private * @ignore */ class Expirables { /** * Expirable_Items instance. * * @since 1.128.0 * @var Expirable_Items */ protected $expirable_items; /** * REST_Expirable_Items_Controller instance. * * @since 1.128.0 * @var REST_Expirable_Items_Controller */ protected $rest_controller; /** * Constructor. * * @since 1.128.0 * * @param Context $context Plugin context. * @param User_Options $user_options Optional. User option API. Default is a new instance. */ public function __construct( Context $context, User_Options $user_options = null ) { $this->expirable_items = new Expirable_Items( $user_options ?: new User_Options( $context ) ); $this->rest_controller = new REST_Expirable_Items_Controller( $this->expirable_items ); } /** * Gets the reference to the Expirable_Items instance. * * @since 1.128.0 * * @return Expirable_Items An instance of the Expirable_Items class. */ public function get_expirable_items() { return $this->expirable_items; } /** * Registers functionality through WordPress hooks. * * @since 1.128.0 */ public function register() { $this->expirable_items->register(); $this->rest_controller->register(); } } Modules/Module_With_Assets_Trait.php 0000644 00000003367 14720521221 0013577 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Modules\Module_With_Assets_Trait * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Assets\Asset; /** * Trait for a module that includes assets. * * @since 1.7.0 * @access private * @ignore */ trait Module_With_Assets_Trait { /** * List of the module's Asset objects to register. * * @since 1.7.0 * @var array */ protected $registerable_assets; /** * Gets the assets to register for the module. * * @since 1.7.0 * * @return Asset[] List of Asset objects. */ public function get_assets() { if ( null === $this->registerable_assets ) { $this->registerable_assets = $this->setup_assets(); } return $this->registerable_assets; } /** * Enqueues all assets necessary for the module. * * This default implementation simply enqueues all assets that the module * has registered. * * @since 1.7.0 * @since 1.37.0 Added the $asset_context argument; only enqueue assets in the correct context. * * @param string $asset_context The page context to load this asset, see `Asset::CONTEXT_*` constants. */ public function enqueue_assets( $asset_context = Asset::CONTEXT_ADMIN_SITEKIT ) { $assets = $this->get_assets(); array_walk( $assets, function ( Asset $asset, $index, $asset_context ) { if ( $asset->has_context( $asset_context ) ) { $asset->enqueue(); } }, $asset_context ); } /** * Sets up the module's assets to register. * * @since 1.7.0 * * @return Asset[] List of Asset objects. */ abstract protected function setup_assets(); } Modules/Module_With_Deactivation.php 0000644 00000001103 14720521221 0013566 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Deactivation * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Interface for a module that has additional behavior when deactivated. * * @since 1.36.0 * @access private * @ignore */ interface Module_With_Deactivation { /** * Handles module deactivation. * * @since 1.36.0 */ public function on_deactivation(); } Modules/Module_With_Tag_Trait.php 0000644 00000004177 14720521221 0013050 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Modules\Module_With_Tag_Trait * * @package Google\Site_Kit\Core\Modules * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Modules\Tags\Module_Tag_Matchers; trait Module_With_Tag_Trait { /** * Checks if the module tag is found in the provided content. * * @since 1.119.0 * * @param string $content Content to search for the tags. * @return bool TRUE if tag is found, FALSE if not. */ public function has_placed_tag_in_content( $content ) { $tag_matchers = $this->get_tag_matchers()->regex_matchers(); $module_name = $this->name; // Remove 4 from translatable string name of the module if present. if ( strpos( $module_name, '4' ) !== false ) { $module_name = trim( str_replace( '4', '', $module_name ) ); } $search_string = 'Google ' . $module_name . ' snippet added by Site Kit'; // @TODO Replace the comment text around the module name with methods that should expose it. $search_translatable_string = sprintf( /* translators: %s: translatable module name */ __( 'Google %s snippet added by Site Kit', 'google-site-kit' ), $module_name ); if ( strpos( $content, $search_string ) !== false || strpos( $content, $search_translatable_string ) !== false ) { return Module_Tag_Matchers::TAG_EXISTS_WITH_COMMENTS; } else { foreach ( $tag_matchers as $pattern ) { if ( preg_match( $pattern, $content ) ) { return Module_Tag_Matchers::TAG_EXISTS; } } } return Module_Tag_Matchers::NO_TAG_FOUND; } /** * Gets the URL of the page where a tag for the module would be placed. * * For all modules like Analytics, Tag Manager, AdSense, Ads, etc. except for * Sign in with Google, tags can be detected on the home page. SiwG places its * snippet on the login page and thus, overrides this method. * * @since 1.140.0 * * @return string The home page URL string where tags are placed for most modules. */ public function get_content_url() { return home_url(); } } Modules/Module_With_Data_Available_State.php 0000644 00000002006 14720521221 0015130 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Data_Available_State * * @package Google\Site_Kit * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Interface for a module that have data available state. * * @since 1.96.0 * @access private * @ignore */ interface Module_With_Data_Available_State { /** * Checks whether the data is available for the module. * * @since 1.96.0 * * @return bool True if data is available, false otherwise. */ public function is_data_available(); /** * Sets the data available state for the module. * * @since 1.96.0 * * @return bool True on success, false otherwise. */ public function set_data_available(); /** * Resets the data available state for the module. * * @since 1.96.0 * * @return bool True on success, false otherwise. */ public function reset_data_available(); } Modules/Module_With_Settings.php 0000644 00000001026 14720521221 0012760 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Settings * * @package Google\Site_Kit\Core\Modules * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; interface Module_With_Settings { /** * Gets the module's Setting instance. * * @since 1.2.0 * * @return Module_Settings The Setting instance for the current module. */ public function get_settings(); } Modules/Module_Registry.php 0000644 00000003160 14720521221 0011776 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Module_Registry * * @package Google\Site_Kit\Core\Modules * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use InvalidArgumentException; /** * Class for managing module registration. * * @since 1.21.0 * @access private * @ignore */ class Module_Registry { /** * Registered modules. * * @since 1.21.0 * @var array */ private $registry = array(); /** * Registers a module class on the registry. * * @since 1.21.0 * * @param string $module_classname Fully-qualified module class name to register. * @throws InvalidArgumentException Thrown if an invalid module class name is provided. */ public function register( $module_classname ) { if ( ! is_string( $module_classname ) || ! $module_classname ) { throw new InvalidArgumentException( 'A module class name is required to register a module.' ); } if ( ! class_exists( $module_classname ) ) { throw new InvalidArgumentException( "No class exists for '$module_classname'" ); } if ( ! is_subclass_of( $module_classname, Module::class ) ) { throw new InvalidArgumentException( sprintf( 'All module classes must extend the base module class: %s', Module::class ) ); } $this->registry[ $module_classname ] = $module_classname; } /** * Gets all registered module class names. * * @since 1.21.0 * * @return string[] Registered module class names. */ public function get_all() { return array_keys( $this->registry ); } } Modules/Modules.php 0000644 00000061775 14720521221 0010311 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Modules * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Assets; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Util\Feature_Flags; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; use Google\Site_Kit\Modules\Ads; use Google\Site_Kit\Modules\AdSense; use Google\Site_Kit\Modules\Analytics_4; use Google\Site_Kit\Modules\PageSpeed_Insights; use Google\Site_Kit\Modules\Reader_Revenue_Manager; use Google\Site_Kit\Modules\Search_Console; use Google\Site_Kit\Modules\Sign_In_With_Google; use Google\Site_Kit\Modules\Site_Verification; use Google\Site_Kit\Modules\Tag_Manager; use Exception; /** * Class managing the different modules. * * @since 1.0.0 * @access private * @ignore */ final class Modules { use Method_Proxy_Trait; const OPTION_ACTIVE_MODULES = 'googlesitekit_active_modules'; /** * Plugin context. * * @since 1.0.0 * @var Context */ private $context; /** * Option API instance. * * @since 1.0.0 * @var Options */ private $options; /** * Module Sharing Settings instance. * * @since 1.68.0 * @var Module_Sharing_Settings */ private $sharing_settings; /** * User Option API instance. * * @since 1.0.0 * @var User_Options */ private $user_options; /** * Authentication instance. * * @since 1.0.0 * @var Authentication */ private $authentication; /** * Available modules as $slug => $module pairs. * * @since 1.0.0 * @var array */ private $modules = array(); /** * Map of module slugs and which other modules they depend on. * * @since 1.0.0 * @var array */ private $dependencies = array(); /** * Map of module slugs and which other modules depend on them. * * @since 1.0.0 * @var array */ private $dependants = array(); /** * Module_Registry instance. * * @since 1.21.0 * @var Module_Registry */ private $registry; /** * Assets API instance. * * @since 1.40.0 * @var Assets */ private $assets; /** * REST_Modules_Controller instance. * * @since 1.92.0 * @var REST_Modules_Controller */ private $rest_controller; /** * REST_Dashboard_Sharing_Controller instance. * * @since 1.109.0 * @var REST_Dashboard_Sharing_Controller */ private $dashboard_sharing_controller; /** * Core module class names. * * @since 1.21.0 * @var string[] Core module class names. */ private $core_modules = array( Site_Verification::MODULE_SLUG => Site_Verification::class, Search_Console::MODULE_SLUG => Search_Console::class, Ads::MODULE_SLUG => Ads::class, Analytics_4::MODULE_SLUG => Analytics_4::class, Tag_Manager::MODULE_SLUG => Tag_Manager::class, AdSense::MODULE_SLUG => AdSense::class, PageSpeed_Insights::MODULE_SLUG => PageSpeed_Insights::class, ); /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. * @param Options $options Optional. Option API instance. Default is a new instance. * @param User_Options $user_options Optional. User Option API instance. Default is a new instance. * @param Authentication $authentication Optional. Authentication instance. Default is a new instance. * @param Assets $assets Optional. Assets API instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null, User_Options $user_options = null, Authentication $authentication = null, Assets $assets = null ) { $this->context = $context; $this->options = $options ?: new Options( $this->context ); $this->sharing_settings = new Module_Sharing_Settings( $this->options ); $this->user_options = $user_options ?: new User_Options( $this->context ); $this->authentication = $authentication ?: new Authentication( $this->context, $this->options, $this->user_options ); $this->assets = $assets ?: new Assets( $this->context ); if ( Feature_Flags::enabled( 'rrmModule' ) ) { $this->core_modules[ Reader_Revenue_Manager::MODULE_SLUG ] = Reader_Revenue_Manager::class; } if ( Feature_Flags::enabled( 'signInWithGoogleModule' ) ) { $this->core_modules[ Sign_In_With_Google::MODULE_SLUG ] = Sign_In_With_Google::class; } $this->rest_controller = new REST_Modules_Controller( $this ); $this->dashboard_sharing_controller = new REST_Dashboard_Sharing_Controller( $this ); } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ public function register() { add_filter( 'googlesitekit_features_request_data', function ( $body ) { $active_modules = $this->get_active_modules(); $connected_modules = array_filter( $active_modules, function ( $module ) { return $module->is_connected(); } ); $body['active_modules'] = implode( ' ', array_keys( $active_modules ) ); $body['connected_modules'] = implode( ' ', array_keys( $connected_modules ) ); return $body; } ); $available_modules = $this->get_available_modules(); array_walk( $available_modules, function ( Module $module ) { if ( $module instanceof Module_With_Settings ) { $module->get_settings()->register(); } if ( $module instanceof Module_With_Persistent_Registration ) { $module->register_persistent(); } } ); $this->rest_controller->register(); $this->sharing_settings->register(); $this->dashboard_sharing_controller->register(); add_filter( 'googlesitekit_assets', function ( $assets ) use ( $available_modules ) { foreach ( $available_modules as $module ) { if ( $module instanceof Module_With_Assets ) { $assets = array_merge( $assets, $module->get_assets() ); } } return $assets; } ); $active_modules = $this->get_active_modules(); array_walk( $active_modules, function ( Module $module ) { $module->register(); } ); add_filter( 'googlesitekit_inline_base_data', $this->get_method_proxy( 'inline_js_data' ) ); add_filter( 'googlesitekit_inline_tracking_data', $this->get_method_proxy( 'inline_js_data' ) ); add_filter( 'googlesitekit_inline_modules_data', $this->get_method_proxy( 'inline_modules_data' ) ); add_filter( 'googlesitekit_dashboard_sharing_data', function ( $data ) { $data['sharedOwnershipModules'] = array_keys( $this->get_shared_ownership_modules() ); $data['defaultSharedOwnershipModuleSettings'] = $this->populate_default_shared_ownership_module_settings( array() ); return $data; } ); add_filter( 'googlesitekit_module_exists', function ( $exists, $slug ) { return $this->module_exists( $slug ); }, 10, 2 ); add_filter( 'googlesitekit_is_module_recoverable', function ( $recoverable, $slug ) { return $this->is_module_recoverable( $slug ); }, 10, 2 ); add_filter( 'googlesitekit_is_module_connected', function ( $connected, $slug ) { return $this->is_module_connected( $slug ); }, 10, 2 ); add_filter( 'option_' . Module_Sharing_Settings::OPTION, $this->get_method_proxy( 'populate_default_shared_ownership_module_settings' ) ); add_filter( 'default_option_' . Module_Sharing_Settings::OPTION, $this->get_method_proxy( 'populate_default_shared_ownership_module_settings' ), 20 ); $this->sharing_settings->on_change( function ( $old_values, $values ) { if ( is_array( $values ) && is_array( $old_values ) ) { array_walk( $values, function ( $value, $module_slug ) use ( $old_values ) { if ( ! $this->module_exists( $module_slug ) ) { return; } $module = $this->get_module( $module_slug ); if ( ! $module instanceof Module_With_Service_Entity ) { // If the option was just added, set the ownerID directly and bail. if ( empty( $old_values ) ) { $module->get_settings()->merge( array( 'ownerID' => get_current_user_id(), ) ); return; } $changed_settings = false; if ( is_array( $value ) ) { array_walk( $value, function ( $setting, $setting_key ) use ( $old_values, $module_slug, &$changed_settings ) { // Check if old value is an array and set, then compare both arrays. if ( is_array( $setting ) && isset( $old_values[ $module_slug ][ $setting_key ] ) && is_array( $old_values[ $module_slug ][ $setting_key ] ) ) { sort( $setting ); sort( $old_values[ $module_slug ][ $setting_key ] ); if ( $setting !== $old_values[ $module_slug ][ $setting_key ] ) { $changed_settings = true; } } elseif ( // If we don't have the old values or the types are different, then we have updated settings. ! isset( $old_values[ $module_slug ][ $setting_key ] ) || gettype( $setting ) !== gettype( $old_values[ $module_slug ][ $setting_key ] ) || $setting !== $old_values[ $module_slug ][ $setting_key ] ) { $changed_settings = true; } } ); } if ( $changed_settings ) { $module->get_settings()->merge( array( 'ownerID' => get_current_user_id(), ) ); } } } ); } } ); } /** * Adds / modifies data to pass to JS. * * @since 1.78.0 * * @param array $data Inline JS data. * @return array Filtered $data. */ private function inline_js_data( $data ) { $all_active_modules = $this->get_active_modules(); $non_internal_active_modules = array_filter( $all_active_modules, function ( Module $module ) { return false === $module->internal; } ); $data['activeModules'] = array_keys( $non_internal_active_modules ); return $data; } /** * Populates modules data to pass to JS. * * @since 1.96.0 * * @param array $modules_data Inline modules data. * @return array Inline modules data. */ private function inline_modules_data( $modules_data ) { $available_modules = $this->get_available_modules(); foreach ( $available_modules as $module ) { if ( $module instanceof Module_With_Data_Available_State ) { $modules_data[ 'data_available_' . $module->slug ] = $this->is_module_active( $module->slug ) && $module->is_connected() && $module->is_data_available(); } } return $modules_data; } /** * Gets the reference to the Module_Sharing_Settings instance. * * @since 1.69.0 * * @return Module_Sharing_Settings An instance of the Module_Sharing_Settings class. */ public function get_module_sharing_settings() { return $this->sharing_settings; } /** * Gets the available modules. * * @since 1.0.0 * @since 1.85.0 Filter out modules which are missing any of the dependencies specified in `depends_on`. * * @return array Available modules as $slug => $module pairs. */ public function get_available_modules() { if ( empty( $this->modules ) ) { $module_classes = $this->get_registry()->get_all(); foreach ( $module_classes as $module_class ) { $instance = new $module_class( $this->context, $this->options, $this->user_options, $this->authentication, $this->assets ); $this->modules[ $instance->slug ] = $instance; $this->dependencies[ $instance->slug ] = array(); $this->dependants[ $instance->slug ] = array(); } uasort( $this->modules, function ( Module $a, Module $b ) { if ( $a->order === $b->order ) { return 0; } return ( $a->order < $b->order ) ? -1 : 1; } ); // Remove any modules which are missing dependencies. This may occur as the result of a dependency // being removed via the googlesitekit_available_modules filter. $this->modules = array_filter( $this->modules, function ( Module $module ) { foreach ( $module->depends_on as $dependency ) { if ( ! isset( $this->modules[ $dependency ] ) ) { return false; } } return true; } ); // Set up dependency maps. foreach ( $this->modules as $module ) { foreach ( $module->depends_on as $dependency ) { if ( $module->slug === $dependency ) { continue; } $this->dependencies[ $module->slug ][] = $dependency; $this->dependants[ $dependency ][] = $module->slug; } } } return $this->modules; } /** * Gets the active modules. * * @since 1.0.0 * * @return array Active modules as $slug => $module pairs. */ public function get_active_modules() { $modules = $this->get_available_modules(); $option = $this->get_active_modules_option(); return array_filter( $modules, function ( Module $module ) use ( $option ) { // Force active OR manually active modules. return $module->force_active || in_array( $module->slug, $option, true ); } ); } /** * Gets the connected modules. * * @since 1.105.0 * * @return array Connected modules as $slug => $module pairs. */ public function get_connected_modules() { $modules = $this->get_available_modules(); return array_filter( $modules, function ( Module $module ) { return $this->is_module_connected( $module->slug ); } ); } /** * Gets the module identified by the given slug. * * @since 1.0.0 * * @param string $slug Unique module slug. * @return Module Module for the slug. * * @throws Exception Thrown when the module slug is invalid. */ public function get_module( $slug ) { $modules = $this->get_available_modules(); if ( ! isset( $modules[ $slug ] ) ) { /* translators: %s: module slug */ throw new Exception( sprintf( __( 'Invalid module slug %s.', 'google-site-kit' ), $slug ) ); } return $modules[ $slug ]; } /** * Checks if the module exists. * * @since 1.80.0 * * @param string $slug Module slug. * @return bool True if the module exists, false otherwise. */ public function module_exists( $slug ) { try { $this->get_module( $slug ); return true; } catch ( Exception $e ) { return false; } } /** * Gets the list of module slugs the module with the given slug depends on. * * @since 1.0.0 * * @param string $slug Unique module slug. * @return array List of slugs for other modules that are dependencies. * * @throws Exception Thrown when the module slug is invalid. */ public function get_module_dependencies( $slug ) { $modules = $this->get_available_modules(); if ( ! isset( $modules[ $slug ] ) ) { /* translators: %s: module slug */ throw new Exception( sprintf( __( 'Invalid module slug %s.', 'google-site-kit' ), $slug ) ); } return $this->dependencies[ $slug ]; } /** * Gets the list of module slugs that depend on the module with the given slug. * * @since 1.0.0 * * @param string $slug Unique module slug. * @return array List of slugs for other modules that are dependants. * * @throws Exception Thrown when the module slug is invalid. */ public function get_module_dependants( $slug ) { $modules = $this->get_available_modules(); if ( ! isset( $modules[ $slug ] ) ) { /* translators: %s: module slug */ throw new Exception( sprintf( __( 'Invalid module slug %s.', 'google-site-kit' ), $slug ) ); } return $this->dependants[ $slug ]; } /** * Checks whether the module identified by the given slug is active. * * @since 1.0.0 * * @param string $slug Unique module slug. * @return bool True if module is active, false otherwise. */ public function is_module_active( $slug ) { $modules = $this->get_active_modules(); return isset( $modules[ $slug ] ); } /** * Checks whether the module identified by the given slug is connected. * * @since 1.0.0 * * @param string $slug Unique module slug. * @return bool True if module is connected, false otherwise. */ public function is_module_connected( $slug ) { if ( ! $this->is_module_active( $slug ) ) { return false; } $module = $this->get_module( $slug ); return (bool) $module->is_connected(); } /** * Checks whether the module identified by the given slug is shareable. * * @since 1.105.0 * * @param string $slug Unique module slug. * @return bool True if module is shareable, false otherwise. */ public function is_module_shareable( $slug ) { $modules = $this->get_shareable_modules(); return isset( $modules[ $slug ] ); } /** * Activates the module identified by the given slug. * * @since 1.0.0 * * @param string $slug Unique module slug. * @return bool True on success, false on failure. */ public function activate_module( $slug ) { try { $module = $this->get_module( $slug ); } catch ( Exception $e ) { return false; } $option = $this->get_active_modules_option(); if ( in_array( $slug, $option, true ) ) { return true; } $option[] = $slug; $this->set_active_modules_option( $option ); if ( $module instanceof Module_With_Activation ) { $module->on_activation(); } return true; } /** * Checks whether the module identified by the given slug is enabled by the option. * * @since 1.46.0 * * @param string $slug Unique module slug. * @return bool True if module has been manually enabled, false otherwise. */ private function manually_enabled( $slug ) { $option = $this->get_active_modules_option(); return in_array( $slug, $option, true ); } /** * Deactivates the module identified by the given slug. * * @since 1.0.0 * * @param string $slug Unique module slug. * @return bool True on success, false on failure. */ public function deactivate_module( $slug ) { try { $module = $this->get_module( $slug ); } catch ( Exception $e ) { return false; } $option = $this->get_active_modules_option(); $key = array_search( $slug, $option, true ); if ( false === $key ) { return true; } // Prevent deactivation if force-active. if ( $module->force_active ) { return false; } unset( $option[ $key ] ); $this->set_active_modules_option( array_values( $option ) ); if ( $module instanceof Module_With_Deactivation ) { $module->on_deactivation(); } $this->sharing_settings->unset_module( $slug ); return true; } /** * Enqueues all module-specific assets. * * @since 1.7.0 */ public function enqueue_assets() { $available_modules = $this->get_available_modules(); array_walk( $available_modules, function ( Module $module ) { if ( $module instanceof Module_With_Assets ) { $module->enqueue_assets(); } } ); } /** * Gets the configured module registry instance. * * @since 1.21.0 * * @return Module_Registry */ protected function get_registry() { if ( ! $this->registry instanceof Module_Registry ) { $this->registry = $this->setup_registry(); } return $this->registry; } /** * Sets up a fresh module registry instance. * * @since 1.21.0 * * @return Module_Registry */ protected function setup_registry() { $registry = new Module_Registry(); /** * Filters core module slugs before registering them in the module registry. Each slug presented on this array will * be registered for inclusion. If a module is forced to be active, then it will be included even if the module slug is * removed from this filter. * * @since 1.49.0 * * @param array $available_modules An array of core module slugs available for registration in the module registry. * @return array An array of filtered module slugs. */ $available_modules = (array) apply_filters( 'googlesitekit_available_modules', array_keys( $this->core_modules ) ); $modules = array_fill_keys( $available_modules, true ); foreach ( $this->core_modules as $slug => $module ) { if ( isset( $modules[ $slug ] ) || call_user_func( array( $module, 'is_force_active' ) ) ) { $registry->register( $module ); } } return $registry; } /** * Gets the option containing the active modules. * * @since 1.0.0 * * @return array List of active module slugs. */ private function get_active_modules_option() { $option = $this->options->get( self::OPTION_ACTIVE_MODULES ); if ( ! is_array( $option ) ) { $option = $this->options->get( 'googlesitekit-active-modules' ); } // If both options are not arrays, use the default value. if ( ! is_array( $option ) ) { $option = array( PageSpeed_Insights::MODULE_SLUG ); } return $option; } /** * Sets the option containing the active modules. * * @since 1.0.0 * * @param array $option List of active module slugs. */ private function set_active_modules_option( array $option ) { $this->options->set( self::OPTION_ACTIVE_MODULES, $option ); } /** * Gets the shareable connected modules. * * @since 1.50.0 * @since 1.105.0 Updated to only return connected shareable modules. * * @return array Shareable modules as $slug => $module pairs. */ public function get_shareable_modules() { $all_connected_modules = $this->get_connected_modules(); return array_filter( $all_connected_modules, function ( Module $module ) { return $module->is_shareable(); } ); } /** * Checks the given module is recoverable. * * A module is recoverable if: * - No user is identified by its owner ID * - the owner lacks the capability to authenticate * - the owner is no longer authenticated * - no user exists for the owner ID * * @since 1.69.0 * * @param Module|string $module A module instance or its slug. * @return bool True if the module is recoverable, false otherwise. */ public function is_module_recoverable( $module ) { if ( is_string( $module ) ) { try { $module = $this->get_module( $module ); } catch ( Exception $e ) { return false; } } if ( ! $module instanceof Module_With_Owner ) { return false; } $shared_roles = $this->sharing_settings->get_shared_roles( $module->slug ); if ( empty( $shared_roles ) ) { return false; } $owner_id = $module->get_owner_id(); if ( ! $owner_id || ! user_can( $owner_id, Permissions::AUTHENTICATE ) ) { return true; } $restore_user = $this->user_options->switch_user( $owner_id ); $owner_authenticated = $this->authentication->is_authenticated(); $restore_user(); if ( ! $owner_authenticated ) { return true; } return false; } /** * Gets the recoverable modules. * * @since 1.50.0 * * @return array Recoverable modules as $slug => $module pairs. */ public function get_recoverable_modules() { return array_filter( $this->get_shareable_modules(), array( $this, 'is_module_recoverable' ) ); } /** * Gets shared ownership modules. * * @since 1.70.0 * * @return array Shared ownership modules as $slug => $module pairs. */ public function get_shared_ownership_modules() { return array_filter( $this->get_shareable_modules(), function ( $module ) { return ! ( $module instanceof Module_With_Service_Entity ); } ); } /** * Inserts default settings for shared ownership modules in passed dashboard sharing settings. * * Sharing settings for shared ownership modules such as pagespeed-insights * should always be manageable by "all admins". This function inserts * this 'default' setting for their respective module slugs even when the * dashboard_sharing settings option is not defined in the database or when settings * are not set for these modules. * * @since 1.75.0 * @since 1.85.0 Renamed from filter_shared_ownership_module_settings to populate_default_shared_ownership_module_settings. * * @param array $sharing_settings The dashboard_sharing settings option fetched from the database. * @return array Dashboard sharing settings option with default settings inserted for shared ownership modules. */ protected function populate_default_shared_ownership_module_settings( $sharing_settings ) { $shared_ownership_modules = array_keys( $this->get_shared_ownership_modules() ); foreach ( $shared_ownership_modules as $shared_ownership_module ) { if ( ! isset( $sharing_settings[ $shared_ownership_module ] ) ) { $sharing_settings[ $shared_ownership_module ] = array( 'sharedRoles' => array(), 'management' => 'all_admins', ); } } return $sharing_settings; } /** * Gets the ownerIDs of all shareable modules. * * @since 1.75.0 * * @return array Array of $module_slug => $owner_id. */ public function get_shareable_modules_owners() { $module_owners = array(); $shareable_modules = $this->get_shareable_modules(); foreach ( $shareable_modules as $module_slug => $module ) { $module_owners[ $module_slug ] = $module->get_owner_id(); } return $module_owners; } /** * Deletes sharing settings. * * @since 1.84.0 * * @return bool True on success, false on failure. */ public function delete_dashboard_sharing_settings() { return $this->options->delete( Module_Sharing_Settings::OPTION ); } } Modules/Module_With_Activation.php 0000644 00000001071 14720521221 0013261 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Activation * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Interface for a module that has additional behavior when activated. * * @since 1.36.0 * @access private * @ignore */ interface Module_With_Activation { /** * Handles module activation. * * @since 1.36.0 */ public function on_activation(); } Modules/REST_Modules_Controller.php 0000644 00000062511 14720521221 0013336 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\REST_Modules_Controller * * @package Google\Site_Kit\Core\Modules * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\Exception\Invalid_Datapoint_Exception; use Google\Site_Kit\Core\Storage\Setting_With_ViewOnly_Keys_Interface; use WP_REST_Server; use WP_REST_Request; use WP_REST_Response; use WP_Error; use Exception; /** * Class for handling modules rest routes. * * @since 1.92.0 * @access private * @ignore */ class REST_Modules_Controller { const REST_ROUTE_CHECK_ACCESS = 'core/modules/data/check-access'; /** * Modules instance. * * @since 1.92.0 * @var Modules */ protected $modules; /** * Constructor. * * @since 1.92.0 * * @param Modules $modules Modules instance. */ public function __construct( Modules $modules ) { $this->modules = $modules; } /** * Registers functionality through WordPress hooks. * * @since 1.92.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { $modules_routes = array( '/' . REST_Routes::REST_ROOT . '/core/modules/data/list', ); $settings_routes = array_map( function ( Module $module ) { if ( $module instanceof Module_With_Settings ) { return '/' . REST_Routes::REST_ROOT . "/modules/{$module->slug}/data/settings"; } return null; }, $this->modules->get_active_modules() ); return array_merge( $paths, $modules_routes, array_filter( $settings_routes ) ); } ); } /** * Gets the REST schema for a module. * * @since 1.92.0 * * @return array Module REST schema. */ private function get_module_schema() { return array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'module', 'type' => 'object', 'properties' => array( 'slug' => array( 'type' => 'string', 'description' => __( 'Identifier for the module.', 'google-site-kit' ), 'readonly' => true, ), 'name' => array( 'type' => 'string', 'description' => __( 'Name of the module.', 'google-site-kit' ), 'readonly' => true, ), 'description' => array( 'type' => 'string', 'description' => __( 'Description of the module.', 'google-site-kit' ), 'readonly' => true, ), 'homepage' => array( 'type' => 'string', 'description' => __( 'The module homepage.', 'google-site-kit' ), 'format' => 'uri', 'readonly' => true, ), 'internal' => array( 'type' => 'boolean', 'description' => __( 'Whether the module is internal, thus without any UI.', 'google-site-kit' ), 'readonly' => true, ), 'active' => array( 'type' => 'boolean', 'description' => __( 'Whether the module is active.', 'google-site-kit' ), ), 'connected' => array( 'type' => 'boolean', 'description' => __( 'Whether the module setup has been completed.', 'google-site-kit' ), 'readonly' => true, ), 'dependencies' => array( 'type' => 'array', 'description' => __( 'List of slugs of other modules that the module depends on.', 'google-site-kit' ), 'items' => array( 'type' => 'string', ), 'readonly' => true, ), 'dependants' => array( 'type' => 'array', 'description' => __( 'List of slugs of other modules depending on the module.', 'google-site-kit' ), 'items' => array( 'type' => 'string', ), 'readonly' => true, ), 'shareable' => array( 'type' => 'boolean', 'description' => __( 'Whether the module is shareable.', 'google-site-kit' ), ), 'recoverable' => array( 'type' => 'boolean', 'description' => __( 'Whether the module is recoverable.', 'google-site-kit' ), ), 'owner' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'description' => __( 'Owner ID.', 'google-site-kit' ), 'readonly' => true, ), 'login' => array( 'type' => 'string', 'description' => __( 'Owner login.', 'google-site-kit' ), 'readonly' => true, ), ), ), ), ); } /** * Gets related REST routes. * * @since 1.92.0 * * @return array List of REST_Route objects. */ private function get_rest_routes() { $can_setup = function () { return current_user_can( Permissions::SETUP ); }; $can_authenticate = function () { return current_user_can( Permissions::AUTHENTICATE ); }; $can_list_data = function () { return current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ); }; $can_view_insights = function () { // This accounts for routes that need to be called before user has completed setup flow. if ( current_user_can( Permissions::SETUP ) ) { return true; } return current_user_can( Permissions::VIEW_POSTS_INSIGHTS ); }; $can_manage_options = function () { // This accounts for routes that need to be called before user has completed setup flow. if ( current_user_can( Permissions::SETUP ) ) { return true; } return current_user_can( Permissions::MANAGE_OPTIONS ); }; $get_module_schema = function () { return $this->get_module_schema(); }; return array( new REST_Route( 'core/modules/data/list', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { $modules = array_map( array( $this, 'prepare_module_data_for_response' ), $this->modules->get_available_modules() ); return new WP_REST_Response( array_values( $modules ) ); }, 'permission_callback' => $can_list_data, ), ), array( 'schema' => $get_module_schema, ) ), new REST_Route( 'core/modules/data/activation', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; $slug = isset( $data['slug'] ) ? $data['slug'] : ''; try { $this->modules->get_module( $slug ); } catch ( Exception $e ) { return new WP_Error( 'invalid_module_slug', $e->getMessage() ); } $modules = $this->modules->get_available_modules(); if ( ! empty( $data['active'] ) ) { // Prevent activation if one of the dependencies is not active. $dependency_slugs = $this->modules->get_module_dependencies( $slug ); foreach ( $dependency_slugs as $dependency_slug ) { if ( ! $this->modules->is_module_active( $dependency_slug ) ) { /* translators: %s: module name */ return new WP_Error( 'inactive_dependencies', sprintf( __( 'Module cannot be activated because of inactive dependency %s.', 'google-site-kit' ), $modules[ $dependency_slug ]->name ), array( 'status' => 500 ) ); } } if ( ! $this->modules->activate_module( $slug ) ) { return new WP_Error( 'cannot_activate_module', __( 'An internal error occurred while trying to activate the module.', 'google-site-kit' ), array( 'status' => 500 ) ); } } else { // Automatically deactivate dependants. $dependant_slugs = $this->modules->get_module_dependants( $slug ); foreach ( $dependant_slugs as $dependant_slug ) { if ( $this->modules->is_module_active( $dependant_slug ) ) { if ( ! $this->modules->deactivate_module( $dependant_slug ) ) { /* translators: %s: module name */ return new WP_Error( 'cannot_deactivate_dependant', sprintf( __( 'Module cannot be deactivated because deactivation of dependant %s failed.', 'google-site-kit' ), $modules[ $dependant_slug ]->name ), array( 'status' => 500 ) ); } } } if ( ! $this->modules->deactivate_module( $slug ) ) { return new WP_Error( 'cannot_deactivate_module', __( 'An internal error occurred while trying to deactivate the module.', 'google-site-kit' ), array( 'status' => 500 ) ); } } return new WP_REST_Response( array( 'success' => true ) ); }, 'permission_callback' => $can_manage_options, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, ), ), ), ), array( 'schema' => $get_module_schema, ) ), new REST_Route( 'core/modules/data/info', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function ( WP_REST_Request $request ) { try { $module = $this->modules->get_module( $request['slug'] ); } catch ( Exception $e ) { return new WP_Error( 'invalid_module_slug', $e->getMessage() ); } return new WP_REST_Response( $this->prepare_module_data_for_response( $module ) ); }, 'permission_callback' => $can_authenticate, 'args' => array( 'slug' => array( 'type' => 'string', 'description' => __( 'Identifier for the module.', 'google-site-kit' ), 'sanitize_callback' => 'sanitize_key', ), ), ), ), array( 'schema' => $get_module_schema, ) ), new REST_Route( self::REST_ROUTE_CHECK_ACCESS, array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; $slug = isset( $data['slug'] ) ? $data['slug'] : ''; try { $module = $this->modules->get_module( $slug ); } catch ( Exception $e ) { return new WP_Error( 'invalid_module_slug', __( 'Invalid module slug.', 'google-site-kit' ), array( 'status' => 404 ) ); } if ( ! $module->is_connected() ) { return new WP_Error( 'module_not_connected', __( 'Module is not connected.', 'google-site-kit' ), array( 'status' => 500 ) ); } if ( ! $module instanceof Module_With_Service_Entity ) { if ( $module->is_shareable() ) { return new WP_REST_Response( array( 'access' => true, ) ); } return new WP_Error( 'invalid_module', __( 'Module access cannot be checked.', 'google-site-kit' ), array( 'status' => 500 ) ); } $access = $module->check_service_entity_access(); if ( is_wp_error( $access ) ) { return $access; } return new WP_REST_Response( array( 'access' => $access, ) ); }, 'permission_callback' => $can_setup, 'args' => array( 'slug' => array( 'type' => 'string', 'description' => __( 'Identifier for the module.', 'google-site-kit' ), 'sanitize_callback' => 'sanitize_key', ), ), ), ) ), new REST_Route( 'modules/(?P<slug>[a-z0-9\-]+)/data/notifications', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function ( WP_REST_Request $request ) { $slug = $request['slug']; $modules = $this->modules->get_available_modules(); if ( ! isset( $modules[ $slug ] ) ) { return new WP_Error( 'invalid_module_slug', __( 'Invalid module slug.', 'google-site-kit' ), array( 'status' => 404 ) ); } $notifications = array(); if ( $this->modules->is_module_active( $slug ) ) { $notifications = $modules[ $slug ]->get_data( 'notifications' ); if ( is_wp_error( $notifications ) ) { // Don't consider it an error if the module does not have a 'notifications' datapoint. if ( Invalid_Datapoint_Exception::WP_ERROR_CODE === $notifications->get_error_code() ) { $notifications = array(); } return $notifications; } } return new WP_REST_Response( $notifications ); }, 'permission_callback' => $can_authenticate, ), ), array( 'args' => array( 'slug' => array( 'type' => 'string', 'description' => __( 'Identifier for the module.', 'google-site-kit' ), 'sanitize_callback' => 'sanitize_key', ), ), ) ), new REST_Route( 'modules/(?P<slug>[a-z0-9\-]+)/data/settings', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function ( WP_REST_Request $request ) use ( $can_manage_options ) { $slug = $request['slug']; try { $module = $this->modules->get_module( $slug ); } catch ( Exception $e ) { return new WP_Error( 'invalid_module_slug', __( 'Invalid module slug.', 'google-site-kit' ), array( 'status' => 404 ) ); } if ( ! $module instanceof Module_With_Settings ) { return new WP_Error( 'invalid_module_slug', __( 'Module does not support settings.', 'google-site-kit' ), array( 'status' => 400 ) ); } $settings = $module->get_settings(); if ( $can_manage_options() ) { return new WP_REST_Response( $settings->get() ); } if ( $settings instanceof Setting_With_ViewOnly_Keys_Interface ) { $view_only_settings = array_intersect_key( $settings->get(), array_flip( $settings->get_view_only_keys() ) ); return new WP_REST_Response( $view_only_settings ); } return new WP_Error( 'no_view_only_settings' ); }, 'permission_callback' => $can_list_data, ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $slug = $request['slug']; try { $module = $this->modules->get_module( $slug ); } catch ( Exception $e ) { return new WP_Error( 'invalid_module_slug', __( 'Invalid module slug.', 'google-site-kit' ), array( 'status' => 404 ) ); } if ( ! $module instanceof Module_With_Settings ) { return new WP_Error( 'invalid_module_slug', __( 'Module does not support settings.', 'google-site-kit' ), array( 'status' => 400 ) ); } do_action( 'googlesitekit_pre_save_settings_' . $slug ); $module->get_settings()->merge( (array) $request['data'] ); do_action( 'googlesitekit_save_settings_' . $slug ); return new WP_REST_Response( $module->get_settings()->get() ); }, 'permission_callback' => $can_manage_options, 'args' => array( 'data' => array( 'type' => 'object', 'description' => __( 'Settings to set.', 'google-site-kit' ), 'validate_callback' => function ( $value ) { return is_array( $value ); }, ), ), ), ), array( 'args' => array( 'slug' => array( 'type' => 'string', 'description' => __( 'Identifier for the module.', 'google-site-kit' ), 'sanitize_callback' => 'sanitize_key', ), ), ) ), new REST_Route( 'modules/(?P<slug>[a-z0-9\-]+)/data/data-available', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { $slug = $request['slug']; try { $module = $this->modules->get_module( $slug ); } catch ( Exception $e ) { return new WP_Error( 'invalid_module_slug', __( 'Invalid module slug.', 'google-site-kit' ), array( 'status' => 404 ) ); } if ( ! $this->modules->is_module_connected( $slug ) ) { return new WP_Error( 'module_not_connected', __( 'Module is not connected.', 'google-site-kit' ), array( 'status' => 500 ) ); } if ( ! $module instanceof Module_With_Data_Available_State ) { return new WP_Error( 'invalid_module_slug', __( 'Module does not support setting data available state.', 'google-site-kit' ), array( 'status' => 500 ) ); } return new WP_REST_Response( $module->set_data_available() ); }, 'permission_callback' => $can_list_data, ), ), array( 'args' => array( 'slug' => array( 'type' => 'string', 'description' => __( 'Identifier for the module.', 'google-site-kit' ), 'sanitize_callback' => 'sanitize_key', ), ), ) ), new REST_Route( 'modules/(?P<slug>[a-z0-9\-]+)/data/(?P<datapoint>[a-z\-]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function ( WP_REST_Request $request ) { $slug = $request['slug']; try { $module = $this->modules->get_module( $slug ); } catch ( Exception $e ) { return new WP_Error( 'invalid_module_slug', __( 'Invalid module slug.', 'google-site-kit' ), array( 'status' => 404 ) ); } if ( ! $this->modules->is_module_active( $slug ) ) { return new WP_Error( 'module_not_active', __( 'Module must be active to request data.', 'google-site-kit' ), array( 'status' => 403 ) ); } $data = $module->get_data( $request['datapoint'], $request->get_params() ); if ( is_wp_error( $data ) ) { return $data; } return new WP_REST_Response( $data ); }, 'permission_callback' => $can_view_insights, ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $slug = $request['slug']; try { $module = $this->modules->get_module( $slug ); } catch ( Exception $e ) { return new WP_Error( 'invalid_module_slug', __( 'Invalid module slug.', 'google-site-kit' ), array( 'status' => 404 ) ); } if ( ! $this->modules->is_module_active( $slug ) ) { return new WP_Error( 'module_not_active', __( 'Module must be active to request data.', 'google-site-kit' ), array( 'status' => 403 ) ); } $data = isset( $request['data'] ) ? (array) $request['data'] : array(); $data = $module->set_data( $request['datapoint'], $data ); if ( is_wp_error( $data ) ) { return $data; } return new WP_REST_Response( $data ); }, 'permission_callback' => $can_manage_options, 'args' => array( 'data' => array( 'type' => 'object', 'description' => __( 'Data to set.', 'google-site-kit' ), 'validate_callback' => function ( $value ) { return is_array( $value ); }, ), ), ), ), array( 'args' => array( 'slug' => array( 'type' => 'string', 'description' => __( 'Identifier for the module.', 'google-site-kit' ), 'sanitize_callback' => 'sanitize_key', ), 'datapoint' => array( 'type' => 'string', 'description' => __( 'Module data point to address.', 'google-site-kit' ), 'sanitize_callback' => 'sanitize_key', ), ), ) ), new REST_Route( 'core/modules/data/recover-modules', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; $slugs = isset( $data['slugs'] ) ? $data['slugs'] : array(); if ( ! is_array( $slugs ) || empty( $slugs ) ) { return new WP_Error( 'invalid_param', __( 'Request parameter slugs is not valid.', 'google-site-kit' ), array( 'status' => 400 ) ); } $response = array( 'success' => array(), 'error' => array(), ); foreach ( $slugs as $slug ) { try { $module = $this->modules->get_module( $slug ); } catch ( Exception $e ) { $response = $this->handle_module_recovery_error( $slug, $response, new WP_Error( 'invalid_module_slug', $e->getMessage(), array( 'status' => 404 ) ) ); continue; } if ( ! $module->is_shareable() ) { $response = $this->handle_module_recovery_error( $slug, $response, new WP_Error( 'module_not_shareable', __( 'Module is not shareable.', 'google-site-kit' ), array( 'status' => 404 ) ) ); continue; } if ( ! $this->modules->is_module_recoverable( $module ) ) { $response = $this->handle_module_recovery_error( $slug, $response, new WP_Error( 'module_not_recoverable', __( 'Module is not recoverable.', 'google-site-kit' ), array( 'status' => 403 ) ) ); continue; } $check_access_endpoint = '/' . REST_Routes::REST_ROOT . '/' . self::REST_ROUTE_CHECK_ACCESS; $check_access_request = new WP_REST_Request( 'POST', $check_access_endpoint ); $check_access_request->set_body_params( array( 'data' => array( 'slug' => $slug, ), ) ); $check_access_response = rest_do_request( $check_access_request ); if ( is_wp_error( $check_access_response ) ) { $response = $this->handle_module_recovery_error( $slug, $response, $check_access_response ); continue; } $access = isset( $check_access_response->data['access'] ) ? $check_access_response->data['access'] : false; if ( ! $access ) { $response = $this->handle_module_recovery_error( $slug, $response, new WP_Error( 'module_not_accessible', __( 'Module is not accessible by current user.', 'google-site-kit' ), array( 'status' => 403 ) ) ); continue; } // Update the module's ownerID to the ID of the user making the request. $module_setting_updates = array( 'ownerID' => get_current_user_id(), ); $recovered_module = $module->get_settings()->merge( $module_setting_updates ); if ( $recovered_module ) { $response['success'][ $slug ] = true; } } // Cast error array to an object so JSON encoded response is // always an object, even when the error array is empty. if ( ! $response['error'] ) { $response['error'] = (object) array(); } return new WP_REST_Response( $response ); }, 'permission_callback' => $can_setup, ), ), array( 'schema' => $get_module_schema, ) ), ); } /** * Prepares module data for a REST response according to the schema. * * @since 1.92.0 * * @param Module $module Module instance. * @return array Module REST response data. */ private function prepare_module_data_for_response( Module $module ) { $module_data = array( 'slug' => $module->slug, 'name' => $module->name, 'description' => $module->description, 'homepage' => $module->homepage, 'internal' => $module->internal, 'order' => $module->order, 'forceActive' => $module->force_active, 'recoverable' => $module->is_recoverable(), 'shareable' => $this->modules->is_module_shareable( $module->slug ), 'active' => $this->modules->is_module_active( $module->slug ), 'connected' => $this->modules->is_module_connected( $module->slug ), 'dependencies' => $this->modules->get_module_dependencies( $module->slug ), 'dependants' => $this->modules->get_module_dependants( $module->slug ), 'owner' => null, ); if ( current_user_can( 'list_users' ) && $module instanceof Module_With_Owner ) { $owner_id = $module->get_owner_id(); if ( $owner_id ) { $module_data['owner'] = array( 'id' => $owner_id, 'login' => get_the_author_meta( 'user_login', $owner_id ), ); } } return $module_data; } /** * Prepares error data to pass with WP_REST_Response. * * @since 1.92.0 * * @param WP_Error $error Error (WP_Error) to prepare. * * @return array Formatted error response suitable for the client. */ protected function prepare_error_response( $error ) { return array( 'code' => $error->get_error_code(), 'message' => $error->get_error_message(), 'data' => $error->get_error_data(), ); } /** * Updates response with error encounted during module recovery. * * @since 1.92.0 * * @param string $slug The module slug. * @param array $response The existing response. * @param WP_Error $error The error encountered. * * @return array The updated response with error included. */ protected function handle_module_recovery_error( $slug, $response, $error ) { $response['success'][ $slug ] = false; $response['error'][ $slug ] = $this->prepare_error_response( $error ); return $response; } } Modules/Module_With_Scopes_Trait.php 0000644 00000001322 14720521221 0013556 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Modules\Module_With_Scopes_Trait * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Trait for a module that requires Google OAuth scopes. * * @since 1.0.0 * @access private * @ignore */ trait Module_With_Scopes_Trait { /** * Registers the hook to add required scopes. * * @since 1.0.0 */ private function register_scopes_hook() { add_filter( 'googlesitekit_auth_scopes', function ( array $scopes ) { return array_merge( $scopes, $this->get_scopes() ); } ); } } Modules/Tags/Module_Tag_Matchers.php 0000644 00000001466 14720521221 0013434 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Tags\Module_Tag_Matchers * * @package Google\Site_Kit\Core\Modules\Tags * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules\Tags; use Google\Site_Kit\Core\Tags\Tag_Matchers_Interface; /** * Base class for Tag matchers. * * @since 1.119.0 * @access private * @ignore */ abstract class Module_Tag_Matchers implements Tag_Matchers_Interface { const NO_TAG_FOUND = 0; const TAG_EXISTS = 1; const TAG_EXISTS_WITH_COMMENTS = 2; /** * Holds array of regex tag matchers. * * @since 1.119.0 * * @return array Array of regex matchers. */ abstract public function regex_matchers(); } Modules/Tags/Module_Tag.php 0000644 00000001725 14720521221 0011604 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Tags\Module_Tag * * @package Google\Site_Kit\Core\Tags * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules\Tags; use Google\Site_Kit\Core\Tags\Tag; /** * Base class for a module tag. * * @since 1.24.0 * @access private * @ignore */ abstract class Module_Tag extends Tag { /** * Module slug. * * @since 1.24.0 * @since 1.109.0 Renamed from slug to module_slug. * * @var string */ protected $module_slug; /** * Constructor. * * @since 1.24.0 * * @param string $tag_id Tag ID. * @param string $module_slug Module slug. */ public function __construct( $tag_id, $module_slug ) { parent::__construct( $tag_id ); $this->module_slug = $module_slug; } /** * Outputs the tag. * * @since 1.24.0 */ abstract protected function render(); } Modules/Tags/Module_Web_Tag.php 0000644 00000006521 14720521221 0012400 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Tags\Module_Web_Tag * * @package Google\Site_Kit\Core\Modules\Tags * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules\Tags; use Google\Site_Kit\Core\Tags\Blockable_Tag_Interface; /** * Base class for Web tag. * * @since 1.24.0 * @access private * @ignore */ abstract class Module_Web_Tag extends Module_Tag implements Blockable_Tag_Interface { /** * Checks whether or not the tag should be blocked from rendering. * * @since 1.24.0 * * @return bool TRUE if the tag should be blocked, otherwise FALSE. */ public function is_tag_blocked() { /** * Filters whether or not the tag should be blocked from rendering. * * @since 1.24.0 * * @param bool $blocked Whether or not the tag output is suppressed. Default: false. */ return (bool) apply_filters( "googlesitekit_{$this->module_slug}_tag_blocked", false ); } /** * Gets the HTML attributes for a script tag that may potentially require user consent before loading. * * @since 1.24.0 * * @return string HTML attributes to add if the tag requires consent to load, or an empty string. */ public function get_tag_blocked_on_consent_attribute() { if ( $this->is_tag_blocked_on_consent() ) { return ' type="text/plain" data-block-on-consent'; } return ''; } /** * Gets the array of HTML attributes for a script tag that may potentially require user consent before loading. * * @since 1.41.0 * * @return array containing HTML attributes to add if the tag requires consent to load, or an empty array. */ public function get_tag_blocked_on_consent_attribute_array() { if ( $this->is_tag_blocked_on_consent() ) { return array( 'type' => 'text/plain', 'data-block-on-consent' => true, ); } return array(); } /** * Check if the tag is set to be manually blocked for consent. * * @since 1.122.0 * * @return bool */ protected function is_tag_blocked_on_consent() { $deprecated_args = (array) $this->get_tag_blocked_on_consent_deprecated_args(); /** * Filters whether the tag requires user consent before loading. * * @since 1.24.0 * * @param bool $blocked Whether or not the tag requires user consent to load. Default: false. */ if ( $deprecated_args ) { return (bool) apply_filters_deprecated( "googlesitekit_{$this->module_slug}_tag_block_on_consent", array( false ), ...$deprecated_args ); } return (bool) apply_filters( "googlesitekit_{$this->module_slug}_tag_block_on_consent", false ); } /** * Get contextual arguments for apply_filters_deprecated if block_on_consent is deprecated. * * @since 1.122.0 * * @return array */ protected function get_tag_blocked_on_consent_deprecated_args() { return array(); } /** * Fires the "googlesitekit_{module_slug}_init_tag" action to let 3rd party plugins to perform required setup. * * @since 1.24.0 */ protected function do_init_tag_action() { /** * Fires when the tag has been initialized which means that the tag will be rendered in the current request. * * @since 1.24.0 * * @param string $tag_id Tag ID. */ do_action( "googlesitekit_{$this->module_slug}_init_tag", $this->tag_id ); } } Modules/Tags/Module_Tag_Guard.php 0000644 00000002150 14720521221 0012717 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Tags\Module_Tag_Guard * * @package Google\Site_Kit\Core\Tags * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules\Tags; use Google\Site_Kit\Core\Guards\Guard_Interface; use Google\Site_Kit\Core\Modules\Module_Settings; use WP_Error; /** * Base class for a module tag guard. * * @since 1.24.0 * @access private * @ignore */ abstract class Module_Tag_Guard implements Guard_Interface { /** * Module settings. * * @since 1.24.0 * @var Module_Settings */ protected $settings; /** * Constructor. * * @since 1.24.0 * * @param Module_Settings $settings Module settings. */ public function __construct( Module_Settings $settings ) { $this->settings = $settings; } /** * Determines whether the guarded tag can be activated or not. * * @since 1.24.0 * * @return bool|WP_Error TRUE if guarded tag can be activated, otherwise FALSE or an error. */ abstract public function can_activate(); } Modules/Tags/Module_AMP_Tag.php 0000644 00000006742 14720521221 0012305 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Tags\Module_AMP_Tag * * @package Google\Site_Kit\Core\Tags * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules\Tags; use Google\Site_Kit\Core\Tags\Blockable_Tag_Interface; /** * Base class for AMP tag. * * @since 1.24.0 * @access private * @ignore */ abstract class Module_AMP_Tag extends Module_Tag implements Blockable_Tag_Interface { /** * Checks whether or not the tag should be blocked from rendering. * * @since 1.24.0 * * @return bool TRUE if the tag should be blocked, otherwise FALSE. */ public function is_tag_blocked() { /** * Filters whether or not the AMP tag should be blocked from rendering. * * @since 1.24.0 * * @param bool $blocked Whether or not the tag output is suppressed. Default: false. */ return (bool) apply_filters( "googlesitekit_{$this->module_slug}_tag_amp_blocked", false ); } /** * Gets the HTML attributes for a script tag that may potentially require user consent before loading. * * @since 1.24.0 * * @return string HTML attributes to add if the tag requires consent to load, or an empty string. */ public function get_tag_blocked_on_consent_attribute() { // @see https://amp.dev/documentation/components/amp-consent/#advanced-predefined-consent-blocking-behaviors $allowed_amp_block_on_consent_values = array( '_till_responded', '_till_accepted', '_auto_reject', ); /** * Filters whether the tag requires user consent before loading. * * @since 1.24.0 * * @param bool|string $blocked Whether or not the tag requires user consent to load. Alternatively, this can also be one of * the special string values '_till_responded', '_till_accepted', or '_auto_reject'. Default: false. */ $block_on_consent = apply_filters( "googlesitekit_{$this->module_slug}_tag_amp_block_on_consent", false ); if ( in_array( $block_on_consent, $allowed_amp_block_on_consent_values, true ) ) { return sprintf( ' data-block-on-consent="%s"', $block_on_consent ); } if ( filter_var( $block_on_consent, FILTER_VALIDATE_BOOLEAN ) ) { return ' data-block-on-consent'; } return ''; } /** * Enqueues a component script for AMP Reader. * * @since 1.24.0 * * @param string $handle Script handle. * @param string $src Script source URL. * @return callable Hook function. */ protected function enqueue_amp_reader_component_script( $handle, $src ) { $component_script_hook = function ( $data ) use ( $handle, $src ) { if ( ! isset( $data['amp_component_scripts'] ) || ! is_array( $data['amp_component_scripts'] ) ) { $data['amp_component_scripts'] = array(); } if ( ! isset( $data['amp_component_scripts'][ $handle ] ) ) { $data['amp_component_scripts'][ $handle ] = $src; } return $data; }; add_filter( 'amp_post_template_data', $component_script_hook ); return $component_script_hook; } /** * Fires the "googlesitekit_{module_slug}_init_tag_amp" action to let 3rd party plugins to perform required setup. * * @since 1.24.0 */ protected function do_init_tag_action() { /** * Fires when the tag has been initialized which means that the tag will be rendered in the current request. * * @since 1.24.0 * * @param string $tag_id Tag ID. */ do_action( "googlesitekit_{$this->module_slug}_init_tag_amp", $this->tag_id ); } } Modules/Module_With_Persistent_Registration.php 0000644 00000001205 14720521221 0016051 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Persistent_Registration * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Interface for a module that requires persistent registration. * * @since 1.38.0 * @access private * @ignore */ interface Module_With_Persistent_Registration { /** * The registration method that is called even if the module is not activated. * * @since 1.38.0 */ public function register_persistent(); } Modules/Datapoint.php 0000644 00000004354 14720521221 0010612 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Datapoint * * @package Google\Site_Kit\Core\Modules * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Class representing a datapoint definition. * * @since 1.77.0 * @access private * @ignore */ class Datapoint { /** * Service identifier. * * @since 1.77.0 * @var string */ private $service = ''; /** * Required scopes. * * @since 1.77.0 * @var string[] */ private $scopes = array(); /** * Shareable status. * * @since 1.77.0 * @var bool */ private $shareable; /** * Request scopes message. * * @since 1.77.0 * @var string */ private $request_scopes_message; /** * Constructor. * * @since 1.77.0 * * @param array $definition Definition fields. */ public function __construct( array $definition ) { $this->shareable = ! empty( $definition['shareable'] ); if ( isset( $definition['service'] ) && is_string( $definition['service'] ) ) { $this->service = $definition['service']; } if ( isset( $definition['scopes'] ) && is_array( $definition['scopes'] ) ) { $this->scopes = $definition['scopes']; } if ( isset( $definition['request_scopes_message'] ) && is_string( $definition['request_scopes_message'] ) ) { $this->request_scopes_message = $definition['request_scopes_message']; } } /** * Checks if the datapoint is shareable. * * @since 1.77.0 * * @return bool */ public function is_shareable() { return $this->shareable; } /** * Gets the service identifier. * * @since 1.77.0 * * @return string */ public function get_service() { return $this->service; } /** * Gets the list of required scopes. * * @since 1.77.0 * * @return string[] */ public function get_required_scopes() { return $this->scopes; } /** * Gets the request scopes message. * * @since 1.77.0 * * @return string */ public function get_request_scopes_message() { if ( $this->request_scopes_message ) { return $this->request_scopes_message; } return __( 'You’ll need to grant Site Kit permission to do this.', 'google-site-kit' ); } } Modules/REST_Dashboard_Sharing_Controller.php 0000644 00000007063 14720521221 0015271 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\REST_Dashboard_Sharing_Controller * * @package Google\Site_Kit\Core\Modules * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\Util\Collection_Key_Cap_Filter; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling dashboard sharing rest routes. * * @since 1.75.0 * @access private * @ignore */ class REST_Dashboard_Sharing_Controller { /** * Modules instance. * * @since 1.75.0 * @var Modules */ protected $modules; /** * Constructor. * * @since 1.75.0 * * @param Modules $modules Modules instance. */ public function __construct( Modules $modules ) { $this->modules = $modules; } /** * Registers functionality through WordPress hooks. * * @since 1.75.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); } /** * Gets REST route instances. * * @since 1.75.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_manage_options = function () { return current_user_can( Permissions::MANAGE_OPTIONS ); }; return array( new REST_Route( 'core/modules/data/sharing-settings', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $original_module_owners = $this->modules->get_shareable_modules_owners(); $sharing_settings = $this->modules->get_module_sharing_settings(); $new_sharing_settings = array_reduce( array( new Collection_Key_Cap_Filter( 'sharedRoles', Permissions::MANAGE_MODULE_SHARING_OPTIONS ), new Collection_Key_Cap_Filter( 'management', Permissions::DELEGATE_MODULE_SHARING_MANAGEMENT ), ), function ( $settings, Collection_Key_Cap_Filter $filter ) { return $filter->filter_key_by_cap( $settings ); }, (array) $request['data'] ); $sharing_settings->merge( $new_sharing_settings ); $new_module_owners = $this->modules->get_shareable_modules_owners(); $changed_module_owners = array_filter( $new_module_owners, function ( $new_owner_id, $module_slug ) use ( $original_module_owners ) { return $new_owner_id !== $original_module_owners[ $module_slug ]; }, ARRAY_FILTER_USE_BOTH ); return new WP_REST_Response( array( 'settings' => $sharing_settings->get(), // Cast array to an object so JSON encoded response is always an object, // even when the array is empty. 'newOwnerIDs' => (object) $changed_module_owners, ) ); }, 'permission_callback' => $can_manage_options, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, ), ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => function () { $delete_settings = $this->modules->delete_dashboard_sharing_settings(); return new WP_REST_Response( $delete_settings ); }, 'permission_callback' => $can_manage_options, ), ) ), ); } } Modules/Module.php 0000644 00000055046 14720521221 0010120 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Module * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Closure; use Exception; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Assets; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Authentication\Exception\Insufficient_Scopes_Exception; use Google\Site_Kit\Core\Authentication\Exception\Google_Proxy_Code_Exception; use Google\Site_Kit\Core\Contracts\WP_Errorable; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Authentication\Clients\Google_Site_Kit_Client; use Google\Site_Kit\Core\REST_API\Exception\Invalid_Datapoint_Exception; use Google\Site_Kit\Core\REST_API\Data_Request; use Google\Site_Kit\Core\Storage\Transients; use Google\Site_Kit_Dependencies\Google\Service as Google_Service; use Google\Site_Kit_Dependencies\Google_Service_Exception; use Google\Site_Kit_Dependencies\Psr\Http\Message\RequestInterface; use WP_Error; /** * Base class for a module. * * @since 1.0.0 * @access private * @ignore * * @property-read string $slug Unique module identifier. * @property-read string $name Module name. * @property-read string $description Module description. * @property-read int $order Module order within module lists. * @property-read string $homepage External module homepage URL. * @property-read array $depends_on List of other module slugs the module depends on. * @property-read bool $force_active Whether the module cannot be disabled. * @property-read bool $internal Whether the module is internal, thus without any UI. */ abstract class Module { /** * Plugin context. * * @since 1.0.0 * @var Context */ protected $context; /** * Option API instance. * * @since 1.0.0 * @var Options */ protected $options; /** * User Option API instance. * * @since 1.0.0 * @var User_Options */ protected $user_options; /** * Authentication instance. * * @since 1.0.0 * @var Authentication */ protected $authentication; /** * Assets API instance. * * @since 1.40.0 * @var Assets */ protected $assets; /** * Transients instance. * * @since 1.96.0 * @var Transients */ protected $transients; /** * Module information. * * @since 1.0.0 * @var array */ private $info = array(); /** * Google API client instance. * * @since 1.0.0 * @var Google_Site_Kit_Client|null */ private $google_client; /** * Google services as $identifier => $service_instance pairs. * * @since 1.0.0 * @var array|null */ private $google_services; /** * Constructor. * * @since 1.0.0 * * @param Context $context Plugin context. * @param Options $options Optional. Option API instance. Default is a new instance. * @param User_Options $user_options Optional. User Option API instance. Default is a new instance. * @param Authentication $authentication Optional. Authentication instance. Default is a new instance. * @param Assets $assets Optional. Assets API instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null, User_Options $user_options = null, Authentication $authentication = null, Assets $assets = null ) { $this->context = $context; $this->options = $options ?: new Options( $this->context ); $this->user_options = $user_options ?: new User_Options( $this->context ); $this->authentication = $authentication ?: new Authentication( $this->context, $this->options, $this->user_options ); $this->assets = $assets ?: new Assets( $this->context ); $this->transients = new Transients( $this->context ); $this->info = $this->parse_info( (array) $this->setup_info() ); } /** * Registers functionality through WordPress hooks. * * @since 1.0.0 */ abstract public function register(); /** * Magic isset-er. * * Allows checking for existence of module information. * * @since 1.0.0 * * @param string $key Key to check.. * @return bool True if value for $key is available, false otherwise. */ final public function __isset( $key ) { return isset( $this->info[ $key ] ); } /** * Magic getter. * * Allows reading module information. * * @since 1.0.0 * * @param string $key Key to get value for. * @return mixed Value for $key, or null if not available. */ final public function __get( $key ) { if ( ! isset( $this->info[ $key ] ) ) { return null; } return $this->info[ $key ]; } /** * Checks whether the module is connected. * * A module being connected means that all steps required as part of its activation are completed. * * @since 1.0.0 * * @return bool True if module is connected, false otherwise. */ public function is_connected() { return true; } /** * Gets data for the given datapoint. * * @since 1.0.0 * * @param string $datapoint Datapoint to get data for. * @param array|Data_Request $data Optional. Contextual data to provide. Default empty array. * @return mixed Data on success, or WP_Error on failure. */ final public function get_data( $datapoint, $data = array() ) { return $this->execute_data_request( new Data_Request( 'GET', 'modules', $this->slug, $datapoint, $data ) ); } /** * Sets data for the given datapoint. * * @since 1.0.0 * * @param string $datapoint Datapoint to get data for. * @param array|Data_Request $data Data to set. * @return mixed Response data on success, or WP_Error on failure. */ final public function set_data( $datapoint, $data ) { return $this->execute_data_request( new Data_Request( 'POST', 'modules', $this->slug, $datapoint, $data ) ); } /** * Returns the list of datapoints the class provides data for. * * @since 1.0.0 * * @return array List of datapoints. */ final public function get_datapoints() { $keys = array(); $definitions = $this->get_datapoint_definitions(); foreach ( array_keys( $definitions ) as $key ) { $parts = explode( ':', $key ); $name = end( $parts ); if ( ! empty( $name ) ) { $keys[ $name ] = $name; } } return array_values( $keys ); } /** * Returns the mapping between available datapoints and their services. * * @since 1.0.0 * @since 1.9.0 No longer abstract. * @deprecated 1.12.0 * * @return array Associative array of $datapoint => $service_identifier pairs. */ protected function get_datapoint_services() { _deprecated_function( __METHOD__, '1.12.0', static::class . '::get_datapoint_definitions' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped return array(); } /** * Gets map of datapoint to definition data for each. * * @since 1.9.0 * * @return array Map of datapoints to their definitions. */ protected function get_datapoint_definitions() { return array(); } /** * Gets the datapoint definition instance. * * @since 1.77.0 * * @param string $datapoint_id Datapoint ID. * @return Datapoint Datapoint instance. * @throws Invalid_Datapoint_Exception Thrown if no datapoint exists by the given ID. */ protected function get_datapoint_definition( $datapoint_id ) { $definitions = $this->get_datapoint_definitions(); // All datapoints must be defined. if ( empty( $definitions[ $datapoint_id ] ) ) { throw new Invalid_Datapoint_Exception(); } return new Datapoint( $definitions[ $datapoint_id ] ); } /** * Creates a request object for the given datapoint. * * @since 1.0.0 * * @param Data_Request $data Data request object. * * // phpcs:ignore Squiz.Commenting.FunctionComment.InvalidNoReturn * @return RequestInterface|callable|WP_Error Request object or callable on success, or WP_Error on failure. * @throws Invalid_Datapoint_Exception Override in a sub-class. */ protected function create_data_request( Data_Request $data ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found,Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed throw new Invalid_Datapoint_Exception(); } /** * Parses a response for the given datapoint. * * @since 1.0.0 * * @param Data_Request $data Data request object. * @param mixed $response Request response. * * @return mixed Parsed response data on success, or WP_Error on failure. */ protected function parse_data_response( Data_Request $data, $response ) { return $response; } /** * Creates a request object for the given datapoint. * * @since 1.0.0 * * @param Data_Request $data Data request object. * @return mixed Data on success, or WP_Error on failure. */ final protected function execute_data_request( Data_Request $data ) { $restore_defers = array(); try { $datapoint = $this->get_datapoint_definition( "{$data->method}:{$data->datapoint}" ); $oauth_client = $this->get_oauth_client_for_datapoint( $datapoint ); $this->validate_datapoint_scopes( $datapoint, $oauth_client ); $this->validate_base_scopes( $oauth_client ); // In order for a request to leverage a client other than the default // it must return a RequestInterface (Google Services return this when defer = true). // If not deferred, the request will be executed immediately with the client // the service instance was instantiated with, which will always be the // default client, configured for the current user and provided in `get_service`. // Client defer is false by default, so we need to configure the default to defer // even if a different client will be the one to execute the request because // the default instance is what services are setup with. $restore_defers[] = $this->get_client()->withDefer( true ); if ( $this->authentication->get_oauth_client() !== $oauth_client ) { $restore_defers[] = $oauth_client->get_client()->withDefer( true ); $current_user = wp_get_current_user(); // Adds the current user to the active consumers list. $oauth_client->add_active_consumer( $current_user ); } $request = $this->create_data_request( $data ); if ( is_wp_error( $request ) ) { return $request; } elseif ( $request instanceof Closure ) { $response = $request(); } elseif ( $request instanceof RequestInterface ) { $response = $oauth_client->get_client()->execute( $request ); } else { return new WP_Error( 'invalid_datapoint_request', __( 'Invalid datapoint request.', 'google-site-kit' ), array( 'status' => 400 ) ); } } catch ( Exception $e ) { return $this->exception_to_error( $e, $data->datapoint ); } finally { foreach ( $restore_defers as $restore_defer ) { $restore_defer(); } } if ( is_wp_error( $response ) ) { return $response; } return $this->parse_data_response( $data, $response ); } /** * Validates necessary scopes for the given datapoint. * * @since 1.77.0 * * @param Datapoint $datapoint Datapoint instance. * @param OAuth_Client $oauth_client OAuth_Client instance. * @throws Insufficient_Scopes_Exception Thrown if required scopes are not satisfied. */ private function validate_datapoint_scopes( Datapoint $datapoint, OAuth_Client $oauth_client ) { $required_scopes = $datapoint->get_required_scopes(); if ( $required_scopes && ! $oauth_client->has_sufficient_scopes( $required_scopes ) ) { $message = $datapoint->get_request_scopes_message(); throw new Insufficient_Scopes_Exception( $message, 0, null, $required_scopes ); } } /** * Validates necessary scopes for the module. * * @since 1.77.0 * * @param OAuth_Client $oauth_client OAuth_Client instance. * @throws Insufficient_Scopes_Exception Thrown if required scopes are not satisfied. */ private function validate_base_scopes( OAuth_Client $oauth_client ) { if ( ! $this instanceof Module_With_Scopes ) { return; } if ( ! $oauth_client->has_sufficient_scopes( $this->get_scopes() ) ) { $message = sprintf( /* translators: %s: module name */ __( 'Site Kit can’t access the relevant data from %s because you haven’t granted all permissions requested during setup.', 'google-site-kit' ), $this->name ); throw new Insufficient_Scopes_Exception( $message, 0, null, $this->get_scopes() ); } } /** * Gets the output for a specific frontend hook. * * @since 1.0.0 * * @param string $hook Frontend hook name, e.g. 'wp_head', 'wp_footer', etc. * @return string Output the hook generates. */ final protected function get_frontend_hook_output( $hook ) { $current_user_id = get_current_user_id(); // Unset current user to make WordPress behave as if nobody was logged in. wp_set_current_user( false ); ob_start(); do_action( $hook ); $output = ob_get_clean(); // Restore the current user. wp_set_current_user( $current_user_id ); return $output; } /** * Gets the Google client the module uses. * * This method should be used to access the client. * * @since 1.0.0 * @since 1.2.0 Now returns Google_Site_Kit_Client instance. * @since 1.35.0 Updated to be public. * * @return Google_Site_Kit_Client Google client instance. * * @throws Exception Thrown when the module did not correctly set up the client. */ final public function get_client() { if ( null === $this->google_client ) { $client = $this->setup_client(); if ( ! $client instanceof Google_Site_Kit_Client ) { throw new Exception( __( 'Google client not set up correctly.', 'google-site-kit' ) ); } $this->google_client = $client; } return $this->google_client; } /** * Gets the oAuth client instance to use for the given datapoint. * * @since 1.77.0 * * @param Datapoint $datapoint Datapoint definition. * @return OAuth_Client OAuth_Client instance. */ private function get_oauth_client_for_datapoint( Datapoint $datapoint ) { if ( $this instanceof Module_With_Owner && $this->is_shareable() && $datapoint->is_shareable() && $this->get_owner_id() !== get_current_user_id() && ! $this->is_recoverable() && current_user_can( Permissions::READ_SHARED_MODULE_DATA, $this->slug ) ) { $oauth_client = $this->get_owner_oauth_client(); try { $this->validate_base_scopes( $oauth_client ); return $oauth_client; } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // Fallthrough to default oauth client if scopes are unsatisfied. } } return $this->authentication->get_oauth_client(); } /** * Gets the Google service for the given identifier. * * This method should be used to access Google services. * * @since 1.0.0 * * @param string $identifier Identifier for the service. * @return Google_Service Google service instance. * * @throws Exception Thrown when the module did not correctly set up the services or when the identifier is invalid. */ final protected function get_service( $identifier ) { if ( null === $this->google_services ) { $services = $this->setup_services( $this->get_client() ); if ( ! is_array( $services ) ) { throw new Exception( __( 'Google services not set up correctly.', 'google-site-kit' ) ); } foreach ( $services as $service ) { if ( ! $service instanceof Google_Service ) { throw new Exception( __( 'Google services not set up correctly.', 'google-site-kit' ) ); } } $this->google_services = $services; } if ( ! isset( $this->google_services[ $identifier ] ) ) { /* translators: %s: service identifier */ throw new Exception( sprintf( __( 'Google service identified by %s does not exist.', 'google-site-kit' ), $identifier ) ); } return $this->google_services[ $identifier ]; } /** * Sets up information about the module. * * @since 1.0.0 * * @return array Associative array of module info. */ abstract protected function setup_info(); /** * Sets up the Google client the module should use. * * This method is invoked once by {@see Module::get_client()} to lazily set up the client when it is requested * for the first time. * * @since 1.0.0 * @since 1.2.0 Now returns Google_Site_Kit_Client instance. * * @return Google_Site_Kit_Client Google client instance. */ protected function setup_client() { return $this->authentication->get_oauth_client()->get_client(); } /** * Sets up the Google services the module should use. * * This method is invoked once by {@see Module::get_service()} to lazily set up the services when one is requested * for the first time. * * @since 1.0.0 * @since 1.2.0 Now requires Google_Site_Kit_Client instance. * * @param Google_Site_Kit_Client $client Google client instance. * @return array Google services as $identifier => $service_instance pairs. Every $service_instance must be an * instance of Google_Service. */ protected function setup_services( Google_Site_Kit_Client $client ) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found return array(); } /** * Sets whether or not to return raw requests and returns a callback to reset to the previous value. * * @since 1.2.0 * * @param bool $defer Whether or not to return raw requests. * @return callable Callback function that resets to the original $defer value. */ protected function with_client_defer( $defer ) { return $this->get_client()->withDefer( $defer ); } /** * Parses information about the module. * * @since 1.0.0 * * @param array $info Associative array of module info. * @return array Parsed $info. */ private function parse_info( array $info ) { $info = wp_parse_args( $info, array( 'slug' => '', 'name' => '', 'description' => '', 'order' => 10, 'homepage' => '', 'feature' => '', 'depends_on' => array(), 'force_active' => static::is_force_active(), 'internal' => false, ) ); if ( empty( $info['name'] ) && ! empty( $info['slug'] ) ) { $info['name'] = $info['slug']; } $info['depends_on'] = (array) $info['depends_on']; return $info; } /** * Transforms an exception into a WP_Error object. * * @since 1.0.0 * @since 1.49.0 Uses the new `Google_Proxy::setup_url_v2` method when the `serviceSetupV2` feature flag is enabled. * @since 1.70.0 $datapoint parameter is optional. * * @param Exception $e Exception object. * @param string $datapoint Optional. Datapoint originally requested. Default is an empty string. * @return WP_Error WordPress error object. */ protected function exception_to_error( Exception $e, $datapoint = '' ) { // phpcs:ignore phpcs:enable Generic.CodeAnalysis.UnusedFunctionParameter.Found,Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed if ( $e instanceof WP_Errorable ) { return $e->to_wp_error(); } $code = $e->getCode(); $message = $e->getMessage(); $status = is_numeric( $code ) && $code ? (int) $code : 500; $reason = ''; $reconnect_url = ''; if ( $e instanceof Google_Service_Exception ) { $errors = $e->getErrors(); if ( isset( $errors[0]['message'] ) ) { $message = $errors[0]['message']; } if ( isset( $errors[0]['reason'] ) ) { $reason = $errors[0]['reason']; } } elseif ( $e instanceof Google_Proxy_Code_Exception ) { $status = 401; $code = $message; $auth_client = $this->authentication->get_oauth_client(); $message = $auth_client->get_error_message( $code ); $google_proxy = $this->authentication->get_google_proxy(); $credentials = $this->authentication->credentials()->get(); $params = array( 'code' => $e->getAccessCode(), 'site_id' => ! empty( $credentials['oauth2_client_id'] ) ? $credentials['oauth2_client_id'] : '', ); $params = $google_proxy->add_setup_step_from_error_code( $params, $code ); $reconnect_url = $google_proxy->setup_url( $params ); } if ( empty( $code ) ) { $code = 'unknown'; } $data = array( 'status' => $status, 'reason' => $reason, ); if ( ! empty( $reconnect_url ) ) { $data['reconnectURL'] = $reconnect_url; } return new WP_Error( $code, $message, $data ); } /** * Parses the string list into an array of strings. * * @since 1.15.0 * * @param string|array $items Items to parse. * @return array An array of string items. */ protected function parse_string_list( $items ) { if ( is_string( $items ) ) { $items = explode( ',', $items ); } if ( ! is_array( $items ) || empty( $items ) ) { return array(); } $items = array_map( function ( $item ) { if ( ! is_string( $item ) ) { return false; } $item = trim( $item ); if ( empty( $item ) ) { return false; } return $item; }, $items ); $items = array_filter( $items ); $items = array_values( $items ); return $items; } /** * Determines whether the current request is for shared data. * * @since 1.98.0 * * @param Data_Request $data Data request object. * @return bool TRUE if the request is for shared data, otherwise FALSE. */ protected function is_shared_data_request( Data_Request $data ) { $datapoint = $this->get_datapoint_definition( "{$data->method}:{$data->datapoint}" ); $oauth_client = $this->get_oauth_client_for_datapoint( $datapoint ); if ( $this->authentication->get_oauth_client() !== $oauth_client ) { return true; } return false; } /** * Determines whether the current module is forced to be active or not. * * @since 1.49.0 * * @return bool TRUE if the module forced to be active, otherwise FALSE. */ public static function is_force_active() { return false; } /** * Checks whether the module is shareable. * * @since 1.50.0 * * @return bool True if module is shareable, false otherwise. */ public function is_shareable() { if ( $this instanceof Module_With_Owner && $this->is_connected() ) { $datapoints = $this->get_datapoint_definitions(); foreach ( $datapoints as $details ) { if ( ! empty( $details['shareable'] ) ) { return true; } } } return false; } /** * Checks whether the module is recoverable. * * @since 1.78.0 * * @return bool */ public function is_recoverable() { /** * Filters the recoverable status of the module. * * @since 1.78.0 * @param bool $_ Whether or not the module is recoverable. Default: false * @param string $slug Module slug. */ return (bool) apply_filters( 'googlesitekit_is_module_recoverable', false, $this->slug ); } } Modules/Module_With_Scopes.php 0000644 00000001155 14720521221 0012417 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Scopes * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Interface for a module that requires Google OAuth scopes. * * @since 1.0.0 * @access private * @ignore */ interface Module_With_Scopes { /** * Gets required Google OAuth scopes for the module. * * @since 1.0.0 * * @return array List of Google OAuth scopes. */ public function get_scopes(); } Modules/Module_With_Data_Available_State_Trait.php 0000644 00000003017 14720521221 0016276 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Modules\Module_With_Data_Available_State_Trait * * @package Google\Site_Kit * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Trait for a module that has data available state. * * @since 1.96.0 * @access private * @ignore */ trait Module_With_Data_Available_State_Trait { /** * Gets data available transient name of the module. * * @since 1.96.0 * * @return string Data available transient name. */ protected function get_data_available_transient_name() { return "googlesitekit_{$this->slug}_data_available"; } /** * Checks whether the data is available for the module. * * @since 1.96.0 * * @return bool True if data is available, false otherwise. */ public function is_data_available() { return (bool) $this->transients->get( $this->get_data_available_transient_name() ); } /** * Sets the data available state for the module. * * @since 1.96.0 * * @return bool True on success, false otherwise. */ public function set_data_available() { return $this->transients->set( $this->get_data_available_transient_name(), true ); } /** * Resets the data available state for the module. * * @since 1.96.0 * * @return bool True on success, false otherwise. */ public function reset_data_available() { return $this->transients->delete( $this->get_data_available_transient_name() ); } } Modules/Module_With_Owner.php 0000644 00000001076 14720521221 0012257 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Owner * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Interface for a module that includes an owner. * * @since 1.16.0 * @access private * @ignore */ interface Module_With_Owner { /** * Gets an owner ID for the module. * * @since 1.16.0 * * @return int Owner ID. */ public function get_owner_id(); } Modules/Module_With_Assets.php 0000644 00000001660 14720521221 0012426 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Assets * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Assets\Asset; /** * Interface for a module that includes assets. * * @since 1.7.0 * @access private * @ignore */ interface Module_With_Assets { /** * Gets the assets to register for the module. * * @since 1.7.0 * * @return Asset[] List of Asset objects. */ public function get_assets(); /** * Enqueues all assets necessary for the module. * * @since 1.7.0 * @since 1.37.0 Added the $asset_context argument. * * @param string $asset_context Context for page, see `Asset::CONTEXT_*` constants. */ public function enqueue_assets( $asset_context = Asset::CONTEXT_ADMIN_SITEKIT ); } Modules/Module_With_Service_Entity.php 0000644 00000001250 14720521221 0014113 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Modules\Module_With_Service_Entity * * @package Google\Site_Kit * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use WP_Error; /** * Interface for a module that includes a service entity. * * @since 1.70.0 * @access private * @ignore */ interface Module_With_Service_Entity { /** * Checks if the current user has access to the current configured service entity. * * @since 1.70.0 * * @return boolean|WP_Error */ public function check_service_entity_access(); } Modules/Module_With_Settings_Trait.php 0000644 00000001760 14720521221 0014130 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Modules\Module_With_Settings_Trait * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Trait for a module that includes a screen. * * @since 1.2.0 * @access private * @ignore */ trait Module_With_Settings_Trait { /** * Settings instance. * * @since 1.2.0 * * @var Module_Settings */ protected $settings; /** * Sets up the module's settings instance. * * @since 1.2.0 * * @return Module_Settings */ abstract protected function setup_settings(); /** * Gets the module's Settings instance. * * @since 1.2.0 * * @return Module_Settings Module_Settings instance. */ public function get_settings() { if ( ! $this->settings instanceof Module_Settings ) { $this->settings = $this->setup_settings(); } return $this->settings; } } Modules/Module_Sharing_Settings.php 0000644 00000015222 14720521221 0013443 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Module_Sharing_Settings * * @package Google\Site_Kit\Core\Modules * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Storage\Setting; use Google\Site_Kit\Core\Util\Sanitize; /** * Class for module sharing settings. * * @since 1.50.0 * @access private * @ignore */ class Module_Sharing_Settings extends Setting { const OPTION = 'googlesitekit_dashboard_sharing'; /** * Gets the default value. * * @since 1.50.0 * * @return array */ protected function get_default() { return array(); } /** * Gets the expected value type. * * @since 1.50.0 * * @return string The type name. */ protected function get_type() { return 'object'; } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.50.0 * * @return callable Callback method that filters or type casts invalid setting values. */ protected function get_sanitize_callback() { return function ( $option ) { if ( ! is_array( $option ) ) { return array(); } $sanitized_option = array(); foreach ( $option as $module_slug => $sharing_settings ) { $sanitized_option[ $module_slug ] = array(); if ( isset( $sharing_settings['sharedRoles'] ) ) { $filtered_shared_roles = $this->filter_shared_roles( Sanitize::sanitize_string_list( $sharing_settings['sharedRoles'] ) ); $sanitized_option[ $module_slug ]['sharedRoles'] = $filtered_shared_roles; } if ( isset( $sharing_settings['management'] ) ) { $sanitized_option[ $module_slug ]['management'] = (string) $sharing_settings['management']; } } return $sanitized_option; }; } /** * Filters the shared roles to only include roles with the edit_posts capability. * * @since 1.85.0. * * @param array $shared_roles The shared roles list. * @return string[] The sanitized shared roles list. */ private function filter_shared_roles( array $shared_roles ) { $filtered_shared_roles = array_filter( $shared_roles, function ( $role_slug ) { $role = get_role( $role_slug ); if ( empty( $role ) || ! $role->has_cap( 'edit_posts' ) ) { return false; } return true; } ); return array_values( $filtered_shared_roles ); } /** * Gets the settings after filling in default values. * * @since 1.50.0 * * @return array Value set for the option, or registered default if not set. */ public function get() { $settings = parent::get(); foreach ( $settings as $module_slug => $sharing_settings ) { if ( ! isset( $sharing_settings['sharedRoles'] ) || ! is_array( $sharing_settings['sharedRoles'] ) ) { $settings[ $module_slug ]['sharedRoles'] = array(); } if ( ! isset( $sharing_settings['management'] ) || ! in_array( $sharing_settings['management'], array( 'all_admins', 'owner' ), true ) ) { $settings[ $module_slug ]['management'] = 'owner'; } if ( isset( $sharing_settings['sharedRoles'] ) && is_array( $sharing_settings['sharedRoles'] ) ) { $filtered_shared_roles = $this->filter_shared_roles( $sharing_settings['sharedRoles'] ); $settings[ $module_slug ]['sharedRoles'] = $filtered_shared_roles; } } return $settings; } /** * Merges a partial Module_Sharing_Settings option array into existing sharing settings. * * @since 1.75.0 * @since 1.77.0 Removed capability checks. * * @param array $partial Partial settings array to update existing settings with. * * @return bool True if sharing settings option was updated, false otherwise. */ public function merge( array $partial ) { $settings = $this->get(); $partial = array_filter( $partial, function ( $value ) { return ! empty( $value ); } ); return $this->set( $this->array_merge_deep( $settings, $partial ) ); } /** * Gets the sharing settings for a given module, or the defaults. * * @since 1.95.0 * * @param string $slug Module slug. * @return array { * Sharing settings for the given module. * Default sharing settings do not grant any access so they * are safe to return for a non-existent or non-shareable module. * * @type array $sharedRoles A list of WP Role IDs that the module is shared with. * @type string $management Which users can manage the sharing settings. * } */ public function get_module( $slug ) { $settings = $this->get(); if ( isset( $settings[ $slug ] ) ) { return $settings[ $slug ]; } return array( 'sharedRoles' => array(), 'management' => 'owner', ); } /** * Unsets the settings for a given module. * * @since 1.68.0 * * @param string $slug Module slug. */ public function unset_module( $slug ) { $settings = $this->get(); if ( isset( $settings[ $slug ] ) ) { unset( $settings[ $slug ] ); $this->set( $settings ); } } /** * Gets the combined roles that are set as shareable for all modules. * * @since 1.69.0 * * @return array Combined array of shared roles for all modules. */ public function get_all_shared_roles() { $shared_roles = array(); $settings = $this->get(); foreach ( $settings as $sharing_settings ) { if ( ! isset( $sharing_settings['sharedRoles'] ) ) { continue; } $shared_roles = array_merge( $shared_roles, $sharing_settings['sharedRoles'] ); } return array_unique( $shared_roles ); } /** * Gets the shared roles for the given module slug. * * @since 1.69.0 * * @param string $slug Module slug. * @return array list of shared roles for the module, otherwise an empty list. */ public function get_shared_roles( $slug ) { $settings = $this->get(); if ( isset( $settings[ $slug ]['sharedRoles'] ) ) { return $settings[ $slug ]['sharedRoles']; } return array(); } /** * Merges two arrays recursively to a specific depth. * * When array1 and array2 have the same string keys, it overwrites * the elements of array1 with elements of array2. Otherwise, it adds/appends * elements of array2. * * @since 1.77.0 * * @param array $array1 First array. * @param array $array2 Second array. * @param int $depth Optional. Depth to merge to. Default is 1. * * @return array Merged array. */ private function array_merge_deep( $array1, $array2, $depth = 1 ) { foreach ( $array2 as $key => $value ) { if ( $depth > 0 && is_array( $value ) ) { $array1_key = isset( $array1[ $key ] ) ? $array1[ $key ] : null; $array1[ $key ] = $this->array_merge_deep( $array1_key, $value, $depth - 1 ); } else { $array1[ $key ] = $value; } } return $array1; } } Modules/Module_With_Owner_Trait.php 0000644 00000003352 14720521221 0013421 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Modules\Module_With_Owner_Trait * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Authentication\Profile; use Google\Site_Kit\Core\Authentication\Token; use Google\Site_Kit\Core\Storage\User_Options; /** * Trait for a module that includes an owner ID. * * @since 1.16.0 * @access private * @ignore */ trait Module_With_Owner_Trait { /** * OAuth_Client instance. * * @since 1.77.0. * @var OAuth_Client */ protected $owner_oauth_client; /** * Gets an owner ID for the module. * * @since 1.16.0 * * @return int Owner ID. */ public function get_owner_id() { if ( ! $this instanceof Module_With_Settings ) { return 0; } $settings = $this->get_settings()->get(); if ( empty( $settings['ownerID'] ) ) { return 0; } return $settings['ownerID']; } /** * Gets the OAuth_Client instance for the module owner. * * @since 1.77.0 * * @return OAuth_Client OAuth_Client instance. */ public function get_owner_oauth_client() { if ( $this->owner_oauth_client instanceof OAuth_Client ) { return $this->owner_oauth_client; } $user_options = new User_Options( $this->context, $this->get_owner_id() ); $this->owner_oauth_client = new OAuth_Client( $this->context, $this->options, $user_options, $this->authentication->credentials(), $this->authentication->get_google_proxy(), new Profile( $user_options ), new Token( $user_options ) ); return $this->owner_oauth_client; } } Modules/Module_With_Debug_Fields.php 0000644 00000001056 14720521221 0013477 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Module_With_Debug_Fields * * @package Google\Site_Kit\Core\Modules * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; /** * Interface Module_With_Debug_Fields * * @since 1.5.0 */ interface Module_With_Debug_Fields { /** * Gets an array of debug field definitions. * * @since 1.5.0 * * @return array */ public function get_debug_fields(); } Modules/Module_With_Tag.php 0000644 00000001237 14720521221 0011677 0 ustar 00 <?php /** * Trait Google\Site_Kit\Core\Modules\Module_With_Tag * * @package Google\Site_Kit\Core\Modules * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Modules\Tags\Module_Tag_Matchers; interface Module_With_Tag { /** * Registers the tag. * * @since 1.119.0 */ public function register_tag(); /** * Returns the Module_Tag_Matchers instance. * * @since 1.119.0 * * @return Module_Tag_Matchers Module_Tag_Matchers instance. */ public function get_tag_matchers(); } Modules/Module_Settings.php 0000644 00000003721 14720521221 0011771 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Modules\Module_Settings * * @package Google\Site_Kit\Core\Modules * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Modules; use Google\Site_Kit\Core\Storage\Setting; /** * Base class for module settings. * * @since 1.2.0 * @access private * @ignore */ abstract class Module_Settings extends Setting { /** * Registers the setting in WordPress. * * @since 1.2.0 */ public function register() { parent::register(); $this->add_option_default_filters(); } /** * Merges an array of settings to update. * * Only existing keys will be updated. * * @since 1.3.0 * * @param array $partial Partial settings array to save. * * @return bool True on success, false on failure. */ public function merge( array $partial ) { $settings = $this->get(); $partial = array_filter( $partial, function ( $value ) { return null !== $value; } ); $updated = array_intersect_key( $partial, $settings ); return $this->set( array_merge( $settings, $updated ) ); } /** * Registers a filter to ensure default values are present in the saved option. * * @since 1.2.0 */ protected function add_option_default_filters() { add_filter( 'option_' . static::OPTION, function ( $option ) { if ( ! is_array( $option ) ) { return $this->get_default(); } return $option; }, 0 ); // Fill in any missing keys with defaults. // Must run later to not conflict with legacy key migration. add_filter( 'option_' . static::OPTION, function ( $option ) { if ( is_array( $option ) ) { return $option + $this->get_default(); } return $option; }, 99 ); } /** * Gets the expected value type. * * @since 1.2.0 * * @return string The type name. */ protected function get_type() { return 'object'; } } Tracking/Tracking.php 0000644 00000005233 14720521221 0010560 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tracking\Tracking * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tracking; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Admin\Screen; use Google\Site_Kit\Core\Admin\Screens; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; /** * Class managing admin tracking. * * @since 1.49.0 * @access private * @ignore */ final class Tracking { use Method_Proxy_Trait; const TRACKING_ID = 'G-EQDN3BWDSD'; /** * Screens instance. * * @since 1.49.0 * * @var Screens */ protected $screens; /** * Tracking_Consent instance. * * @since 1.49.0 * * @var Tracking_Consent */ protected $consent; /** * REST_Tracking_Consent_Controller instance. * * @since 1.49.0 * * @var REST_Tracking_Consent_Controller */ private $rest_controller; /** * Constructor. * * @since 1.49.0 * * @param Context $context Context instance. * @param User_Options $user_options Optional. User_Options instance. Default is a new instance. * @param Screens $screens Optional. Screens instance. Default is a new instance. */ public function __construct( Context $context, User_Options $user_options = null, Screens $screens = null ) { $user_options = $user_options ?: new User_Options( $context ); $this->screens = $screens ?: new Screens( $context ); $this->consent = new Tracking_Consent( $user_options ); $this->rest_controller = new REST_Tracking_Consent_Controller( $this->consent ); } /** * Registers functionality through WordPress hooks. * * @since 1.49.0 */ public function register() { $this->consent->register(); $this->rest_controller->register(); add_filter( 'googlesitekit_inline_tracking_data', $this->get_method_proxy( 'inline_js_tracking_data' ) ); } /** * Is tracking active for the current user? * * @since 1.49.0 * * @return bool True if tracking enabled, and False if not. */ public function is_active() { return (bool) $this->consent->get(); } /** * Adds / modifies tracking relevant data to pass to JS. * * @since 1.78.0 * * @param array $data Inline JS data. * @return array Filtered $data. */ private function inline_js_tracking_data( $data ) { global $hook_suffix; $data['isSiteKitScreen'] = $this->screens->get_screen( $hook_suffix ) instanceof Screen; $data['trackingEnabled'] = $this->is_active(); $data['trackingID'] = self::TRACKING_ID; return $data; } } Tracking/REST_Tracking_Consent_Controller.php 0000644 00000006362 14720521221 0015315 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tracking\REST_Tracking_Consent_Controller * * @package Google\Site_Kit * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tracking; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; use WP_REST_Server; use WP_REST_Request; use WP_REST_Response; /** * Class managing admin tracking. * * @since 1.49.0 * @access private * @ignore */ class REST_Tracking_Consent_Controller { use Method_Proxy_Trait; /** * Tracking_Consent instance. * * @since 1.49.0 * * @var Tracking_Consent */ protected $consent; /** * Constructor. * * @@since 1.49.0 * * @param Tracking_Consent $tracking_consent Tracking consent instance. */ public function __construct( Tracking_Consent $tracking_consent ) { $this->consent = $tracking_consent; } /** * Registers functionality through WordPress hooks. * * @since 1.49.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', $this->get_method_proxy( 'get_rest_routes' ) ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $routes ) { return array_merge( $routes, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/tracking', ) ); } ); } /** * Is tracking active for the current user? * * @since 1.49.0 * * @return bool True if tracking enabled, and False if not. */ public function is_active() { return (bool) $this->consent->get(); } /** * Gets tracking routes. * * @since 1.49.0 * * @param array $routes Array of routes. * @return array Modified array of routes that contains tracking related routes. */ private function get_rest_routes( $routes ) { $can_access_tracking = function () { return current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ); }; $tracking_callback = function () { return new WP_REST_Response( array( 'enabled' => $this->is_active(), ) ); }; return array_merge( $routes, array( new REST_Route( 'core/user/data/tracking', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => $tracking_callback, 'permission_callback' => $can_access_tracking, ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) use ( $tracking_callback ) { $data = $request->get_param( 'data' ); $enabled = ! empty( $data['enabled'] ); $this->consent->set( $enabled ); return $tracking_callback( $request ); }, 'permission_callback' => $can_access_tracking, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'enabled' => array( 'type' => 'boolean', 'required' => true, ), ), ), ), ), ) ), ) ); } } Tracking/Tracking_Consent.php 0000644 00000002265 14720521221 0012253 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Tracking\Tracking_Consent * * @package Google\Site_Kit\Core\Tracking * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Tracking; use Google\Site_Kit\Core\Storage\User_Setting; /** * Class managing a user's anonymous usage tracking consent. * * @since 1.49.0 * @access private * @ignore */ class Tracking_Consent extends User_Setting { /** * The user option name for this setting. * * @var string */ const OPTION = 'googlesitekit_tracking_optin'; /** * Gets the value of the setting. * * @since 1.49.0 * * @return bool Whether the current user has consented to anonymous tracking. */ public function get() { return (bool) $this->user_options->get( static::OPTION ); } /** * Gets the expected value type. * * @since 1.49.0 * * @return string The type name. */ protected function get_type() { return 'boolean'; } /** * Gets the default value. * * @since 1.49.0 * * @return bool The default value. */ protected function get_default() { return false; } } Key_Metrics/Key_Metrics_Setup_New.php 0000644 00000003701 14720521221 0013677 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Key_Metrics\Key_Metrics_Setup_New * * @package Google\Site_Kit\Core\Key_Metrics * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Key_Metrics; use Google\Site_Kit\Core\Storage\Transients; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; /** * Class for handling Key_Metrics_Setup_New state. * * @since 1.115.0 * @access private * @ignore */ class Key_Metrics_Setup_New { use Method_Proxy_Trait; const TRANSIENT = 'googlesitekit_key_metrics_setup_new'; /** * Transients instance. * * @var Transients */ private $transients; /** * Constructor. * * @since 1.115.0 * * @param Transients $transients Transients instance. */ public function __construct( Transients $transients ) { $this->transients = $transients; } /** * Registers functionality through WordPress hooks. * * @since 1.115.0 */ public function register() { add_action( 'add_option_' . Key_Metrics_Setup_Completed_By::OPTION, $this->get_method_proxy( 'mark_setup_completed' ), 10, 2 ); add_filter( 'googlesitekit_inline_base_data', $this->get_method_proxy( 'inline_js_base_data' ) ); } /** * Marks Key Metrics setup as just completed for a limited period of time. * * @since 1.115.0 * * @param string $option Key_Metrics_Setup_Completed_By option name. * @param mixed $value Option value added. */ protected function mark_setup_completed( $option, $value ) { if ( $value ) { $this->transients->set( self::TRANSIENT, true, 2 * WEEK_IN_SECONDS ); } } /** * Extends base data with setup new state. * * @since 1.115.0 * * @param array $data Inline base data. * @return array */ protected function inline_js_base_data( $data ) { $data['keyMetricsSetupNew'] = (bool) $this->transients->get( self::TRANSIENT ); return $data; } } Key_Metrics/Key_Metrics_Setup_Completed_By.php 0000644 00000001502 14720521221 0015511 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Key_Metrics\Key_Metrics_Setup_Completed_By * * @package Google\Site_Kit\Core\Key_Metrics * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Key_Metrics; use Google\Site_Kit\Core\Storage\Setting; /** * Class for handling the setup completion state of Key Metrics. * * @since 1.113.0 * @access private * @ignore */ class Key_Metrics_Setup_Completed_By extends Setting { /** * The option_name for this setting. */ const OPTION = 'googlesitekit_key_metrics_setup_completed_by'; /** * Gets the expected value type. * * @since 1.113.0 * * @return string The type name. */ protected function get_type() { return 'integer'; } } Key_Metrics/REST_Key_Metrics_Controller.php 0000644 00000012571 14720521221 0014753 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Dismissals\REST_Key_Metrics_Controller * * @package Google\Site_Kit\Core\Key_Metrics * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Key_Metrics; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use Google\Site_Kit\Core\Util\Feature_Flags; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for handling rest routes for Key Metrics settings. * * @since 1.93.0 * @access private * @ignore */ class REST_Key_Metrics_Controller { /** * Key_Metrics_Settings instance. * * @since 1.93.0 * @var Key_Metrics_Settings */ protected $settings; /** * Key_Metrics_Setup_Completed_By instance. * * @since 1.113.0 * @var Key_Metrics_Setup_Completed_By */ protected $key_metrics_setup_completed_by; /** * Constructor. * * @since 1.93.0 * * @param Key_Metrics_Settings $settings Key Metrics settings. * @param Key_Metrics_Setup_Completed_By $key_metrics_setup_completed_by Site-wide option to check if key metrics set up is complete. */ public function __construct( Key_Metrics_Settings $settings, Key_Metrics_Setup_Completed_By $key_metrics_setup_completed_by ) { $this->settings = $settings; $this->key_metrics_setup_completed_by = $key_metrics_setup_completed_by; } /** * Registers functionality through WordPress hooks. * * @since 1.93.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/user/data/key-metrics', ) ); } ); } /** * Gets REST route instances. * * @since 1.93.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $has_capabilities = function () { return current_user_can( Permissions::VIEW_SPLASH ) || current_user_can( Permissions::VIEW_DASHBOARD ); }; return array( new REST_Route( 'core/user/data/key-metrics', array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->settings->get() ); }, 'permission_callback' => $has_capabilities, ) ), new REST_Route( 'core/user/data/key-metrics', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => function ( WP_REST_Request $request ) { // Data is already validated because we've defined the detailed schema. // If the incoming data param doesn't match the schema, then WordPress // will automatically return the rest_invalid_param error and we will // never get to here. $data = $request->get_param( 'data' ); $settings = $data['settings']; if ( isset( $settings['widgetSlugs'] ) ) { $num_widgets = count( $settings['widgetSlugs'] ); if ( ! $num_widgets ) { return new WP_Error( 'rest_invalid_param', __( 'Selected metrics cannot be empty.', 'google-site-kit' ), array( 'status' => 400 ) ); } // Additional check is needed to ensure that we have no more than 4 widget // slugs provided. This is required until we drop support for WP versions below 5.5.0, after // which we can solely rely on `maxItems` in the schema validation (see below). // See https://github.com/WordPress/WordPress/blob/965fcddcf68cf4fd122ae24b992e242dfea1d773/wp-includes/rest-api.php#L1922-L1925. $max_num_widgets = Feature_Flags::enabled( 'conversionReporting' ) ? 8 : 4; if ( $num_widgets > $max_num_widgets ) { return new WP_Error( 'rest_invalid_param', __( 'No more than 4 key metrics can be selected.', 'google-site-kit' ), array( 'status' => 400 ) ); } $key_metrics_setup_already_done_by_user = $this->key_metrics_setup_completed_by->get(); if ( empty( $key_metrics_setup_already_done_by_user ) ) { $current_user_id = get_current_user_id(); $this->key_metrics_setup_completed_by->set( $current_user_id ); } } $this->settings->merge( $data['settings'] ); return new WP_REST_Response( $this->settings->get() ); }, 'permission_callback' => $has_capabilities, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'settings' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'isWidgetHidden' => array( 'type' => 'boolean', 'required' => true, ), 'widgetSlugs' => array( 'type' => 'array', 'required' => false, 'maxItems' => Feature_Flags::enabled( 'conversionReporting' ) ? 8 : 4, 'items' => array( 'type' => 'string', ), ), ), ), ), ), ), ) ), ); } } Key_Metrics/Key_Metrics.php 0000644 00000005635 14720521221 0011716 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Key_Metrics\Key_Metrics * * @package Google\Site_Kit\Core\Key_Metrics * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Key_Metrics; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Storage\Transients; use Google\Site_Kit\Core\Storage\User_Options; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; /** * Class for handling Key_Metrics. * * @since 1.93.0 * @access private * @ignore */ class Key_Metrics { use Method_Proxy_Trait; /** * Key_Metrics_Settings instance. * * @since 1.93.0 * @var Key_Metrics_Settings */ protected $key_metrics_settings; /** * Key_Metrics_Setup_Completed_By instance. * * @since 1.113.0 * @var Key_Metrics_Setup_Completed_By */ protected $key_metrics_setup_completed_by; /** * REST_Key_Metrics_Controller instance. * * @since 1.93.0 * @var REST_Key_Metrics_Controller */ protected $rest_controller; /** * Key_Metrics_Setup_New instance. * * @since 1.115.0 * @var Key_Metrics_Setup_New */ protected $key_metrics_setup_new; /** * Constructor. * * @since 1.93.0 * * @param Context $context Plugin context. * @param User_Options $user_options Optional. User option API. Default is a new instance. * @param Options $options Optional. Option API instance. Default is a new instance. */ public function __construct( Context $context, User_Options $user_options = null, Options $options = null ) { $this->key_metrics_settings = new Key_Metrics_Settings( $user_options ?: new User_Options( $context ) ); $this->key_metrics_setup_completed_by = new Key_Metrics_Setup_Completed_By( $options ?: new Options( $context ) ); $this->key_metrics_setup_new = new Key_Metrics_Setup_New( new Transients( $context ) ); $this->rest_controller = new REST_Key_Metrics_Controller( $this->key_metrics_settings, $this->key_metrics_setup_completed_by ); } /** * Registers functionality through WordPress hooks. * * @since 1.93.0 */ public function register() { $this->key_metrics_settings->register(); $this->key_metrics_setup_completed_by->register(); $this->key_metrics_setup_new->register(); $this->rest_controller->register(); add_filter( 'googlesitekit_inline_base_data', $this->get_method_proxy( 'inline_js_base_data' ) ); } /** * Adds the status of the Key Metrics widget setup to the inline JS data. * * @since 1.108.0 * @since 1.113.0 Add keyMetricsSetupCompletedBy (id) instead of keyMetricsSetupCompleted boolean. * * @param array $data Inline JS data. * @return array Filtered $data. */ private function inline_js_base_data( $data ) { $data['keyMetricsSetupCompletedBy'] = (int) $this->key_metrics_setup_completed_by->get(); return $data; } } Key_Metrics/Key_Metrics_Settings.php 0000644 00000005146 14720521221 0013573 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Key_Metrics\Key_Metrics_Settings * * @package Google\Site_Kit\Core\Key_Metrics * @copyright 2023 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Key_Metrics; use Google\Site_Kit\Core\Storage\User_Setting; use Google\Site_Kit\Core\Util\Sanitize; /** * Class to store user key metrics settings. * * @since 1.93.0 * @access private * @ignore */ class Key_Metrics_Settings extends User_Setting { /** * The user option name for this setting. */ const OPTION = 'googlesitekit_key_metrics_settings'; /** * Gets the expected value type. * * @since 1.93.0 * * @return string The type name. */ protected function get_type() { return 'object'; } /** * Gets the default value. * * @since 1.93.0 * * @return array The default value. */ protected function get_default() { return array( 'widgetSlugs' => array(), 'isWidgetHidden' => false, 'includeConversionTailoredMetrics' => false, ); } /** * Merges an array of settings to update. * * @since 1.93.0 * * @param array $partial Partial settings array to save. * @return bool True on success, false on failure. */ public function merge( array $partial ) { $settings = $this->get(); $partial = array_filter( $partial, function ( $value ) { return null !== $value; } ); $allowed_settings = array( 'widgetSlugs' => true, 'isWidgetHidden' => true, 'includeConversionTailoredMetrics' => true, ); $updated = array_intersect_key( $partial, $allowed_settings ); return $this->set( array_merge( $settings, $updated ) ); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.93.0 * * @return callable Sanitize callback. */ protected function get_sanitize_callback() { return function ( $settings ) { if ( ! is_array( $settings ) ) { return array(); } $sanitized_settings = array(); if ( isset( $settings['widgetSlugs'] ) ) { $sanitized_settings['widgetSlugs'] = Sanitize::sanitize_string_list( $settings['widgetSlugs'] ); } if ( isset( $settings['isWidgetHidden'] ) ) { $sanitized_settings['isWidgetHidden'] = false !== $settings['isWidgetHidden']; } if ( isset( $settings['includeConversionTailoredMetrics'] ) ) { $sanitized_settings['includeConversionTailoredMetrics'] = false !== $settings['includeConversionTailoredMetrics']; } return $sanitized_settings; }; } } Validation/Exception/Invalid_Report_Dimensions_Exception.php 0000644 00000001114 14720521221 0020425 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Validation\Exception\Invalid_Report_Dimensions_Exception * * @package Google\Site_Kit\Core\Validation\Exception * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Validation\Exception; use DomainException; /** * Exception thrown when dimensions are invalid for a report request. * * @since 1.82.0 * @access private * @ignore */ class Invalid_Report_Dimensions_Exception extends DomainException { } Validation/Exception/Invalid_Report_Metrics_Exception.php 0000644 00000001103 14720521221 0017721 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Validation\Exception\Invalid_Report_Metrics_Exception * * @package Google\Site_Kit\Core\Validation\Exception * @copyright 2022 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Validation\Exception; use DomainException; /** * Exception thrown when metrics are invalid for a report request. * * @since 1.82.0 * @access private * @ignore */ class Invalid_Report_Metrics_Exception extends DomainException { } Consent_Mode/REST_Consent_Mode_Controller.php 0000644 00000014145 14720521221 0015250 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Consent_Mode\REST_Consent_Mode_Controller * * @package Google\Site_Kit\Core\Consent_Mode * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Consent_Mode; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; use WP_Error; /** * Class for handling Consent Mode. * * @since 1.122.0 * @access private * @ignore */ class REST_Consent_Mode_Controller { /** * Consent_Mode_Settings instance. * * @since 1.122.0 * @var Consent_Mode_Settings */ private $consent_mode_settings; /** * Constructor. * * @since 1.122.0 * * @param Consent_Mode_Settings $consent_mode_settings Consent_Mode_Settings instance. */ public function __construct( Consent_Mode_Settings $consent_mode_settings ) { $this->consent_mode_settings = $consent_mode_settings; } /** * Registers functionality through WordPress hooks. * * @since 1.122.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/site/data/consent-mode', ) ); } ); add_filter( 'googlesitekit_apifetch_preload_paths', function ( $paths ) { return array_merge( $paths, array( '/' . REST_Routes::REST_ROOT . '/core/site/data/consent-api-info', ) ); } ); } /** * Gets REST route instances. * * @since 1.122.0 * * @return REST_Route[] List of REST_Route objects. */ protected function get_rest_routes() { $can_manage_options = function () { return current_user_can( Permissions::MANAGE_OPTIONS ); }; $can_update_plugins = function () { return current_user_can( Permissions::UPDATE_PLUGINS ); }; return array( new REST_Route( 'core/site/data/consent-mode', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { return new WP_REST_Response( $this->consent_mode_settings->get() ); }, 'permission_callback' => $can_manage_options, ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $this->consent_mode_settings->set( $request['data']['settings'] ); return new WP_REST_Response( $this->consent_mode_settings->get() ); }, 'permission_callback' => $can_manage_options, 'args' => array( 'data' => array( 'type' => 'object', 'required' => true, 'properties' => array( 'settings' => array( 'type' => 'object', 'required' => true, 'minProperties' => 1, 'additionalProperties' => false, 'properties' => array( 'enabled' => array( 'type' => 'boolean', ), 'regions' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), ), ), ), ), ), ), ) ), new REST_Route( 'core/site/data/consent-api-info', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { $is_active = function_exists( 'wp_set_consent' ); $installed = $is_active; $plugin_uri = 'https://wordpress.org/plugins/wp-consent-api'; $plugin = 'wp-consent-api/wp-consent-api.php'; $response = array( 'hasConsentAPI' => $is_active, ); if ( ! $is_active ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugins = get_plugins(); if ( array_key_exists( $plugin, $plugins ) ) { $installed = true; } else { foreach ( $plugins as $plugin_file => $installed_plugin ) { if ( $installed_plugin['PluginURI'] === $plugin_uri ) { $plugin = $plugin_file; $installed = true; break; } } } // Alternate wp_nonce_url without esc_html breaking query parameters. $nonce_url = function ( $action_url, $action ) { return add_query_arg( '_wpnonce', wp_create_nonce( $action ), $action_url ); }; $activate_url = $nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=' . $plugin ), 'activate-plugin_' . $plugin ); $install_url = $nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=wp-consent-api' ), 'install-plugin_wp-consent-api' ); $response['wpConsentPlugin'] = array( 'installed' => $installed, 'activateURL' => current_user_can( 'activate_plugin', $plugin ) ? esc_url_raw( $activate_url ) : false, 'installURL' => current_user_can( 'install_plugins' ) ? esc_url_raw( $install_url ) : false, ); } return new WP_REST_Response( $response ); }, 'permission_callback' => $can_manage_options, ), ) ), new REST_Route( 'core/site/data/consent-api-activate', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function () { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $slug = 'wp-consent-api'; $plugin = "$slug/$slug.php"; $activated = activate_plugin( $plugin ); if ( is_wp_error( $activated ) ) { return new WP_Error( 'invalid_module_slug', $activated->get_error_message() ); } return new WP_REST_Response( array( 'success' => true ) ); }, 'permission_callback' => $can_update_plugins, ), ), ), ); } } Consent_Mode/Consent_Mode_Settings.php 0000644 00000004556 14720521221 0014075 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Consent_Mode\Consent_Mode_Settings * * @package Google\Site_Kit\Core\Consent_Mode * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Consent_Mode; use Google\Site_Kit\Core\Storage\Setting; /** * Class to store user Consent Mode settings. * * @since 1.122.0 * @access private * @ignore */ class Consent_Mode_Settings extends Setting { /** * The user option name for this setting. */ const OPTION = 'googlesitekit_consent_mode'; /** * Gets the expected value type. * * @since 1.122.0 * * @return string The type name. */ protected function get_type() { return 'object'; } /** * Gets the default value. * * @since 1.122.0 * * @return array The default value. */ protected function get_default() { return array( 'enabled' => false, 'regions' => Regions::get_regions(), ); } /** * Gets the callback for sanitizing the setting's value before saving. * * @since 1.122.0 * * @return callable Sanitize callback. */ protected function get_sanitize_callback() { return function ( $value ) { $new_value = $this->get(); if ( isset( $value['enabled'] ) ) { $new_value['enabled'] = (bool) $value['enabled']; } if ( ! empty( $value['regions'] ) && is_array( $value['regions'] ) ) { $region_codes = array_reduce( $value['regions'], static function ( $regions, $region_code ) { $region_code = strtoupper( $region_code ); // Match ISO 3166-2 (`AB` or `CD-EF`). if ( ! preg_match( '#^[A-Z]{2}(-[A-Z]{2})?$#', $region_code ) ) { return $regions; } // Store as keys to remove duplicates. $regions[ $region_code ] = true; return $regions; }, array() ); $new_value['regions'] = array_keys( $region_codes ); } return $new_value; }; } /** * Accessor for the `enabled` setting. * * @since 1.122.0 * * @return bool TRUE if Consent Mode is enabled, otherwise FALSE. */ public function is_consent_mode_enabled() { return $this->get()['enabled']; } /** * Accessor for the `regions` setting. * * @since 1.122.0 * * @return array<string> Array of ISO 3166-2 region codes. */ public function get_regions() { return $this->get()['regions']; } } Consent_Mode/Regions.php 0000644 00000002236 14720521221 0011237 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Consent_Mode\Regions * * @package Google\Site_Kit\Core\Consent_Mode * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Consent_Mode; use Google\Site_Kit\Core\Util\Feature_Flags; /** * Class containing Consent Mode Regions. * * @since 1.122.0 * @access private * @ignore */ class Regions { /** * List of countries that Google's EU user consent policy applies to, which are the * countries in the European Economic Area (EEA) plus the UK. */ const EU_USER_CONSENT_POLICY = array( 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', ); /** * Returns the list of regions that Google's EU user consent policy applies to. * * @since 1.128.0 * * @return array<string> List of regions. */ public static function get_regions() { return self::EU_USER_CONSENT_POLICY; } } Consent_Mode/Consent_Mode.php 0000644 00000020072 14720521221 0012204 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Consent_Mode\Consent_Mode * * @package Google\Site_Kit\Core\Consent_Mode * @copyright 2024 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Consent_Mode; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Assets\Script; use Google\Site_Kit\Core\Storage\Options; use Google\Site_Kit\Core\Util\Method_Proxy_Trait; use Plugin_Upgrader; use Plugin_Installer_Skin; /** * Class for handling Consent Mode. * * @since 1.122.0 * @access private * @ignore */ class Consent_Mode { use Method_Proxy_Trait; /** * Context instance. * * @since 1.132.0 * @var Context */ protected $context; /** * Consent_Mode_Settings instance. * * @since 1.122.0 * @var Consent_Mode_Settings */ protected $consent_mode_settings; /** * REST_Consent_Mode_Controller instance. * * @since 1.122.0 * @var REST_Consent_Mode_Controller */ protected $rest_controller; /** * Constructor. * * @since 1.122.0 * * @param Context $context Plugin context. * @param Options $options Optional. Option API instance. Default is a new instance. */ public function __construct( Context $context, Options $options = null ) { $this->context = $context; $options = $options ?: new Options( $context ); $this->consent_mode_settings = new Consent_Mode_Settings( $options ); $this->rest_controller = new REST_Consent_Mode_Controller( $this->consent_mode_settings ); } /** * Registers functionality through WordPress hooks. * * @since 1.122.0 */ public function register() { $this->consent_mode_settings->register(); $this->rest_controller->register(); // Declare that the plugin is compatible with the WP Consent API. $plugin = GOOGLESITEKIT_PLUGIN_BASENAME; add_filter( "wp_consent_api_registered_{$plugin}", '__return_true' ); $consent_mode_enabled = $this->consent_mode_settings->is_consent_mode_enabled(); if ( $consent_mode_enabled ) { // The `wp_head` action is used to ensure the snippets are printed in the head on the front-end only, not admin pages. add_action( 'wp_head', $this->get_method_proxy( 'render_gtag_consent_data_layer_snippet' ), 1 // Set priority to 1 to ensure the snippet is printed with top priority in the head. ); add_action( 'wp_enqueue_scripts', fn () => $this->register_and_enqueue_script() ); } add_filter( 'googlesitekit_consent_mode_status', function () use ( $consent_mode_enabled ) { return $consent_mode_enabled ? 'enabled' : 'disabled'; } ); add_filter( 'googlesitekit_inline_base_data', $this->get_method_proxy( 'inline_js_base_data' ) ); add_action( 'wp_ajax_install_activate_wp_consent_api', array( $this, 'install_activate_wp_consent_api' ) ); } /** * AJAX callback that installs and activates the WP Consent API plugin. * * This function utilizes an AJAX approach instead of the standardized REST approach * due to the requirement of the Plugin_Upgrader class, which relies on functions * from `admin.php` among others. These functions are properly loaded during the * AJAX callback, ensuring the installation and activation processes can execute correctly. * * @since 1.132.0 */ public function install_activate_wp_consent_api() { check_ajax_referer( 'updates' ); $slug = 'wp-consent-api'; $plugin = "$slug/$slug.php"; if ( ! current_user_can( 'activate_plugin', $plugin ) ) { wp_send_json( array( 'error' => __( 'You do not have permission to activate plugins on this site.', 'google-site-kit' ) ) ); } /** WordPress Administration Bootstrap */ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; // For Plugin_Upgrader and Plugin_Installer_Skin. require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; // For plugins_api. $api = plugins_api( 'plugin_information', array( 'slug' => $slug, 'fields' => array( 'sections' => false, ), ) ); if ( is_wp_error( $api ) ) { wp_send_json( array( 'error' => $api->get_error_message() ) ); } $title = ''; $nonce = 'install-plugin_' . $plugin; $url = 'update.php?action=install-plugin&plugin=' . rawurlencode( $plugin ); $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact( 'title', 'url', 'nonce', 'plugin', 'api' ) ) ); $install_plugin = $upgrader->install( $api->download_link ); if ( is_wp_error( $install_plugin ) ) { wp_send_json( array( 'error' => $install_plugin->get_error_message() ) ); } $activated = activate_plugin( $plugin ); if ( is_wp_error( $activated ) ) { wp_send_json( array( 'error' => $activated->get_error_message() ) ); } wp_send_json( array( 'success' => true ) ); } /** * Registers and Enqueues the consent mode script. * * @since 1.132.0 */ protected function register_and_enqueue_script() { $consent_mode_script = new Script( 'googlesitekit-consent-mode', array( 'src' => $this->context->url( 'dist/assets/js/googlesitekit-consent-mode.js' ), ) ); $consent_mode_script->register( $this->context ); $consent_mode_script->enqueue(); } /** * Prints the gtag consent snippet. * * @since 1.122.0 * @since 1.132.0 Refactored core script to external js file transpiled with webpack. */ protected function render_gtag_consent_data_layer_snippet() { /** * Filters the consent mode defaults. * * Allows these defaults to be modified, thus allowing users complete control over the consent mode parameters. * * @since 1.126.0 * * @param array $consent_mode_defaults Default values for consent mode. */ $consent_defaults = apply_filters( 'googlesitekit_consent_defaults', array( 'ad_personalization' => 'denied', 'ad_storage' => 'denied', 'ad_user_data' => 'denied', 'analytics_storage' => 'denied', 'functionality_storage' => 'denied', 'security_storage' => 'denied', 'personalization_storage' => 'denied', // TODO: The value for `region` should be retrieved from $this->consent_mode_settings->get_regions(), // but we'll need to migrate/clean up the incorrect values that were set from the initial release. // See https://github.com/google/site-kit-wp/issues/8444. 'region' => Regions::get_regions(), 'wait_for_update' => 500, // Allow 500ms for Consent Management Platforms (CMPs) to update the consent status. ) ); /** * Filters the consent category mapping. * * @since 1.124.0 * * @param array $consent_category_map Default consent category mapping. */ $consent_category_map = apply_filters( 'googlesitekit_consent_category_map', array( 'statistics' => array( 'analytics_storage' ), 'marketing' => array( 'ad_storage', 'ad_user_data', 'ad_personalization' ), 'functional' => array( 'functionality_storage', 'security_storage' ), 'preferences' => array( 'personalization_storage' ), ) ); // The core Consent Mode code is in assets/js/consent-mode/consent-mode.js. // Only code that passes data from PHP to JS should be in this file. ?> <!-- <?php echo esc_html__( 'Google tag (gtag.js) Consent Mode dataLayer added by Site Kit', 'google-site-kit' ); ?> --> <script id='google_gtagjs-js-consent-mode-data-layer'> window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);} gtag('consent', 'default', <?php echo wp_json_encode( $consent_defaults ); ?>); window._googlesitekitConsentCategoryMap = <?php echo wp_json_encode( $consent_category_map ); ?>; window._googlesitekitConsents = <?php echo wp_json_encode( $consent_defaults ); ?> </script> <!-- <?php echo esc_html__( 'End Google tag (gtag.js) Consent Mode dataLayer added by Site Kit', 'google-site-kit' ); ?> --> <?php } /** * Extends base data with a static list of consent mode regions. * * @since 1.128.0 * * @param array $data Inline base data. * @return array Filtered $data. */ protected function inline_js_base_data( $data ) { $data['consentModeRegions'] = Regions::get_regions(); return $data; } } Guards/Guard_Interface.php 0000644 00000001237 14720521221 0011523 0 ustar 00 <?php /** * Interface Google\Site_Kit\Core\Guards\Guard_Interface * * @package Google\Site_Kit\Core\Guards * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Guards; use WP_Error; /** * Interface for a guard. * * @since 1.24.0 * @access private * @ignore */ interface Guard_Interface { /** * Determines whether the guarded entity can be activated or not. * * @since 1.24.0 * * @return bool|WP_Error TRUE if guarded entity can be activated, otherwise FALSE or an error. */ public function can_activate(); } Notifications/Notification.php 0000644 00000005270 14720521221 0012514 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Notifications\Notification * * @package Google\Site_Kit\Core\Notifications * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Notifications; /** * Class for representing a notification. * * @since 1.4.0 * @access private * @ignore */ class Notification { /** * Unique notification slug. * * @since 1.4.0 * @var string */ private $slug; /** * Notification arguments. * * @since 1.4.0 * @var array */ private $args; /** * Constructor. * * @since 1.4.0 * * @param string $slug Unique notification slug. * @param array $args { * Associative array of notification arguments. * * @type string $title Required notification title. * @type string $content Required notification content. May contain inline HTML tags. * @type string $cta_url Call to action URL. * @type string $cta_label Call to action anchor text. * @type string $cta_target Call to action anchor target. * @type string $learn_more_url Learn more URL. * @type string $learn_more_label Learn more anchor text. * @type bool $dismissible Whether the notice should be dismissible. Default false. * @type string $dismiss_label Dismiss anchor text. * } */ public function __construct( $slug, array $args ) { $this->slug = (string) $slug; $this->args = array_merge( array( 'title' => '', 'content' => '', 'cta_url' => '', 'cta_label' => '', 'cta_target' => '', 'learn_more_url' => '', 'learn_more_label' => '', 'dismissible' => false, 'dismiss_label' => __( 'Dismiss', 'google-site-kit' ), ), $args ); } /** * Gets the notification's slug. * * @since 1.4.0 * * @return string Unique notification slug. */ public function get_slug() { return $this->slug; } /** * Prepares the JS representation of the Notification. * * @since 1.4.0 * * @return array */ public function prepare_for_js() { return array( 'id' => $this->get_slug(), 'title' => $this->args['title'], 'content' => $this->args['content'], 'ctaURL' => $this->args['cta_url'], 'ctaLabel' => $this->args['cta_label'], 'ctaTarget' => $this->args['cta_target'], 'learnMoreURL' => $this->args['learn_more_url'], 'learnMoreLabel' => $this->args['learn_more_label'], 'dismissible' => $this->args['dismissible'], 'dismissLabel' => $this->args['dismiss_label'], ); } } Notifications/Notifications.php 0000644 00000016652 14720521221 0012705 0 ustar 00 <?php /** * Class Google\Site_Kit\Core\Notifications\Notifications.php * * @package Google\Site_Kit\Core\Notifications * @copyright 2021 Google LLC * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://sitekit.withgoogle.com */ namespace Google\Site_Kit\Core\Notifications; use Exception; use Google\Site_Kit\Context; use Google\Site_Kit\Core\Authentication\Credentials; use Google\Site_Kit\Core\Authentication\Google_Proxy; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; use Google\Site_Kit\Core\Storage\Encrypted_Options; use Google\Site_Kit\Core\Storage\Options; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** * Class for managing core notifications. * * @since 1.4.0 * @access private * @ignore */ class Notifications { /** * Context instance. * * @since 1.4.0 * @var Context */ private $context; /** * Options instance. * * @since 1.4.0 * @var Options */ private $options; /** * Authentication instance. * * @since 1.8.0 * @var Authentication */ private $authentication; /** * Google_Proxy instance. * * @since 1.4.0 * @var Google_Proxy */ private $google_proxy; /** * Credentials instance. * * @since 1.4.0 * @var Credentials */ private $credentials; /** * Constructor. * * @since 1.4.0 * * @param Context $context Context instance. * @param Options $options Options instance. * @param Authentication $authentication Authentication instance. */ public function __construct( Context $context, Options $options = null, Authentication $authentication = null ) { $this->context = $context; $this->options = $options ?: new Options( $context ); $this->google_proxy = new Google_Proxy( $this->context ); $this->authentication = $authentication ?: new Authentication( $this->context ); $this->credentials = $this->authentication->credentials(); } /** * Registers core notifications. * * @since 1.4.0 */ public function register() { add_filter( 'googlesitekit_rest_routes', function ( $routes ) { return array_merge( $routes, $this->get_rest_routes() ); } ); } /** * Gets related REST routes. * * @since 1.4.0 * * @return array List of REST_Route objects. */ private function get_rest_routes() { $can_use_notifications = function () { return current_user_can( Permissions::SETUP ) && $this->credentials->has(); }; return array( new REST_Route( 'core/site/data/notifications', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => function () { $endpoint = add_query_arg( array( 'site_id' => $this->credentials->get()['oauth2_client_id'], ), $this->google_proxy->url( '/notifications/' ) ); // Return an empty array of notifications if the user isn't using the proxy. if ( ! $this->credentials->using_proxy() ) { return new WP_REST_Response( array() ); } $response = wp_remote_get( $endpoint ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get if ( is_wp_error( $response ) ) { return $response; } try { $response = $this->parse_response( $response ); } catch ( Exception $e ) { return new WP_Error( 'exception', $e->getMessage() ); } $data = array_map( function ( Notification $notification ) { return $notification->prepare_for_js(); }, $this->map_response_to_notifications( $response ) ); return new WP_REST_Response( $data ); }, 'permission_callback' => $can_use_notifications, ), ) ), new REST_Route( 'core/site/data/mark-notification', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => function ( WP_REST_Request $request ) { $data = $request['data']; if ( empty( $data['notificationID'] ) ) { return $this->missing_required_param( 'data.notificationID' ); } if ( empty( $data['notificationState'] ) ) { return $this->missing_required_param( 'data.notificationState' ); } $credentials = $this->credentials->get(); $response = wp_remote_post( $this->google_proxy->url( '/notifications/mark/' ), array( 'body' => array( 'site_id' => $credentials['oauth2_client_id'], 'site_secret' => $credentials['oauth2_client_secret'], 'notification_id' => $data['notificationID'], 'notification_state' => $data['notificationState'], ), ) ); if ( is_wp_error( $response ) ) { return $response; } try { $response = $this->parse_response( $response ); } catch ( Exception $e ) { return new WP_Error( 'exception', $e->getMessage() ); } return new WP_REST_Response( array( 'success' => isset( $response['success'] ) ? (bool) $response['success'] : false, ) ); }, 'args' => array( 'data' => array( 'required' => true, 'type' => 'object', ), ), 'permission_callback' => $can_use_notifications, ), ) ), ); } /** * Validates and parses the given JSON response into an array. * * @since 1.4.0 * * @param array $response HTTP response array. * @return mixed JSON decoded response. * @throws Exception Throws exception if response cannot be parsed or if an error is returned. */ private function parse_response( $response ) { $body = wp_remote_retrieve_body( $response ); $decoded = json_decode( $body, true ); if ( json_last_error() ) { throw new Exception( 'Error while decoding response: ' . json_last_error() ); } if ( ! empty( $decoded['error'] ) ) { throw new Exception( $decoded['error'] ); } return $decoded; } /** * Maps the response objects into Notification objects. * * @since 1.4.0 * * @param array $response Array of notification objects from API. * @return Notification[] Array of Notification objects. */ private function map_response_to_notifications( array $response ) { return array_map( function ( $notification ) { return new Notification( $notification['id'], array( 'title' => $notification['title'], 'content' => $notification['content'], 'cta_url' => $notification['ctaURL'], 'cta_label' => $notification['ctaLabel'], 'cta_target' => $notification['ctaTarget'], 'learn_more_url' => $notification['learnMoreURL'], 'learn_more_label' => $notification['learnMoreLabel'], 'dismissible' => $notification['dismissible'], 'dismiss_label' => $notification['dismissLabel'], ) ); }, $response ); } /** * Gets a WP_Error instance for the given missing required parameter. * * @since 1.4.0 * * @param string $param Missing required parameter. * @return WP_Error */ private function missing_required_param( $param ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), $param ), array( 'status' => 400 ) ); } } AES/Block.php 0000644 00000024342 14721742304 0006730 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_AES_Block', false)) { return; } /** * @internal This should only be used by sodium_compat */ class ParagonIE_Sodium_Core_AES_Block extends SplFixedArray { /** * @var array<int, int> */ protected $values = array(); /** * @var int */ protected $size; /** * @param int $size */ public function __construct($size = 8) { parent::__construct($size); $this->size = $size; $this->values = array_fill(0, $size, 0); } /** * @return self */ public static function init() { return new self(8); } /** * @internal You should not use this directly from another application * * @param array<int, int> $array * @param bool $save_indexes * @return self * * @psalm-suppress MethodSignatureMismatch */ #[ReturnTypeWillChange] public static function fromArray($array, $save_indexes = null) { $count = count($array); if ($save_indexes) { $keys = array_keys($array); } else { $keys = range(0, $count - 1); } $array = array_values($array); /** @var array<int, int> $keys */ $obj = new ParagonIE_Sodium_Core_AES_Block(); if ($save_indexes) { for ($i = 0; $i < $count; ++$i) { $obj->offsetSet($keys[$i], $array[$i]); } } else { for ($i = 0; $i < $count; ++$i) { $obj->offsetSet($i, $array[$i]); } } return $obj; } /** * @internal You should not use this directly from another application * * @param int|null $offset * @param int $value * @return void * * @psalm-suppress MethodSignatureMismatch * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if (!is_int($value)) { throw new InvalidArgumentException('Expected an integer'); } if (is_null($offset)) { $this->values[] = $value; } else { $this->values[$offset] = $value; } } /** * @internal You should not use this directly from another application * * @param int $offset * @return bool * * @psalm-suppress MethodSignatureMismatch * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->values[$offset]); } /** * @internal You should not use this directly from another application * * @param int $offset * @return void * * @psalm-suppress MethodSignatureMismatch * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->values[$offset]); } /** * @internal You should not use this directly from another application * * @param int $offset * @return int * * @psalm-suppress MethodSignatureMismatch * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetGet($offset) { if (!isset($this->values[$offset])) { $this->values[$offset] = 0; } return (int) ($this->values[$offset]); } /** * @internal You should not use this directly from another application * * @return array */ public function __debugInfo() { $out = array(); foreach ($this->values as $v) { $out[] = str_pad(dechex($v), 8, '0', STR_PAD_LEFT); } return array(implode(', ', $out)); /* return array(implode(', ', $this->values)); */ } /** * @param int $cl low bit mask * @param int $ch high bit mask * @param int $s shift * @param int $x index 1 * @param int $y index 2 * @return self */ public function swapN($cl, $ch, $s, $x, $y) { static $u32mask = ParagonIE_Sodium_Core_Util::U32_MAX; $a = $this->values[$x] & $u32mask; $b = $this->values[$y] & $u32mask; // (x) = (a & cl) | ((b & cl) << (s)); $this->values[$x] = ($a & $cl) | ((($b & $cl) << $s) & $u32mask); // (y) = ((a & ch) >> (s)) | (b & ch); $this->values[$y] = ((($a & $ch) & $u32mask) >> $s) | ($b & $ch); return $this; } /** * @param int $x index 1 * @param int $y index 2 * @return self */ public function swap2($x, $y) { return $this->swapN(0x55555555, 0xAAAAAAAA, 1, $x, $y); } /** * @param int $x index 1 * @param int $y index 2 * @return self */ public function swap4($x, $y) { return $this->swapN(0x33333333, 0xCCCCCCCC, 2, $x, $y); } /** * @param int $x index 1 * @param int $y index 2 * @return self */ public function swap8($x, $y) { return $this->swapN(0x0F0F0F0F, 0xF0F0F0F0, 4, $x, $y); } /** * @return self */ public function orthogonalize() { return $this ->swap2(0, 1) ->swap2(2, 3) ->swap2(4, 5) ->swap2(6, 7) ->swap4(0, 2) ->swap4(1, 3) ->swap4(4, 6) ->swap4(5, 7) ->swap8(0, 4) ->swap8(1, 5) ->swap8(2, 6) ->swap8(3, 7); } /** * @return self */ public function shiftRows() { for ($i = 0; $i < 8; ++$i) { $x = $this->values[$i] & ParagonIE_Sodium_Core_Util::U32_MAX; $this->values[$i] = ( ($x & 0x000000FF) | (($x & 0x0000FC00) >> 2) | (($x & 0x00000300) << 6) | (($x & 0x00F00000) >> 4) | (($x & 0x000F0000) << 4) | (($x & 0xC0000000) >> 6) | (($x & 0x3F000000) << 2) ) & ParagonIE_Sodium_Core_Util::U32_MAX; } return $this; } /** * @param int $x * @return int */ public static function rotr16($x) { return (($x << 16) & ParagonIE_Sodium_Core_Util::U32_MAX) | ($x >> 16); } /** * @return self */ public function mixColumns() { $q0 = $this->values[0]; $q1 = $this->values[1]; $q2 = $this->values[2]; $q3 = $this->values[3]; $q4 = $this->values[4]; $q5 = $this->values[5]; $q6 = $this->values[6]; $q7 = $this->values[7]; $r0 = (($q0 >> 8) | ($q0 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r1 = (($q1 >> 8) | ($q1 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r2 = (($q2 >> 8) | ($q2 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r3 = (($q3 >> 8) | ($q3 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r4 = (($q4 >> 8) | ($q4 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r5 = (($q5 >> 8) | ($q5 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r6 = (($q6 >> 8) | ($q6 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r7 = (($q7 >> 8) | ($q7 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $this->values[0] = $q7 ^ $r7 ^ $r0 ^ self::rotr16($q0 ^ $r0); $this->values[1] = $q0 ^ $r0 ^ $q7 ^ $r7 ^ $r1 ^ self::rotr16($q1 ^ $r1); $this->values[2] = $q1 ^ $r1 ^ $r2 ^ self::rotr16($q2 ^ $r2); $this->values[3] = $q2 ^ $r2 ^ $q7 ^ $r7 ^ $r3 ^ self::rotr16($q3 ^ $r3); $this->values[4] = $q3 ^ $r3 ^ $q7 ^ $r7 ^ $r4 ^ self::rotr16($q4 ^ $r4); $this->values[5] = $q4 ^ $r4 ^ $r5 ^ self::rotr16($q5 ^ $r5); $this->values[6] = $q5 ^ $r5 ^ $r6 ^ self::rotr16($q6 ^ $r6); $this->values[7] = $q6 ^ $r6 ^ $r7 ^ self::rotr16($q7 ^ $r7); return $this; } /** * @return self */ public function inverseMixColumns() { $q0 = $this->values[0]; $q1 = $this->values[1]; $q2 = $this->values[2]; $q3 = $this->values[3]; $q4 = $this->values[4]; $q5 = $this->values[5]; $q6 = $this->values[6]; $q7 = $this->values[7]; $r0 = (($q0 >> 8) | ($q0 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r1 = (($q1 >> 8) | ($q1 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r2 = (($q2 >> 8) | ($q2 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r3 = (($q3 >> 8) | ($q3 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r4 = (($q4 >> 8) | ($q4 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r5 = (($q5 >> 8) | ($q5 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r6 = (($q6 >> 8) | ($q6 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $r7 = (($q7 >> 8) | ($q7 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; $this->values[0] = $q5 ^ $q6 ^ $q7 ^ $r0 ^ $r5 ^ $r7 ^ self::rotr16($q0 ^ $q5 ^ $q6 ^ $r0 ^ $r5); $this->values[1] = $q0 ^ $q5 ^ $r0 ^ $r1 ^ $r5 ^ $r6 ^ $r7 ^ self::rotr16($q1 ^ $q5 ^ $q7 ^ $r1 ^ $r5 ^ $r6); $this->values[2] = $q0 ^ $q1 ^ $q6 ^ $r1 ^ $r2 ^ $r6 ^ $r7 ^ self::rotr16($q0 ^ $q2 ^ $q6 ^ $r2 ^ $r6 ^ $r7); $this->values[3] = $q0 ^ $q1 ^ $q2 ^ $q5 ^ $q6 ^ $r0 ^ $r2 ^ $r3 ^ $r5 ^ self::rotr16($q0 ^ $q1 ^ $q3 ^ $q5 ^ $q6 ^ $q7 ^ $r0 ^ $r3 ^ $r5 ^ $r7); $this->values[4] = $q1 ^ $q2 ^ $q3 ^ $q5 ^ $r1 ^ $r3 ^ $r4 ^ $r5 ^ $r6 ^ $r7 ^ self::rotr16($q1 ^ $q2 ^ $q4 ^ $q5 ^ $q7 ^ $r1 ^ $r4 ^ $r5 ^ $r6); $this->values[5] = $q2 ^ $q3 ^ $q4 ^ $q6 ^ $r2 ^ $r4 ^ $r5 ^ $r6 ^ $r7 ^ self::rotr16($q2 ^ $q3 ^ $q5 ^ $q6 ^ $r2 ^ $r5 ^ $r6 ^ $r7); $this->values[6] = $q3 ^ $q4 ^ $q5 ^ $q7 ^ $r3 ^ $r5 ^ $r6 ^ $r7 ^ self::rotr16($q3 ^ $q4 ^ $q6 ^ $q7 ^ $r3 ^ $r6 ^ $r7); $this->values[7] = $q4 ^ $q5 ^ $q6 ^ $r4 ^ $r6 ^ $r7 ^ self::rotr16($q4 ^ $q5 ^ $q7 ^ $r4 ^ $r7); return $this; } /** * @return self */ public function inverseShiftRows() { for ($i = 0; $i < 8; ++$i) { $x = $this->values[$i]; $this->values[$i] = ParagonIE_Sodium_Core_Util::U32_MAX & ( ($x & 0x000000FF) | (($x & 0x00003F00) << 2) | (($x & 0x0000C000) >> 6) | (($x & 0x000F0000) << 4) | (($x & 0x00F00000) >> 4) | (($x & 0x03000000) << 6) | (($x & 0xFC000000) >> 2) ); } return $this; } } AES/Expanded.php 0000644 00000000460 14721742304 0007421 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_AES_Expanded', false)) { return; } /** * @internal This should only be used by sodium_compat */ class ParagonIE_Sodium_Core_AES_Expanded extends ParagonIE_Sodium_Core_AES_KeySchedule { /** @var bool $expanded */ protected $expanded = true; } AES/KeySchedule.php 0000644 00000003531 14721742304 0010100 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_AES_KeySchedule', false)) { return; } /** * @internal This should only be used by sodium_compat */ class ParagonIE_Sodium_Core_AES_KeySchedule { /** @var array<int, int> $skey -- has size 120 */ protected $skey; /** @var bool $expanded */ protected $expanded = false; /** @var int $numRounds */ private $numRounds; /** * @param array $skey * @param int $numRounds */ public function __construct(array $skey, $numRounds = 10) { $this->skey = $skey; $this->numRounds = $numRounds; } /** * Get a value at an arbitrary index. Mostly used for unit testing. * * @param int $i * @return int */ public function get($i) { return $this->skey[$i]; } /** * @return int */ public function getNumRounds() { return $this->numRounds; } /** * @param int $offset * @return ParagonIE_Sodium_Core_AES_Block */ public function getRoundKey($offset) { return ParagonIE_Sodium_Core_AES_Block::fromArray( array_slice($this->skey, $offset, 8) ); } /** * Return an expanded key schedule * * @return ParagonIE_Sodium_Core_AES_Expanded */ public function expand() { $exp = new ParagonIE_Sodium_Core_AES_Expanded( array_fill(0, 120, 0), $this->numRounds ); $n = ($exp->numRounds + 1) << 2; for ($u = 0, $v = 0; $u < $n; ++$u, $v += 2) { $x = $y = $this->skey[$u]; $x &= 0x55555555; $exp->skey[$v] = ($x | ($x << 1)) & ParagonIE_Sodium_Core_Util::U32_MAX; $y &= 0xAAAAAAAA; $exp->skey[$v + 1] = ($y | ($y >> 1)) & ParagonIE_Sodium_Core_Util::U32_MAX; } return $exp; } } BLAKE2b.php 0000644 00000057200 14721742304 0006327 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_BLAKE2b', false)) { return; } /** * Class ParagonIE_Sodium_Core_BLAKE2b * * Based on the work of Devi Mandiri in devi/salt. */ abstract class ParagonIE_Sodium_Core_BLAKE2b extends ParagonIE_Sodium_Core_Util { /** * @var SplFixedArray */ protected static $iv; /** * @var array<int, array<int, int>> */ protected static $sigma = array( array( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), array( 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3), array( 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4), array( 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8), array( 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13), array( 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9), array( 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11), array( 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10), array( 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5), array( 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0), array( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), array( 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3) ); const BLOCKBYTES = 128; const OUTBYTES = 64; const KEYBYTES = 64; /** * Turn two 32-bit integers into a fixed array representing a 64-bit integer. * * @internal You should not use this directly from another application * * @param int $high * @param int $low * @return SplFixedArray * @psalm-suppress MixedAssignment */ public static function new64($high, $low) { if (PHP_INT_SIZE === 4) { throw new SodiumException("Error, use 32-bit"); } $i64 = new SplFixedArray(2); $i64[0] = $high & 0xffffffff; $i64[1] = $low & 0xffffffff; return $i64; } /** * Convert an arbitrary number into an SplFixedArray of two 32-bit integers * that represents a 64-bit integer. * * @internal You should not use this directly from another application * * @param int $num * @return SplFixedArray */ protected static function to64($num) { list($hi, $lo) = self::numericTo64BitInteger($num); return self::new64($hi, $lo); } /** * Adds two 64-bit integers together, returning their sum as a SplFixedArray * containing two 32-bit integers (representing a 64-bit integer). * * @internal You should not use this directly from another application * * @param SplFixedArray $x * @param SplFixedArray $y * @return SplFixedArray * @psalm-suppress MixedArgument * @psalm-suppress MixedAssignment * @psalm-suppress MixedOperand */ protected static function add64($x, $y) { if (PHP_INT_SIZE === 4) { throw new SodiumException("Error, use 32-bit"); } $l = ($x[1] + $y[1]) & 0xffffffff; return self::new64( (int) ($x[0] + $y[0] + ( ($l < $x[1]) ? 1 : 0 )), (int) $l ); } /** * @internal You should not use this directly from another application * * @param SplFixedArray $x * @param SplFixedArray $y * @param SplFixedArray $z * @return SplFixedArray */ protected static function add364($x, $y, $z) { return self::add64($x, self::add64($y, $z)); } /** * @internal You should not use this directly from another application * * @param SplFixedArray $x * @param SplFixedArray $y * @return SplFixedArray * @throws SodiumException * @throws TypeError */ protected static function xor64(SplFixedArray $x, SplFixedArray $y) { if (PHP_INT_SIZE === 4) { throw new SodiumException("Error, use 32-bit"); } if (!is_numeric($x[0])) { throw new SodiumException('x[0] is not an integer'); } if (!is_numeric($x[1])) { throw new SodiumException('x[1] is not an integer'); } if (!is_numeric($y[0])) { throw new SodiumException('y[0] is not an integer'); } if (!is_numeric($y[1])) { throw new SodiumException('y[1] is not an integer'); } return self::new64( (int) (($x[0] ^ $y[0]) & 0xffffffff), (int) (($x[1] ^ $y[1]) & 0xffffffff) ); } /** * @internal You should not use this directly from another application * * @param SplFixedArray $x * @param int $c * @return SplFixedArray * @psalm-suppress MixedAssignment */ public static function rotr64($x, $c) { if (PHP_INT_SIZE === 4) { throw new SodiumException("Error, use 32-bit"); } if ($c >= 64) { $c %= 64; } if ($c >= 32) { /** @var int $tmp */ $tmp = $x[0]; $x[0] = $x[1]; $x[1] = $tmp; $c -= 32; } if ($c === 0) { return $x; } $l0 = 0; $c = 64 - $c; /** @var int $c */ if ($c < 32) { $h0 = ((int) ($x[0]) << $c) | ( ( (int) ($x[1]) & ((1 << $c) - 1) << (32 - $c) ) >> (32 - $c) ); $l0 = (int) ($x[1]) << $c; } else { $h0 = (int) ($x[1]) << ($c - 32); } $h1 = 0; $c1 = 64 - $c; if ($c1 < 32) { $h1 = (int) ($x[0]) >> $c1; $l1 = ((int) ($x[1]) >> $c1) | ((int) ($x[0]) & ((1 << $c1) - 1)) << (32 - $c1); } else { $l1 = (int) ($x[0]) >> ($c1 - 32); } return self::new64($h0 | $h1, $l0 | $l1); } /** * @internal You should not use this directly from another application * * @param SplFixedArray $x * @return int * @psalm-suppress MixedOperand */ protected static function flatten64($x) { return (int) ($x[0] * 4294967296 + $x[1]); } /** * @internal You should not use this directly from another application * * @param SplFixedArray $x * @param int $i * @return SplFixedArray * @psalm-suppress MixedArgument * @psalm-suppress MixedArrayOffset */ protected static function load64(SplFixedArray $x, $i) { /** @var int $l */ $l = (int) ($x[$i]) | ((int) ($x[$i+1]) << 8) | ((int) ($x[$i+2]) << 16) | ((int) ($x[$i+3]) << 24); /** @var int $h */ $h = (int) ($x[$i+4]) | ((int) ($x[$i+5]) << 8) | ((int) ($x[$i+6]) << 16) | ((int) ($x[$i+7]) << 24); return self::new64($h, $l); } /** * @internal You should not use this directly from another application * * @param SplFixedArray $x * @param int $i * @param SplFixedArray $u * @return void * @psalm-suppress MixedAssignment */ protected static function store64(SplFixedArray $x, $i, SplFixedArray $u) { $maxLength = $x->getSize() - 1; for ($j = 0; $j < 8; ++$j) { /* [0, 1, 2, 3, 4, 5, 6, 7] ... becomes ... [0, 0, 0, 0, 1, 1, 1, 1] */ /** @var int $uIdx */ $uIdx = ((7 - $j) & 4) >> 2; $x[$i] = ((int) ($u[$uIdx]) & 0xff); if (++$i > $maxLength) { return; } /** @psalm-suppress MixedOperand */ $u[$uIdx] >>= 8; } } /** * This just sets the $iv static variable. * * @internal You should not use this directly from another application * * @return void */ public static function pseudoConstructor() { static $called = false; if ($called) { return; } self::$iv = new SplFixedArray(8); self::$iv[0] = self::new64(0x6a09e667, 0xf3bcc908); self::$iv[1] = self::new64(0xbb67ae85, 0x84caa73b); self::$iv[2] = self::new64(0x3c6ef372, 0xfe94f82b); self::$iv[3] = self::new64(0xa54ff53a, 0x5f1d36f1); self::$iv[4] = self::new64(0x510e527f, 0xade682d1); self::$iv[5] = self::new64(0x9b05688c, 0x2b3e6c1f); self::$iv[6] = self::new64(0x1f83d9ab, 0xfb41bd6b); self::$iv[7] = self::new64(0x5be0cd19, 0x137e2179); $called = true; } /** * Returns a fresh BLAKE2 context. * * @internal You should not use this directly from another application * * @return SplFixedArray * @psalm-suppress MixedAssignment * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArrayAssignment */ protected static function context() { $ctx = new SplFixedArray(6); $ctx[0] = new SplFixedArray(8); // h $ctx[1] = new SplFixedArray(2); // t $ctx[2] = new SplFixedArray(2); // f $ctx[3] = new SplFixedArray(256); // buf $ctx[4] = 0; // buflen $ctx[5] = 0; // last_node (uint8_t) for ($i = 8; $i--;) { $ctx[0][$i] = self::$iv[$i]; } for ($i = 256; $i--;) { $ctx[3][$i] = 0; } $zero = self::new64(0, 0); $ctx[1][0] = $zero; $ctx[1][1] = $zero; $ctx[2][0] = $zero; $ctx[2][1] = $zero; return $ctx; } /** * @internal You should not use this directly from another application * * @param SplFixedArray $ctx * @param SplFixedArray $buf * @return void * @throws SodiumException * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress MixedAssignment * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArrayAssignment * @psalm-suppress MixedArrayOffset */ protected static function compress(SplFixedArray $ctx, SplFixedArray $buf) { $m = new SplFixedArray(16); $v = new SplFixedArray(16); for ($i = 16; $i--;) { $m[$i] = self::load64($buf, $i << 3); } for ($i = 8; $i--;) { $v[$i] = $ctx[0][$i]; } $v[ 8] = self::$iv[0]; $v[ 9] = self::$iv[1]; $v[10] = self::$iv[2]; $v[11] = self::$iv[3]; $v[12] = self::xor64($ctx[1][0], self::$iv[4]); $v[13] = self::xor64($ctx[1][1], self::$iv[5]); $v[14] = self::xor64($ctx[2][0], self::$iv[6]); $v[15] = self::xor64($ctx[2][1], self::$iv[7]); for ($r = 0; $r < 12; ++$r) { $v = self::G($r, 0, 0, 4, 8, 12, $v, $m); $v = self::G($r, 1, 1, 5, 9, 13, $v, $m); $v = self::G($r, 2, 2, 6, 10, 14, $v, $m); $v = self::G($r, 3, 3, 7, 11, 15, $v, $m); $v = self::G($r, 4, 0, 5, 10, 15, $v, $m); $v = self::G($r, 5, 1, 6, 11, 12, $v, $m); $v = self::G($r, 6, 2, 7, 8, 13, $v, $m); $v = self::G($r, 7, 3, 4, 9, 14, $v, $m); } for ($i = 8; $i--;) { $ctx[0][$i] = self::xor64( $ctx[0][$i], self::xor64($v[$i], $v[$i+8]) ); } } /** * @internal You should not use this directly from another application * * @param int $r * @param int $i * @param int $a * @param int $b * @param int $c * @param int $d * @param SplFixedArray $v * @param SplFixedArray $m * @return SplFixedArray * @throws SodiumException * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress MixedArrayOffset */ public static function G($r, $i, $a, $b, $c, $d, SplFixedArray $v, SplFixedArray $m) { $v[$a] = self::add364($v[$a], $v[$b], $m[self::$sigma[$r][$i << 1]]); $v[$d] = self::rotr64(self::xor64($v[$d], $v[$a]), 32); $v[$c] = self::add64($v[$c], $v[$d]); $v[$b] = self::rotr64(self::xor64($v[$b], $v[$c]), 24); $v[$a] = self::add364($v[$a], $v[$b], $m[self::$sigma[$r][($i << 1) + 1]]); $v[$d] = self::rotr64(self::xor64($v[$d], $v[$a]), 16); $v[$c] = self::add64($v[$c], $v[$d]); $v[$b] = self::rotr64(self::xor64($v[$b], $v[$c]), 63); return $v; } /** * @internal You should not use this directly from another application * * @param SplFixedArray $ctx * @param int $inc * @return void * @throws SodiumException * @psalm-suppress MixedArgument * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArrayAssignment */ public static function increment_counter($ctx, $inc) { if ($inc < 0) { throw new SodiumException('Increasing by a negative number makes no sense.'); } $t = self::to64($inc); # S->t is $ctx[1] in our implementation # S->t[0] = ( uint64_t )( t >> 0 ); $ctx[1][0] = self::add64($ctx[1][0], $t); # S->t[1] += ( S->t[0] < inc ); if (self::flatten64($ctx[1][0]) < $inc) { $ctx[1][1] = self::add64($ctx[1][1], self::to64(1)); } } /** * @internal You should not use this directly from another application * * @param SplFixedArray $ctx * @param SplFixedArray $p * @param int $plen * @return void * @throws SodiumException * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress MixedAssignment * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArrayAssignment * @psalm-suppress MixedArrayOffset * @psalm-suppress MixedOperand */ public static function update(SplFixedArray $ctx, SplFixedArray $p, $plen) { self::pseudoConstructor(); $offset = 0; while ($plen > 0) { $left = $ctx[4]; $fill = 256 - $left; if ($plen > $fill) { # memcpy( S->buf + left, in, fill ); /* Fill buffer */ for ($i = $fill; $i--;) { $ctx[3][$i + $left] = $p[$i + $offset]; } # S->buflen += fill; $ctx[4] += $fill; # blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES ); self::increment_counter($ctx, 128); # blake2b_compress( S, S->buf ); /* Compress */ self::compress($ctx, $ctx[3]); # memcpy( S->buf, S->buf + BLAKE2B_BLOCKBYTES, BLAKE2B_BLOCKBYTES ); /* Shift buffer left */ for ($i = 128; $i--;) { $ctx[3][$i] = $ctx[3][$i + 128]; } # S->buflen -= BLAKE2B_BLOCKBYTES; $ctx[4] -= 128; # in += fill; $offset += $fill; # inlen -= fill; $plen -= $fill; } else { for ($i = $plen; $i--;) { $ctx[3][$i + $left] = $p[$i + $offset]; } $ctx[4] += $plen; $offset += $plen; $plen -= $plen; } } } /** * @internal You should not use this directly from another application * * @param SplFixedArray $ctx * @param SplFixedArray $out * @return SplFixedArray * @throws SodiumException * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress MixedAssignment * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArrayAssignment * @psalm-suppress MixedArrayOffset * @psalm-suppress MixedOperand */ public static function finish(SplFixedArray $ctx, SplFixedArray $out) { self::pseudoConstructor(); if ($ctx[4] > 128) { self::increment_counter($ctx, 128); self::compress($ctx, $ctx[3]); $ctx[4] -= 128; if ($ctx[4] > 128) { throw new SodiumException('Failed to assert that buflen <= 128 bytes'); } for ($i = $ctx[4]; $i--;) { $ctx[3][$i] = $ctx[3][$i + 128]; } } self::increment_counter($ctx, $ctx[4]); $ctx[2][0] = self::new64(0xffffffff, 0xffffffff); for ($i = 256 - $ctx[4]; $i--;) { $ctx[3][$i+$ctx[4]] = 0; } self::compress($ctx, $ctx[3]); $i = (int) (($out->getSize() - 1) / 8); for (; $i >= 0; --$i) { self::store64($out, $i << 3, $ctx[0][$i]); } return $out; } /** * @internal You should not use this directly from another application * * @param SplFixedArray|null $key * @param int $outlen * @param SplFixedArray|null $salt * @param SplFixedArray|null $personal * @return SplFixedArray * @throws SodiumException * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress MixedAssignment * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArrayAssignment * @psalm-suppress MixedArrayOffset */ public static function init( $key = null, $outlen = 64, $salt = null, $personal = null ) { self::pseudoConstructor(); $klen = 0; if ($key !== null) { if (count($key) > 64) { throw new SodiumException('Invalid key size'); } $klen = count($key); } if ($outlen > 64) { throw new SodiumException('Invalid output size'); } $ctx = self::context(); $p = new SplFixedArray(64); // Zero our param buffer... for ($i = 64; --$i;) { $p[$i] = 0; } $p[0] = $outlen; // digest_length $p[1] = $klen; // key_length $p[2] = 1; // fanout $p[3] = 1; // depth if ($salt instanceof SplFixedArray) { // salt: [32] through [47] for ($i = 0; $i < 16; ++$i) { $p[32 + $i] = (int) $salt[$i]; } } if ($personal instanceof SplFixedArray) { // personal: [48] through [63] for ($i = 0; $i < 16; ++$i) { $p[48 + $i] = (int) $personal[$i]; } } $ctx[0][0] = self::xor64( $ctx[0][0], self::load64($p, 0) ); if ($salt instanceof SplFixedArray || $personal instanceof SplFixedArray) { // We need to do what blake2b_init_param() does: for ($i = 1; $i < 8; ++$i) { $ctx[0][$i] = self::xor64( $ctx[0][$i], self::load64($p, $i << 3) ); } } if ($klen > 0 && $key instanceof SplFixedArray) { $block = new SplFixedArray(128); for ($i = 128; $i--;) { $block[$i] = 0; } for ($i = $klen; $i--;) { $block[$i] = $key[$i]; } self::update($ctx, $block, 128); $ctx[4] = 128; } return $ctx; } /** * Convert a string into an SplFixedArray of integers * * @internal You should not use this directly from another application * * @param string $str * @return SplFixedArray * @psalm-suppress MixedArgumentTypeCoercion */ public static function stringToSplFixedArray($str = '') { $values = unpack('C*', $str); return SplFixedArray::fromArray(array_values($values)); } /** * Convert an SplFixedArray of integers into a string * * @internal You should not use this directly from another application * * @param SplFixedArray $a * @return string * @throws TypeError */ public static function SplFixedArrayToString(SplFixedArray $a) { /** * @var array<int, int|string> $arr */ $arr = $a->toArray(); $c = $a->count(); array_unshift($arr, str_repeat('C', $c)); return (string) (call_user_func_array('pack', $arr)); } /** * @internal You should not use this directly from another application * * @param SplFixedArray $ctx * @return string * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress MixedAssignment * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArrayAssignment * @psalm-suppress MixedArrayOffset * @psalm-suppress MixedMethodCall */ public static function contextToString(SplFixedArray $ctx) { $str = ''; /** @var array<int, array<int, int>> $ctxA */ $ctxA = $ctx[0]->toArray(); # uint64_t h[8]; for ($i = 0; $i < 8; ++$i) { $str .= self::store32_le($ctxA[$i][1]); $str .= self::store32_le($ctxA[$i][0]); } # uint64_t t[2]; # uint64_t f[2]; for ($i = 1; $i < 3; ++$i) { $ctxA = $ctx[$i]->toArray(); $str .= self::store32_le($ctxA[0][1]); $str .= self::store32_le($ctxA[0][0]); $str .= self::store32_le($ctxA[1][1]); $str .= self::store32_le($ctxA[1][0]); } # uint8_t buf[2 * 128]; $str .= self::SplFixedArrayToString($ctx[3]); /** @var int $ctx4 */ $ctx4 = (int) $ctx[4]; # size_t buflen; $str .= implode('', array( self::intToChr($ctx4 & 0xff), self::intToChr(($ctx4 >> 8) & 0xff), self::intToChr(($ctx4 >> 16) & 0xff), self::intToChr(($ctx4 >> 24) & 0xff), self::intToChr(($ctx4 >> 32) & 0xff), self::intToChr(($ctx4 >> 40) & 0xff), self::intToChr(($ctx4 >> 48) & 0xff), self::intToChr(($ctx4 >> 56) & 0xff) )); # uint8_t last_node; return $str . self::intToChr($ctx[5]) . str_repeat("\x00", 23); } /** * Creates an SplFixedArray containing other SplFixedArray elements, from * a string (compatible with \Sodium\crypto_generichash_{init, update, final}) * * @internal You should not use this directly from another application * * @param string $string * @return SplFixedArray * @throws SodiumException * @throws TypeError * @psalm-suppress MixedArrayAssignment */ public static function stringToContext($string) { $ctx = self::context(); # uint64_t h[8]; for ($i = 0; $i < 8; ++$i) { $ctx[0][$i] = SplFixedArray::fromArray( array( self::load_4( self::substr($string, (($i << 3) + 4), 4) ), self::load_4( self::substr($string, (($i << 3) + 0), 4) ) ) ); } # uint64_t t[2]; # uint64_t f[2]; for ($i = 1; $i < 3; ++$i) { $ctx[$i][1] = SplFixedArray::fromArray( array( self::load_4(self::substr($string, 76 + (($i - 1) << 4), 4)), self::load_4(self::substr($string, 72 + (($i - 1) << 4), 4)) ) ); $ctx[$i][0] = SplFixedArray::fromArray( array( self::load_4(self::substr($string, 68 + (($i - 1) << 4), 4)), self::load_4(self::substr($string, 64 + (($i - 1) << 4), 4)) ) ); } # uint8_t buf[2 * 128]; $ctx[3] = self::stringToSplFixedArray(self::substr($string, 96, 256)); # uint8_t buf[2 * 128]; $int = 0; for ($i = 0; $i < 8; ++$i) { $int |= self::chrToInt($string[352 + $i]) << ($i << 3); } $ctx[4] = $int; return $ctx; } } AEGIS128L.php 0000644 00000007124 14721742304 0006464 0 ustar 00 <?php if (!defined('SODIUM_COMPAT_AEGIS_C0')) { define('SODIUM_COMPAT_AEGIS_C0', "\x00\x01\x01\x02\x03\x05\x08\x0d\x15\x22\x37\x59\x90\xe9\x79\x62"); } if (!defined('SODIUM_COMPAT_AEGIS_C1')) { define('SODIUM_COMPAT_AEGIS_C1', "\xdb\x3d\x18\x55\x6d\xc2\x2f\xf1\x20\x11\x31\x42\x73\xb5\x28\xdd"); } class ParagonIE_Sodium_Core_AEGIS128L extends ParagonIE_Sodium_Core_AES { /** * @param string $ct * @param string $tag * @param string $ad * @param string $key * @param string $nonce * @return string * @throws SodiumException */ public static function decrypt($ct, $tag, $ad, $key, $nonce) { $state = self::init($key, $nonce); $ad_blocks = (self::strlen($ad) + 31) >> 5; for ($i = 0; $i < $ad_blocks; ++$i) { $ai = self::substr($ad, $i << 5, 32); if (self::strlen($ai) < 32) { $ai = str_pad($ai, 32, "\0", STR_PAD_RIGHT); } $state->absorb($ai); } $msg = ''; $cn = self::strlen($ct) & 31; $ct_blocks = self::strlen($ct) >> 5; for ($i = 0; $i < $ct_blocks; ++$i) { $msg .= $state->dec(self::substr($ct, $i << 5, 32)); } if ($cn) { $start = $ct_blocks << 5; $msg .= $state->decPartial(self::substr($ct, $start, $cn)); } $expected_tag = $state->finalize( self::strlen($ad) << 3, self::strlen($msg) << 3 ); if (!self::hashEquals($expected_tag, $tag)) { try { // The RFC says to erase msg, so we shall try: ParagonIE_Sodium_Compat::memzero($msg); } catch (SodiumException $ex) { // Do nothing if we cannot memzero } throw new SodiumException('verification failed'); } return $msg; } /** * @param string $msg * @param string $ad * @param string $key * @param string $nonce * @return array * * @throws SodiumException */ public static function encrypt($msg, $ad, $key, $nonce) { $state = self::init($key, $nonce); // ad_blocks = Split(ZeroPad(ad, 256), 256) // for ai in ad_blocks: // Absorb(ai) $ad_len = self::strlen($ad); $msg_len = self::strlen($msg); $ad_blocks = ($ad_len + 31) >> 5; for ($i = 0; $i < $ad_blocks; ++$i) { $ai = self::substr($ad, $i << 5, 32); if (self::strlen($ai) < 32) { $ai = str_pad($ai, 32, "\0", STR_PAD_RIGHT); } $state->absorb($ai); } // msg_blocks = Split(ZeroPad(msg, 256), 256) // for xi in msg_blocks: // ct = ct || Enc(xi) $ct = ''; $msg_blocks = ($msg_len + 31) >> 5; for ($i = 0; $i < $msg_blocks; ++$i) { $xi = self::substr($msg, $i << 5, 32); if (self::strlen($xi) < 32) { $xi = str_pad($xi, 32, "\0", STR_PAD_RIGHT); } $ct .= $state->enc($xi); } // tag = Finalize(|ad|, |msg|) // ct = Truncate(ct, |msg|) $tag = $state->finalize( $ad_len << 3, $msg_len << 3 ); // return ct and tag return array( self::substr($ct, 0, $msg_len), $tag ); } /** * @param string $key * @param string $nonce * @return ParagonIE_Sodium_Core_AEGIS_State128L */ public static function init($key, $nonce) { return ParagonIE_Sodium_Core_AEGIS_State128L::init($key, $nonce); } } AEGIS/State256.php 0000644 00000014575 14721742304 0007442 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_AEGIS_State256', false)) { return; } if (!defined('SODIUM_COMPAT_AEGIS_C0')) { define('SODIUM_COMPAT_AEGIS_C0', "\x00\x01\x01\x02\x03\x05\x08\x0d\x15\x22\x37\x59\x90\xe9\x79\x62"); } if (!defined('SODIUM_COMPAT_AEGIS_C1')) { define('SODIUM_COMPAT_AEGIS_C1', "\xdb\x3d\x18\x55\x6d\xc2\x2f\xf1\x20\x11\x31\x42\x73\xb5\x28\xdd"); } class ParagonIE_Sodium_Core_AEGIS_State256 { /** @var array<int, string> $state */ protected $state; public function __construct() { $this->state = array_fill(0, 6, ''); } /** * @internal Only use this for unit tests! * @return string[] */ public function getState() { return array_values($this->state); } /** * @param array $input * @return self * @throws SodiumException * * @internal Only for unit tests */ public static function initForUnitTests(array $input) { if (count($input) < 6) { throw new SodiumException('invalid input'); } $state = new self(); for ($i = 0; $i < 6; ++$i) { $state->state[$i] = $input[$i]; } return $state; } /** * @param string $key * @param string $nonce * @return self */ public static function init($key, $nonce) { $state = new self(); $k0 = ParagonIE_Sodium_Core_Util::substr($key, 0, 16); $k1 = ParagonIE_Sodium_Core_Util::substr($key, 16, 16); $n0 = ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16); $n1 = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 16); // S0 = k0 ^ n0 // S1 = k1 ^ n1 // S2 = C1 // S3 = C0 // S4 = k0 ^ C0 // S5 = k1 ^ C1 $k0_n0 = $k0 ^ $n0; $k1_n1 = $k1 ^ $n1; $state->state[0] = $k0_n0; $state->state[1] = $k1_n1; $state->state[2] = SODIUM_COMPAT_AEGIS_C1; $state->state[3] = SODIUM_COMPAT_AEGIS_C0; $state->state[4] = $k0 ^ SODIUM_COMPAT_AEGIS_C0; $state->state[5] = $k1 ^ SODIUM_COMPAT_AEGIS_C1; // Repeat(4, // Update(k0) // Update(k1) // Update(k0 ^ n0) // Update(k1 ^ n1) // ) for ($i = 0; $i < 4; ++$i) { $state->update($k0); $state->update($k1); $state->update($k0 ^ $n0); $state->update($k1 ^ $n1); } return $state; } /** * @param string $ai * @return self * @throws SodiumException */ public function absorb($ai) { if (ParagonIE_Sodium_Core_Util::strlen($ai) !== 16) { throw new SodiumException('Input must be an AES block in size'); } return $this->update($ai); } /** * @param string $ci * @return string * @throws SodiumException */ public function dec($ci) { if (ParagonIE_Sodium_Core_Util::strlen($ci) !== 16) { throw new SodiumException('Input must be an AES block in size'); } // z = S1 ^ S4 ^ S5 ^ (S2 & S3) $z = $this->state[1] ^ $this->state[4] ^ $this->state[5] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); $xi = $ci ^ $z; $this->update($xi); return $xi; } /** * @param string $cn * @return string */ public function decPartial($cn) { $len = ParagonIE_Sodium_Core_Util::strlen($cn); // z = S1 ^ S4 ^ S5 ^ (S2 & S3) $z = $this->state[1] ^ $this->state[4] ^ $this->state[5] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); // t = ZeroPad(cn, 128) $t = str_pad($cn, 16, "\0", STR_PAD_RIGHT); // out = t ^ z $out = $t ^ $z; // xn = Truncate(out, |cn|) $xn = ParagonIE_Sodium_Core_Util::substr($out, 0, $len); // v = ZeroPad(xn, 128) $v = str_pad($xn, 16, "\0", STR_PAD_RIGHT); // Update(v) $this->update($v); // return xn return $xn; } /** * @param string $xi * @return string * @throws SodiumException */ public function enc($xi) { if (ParagonIE_Sodium_Core_Util::strlen($xi) !== 16) { throw new SodiumException('Input must be an AES block in size'); } // z = S1 ^ S4 ^ S5 ^ (S2 & S3) $z = $this->state[1] ^ $this->state[4] ^ $this->state[5] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); $this->update($xi); return $xi ^ $z; } /** * @param int $ad_len_bits * @param int $msg_len_bits * @return string */ public function finalize($ad_len_bits, $msg_len_bits) { $encoded = ParagonIE_Sodium_Core_Util::store64_le($ad_len_bits) . ParagonIE_Sodium_Core_Util::store64_le($msg_len_bits); $t = $this->state[3] ^ $encoded; for ($i = 0; $i < 7; ++$i) { $this->update($t); } return ($this->state[0] ^ $this->state[1] ^ $this->state[2]) . ($this->state[3] ^ $this->state[4] ^ $this->state[5]); } /** * @param string $m * @return self */ public function update($m) { /* S'0 = AESRound(S5, S0 ^ M) S'1 = AESRound(S0, S1) S'2 = AESRound(S1, S2) S'3 = AESRound(S2, S3) S'4 = AESRound(S3, S4) S'5 = AESRound(S4, S5) */ list($s_0, $s_1) = ParagonIE_Sodium_Core_AES::doubleRound( $this->state[5],$this->state[0] ^ $m, $this->state[0], $this->state[1] ); list($s_2, $s_3) = ParagonIE_Sodium_Core_AES::doubleRound( $this->state[1], $this->state[2], $this->state[2], $this->state[3] ); list($s_4, $s_5) = ParagonIE_Sodium_Core_AES::doubleRound( $this->state[3], $this->state[4], $this->state[4], $this->state[5] ); /* S0 = S'0 S1 = S'1 S2 = S'2 S3 = S'3 S4 = S'4 S5 = S'5 */ $this->state[0] = $s_0; $this->state[1] = $s_1; $this->state[2] = $s_2; $this->state[3] = $s_3; $this->state[4] = $s_4; $this->state[5] = $s_5; return $this; } } AEGIS/State128L.php 0000644 00000020052 14721742304 0007537 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_AEGIS_State128L', false)) { return; } if (!defined('SODIUM_COMPAT_AEGIS_C0')) { define('SODIUM_COMPAT_AEGIS_C0', "\x00\x01\x01\x02\x03\x05\x08\x0d\x15\x22\x37\x59\x90\xe9\x79\x62"); } if (!defined('SODIUM_COMPAT_AEGIS_C1')) { define('SODIUM_COMPAT_AEGIS_C1', "\xdb\x3d\x18\x55\x6d\xc2\x2f\xf1\x20\x11\x31\x42\x73\xb5\x28\xdd"); } class ParagonIE_Sodium_Core_AEGIS_State128L { /** @var array<int, string> $state */ protected $state; public function __construct() { $this->state = array_fill(0, 8, ''); } /** * @internal Only use this for unit tests! * @return string[] */ public function getState() { return array_values($this->state); } /** * @param array $input * @return self * @throws SodiumException * * @internal Only for unit tests */ public static function initForUnitTests(array $input) { if (count($input) < 8) { throw new SodiumException('invalid input'); } $state = new self(); for ($i = 0; $i < 8; ++$i) { $state->state[$i] = $input[$i]; } return $state; } /** * @param string $key * @param string $nonce * @return self */ public static function init($key, $nonce) { $state = new self(); // S0 = key ^ nonce $state->state[0] = $key ^ $nonce; // S1 = C1 $state->state[1] = SODIUM_COMPAT_AEGIS_C1; // S2 = C0 $state->state[2] = SODIUM_COMPAT_AEGIS_C0; // S3 = C1 $state->state[3] = SODIUM_COMPAT_AEGIS_C1; // S4 = key ^ nonce $state->state[4] = $key ^ $nonce; // S5 = key ^ C0 $state->state[5] = $key ^ SODIUM_COMPAT_AEGIS_C0; // S6 = key ^ C1 $state->state[6] = $key ^ SODIUM_COMPAT_AEGIS_C1; // S7 = key ^ C0 $state->state[7] = $key ^ SODIUM_COMPAT_AEGIS_C0; // Repeat(10, Update(nonce, key)) for ($i = 0; $i < 10; ++$i) { $state->update($nonce, $key); } return $state; } /** * @param string $ai * @return self */ public function absorb($ai) { if (ParagonIE_Sodium_Core_Util::strlen($ai) !== 32) { throw new SodiumException('Input must be two AES blocks in size'); } $t0 = ParagonIE_Sodium_Core_Util::substr($ai, 0, 16); $t1 = ParagonIE_Sodium_Core_Util::substr($ai, 16, 16); return $this->update($t0, $t1); } /** * @param string $ci * @return string * @throws SodiumException */ public function dec($ci) { if (ParagonIE_Sodium_Core_Util::strlen($ci) !== 32) { throw new SodiumException('Input must be two AES blocks in size'); } // z0 = S6 ^ S1 ^ (S2 & S3) $z0 = $this->state[6] ^ $this->state[1] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); // z1 = S2 ^ S5 ^ (S6 & S7) $z1 = $this->state[2] ^ $this->state[5] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); // t0, t1 = Split(xi, 128) $t0 = ParagonIE_Sodium_Core_Util::substr($ci, 0, 16); $t1 = ParagonIE_Sodium_Core_Util::substr($ci, 16, 16); // out0 = t0 ^ z0 // out1 = t1 ^ z1 $out0 = $t0 ^ $z0; $out1 = $t1 ^ $z1; // Update(out0, out1) // xi = out0 || out1 $this->update($out0, $out1); return $out0 . $out1; } /** * @param string $cn * @return string */ public function decPartial($cn) { $len = ParagonIE_Sodium_Core_Util::strlen($cn); // z0 = S6 ^ S1 ^ (S2 & S3) $z0 = $this->state[6] ^ $this->state[1] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); // z1 = S2 ^ S5 ^ (S6 & S7) $z1 = $this->state[2] ^ $this->state[5] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); // t0, t1 = Split(ZeroPad(cn, 256), 128) $cn = str_pad($cn, 32, "\0", STR_PAD_RIGHT); $t0 = ParagonIE_Sodium_Core_Util::substr($cn, 0, 16); $t1 = ParagonIE_Sodium_Core_Util::substr($cn, 16, 16); // out0 = t0 ^ z0 // out1 = t1 ^ z1 $out0 = $t0 ^ $z0; $out1 = $t1 ^ $z1; // xn = Truncate(out0 || out1, |cn|) $xn = ParagonIE_Sodium_Core_Util::substr($out0 . $out1, 0, $len); // v0, v1 = Split(ZeroPad(xn, 256), 128) $padded = str_pad($xn, 32, "\0", STR_PAD_RIGHT); $v0 = ParagonIE_Sodium_Core_Util::substr($padded, 0, 16); $v1 = ParagonIE_Sodium_Core_Util::substr($padded, 16, 16); // Update(v0, v1) $this->update($v0, $v1); // return xn return $xn; } /** * @param string $xi * @return string * @throws SodiumException */ public function enc($xi) { if (ParagonIE_Sodium_Core_Util::strlen($xi) !== 32) { throw new SodiumException('Input must be two AES blocks in size'); } // z0 = S6 ^ S1 ^ (S2 & S3) $z0 = $this->state[6] ^ $this->state[1] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); // z1 = S2 ^ S5 ^ (S6 & S7) $z1 = $this->state[2] ^ $this->state[5] ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); // t0, t1 = Split(xi, 128) $t0 = ParagonIE_Sodium_Core_Util::substr($xi, 0, 16); $t1 = ParagonIE_Sodium_Core_Util::substr($xi, 16, 16); // out0 = t0 ^ z0 // out1 = t1 ^ z1 $out0 = $t0 ^ $z0; $out1 = $t1 ^ $z1; // Update(t0, t1) // ci = out0 || out1 $this->update($t0, $t1); // return ci return $out0 . $out1; } /** * @param int $ad_len_bits * @param int $msg_len_bits * @return string */ public function finalize($ad_len_bits, $msg_len_bits) { $encoded = ParagonIE_Sodium_Core_Util::store64_le($ad_len_bits) . ParagonIE_Sodium_Core_Util::store64_le($msg_len_bits); $t = $this->state[2] ^ $encoded; for ($i = 0; $i < 7; ++$i) { $this->update($t, $t); } return ($this->state[0] ^ $this->state[1] ^ $this->state[2] ^ $this->state[3]) . ($this->state[4] ^ $this->state[5] ^ $this->state[6] ^ $this->state[7]); } /** * @param string $m0 * @param string $m1 * @return self */ public function update($m0, $m1) { /* S'0 = AESRound(S7, S0 ^ M0) S'1 = AESRound(S0, S1) S'2 = AESRound(S1, S2) S'3 = AESRound(S2, S3) S'4 = AESRound(S3, S4 ^ M1) S'5 = AESRound(S4, S5) S'6 = AESRound(S5, S6) S'7 = AESRound(S6, S7) */ list($s_0, $s_1) = ParagonIE_Sodium_Core_AES::doubleRound( $this->state[7], $this->state[0] ^ $m0, $this->state[0], $this->state[1] ); list($s_2, $s_3) = ParagonIE_Sodium_Core_AES::doubleRound( $this->state[1], $this->state[2], $this->state[2], $this->state[3] ); list($s_4, $s_5) = ParagonIE_Sodium_Core_AES::doubleRound( $this->state[3], $this->state[4] ^ $m1, $this->state[4], $this->state[5] ); list($s_6, $s_7) = ParagonIE_Sodium_Core_AES::doubleRound( $this->state[5], $this->state[6], $this->state[6], $this->state[7] ); /* S0 = S'0 S1 = S'1 S2 = S'2 S3 = S'3 S4 = S'4 S5 = S'5 S6 = S'6 S7 = S'7 */ $this->state[0] = $s_0; $this->state[1] = $s_1; $this->state[2] = $s_2; $this->state[3] = $s_3; $this->state[4] = $s_4; $this->state[5] = $s_5; $this->state[6] = $s_6; $this->state[7] = $s_7; return $this; } } Base64/Original.php 0000644 00000017055 14721742304 0010061 0 ustar 00 <?php /** * Class ParagonIE_Sodium_Core_Base64 * * Copyright (c) 2016 - 2018 Paragon Initiative Enterprises. * Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) */ class ParagonIE_Sodium_Core_Base64_Original { // COPY ParagonIE_Sodium_Core_Base64_Common STARTING HERE /** * Encode into Base64 * * Base64 character set "[A-Z][a-z][0-9]+/" * * @param string $src * @return string * @throws TypeError */ public static function encode($src) { return self::doEncode($src, true); } /** * Encode into Base64, no = padding * * Base64 character set "[A-Z][a-z][0-9]+/" * * @param string $src * @return string * @throws TypeError */ public static function encodeUnpadded($src) { return self::doEncode($src, false); } /** * @param string $src * @param bool $pad Include = padding? * @return string * @throws TypeError */ protected static function doEncode($src, $pad = true) { $dest = ''; $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); // Main loop (no padding): for ($i = 0; $i + 3 <= $srcLen; $i += 3) { /** @var array<int, int> $chunk */ $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 3)); $b0 = $chunk[1]; $b1 = $chunk[2]; $b2 = $chunk[3]; $dest .= self::encode6Bits( $b0 >> 2 ) . self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . self::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . self::encode6Bits( $b2 & 63); } // The last chunk, which may have padding: if ($i < $srcLen) { /** @var array<int, int> $chunk */ $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); $b0 = $chunk[1]; if ($i + 1 < $srcLen) { $b1 = $chunk[2]; $dest .= self::encode6Bits($b0 >> 2) . self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . self::encode6Bits(($b1 << 2) & 63); if ($pad) { $dest .= '='; } } else { $dest .= self::encode6Bits( $b0 >> 2) . self::encode6Bits(($b0 << 4) & 63); if ($pad) { $dest .= '=='; } } } return $dest; } /** * decode from base64 into binary * * Base64 character set "./[A-Z][a-z][0-9]" * * @param string $src * @param bool $strictPadding * @return string * @throws RangeException * @throws TypeError * @psalm-suppress RedundantCondition */ public static function decode($src, $strictPadding = false) { // Remove padding $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); if ($srcLen === 0) { return ''; } if ($strictPadding) { if (($srcLen & 3) === 0) { if ($src[$srcLen - 1] === '=') { $srcLen--; if ($src[$srcLen - 1] === '=') { $srcLen--; } } } if (($srcLen & 3) === 1) { throw new RangeException( 'Incorrect padding' ); } if ($src[$srcLen - 1] === '=') { throw new RangeException( 'Incorrect padding' ); } } else { $src = rtrim($src, '='); $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); } $err = 0; $dest = ''; // Main loop (no padding): for ($i = 0; $i + 4 <= $srcLen; $i += 4) { /** @var array<int, int> $chunk */ $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 4)); $c0 = self::decode6Bits($chunk[1]); $c1 = self::decode6Bits($chunk[2]); $c2 = self::decode6Bits($chunk[3]); $c3 = self::decode6Bits($chunk[4]); $dest .= pack( 'CCC', ((($c0 << 2) | ($c1 >> 4)) & 0xff), ((($c1 << 4) | ($c2 >> 2)) & 0xff), ((($c2 << 6) | $c3) & 0xff) ); $err |= ($c0 | $c1 | $c2 | $c3) >> 8; } // The last chunk, which may have padding: if ($i < $srcLen) { /** @var array<int, int> $chunk */ $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); $c0 = self::decode6Bits($chunk[1]); if ($i + 2 < $srcLen) { $c1 = self::decode6Bits($chunk[2]); $c2 = self::decode6Bits($chunk[3]); $dest .= pack( 'CC', ((($c0 << 2) | ($c1 >> 4)) & 0xff), ((($c1 << 4) | ($c2 >> 2)) & 0xff) ); $err |= ($c0 | $c1 | $c2) >> 8; } elseif ($i + 1 < $srcLen) { $c1 = self::decode6Bits($chunk[2]); $dest .= pack( 'C', ((($c0 << 2) | ($c1 >> 4)) & 0xff) ); $err |= ($c0 | $c1) >> 8; } elseif ($i < $srcLen && $strictPadding) { $err |= 1; } } /** @var bool $check */ $check = ($err === 0); if (!$check) { throw new RangeException( 'Base64::decode() only expects characters in the correct base64 alphabet' ); } return $dest; } // COPY ParagonIE_Sodium_Core_Base64_Common ENDING HERE /** * Uses bitwise operators instead of table-lookups to turn 6-bit integers * into 8-bit integers. * * Base64 character set: * [A-Z] [a-z] [0-9] + / * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f * * @param int $src * @return int */ protected static function decode6Bits($src) { $ret = -1; // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); // if ($src == 0x2b) $ret += 62 + 1; $ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63; // if ($src == 0x2f) ret += 63 + 1; $ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64; return $ret; } /** * Uses bitwise operators instead of table-lookups to turn 8-bit integers * into 6-bit integers. * * @param int $src * @return string */ protected static function encode6Bits($src) { $diff = 0x41; // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 $diff += ((25 - $src) >> 8) & 6; // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 $diff -= ((51 - $src) >> 8) & 75; // if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15 $diff -= ((61 - $src) >> 8) & 15; // if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3 $diff += ((62 - $src) >> 8) & 3; return pack('C', $src + $diff); } } Base64/UrlSafe.php 0000644 00000017063 14721742304 0007655 0 ustar 00 <?php /** * Class ParagonIE_Sodium_Core_Base64UrlSafe * * Copyright (c) 2016 - 2018 Paragon Initiative Enterprises. * Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) */ class ParagonIE_Sodium_Core_Base64_UrlSafe { // COPY ParagonIE_Sodium_Core_Base64_Common STARTING HERE /** * Encode into Base64 * * Base64 character set "[A-Z][a-z][0-9]+/" * * @param string $src * @return string * @throws TypeError */ public static function encode($src) { return self::doEncode($src, true); } /** * Encode into Base64, no = padding * * Base64 character set "[A-Z][a-z][0-9]+/" * * @param string $src * @return string * @throws TypeError */ public static function encodeUnpadded($src) { return self::doEncode($src, false); } /** * @param string $src * @param bool $pad Include = padding? * @return string * @throws TypeError */ protected static function doEncode($src, $pad = true) { $dest = ''; $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); // Main loop (no padding): for ($i = 0; $i + 3 <= $srcLen; $i += 3) { /** @var array<int, int> $chunk */ $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 3)); $b0 = $chunk[1]; $b1 = $chunk[2]; $b2 = $chunk[3]; $dest .= self::encode6Bits( $b0 >> 2 ) . self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . self::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . self::encode6Bits( $b2 & 63); } // The last chunk, which may have padding: if ($i < $srcLen) { /** @var array<int, int> $chunk */ $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); $b0 = $chunk[1]; if ($i + 1 < $srcLen) { $b1 = $chunk[2]; $dest .= self::encode6Bits($b0 >> 2) . self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . self::encode6Bits(($b1 << 2) & 63); if ($pad) { $dest .= '='; } } else { $dest .= self::encode6Bits( $b0 >> 2) . self::encode6Bits(($b0 << 4) & 63); if ($pad) { $dest .= '=='; } } } return $dest; } /** * decode from base64 into binary * * Base64 character set "./[A-Z][a-z][0-9]" * * @param string $src * @param bool $strictPadding * @return string * @throws RangeException * @throws TypeError * @psalm-suppress RedundantCondition */ public static function decode($src, $strictPadding = false) { // Remove padding $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); if ($srcLen === 0) { return ''; } if ($strictPadding) { if (($srcLen & 3) === 0) { if ($src[$srcLen - 1] === '=') { $srcLen--; if ($src[$srcLen - 1] === '=') { $srcLen--; } } } if (($srcLen & 3) === 1) { throw new RangeException( 'Incorrect padding' ); } if ($src[$srcLen - 1] === '=') { throw new RangeException( 'Incorrect padding' ); } } else { $src = rtrim($src, '='); $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); } $err = 0; $dest = ''; // Main loop (no padding): for ($i = 0; $i + 4 <= $srcLen; $i += 4) { /** @var array<int, int> $chunk */ $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 4)); $c0 = self::decode6Bits($chunk[1]); $c1 = self::decode6Bits($chunk[2]); $c2 = self::decode6Bits($chunk[3]); $c3 = self::decode6Bits($chunk[4]); $dest .= pack( 'CCC', ((($c0 << 2) | ($c1 >> 4)) & 0xff), ((($c1 << 4) | ($c2 >> 2)) & 0xff), ((($c2 << 6) | $c3) & 0xff) ); $err |= ($c0 | $c1 | $c2 | $c3) >> 8; } // The last chunk, which may have padding: if ($i < $srcLen) { /** @var array<int, int> $chunk */ $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); $c0 = self::decode6Bits($chunk[1]); if ($i + 2 < $srcLen) { $c1 = self::decode6Bits($chunk[2]); $c2 = self::decode6Bits($chunk[3]); $dest .= pack( 'CC', ((($c0 << 2) | ($c1 >> 4)) & 0xff), ((($c1 << 4) | ($c2 >> 2)) & 0xff) ); $err |= ($c0 | $c1 | $c2) >> 8; } elseif ($i + 1 < $srcLen) { $c1 = self::decode6Bits($chunk[2]); $dest .= pack( 'C', ((($c0 << 2) | ($c1 >> 4)) & 0xff) ); $err |= ($c0 | $c1) >> 8; } elseif ($i < $srcLen && $strictPadding) { $err |= 1; } } /** @var bool $check */ $check = ($err === 0); if (!$check) { throw new RangeException( 'Base64::decode() only expects characters in the correct base64 alphabet' ); } return $dest; } // COPY ParagonIE_Sodium_Core_Base64_Common ENDING HERE /** * Uses bitwise operators instead of table-lookups to turn 6-bit integers * into 8-bit integers. * * Base64 character set: * [A-Z] [a-z] [0-9] + / * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f * * @param int $src * @return int */ protected static function decode6Bits($src) { $ret = -1; // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); // if ($src == 0x2c) $ret += 62 + 1; $ret += (((0x2c - $src) & ($src - 0x2e)) >> 8) & 63; // if ($src == 0x5f) ret += 63 + 1; $ret += (((0x5e - $src) & ($src - 0x60)) >> 8) & 64; return $ret; } /** * Uses bitwise operators instead of table-lookups to turn 8-bit integers * into 6-bit integers. * * @param int $src * @return string */ protected static function encode6Bits($src) { $diff = 0x41; // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 $diff += ((25 - $src) >> 8) & 6; // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 $diff -= ((51 - $src) >> 8) & 75; // if ($src > 61) $diff += 0x2d - 0x30 - 10; // -13 $diff -= ((61 - $src) >> 8) & 13; // if ($src > 62) $diff += 0x5f - 0x2b - 1; // 3 $diff += ((62 - $src) >> 8) & 49; return pack('C', $src + $diff); } } Curve25519/Fe.php 0000644 00000006021 14721742304 0007304 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Curve25519_Fe', false)) { return; } /** * Class ParagonIE_Sodium_Core_Curve25519_Fe * * This represents a Field Element */ class ParagonIE_Sodium_Core_Curve25519_Fe implements ArrayAccess { /** * @var array<int, int> */ protected $container = array(); /** * @var int */ protected $size = 10; /** * @internal You should not use this directly from another application * * @param array<int, int> $array * @param bool $save_indexes * @return self */ public static function fromArray($array, $save_indexes = null) { $count = count($array); if ($save_indexes) { $keys = array_keys($array); } else { $keys = range(0, $count - 1); } $array = array_values($array); /** @var array<int, int> $keys */ $obj = new ParagonIE_Sodium_Core_Curve25519_Fe(); if ($save_indexes) { for ($i = 0; $i < $count; ++$i) { $obj->offsetSet($keys[$i], $array[$i]); } } else { for ($i = 0; $i < $count; ++$i) { $obj->offsetSet($i, $array[$i]); } } return $obj; } /** * @internal You should not use this directly from another application * * @param int|null $offset * @param int $value * @return void * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if (!is_int($value)) { throw new InvalidArgumentException('Expected an integer'); } if (is_null($offset)) { $this->container[] = $value; } else { $this->container[$offset] = $value; } } /** * @internal You should not use this directly from another application * * @param int $offset * @return bool * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->container[$offset]); } /** * @internal You should not use this directly from another application * * @param int $offset * @return void * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->container[$offset]); } /** * @internal You should not use this directly from another application * * @param int $offset * @return int * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetGet($offset) { if (!isset($this->container[$offset])) { $this->container[$offset] = 0; } return (int) ($this->container[$offset]); } /** * @internal You should not use this directly from another application * * @return array */ public function __debugInfo() { return array(implode(', ', $this->container)); } } Curve25519/README.md 0000644 00000000332 14721742304 0007517 0 ustar 00 # Curve25519 Data Structures These are PHP implementation of the [structs used in the ref10 curve25519 code](https://github.com/jedisct1/libsodium/blob/master/src/libsodium/include/sodium/private/curve25519_ref10.h). Curve25519/Ge/Precomp.php 0000644 00000003562 14721742304 0010721 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_Precomp', false)) { return; } /** * Class ParagonIE_Sodium_Core_Curve25519_Ge_Precomp */ class ParagonIE_Sodium_Core_Curve25519_Ge_Precomp { /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $yplusx; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $yminusx; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $xy2d; /** * ParagonIE_Sodium_Core_Curve25519_Ge_Precomp constructor. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $yplusx * @param ParagonIE_Sodium_Core_Curve25519_Fe $yminusx * @param ParagonIE_Sodium_Core_Curve25519_Fe $xy2d */ public function __construct( $yplusx = null, $yminusx = null, $xy2d = null ) { if ($yplusx === null) { $yplusx = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($yplusx instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->yplusx = $yplusx; if ($yminusx === null) { $yminusx = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($yminusx instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->yminusx = $yminusx; if ($xy2d === null) { $xy2d = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($xy2d instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->xy2d = $xy2d; } } Curve25519/Ge/Cached.php 0000644 00000004502 14721742304 0010456 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_Cached', false)) { return; } /** * Class ParagonIE_Sodium_Core_Curve25519_Ge_Cached */ class ParagonIE_Sodium_Core_Curve25519_Ge_Cached { /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $YplusX; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $YminusX; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $Z; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $T2d; /** * ParagonIE_Sodium_Core_Curve25519_Ge_Cached constructor. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $YplusX * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $YminusX * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $Z * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $T2d */ public function __construct( $YplusX = null, $YminusX = null, $Z = null, $T2d = null ) { if ($YplusX === null) { $YplusX = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($YplusX instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->YplusX = $YplusX; if ($YminusX === null) { $YminusX = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($YminusX instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->YminusX = $YminusX; if ($Z === null) { $Z = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($Z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->Z = $Z; if ($T2d === null) { $T2d = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($T2d instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 4 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->T2d = $T2d; } } Curve25519/Ge/P1p1.php 0000644 00000004321 14721742304 0010027 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_P1p1', false)) { return; } /** * Class ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 */ class ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 { /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $X; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $Y; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $Z; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $T; /** * ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 constructor. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $x * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $y * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $z * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $t */ public function __construct( $x = null, $y = null, $z = null, $t = null ) { if ($x === null) { $x = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($x instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->X = $x; if ($y === null) { $y = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($y instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->Y = $y; if ($z === null) { $z = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->Z = $z; if ($t === null) { $t = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($t instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 4 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->T = $t; } } Curve25519/Ge/P3.php 0000644 00000004312 14721742304 0007570 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_P3', false)) { return; } /** * Class ParagonIE_Sodium_Core_Curve25519_Ge_P3 */ class ParagonIE_Sodium_Core_Curve25519_Ge_P3 { /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $X; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $Y; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $Z; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $T; /** * ParagonIE_Sodium_Core_Curve25519_Ge_P3 constructor. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $x * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $y * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $z * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $t */ public function __construct( $x = null, $y = null, $z = null, $t = null ) { if ($x === null) { $x = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($x instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->X = $x; if ($y === null) { $y = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($y instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->Y = $y; if ($z === null) { $z = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->Z = $z; if ($t === null) { $t = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($t instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 4 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->T = $t; } } Curve25519/Ge/P2.php 0000644 00000003375 14721742304 0007577 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_P2', false)) { return; } /** * Class ParagonIE_Sodium_Core_Curve25519_Ge_P2 */ class ParagonIE_Sodium_Core_Curve25519_Ge_P2 { /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $X; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $Y; /** * @var ParagonIE_Sodium_Core_Curve25519_Fe */ public $Z; /** * ParagonIE_Sodium_Core_Curve25519_Ge_P2 constructor. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $x * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $y * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $z */ public function __construct( $x = null, $y = null, $z = null ) { if ($x === null) { $x = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($x instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->X = $x; if ($y === null) { $y = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($y instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->Y = $y; if ($z === null) { $z = new ParagonIE_Sodium_Core_Curve25519_Fe(); } if (!($z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); } $this->Z = $z; } } Curve25519/H.php 0000644 00000327571 14721742304 0007161 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Curve25519_H', false)) { return; } /** * Class ParagonIE_Sodium_Core_Curve25519_H * * This just contains the constants in the ref10/base.h file */ class ParagonIE_Sodium_Core_Curve25519_H extends ParagonIE_Sodium_Core_Util { /** * See: libsodium's crypto_core/curve25519/ref10/base.h * * @var array<int, array<int, array<int, array<int, int>>>> Basically, int[32][8][3][10] */ protected static $base = array( array( array( array(25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605), array(-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378), array(-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546), ), array( array(-12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303), array(-21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081), array(26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697), ), array( array(15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024), array(16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574), array(30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357), ), array( array(-17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540), array(23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397), array(7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325), ), array( array(10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380), array(4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306), array(19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942), ), array( array(-15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777), array(-8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737), array(-18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652), ), array( array(5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766), array(-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701), array(28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300), ), array( array(14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726), array(-7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955), array(27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425), ), ), array( array( array(-13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171), array(27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510), array(17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660), ), array( array(-10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639), array(29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963), array(5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950), ), array( array(-27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568), array(12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335), array(25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628), ), array( array(-26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007), array(-2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772), array(-22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653), ), array( array(2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567), array(13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686), array(21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372), ), array( array(-13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887), array(-23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954), array(-29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953), ), array( array(24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833), array(-16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532), array(-22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876), ), array( array(2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268), array(33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214), array(1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038), ), ), array( array( array(6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800), array(4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645), array(-4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664), ), array( array(1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933), array(-25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182), array(-17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222), ), array( array(-18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991), array(20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880), array(9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092), ), array( array(-16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295), array(19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788), array(8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553), ), array( array(-15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026), array(11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347), array(-18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033), ), array( array(-23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395), array(-27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278), array(1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890), ), array( array(32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995), array(-30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596), array(-11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891), ), array( array(31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060), array(11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608), array(-20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606), ), ), array( array( array(7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389), array(-19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016), array(-11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341), ), array( array(-22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505), array(14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553), array(-28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655), ), array( array(15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220), array(12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631), array(-4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099), ), array( array(26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556), array(14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749), array(236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930), ), array( array(1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391), array(5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253), array(20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066), ), array( array(24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958), array(-11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082), array(-28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383), ), array( array(-30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521), array(-11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807), array(23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948), ), array( array(9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134), array(-32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455), array(27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629), ), ), array( array( array(-8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069), array(-32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746), array(24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919), ), array( array(11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837), array(8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906), array(-28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771), ), array( array(-25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817), array(10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098), array(10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409), ), array( array(-12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504), array(-26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727), array(28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420), ), array( array(-32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003), array(-1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605), array(-30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384), ), array( array(-26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701), array(-23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683), array(29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708), ), array( array(-3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563), array(-19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260), array(-5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387), ), array( array(-19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672), array(23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686), array(-24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665), ), ), array( array( array(11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182), array(-31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277), array(14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628), ), array( array(-4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474), array(-26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539), array(-25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822), ), array( array(-10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970), array(19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756), array(-24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508), ), array( array(-26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683), array(-10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655), array(-20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158), ), array( array(-4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125), array(-15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839), array(-20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664), ), array( array(27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294), array(-18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899), array(-11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070), ), array( array(3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294), array(-15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949), array(-21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083), ), array( array(31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420), array(-5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940), array(29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396), ), ), array( array( array(-12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567), array(20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127), array(-16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294), ), array( array(-12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887), array(22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964), array(16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195), ), array( array(9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244), array(24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999), array(-1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762), ), array( array(-18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274), array(-33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236), array(-16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605), ), array( array(-13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761), array(-22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884), array(-6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482), ), array( array(-24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638), array(-11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490), array(-32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170), ), array( array(5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736), array(10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124), array(-17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392), ), array( array(8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029), array(6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048), array(28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958), ), ), array( array( array(24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593), array(26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071), array(-11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692), ), array( array(11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687), array(-160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441), array(-20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001), ), array( array(-938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460), array(-19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007), array(-21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762), ), array( array(15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005), array(-9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674), array(4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035), ), array( array(7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590), array(-2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957), array(-30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812), ), array( array(33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740), array(-18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122), array(-27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158), ), array( array(8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885), array(26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140), array(19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857), ), array( array(801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155), array(19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260), array(19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483), ), ), array( array( array(-3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677), array(32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815), array(22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751), ), array( array(-16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203), array(-11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208), array(1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230), ), array( array(16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850), array(-21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389), array(-9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968), ), array( array(-11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689), array(14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880), array(5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304), ), array( array(30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632), array(-3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412), array(20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566), ), array( array(-20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038), array(-26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232), array(-1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943), ), array( array(17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856), array(23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738), array(15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971), ), array( array(-27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718), array(-13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697), array(-11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883), ), ), array( array( array(5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912), array(-26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358), array(3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849), ), array( array(29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307), array(-14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977), array(-6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335), ), array( array(-29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644), array(-22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616), array(-27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735), ), array( array(-21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099), array(29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341), array(-936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336), ), array( array(-23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646), array(31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425), array(-17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388), ), array( array(-31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743), array(-16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822), array(-8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462), ), array( array(18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985), array(9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702), array(-22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797), ), array( array(21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293), array(27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100), array(19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688), ), ), array( array( array(12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186), array(2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610), array(-2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707), ), array( array(7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220), array(915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025), array(32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044), ), array( array(32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992), array(-4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027), array(21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197), ), array( array(8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901), array(31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952), array(19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878), ), array( array(-28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390), array(32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730), array(2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730), ), array( array(-19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180), array(-30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272), array(-15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715), ), array( array(-22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970), array(-31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772), array(-17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865), ), array( array(15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750), array(20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373), array(32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348), ), ), array( array( array(9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144), array(-22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195), array(5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086), ), array( array(-13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684), array(-8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518), array(-2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233), ), array( array(-5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793), array(-2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794), array(580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435), ), array( array(23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921), array(13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518), array(2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563), ), array( array(14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278), array(-27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024), array(4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030), ), array( array(10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783), array(27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717), array(6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844), ), array( array(14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333), array(16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048), array(22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760), ), array( array(-4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760), array(-15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757), array(-2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112), ), ), array( array( array(-19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468), array(3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184), array(10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289), ), array( array(15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066), array(24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882), array(13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226), ), array( array(16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101), array(29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279), array(-6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811), ), array( array(27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709), array(20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714), array(-2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121), ), array( array(9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464), array(12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847), array(13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400), ), array( array(4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414), array(-15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158), array(17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045), ), array( array(-461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415), array(-5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459), array(-31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079), ), array( array(21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412), array(-20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743), array(-14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836), ), ), array( array( array(12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022), array(18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429), array(-6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065), ), array( array(30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861), array(10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000), array(-33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101), ), array( array(32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815), array(29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642), array(10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966), ), array( array(25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574), array(-21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742), array(-18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689), ), array( array(12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020), array(-10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772), array(3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982), ), array( array(-14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953), array(-16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218), array(-17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265), ), array( array(29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073), array(-3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325), array(-11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798), ), array( array(-4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870), array(-7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863), array(-13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927), ), ), array( array( array(-2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267), array(-9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663), array(22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862), ), array( array(-25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673), array(15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943), array(15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020), ), array( array(-4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238), array(11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064), array(14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795), ), array( array(15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052), array(-10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904), array(29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531), ), array( array(-13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979), array(-5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841), array(10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431), ), array( array(10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324), array(-31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940), array(10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320), ), array( array(-15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184), array(14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114), array(30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878), ), array( array(12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784), array(-2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091), array(-16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585), ), ), array( array( array(-8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208), array(10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864), array(17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661), ), array( array(7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233), array(26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212), array(-12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525), ), array( array(-24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068), array(9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397), array(-8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988), ), array( array(5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889), array(32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038), array(14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697), ), array( array(20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875), array(-25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905), array(-25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656), ), array( array(11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818), array(27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714), array(10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203), ), array( array(20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931), array(-30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024), array(-23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084), ), array( array(-1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204), array(20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817), array(27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667), ), ), array( array( array(11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504), array(-12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768), array(-19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255), ), array( array(6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790), array(1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438), array(-22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333), ), array( array(17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971), array(31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905), array(29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409), ), array( array(12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409), array(6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499), array(-8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363), ), array( array(28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664), array(-11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324), array(-21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940), ), array( array(13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990), array(-17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914), array(-25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290), ), array( array(24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257), array(-6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433), array(-16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236), ), array( array(-12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045), array(11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093), array(-1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347), ), ), array( array( array(-28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191), array(-15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507), array(-12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906), ), array( array(3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018), array(-16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109), array(-23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926), ), array( array(-24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528), array(8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625), array(-32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286), ), array( array(2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033), array(27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866), array(21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896), ), array( array(30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075), array(26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347), array(-22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437), ), array( array(-5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165), array(-18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588), array(-32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193), ), array( array(-19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017), array(-28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883), array(21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961), ), array( array(8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043), array(29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663), array(-20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362), ), ), array( array( array(-33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860), array(2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466), array(-24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063), ), array( array(-26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997), array(-1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295), array(-13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369), ), array( array(9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385), array(18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109), array(2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906), ), array( array(4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424), array(-19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185), array(7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962), ), array( array(-7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325), array(10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593), array(696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404), ), array( array(-11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644), array(17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801), array(26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804), ), array( array(-31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884), array(-586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577), array(-9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849), ), array( array(32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473), array(-8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644), array(-2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319), ), ), array( array( array(-11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599), array(-9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768), array(-27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084), ), array( array(-27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328), array(-15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369), array(20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920), ), array( array(12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815), array(-32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025), array(-21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397), ), array( array(-20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448), array(6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981), array(30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165), ), array( array(32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501), array(17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073), array(-1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861), ), array( array(14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845), array(-1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211), array(18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870), ), array( array(10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096), array(33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803), array(-32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168), ), array( array(30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965), array(-14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505), array(18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598), ), ), array( array( array(5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782), array(5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900), array(-31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479), ), array( array(-12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208), array(8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232), array(17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719), ), array( array(16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271), array(-4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326), array(-8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132), ), array( array(14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300), array(8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570), array(15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670), ), array( array(-2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994), array(-12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913), array(31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317), ), array( array(-25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730), array(842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096), array(-4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078), ), array( array(-15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411), array(-19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905), array(-9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654), ), array( array(-28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870), array(-23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498), array(12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579), ), ), array( array( array(14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677), array(10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647), array(-2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743), ), array( array(-25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468), array(21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375), array(-25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155), ), array( array(6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725), array(-12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612), array(-10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943), ), array( array(-30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944), array(30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928), array(9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406), ), array( array(22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139), array(-8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963), array(-31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693), ), array( array(1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734), array(-448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680), array(-24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410), ), array( array(-9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931), array(-16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654), array(22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710), ), array( array(29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180), array(-26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684), array(-10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895), ), ), array( array( array(22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501), array(-11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413), array(6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880), ), array( array(-8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874), array(22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962), array(-7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899), ), array( array(21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152), array(9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063), array(7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080), ), array( array(-9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146), array(-17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183), array(-19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133), ), array( array(-32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421), array(-3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622), array(-4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197), ), array( array(2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663), array(31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753), array(4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755), ), array( array(-9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862), array(-26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118), array(26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171), ), array( array(15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380), array(16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824), array(28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270), ), ), array( array( array(-817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438), array(-31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584), array(-594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562), ), array( array(30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471), array(18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610), array(19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269), ), array( array(-30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650), array(14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369), array(19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461), ), array( array(30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462), array(-5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793), array(-2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218), ), array( array(-24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226), array(18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019), array(-15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037), ), array( array(31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171), array(-17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132), array(-28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841), ), array( array(21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181), array(-33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210), array(-1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040), ), array( array(3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935), array(24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105), array(-28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814), ), ), array( array( array(793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852), array(5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581), array(-4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646), ), array( array(10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844), array(10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025), array(27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453), ), array( array(-23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068), array(4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192), array(-17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921), ), array( array(-9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259), array(-12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426), array(-5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072), ), array( array(-17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305), array(13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832), array(28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943), ), array( array(-16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011), array(24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447), array(17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494), ), array( array(-28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245), array(-20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859), array(28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915), ), array( array(16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707), array(10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848), array(-11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224), ), ), array( array( array(-25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391), array(15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215), array(-23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101), ), array( array(23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713), array(21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849), array(-7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930), ), array( array(-29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940), array(-21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031), array(-17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404), ), array( array(-25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243), array(-23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116), array(-24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525), ), array( array(-23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509), array(-10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883), array(15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865), ), array( array(-3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660), array(4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273), array(-28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138), ), array( array(-25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560), array(-10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135), array(2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941), ), array( array(-4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739), array(18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756), array(-30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819), ), ), array( array( array(-6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347), array(-27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028), array(21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075), ), array( array(16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799), array(-2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609), array(-25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817), ), array( array(-23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989), array(-30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523), array(4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278), ), array( array(31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045), array(19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377), array(24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480), ), array( array(17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016), array(510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426), array(18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525), ), array( array(13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396), array(9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080), array(12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892), ), array( array(15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275), array(11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074), array(20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140), ), array( array(-16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717), array(-1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101), array(24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127), ), ), array( array( array(-12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632), array(-26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415), array(-31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160), ), array( array(31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876), array(22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625), array(-15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478), ), array( array(27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164), array(26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595), array(-7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248), ), array( array(-16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858), array(15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193), array(8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184), ), array( array(-18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942), array(-1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635), array(21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948), ), array( array(11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935), array(-25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415), array(-15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416), ), array( array(-7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018), array(4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778), array(366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659), ), array( array(-24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385), array(18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503), array(476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329), ), ), array( array( array(20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056), array(-13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838), array(24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948), ), array( array(-3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691), array(-15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118), array(-23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517), ), array( array(-20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269), array(-6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904), array(-23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589), ), array( array(-28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193), array(-7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910), array(-30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930), ), array( array(-7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667), array(25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481), array(-9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876), ), array( array(22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640), array(-8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278), array(-21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112), ), array( array(26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272), array(17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012), array(-10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221), ), array( array(30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046), array(13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345), array(-19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310), ), ), array( array( array(19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937), array(31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636), array(-9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008), ), array( array(-2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429), array(-15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576), array(31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066), ), array( array(-9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490), array(-12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104), array(33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053), ), array( array(31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275), array(-20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511), array(22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095), ), array( array(-28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439), array(23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939), array(-23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424), ), array( array(2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310), array(3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608), array(-32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079), ), array( array(-23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101), array(21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418), array(18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576), ), array( array(30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356), array(9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996), array(-26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099), ), ), array( array( array(-26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728), array(-13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658), array(-10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242), ), array( array(-21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001), array(-4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766), array(18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373), ), array( array(26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458), array(-17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628), array(-13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657), ), array( array(-23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062), array(25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616), array(31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014), ), array( array(24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383), array(-25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814), array(-20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718), ), array( array(30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417), array(2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222), array(33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444), ), array( array(-20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597), array(23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970), array(1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799), ), array( array(-5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647), array(13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511), array(-29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032), ), ), array( array( array(9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834), array(-23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461), array(29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062), ), array( array(-25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516), array(-20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547), array(-24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240), ), array( array(-17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038), array(-33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741), array(16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103), ), array( array(-19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747), array(-1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323), array(31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016), ), array( array(-14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373), array(15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228), array(-2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141), ), array( array(16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399), array(11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831), array(-185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376), ), array( array(-32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313), array(-18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958), array(-6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577), ), array( array(-22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743), array(29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684), array(-20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476), ), ) ); /** * See: libsodium's crypto_core/curve25519/ref10/base2.h * * @var array basically int[8][3] */ protected static $base2 = array( array( array(25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605), array(-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378), array(-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546), ), array( array(15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024), array(16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574), array(30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357), ), array( array(10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380), array(4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306), array(19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942), ), array( array(5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766), array(-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701), array(28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300), ), array( array(-22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877), array(-6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951), array(4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784), ), array( array(-25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436), array(25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918), array(23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877), ), array( array(-33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800), array(-25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305), array(-13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300), ), array( array(-3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876), array(-24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619), array(-3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683), ) ); /** * 37095705934669439343138083508754565189542113879843219016388785533085940283555 * * @var array<int, int> */ protected static $d = array( -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116 ); /** * 2 * d = 16295367250680780974490674513165176452449235426866156013048779062215315747161 * * @var array<int, int> */ protected static $d2 = array( -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199 ); /** * sqrt(-1) * * @var array<int, int> */ protected static $sqrtm1 = array( -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482 ); /** * 1 / sqrt(a - d) * * @var array<int, int> */ protected static $invsqrtamd = array( 6111485, 4156064, -27798727, 12243468, -25904040, 120897, 20826367, -7060776, 6093568, -1986012 ); /** * sqrt(ad - 1) with a = -1 (mod p) * * @var array<int, int> */ protected static $sqrtadm1 = array( 24849947, -153582, -23613485, 6347715, -21072328, -667138, -25271143, -15367704, -870347, 14525639 ); /** * 1 - d ^ 2 * * @var array<int, int> */ protected static $onemsqd = array( 6275446, -16617371, -22938544, -3773710, 11667077, 7397348, -27922721, 1766195, -24433858, 672203 ); /** * (d - 1) ^ 2 * @var array<int, int> */ protected static $sqdmone = array( 15551795, -11097455, -13425098, -10125071, -11896535, 10178284, -26634327, 4729244, -5282110, -10116402 ); /* * 2^252+27742317777372353535851937790883648493 static const unsigned char L[] = { 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 }; */ const L = "\xed\xd3\xf5\x5c\x1a\x63\x12\x58\xd6\x9c\xf7\xa2\xde\xf9\xde\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10"; } Poly1305.php 0000644 00000003046 14721742304 0006520 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Poly1305', false)) { return; } /** * Class ParagonIE_Sodium_Core_Poly1305 */ abstract class ParagonIE_Sodium_Core_Poly1305 extends ParagonIE_Sodium_Core_Util { const BLOCK_SIZE = 16; /** * @internal You should not use this directly from another application * * @param string $m * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function onetimeauth($m, $key) { if (self::strlen($key) < 32) { throw new InvalidArgumentException( 'Key must be 32 bytes long.' ); } $state = new ParagonIE_Sodium_Core_Poly1305_State( self::substr($key, 0, 32) ); return $state ->update($m) ->finish(); } /** * @internal You should not use this directly from another application * * @param string $mac * @param string $m * @param string $key * @return bool * @throws SodiumException * @throws TypeError */ public static function onetimeauth_verify($mac, $m, $key) { if (self::strlen($key) < 32) { throw new InvalidArgumentException( 'Key must be 32 bytes long.' ); } $state = new ParagonIE_Sodium_Core_Poly1305_State( self::substr($key, 0, 32) ); $calc = $state ->update($m) ->finish(); return self::verify_16($calc, $mac); } } HSalsa20.php 0000644 00000007131 14721742304 0006600 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_HSalsa20', false)) { return; } /** * Class ParagonIE_Sodium_Core_HSalsa20 */ abstract class ParagonIE_Sodium_Core_HSalsa20 extends ParagonIE_Sodium_Core_Salsa20 { /** * Calculate an hsalsa20 hash of a single block * * HSalsa20 doesn't have a counter and will never be used for more than * one block (used to derive a subkey for xsalsa20). * * @internal You should not use this directly from another application * * @param string $in * @param string $k * @param string|null $c * @return string * @throws TypeError */ public static function hsalsa20($in, $k, $c = null) { if ($c === null) { $x0 = 0x61707865; $x5 = 0x3320646e; $x10 = 0x79622d32; $x15 = 0x6b206574; } else { $x0 = self::load_4(self::substr($c, 0, 4)); $x5 = self::load_4(self::substr($c, 4, 4)); $x10 = self::load_4(self::substr($c, 8, 4)); $x15 = self::load_4(self::substr($c, 12, 4)); } $x1 = self::load_4(self::substr($k, 0, 4)); $x2 = self::load_4(self::substr($k, 4, 4)); $x3 = self::load_4(self::substr($k, 8, 4)); $x4 = self::load_4(self::substr($k, 12, 4)); $x11 = self::load_4(self::substr($k, 16, 4)); $x12 = self::load_4(self::substr($k, 20, 4)); $x13 = self::load_4(self::substr($k, 24, 4)); $x14 = self::load_4(self::substr($k, 28, 4)); $x6 = self::load_4(self::substr($in, 0, 4)); $x7 = self::load_4(self::substr($in, 4, 4)); $x8 = self::load_4(self::substr($in, 8, 4)); $x9 = self::load_4(self::substr($in, 12, 4)); for ($i = self::ROUNDS; $i > 0; $i -= 2) { $x4 ^= self::rotate($x0 + $x12, 7); $x8 ^= self::rotate($x4 + $x0, 9); $x12 ^= self::rotate($x8 + $x4, 13); $x0 ^= self::rotate($x12 + $x8, 18); $x9 ^= self::rotate($x5 + $x1, 7); $x13 ^= self::rotate($x9 + $x5, 9); $x1 ^= self::rotate($x13 + $x9, 13); $x5 ^= self::rotate($x1 + $x13, 18); $x14 ^= self::rotate($x10 + $x6, 7); $x2 ^= self::rotate($x14 + $x10, 9); $x6 ^= self::rotate($x2 + $x14, 13); $x10 ^= self::rotate($x6 + $x2, 18); $x3 ^= self::rotate($x15 + $x11, 7); $x7 ^= self::rotate($x3 + $x15, 9); $x11 ^= self::rotate($x7 + $x3, 13); $x15 ^= self::rotate($x11 + $x7, 18); $x1 ^= self::rotate($x0 + $x3, 7); $x2 ^= self::rotate($x1 + $x0, 9); $x3 ^= self::rotate($x2 + $x1, 13); $x0 ^= self::rotate($x3 + $x2, 18); $x6 ^= self::rotate($x5 + $x4, 7); $x7 ^= self::rotate($x6 + $x5, 9); $x4 ^= self::rotate($x7 + $x6, 13); $x5 ^= self::rotate($x4 + $x7, 18); $x11 ^= self::rotate($x10 + $x9, 7); $x8 ^= self::rotate($x11 + $x10, 9); $x9 ^= self::rotate($x8 + $x11, 13); $x10 ^= self::rotate($x9 + $x8, 18); $x12 ^= self::rotate($x15 + $x14, 7); $x13 ^= self::rotate($x12 + $x15, 9); $x14 ^= self::rotate($x13 + $x12, 13); $x15 ^= self::rotate($x14 + $x13, 18); } return self::store32_le($x0) . self::store32_le($x5) . self::store32_le($x10) . self::store32_le($x15) . self::store32_le($x6) . self::store32_le($x7) . self::store32_le($x8) . self::store32_le($x9); } } ChaCha20.php 0000644 00000031206 14721742304 0006534 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_ChaCha20', false)) { return; } /** * Class ParagonIE_Sodium_Core_ChaCha20 */ class ParagonIE_Sodium_Core_ChaCha20 extends ParagonIE_Sodium_Core_Util { /** * Bitwise left rotation * * @internal You should not use this directly from another application * * @param int $v * @param int $n * @return int */ public static function rotate($v, $n) { $v &= 0xffffffff; $n &= 31; return (int) ( 0xffffffff & ( ($v << $n) | ($v >> (32 - $n)) ) ); } /** * The ChaCha20 quarter round function. Works on four 32-bit integers. * * @internal You should not use this directly from another application * * @param int $a * @param int $b * @param int $c * @param int $d * @return array<int, int> */ protected static function quarterRound($a, $b, $c, $d) { # a = PLUS(a,b); d = ROTATE(XOR(d,a),16); /** @var int $a */ $a = ($a + $b) & 0xffffffff; $d = self::rotate($d ^ $a, 16); # c = PLUS(c,d); b = ROTATE(XOR(b,c),12); /** @var int $c */ $c = ($c + $d) & 0xffffffff; $b = self::rotate($b ^ $c, 12); # a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); /** @var int $a */ $a = ($a + $b) & 0xffffffff; $d = self::rotate($d ^ $a, 8); # c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); /** @var int $c */ $c = ($c + $d) & 0xffffffff; $b = self::rotate($b ^ $c, 7); return array((int) $a, (int) $b, (int) $c, (int) $d); } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_ChaCha20_Ctx $ctx * @param string $message * * @return string * @throws TypeError * @throws SodiumException */ public static function encryptBytes( ParagonIE_Sodium_Core_ChaCha20_Ctx $ctx, $message = '' ) { $bytes = self::strlen($message); /* j0 = ctx->input[0]; j1 = ctx->input[1]; j2 = ctx->input[2]; j3 = ctx->input[3]; j4 = ctx->input[4]; j5 = ctx->input[5]; j6 = ctx->input[6]; j7 = ctx->input[7]; j8 = ctx->input[8]; j9 = ctx->input[9]; j10 = ctx->input[10]; j11 = ctx->input[11]; j12 = ctx->input[12]; j13 = ctx->input[13]; j14 = ctx->input[14]; j15 = ctx->input[15]; */ $j0 = (int) $ctx[0]; $j1 = (int) $ctx[1]; $j2 = (int) $ctx[2]; $j3 = (int) $ctx[3]; $j4 = (int) $ctx[4]; $j5 = (int) $ctx[5]; $j6 = (int) $ctx[6]; $j7 = (int) $ctx[7]; $j8 = (int) $ctx[8]; $j9 = (int) $ctx[9]; $j10 = (int) $ctx[10]; $j11 = (int) $ctx[11]; $j12 = (int) $ctx[12]; $j13 = (int) $ctx[13]; $j14 = (int) $ctx[14]; $j15 = (int) $ctx[15]; $c = ''; for (;;) { if ($bytes < 64) { $message .= str_repeat("\x00", 64 - $bytes); } $x0 = (int) $j0; $x1 = (int) $j1; $x2 = (int) $j2; $x3 = (int) $j3; $x4 = (int) $j4; $x5 = (int) $j5; $x6 = (int) $j6; $x7 = (int) $j7; $x8 = (int) $j8; $x9 = (int) $j9; $x10 = (int) $j10; $x11 = (int) $j11; $x12 = (int) $j12; $x13 = (int) $j13; $x14 = (int) $j14; $x15 = (int) $j15; # for (i = 20; i > 0; i -= 2) { for ($i = 20; $i > 0; $i -= 2) { # QUARTERROUND( x0, x4, x8, x12) list($x0, $x4, $x8, $x12) = self::quarterRound($x0, $x4, $x8, $x12); # QUARTERROUND( x1, x5, x9, x13) list($x1, $x5, $x9, $x13) = self::quarterRound($x1, $x5, $x9, $x13); # QUARTERROUND( x2, x6, x10, x14) list($x2, $x6, $x10, $x14) = self::quarterRound($x2, $x6, $x10, $x14); # QUARTERROUND( x3, x7, x11, x15) list($x3, $x7, $x11, $x15) = self::quarterRound($x3, $x7, $x11, $x15); # QUARTERROUND( x0, x5, x10, x15) list($x0, $x5, $x10, $x15) = self::quarterRound($x0, $x5, $x10, $x15); # QUARTERROUND( x1, x6, x11, x12) list($x1, $x6, $x11, $x12) = self::quarterRound($x1, $x6, $x11, $x12); # QUARTERROUND( x2, x7, x8, x13) list($x2, $x7, $x8, $x13) = self::quarterRound($x2, $x7, $x8, $x13); # QUARTERROUND( x3, x4, x9, x14) list($x3, $x4, $x9, $x14) = self::quarterRound($x3, $x4, $x9, $x14); } /* x0 = PLUS(x0, j0); x1 = PLUS(x1, j1); x2 = PLUS(x2, j2); x3 = PLUS(x3, j3); x4 = PLUS(x4, j4); x5 = PLUS(x5, j5); x6 = PLUS(x6, j6); x7 = PLUS(x7, j7); x8 = PLUS(x8, j8); x9 = PLUS(x9, j9); x10 = PLUS(x10, j10); x11 = PLUS(x11, j11); x12 = PLUS(x12, j12); x13 = PLUS(x13, j13); x14 = PLUS(x14, j14); x15 = PLUS(x15, j15); */ /** @var int $x0 */ $x0 = ($x0 & 0xffffffff) + $j0; /** @var int $x1 */ $x1 = ($x1 & 0xffffffff) + $j1; /** @var int $x2 */ $x2 = ($x2 & 0xffffffff) + $j2; /** @var int $x3 */ $x3 = ($x3 & 0xffffffff) + $j3; /** @var int $x4 */ $x4 = ($x4 & 0xffffffff) + $j4; /** @var int $x5 */ $x5 = ($x5 & 0xffffffff) + $j5; /** @var int $x6 */ $x6 = ($x6 & 0xffffffff) + $j6; /** @var int $x7 */ $x7 = ($x7 & 0xffffffff) + $j7; /** @var int $x8 */ $x8 = ($x8 & 0xffffffff) + $j8; /** @var int $x9 */ $x9 = ($x9 & 0xffffffff) + $j9; /** @var int $x10 */ $x10 = ($x10 & 0xffffffff) + $j10; /** @var int $x11 */ $x11 = ($x11 & 0xffffffff) + $j11; /** @var int $x12 */ $x12 = ($x12 & 0xffffffff) + $j12; /** @var int $x13 */ $x13 = ($x13 & 0xffffffff) + $j13; /** @var int $x14 */ $x14 = ($x14 & 0xffffffff) + $j14; /** @var int $x15 */ $x15 = ($x15 & 0xffffffff) + $j15; /* x0 = XOR(x0, LOAD32_LE(m + 0)); x1 = XOR(x1, LOAD32_LE(m + 4)); x2 = XOR(x2, LOAD32_LE(m + 8)); x3 = XOR(x3, LOAD32_LE(m + 12)); x4 = XOR(x4, LOAD32_LE(m + 16)); x5 = XOR(x5, LOAD32_LE(m + 20)); x6 = XOR(x6, LOAD32_LE(m + 24)); x7 = XOR(x7, LOAD32_LE(m + 28)); x8 = XOR(x8, LOAD32_LE(m + 32)); x9 = XOR(x9, LOAD32_LE(m + 36)); x10 = XOR(x10, LOAD32_LE(m + 40)); x11 = XOR(x11, LOAD32_LE(m + 44)); x12 = XOR(x12, LOAD32_LE(m + 48)); x13 = XOR(x13, LOAD32_LE(m + 52)); x14 = XOR(x14, LOAD32_LE(m + 56)); x15 = XOR(x15, LOAD32_LE(m + 60)); */ $x0 ^= self::load_4(self::substr($message, 0, 4)); $x1 ^= self::load_4(self::substr($message, 4, 4)); $x2 ^= self::load_4(self::substr($message, 8, 4)); $x3 ^= self::load_4(self::substr($message, 12, 4)); $x4 ^= self::load_4(self::substr($message, 16, 4)); $x5 ^= self::load_4(self::substr($message, 20, 4)); $x6 ^= self::load_4(self::substr($message, 24, 4)); $x7 ^= self::load_4(self::substr($message, 28, 4)); $x8 ^= self::load_4(self::substr($message, 32, 4)); $x9 ^= self::load_4(self::substr($message, 36, 4)); $x10 ^= self::load_4(self::substr($message, 40, 4)); $x11 ^= self::load_4(self::substr($message, 44, 4)); $x12 ^= self::load_4(self::substr($message, 48, 4)); $x13 ^= self::load_4(self::substr($message, 52, 4)); $x14 ^= self::load_4(self::substr($message, 56, 4)); $x15 ^= self::load_4(self::substr($message, 60, 4)); /* j12 = PLUSONE(j12); if (!j12) { j13 = PLUSONE(j13); } */ ++$j12; if ($j12 & 0xf0000000) { throw new SodiumException('Overflow'); } /* STORE32_LE(c + 0, x0); STORE32_LE(c + 4, x1); STORE32_LE(c + 8, x2); STORE32_LE(c + 12, x3); STORE32_LE(c + 16, x4); STORE32_LE(c + 20, x5); STORE32_LE(c + 24, x6); STORE32_LE(c + 28, x7); STORE32_LE(c + 32, x8); STORE32_LE(c + 36, x9); STORE32_LE(c + 40, x10); STORE32_LE(c + 44, x11); STORE32_LE(c + 48, x12); STORE32_LE(c + 52, x13); STORE32_LE(c + 56, x14); STORE32_LE(c + 60, x15); */ $block = self::store32_le((int) ($x0 & 0xffffffff)) . self::store32_le((int) ($x1 & 0xffffffff)) . self::store32_le((int) ($x2 & 0xffffffff)) . self::store32_le((int) ($x3 & 0xffffffff)) . self::store32_le((int) ($x4 & 0xffffffff)) . self::store32_le((int) ($x5 & 0xffffffff)) . self::store32_le((int) ($x6 & 0xffffffff)) . self::store32_le((int) ($x7 & 0xffffffff)) . self::store32_le((int) ($x8 & 0xffffffff)) . self::store32_le((int) ($x9 & 0xffffffff)) . self::store32_le((int) ($x10 & 0xffffffff)) . self::store32_le((int) ($x11 & 0xffffffff)) . self::store32_le((int) ($x12 & 0xffffffff)) . self::store32_le((int) ($x13 & 0xffffffff)) . self::store32_le((int) ($x14 & 0xffffffff)) . self::store32_le((int) ($x15 & 0xffffffff)); /* Partial block */ if ($bytes < 64) { $c .= self::substr($block, 0, $bytes); break; } /* Full block */ $c .= $block; $bytes -= 64; if ($bytes <= 0) { break; } $message = self::substr($message, 64); } /* end for(;;) loop */ $ctx[12] = $j12; $ctx[13] = $j13; return $c; } /** * @internal You should not use this directly from another application * * @param int $len * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function stream($len = 64, $nonce = '', $key = '') { return self::encryptBytes( new ParagonIE_Sodium_Core_ChaCha20_Ctx($key, $nonce), str_repeat("\x00", $len) ); } /** * @internal You should not use this directly from another application * * @param int $len * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function ietfStream($len, $nonce = '', $key = '') { return self::encryptBytes( new ParagonIE_Sodium_Core_ChaCha20_IetfCtx($key, $nonce), str_repeat("\x00", $len) ); } /** * @internal You should not use this directly from another application * * @param string $message * @param string $nonce * @param string $key * @param string $ic * @return string * @throws SodiumException * @throws TypeError */ public static function ietfStreamXorIc($message, $nonce = '', $key = '', $ic = '') { return self::encryptBytes( new ParagonIE_Sodium_Core_ChaCha20_IetfCtx($key, $nonce, $ic), $message ); } /** * @internal You should not use this directly from another application * * @param string $message * @param string $nonce * @param string $key * @param string $ic * @return string * @throws SodiumException * @throws TypeError */ public static function streamXorIc($message, $nonce = '', $key = '', $ic = '') { return self::encryptBytes( new ParagonIE_Sodium_Core_ChaCha20_Ctx($key, $nonce, $ic), $message ); } } HChaCha20.php 0000644 00000007437 14721742304 0006655 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_HChaCha20', false)) { return; } /** * Class ParagonIE_Sodium_Core_HChaCha20 */ class ParagonIE_Sodium_Core_HChaCha20 extends ParagonIE_Sodium_Core_ChaCha20 { /** * @param string $in * @param string $key * @param string|null $c * @return string * @throws TypeError */ public static function hChaCha20($in = '', $key = '', $c = null) { $ctx = array(); if ($c === null) { $ctx[0] = 0x61707865; $ctx[1] = 0x3320646e; $ctx[2] = 0x79622d32; $ctx[3] = 0x6b206574; } else { $ctx[0] = self::load_4(self::substr($c, 0, 4)); $ctx[1] = self::load_4(self::substr($c, 4, 4)); $ctx[2] = self::load_4(self::substr($c, 8, 4)); $ctx[3] = self::load_4(self::substr($c, 12, 4)); } $ctx[4] = self::load_4(self::substr($key, 0, 4)); $ctx[5] = self::load_4(self::substr($key, 4, 4)); $ctx[6] = self::load_4(self::substr($key, 8, 4)); $ctx[7] = self::load_4(self::substr($key, 12, 4)); $ctx[8] = self::load_4(self::substr($key, 16, 4)); $ctx[9] = self::load_4(self::substr($key, 20, 4)); $ctx[10] = self::load_4(self::substr($key, 24, 4)); $ctx[11] = self::load_4(self::substr($key, 28, 4)); $ctx[12] = self::load_4(self::substr($in, 0, 4)); $ctx[13] = self::load_4(self::substr($in, 4, 4)); $ctx[14] = self::load_4(self::substr($in, 8, 4)); $ctx[15] = self::load_4(self::substr($in, 12, 4)); return self::hChaCha20Bytes($ctx); } /** * @param array $ctx * @return string * @throws TypeError */ protected static function hChaCha20Bytes(array $ctx) { $x0 = (int) $ctx[0]; $x1 = (int) $ctx[1]; $x2 = (int) $ctx[2]; $x3 = (int) $ctx[3]; $x4 = (int) $ctx[4]; $x5 = (int) $ctx[5]; $x6 = (int) $ctx[6]; $x7 = (int) $ctx[7]; $x8 = (int) $ctx[8]; $x9 = (int) $ctx[9]; $x10 = (int) $ctx[10]; $x11 = (int) $ctx[11]; $x12 = (int) $ctx[12]; $x13 = (int) $ctx[13]; $x14 = (int) $ctx[14]; $x15 = (int) $ctx[15]; for ($i = 0; $i < 10; ++$i) { # QUARTERROUND( x0, x4, x8, x12) list($x0, $x4, $x8, $x12) = self::quarterRound($x0, $x4, $x8, $x12); # QUARTERROUND( x1, x5, x9, x13) list($x1, $x5, $x9, $x13) = self::quarterRound($x1, $x5, $x9, $x13); # QUARTERROUND( x2, x6, x10, x14) list($x2, $x6, $x10, $x14) = self::quarterRound($x2, $x6, $x10, $x14); # QUARTERROUND( x3, x7, x11, x15) list($x3, $x7, $x11, $x15) = self::quarterRound($x3, $x7, $x11, $x15); # QUARTERROUND( x0, x5, x10, x15) list($x0, $x5, $x10, $x15) = self::quarterRound($x0, $x5, $x10, $x15); # QUARTERROUND( x1, x6, x11, x12) list($x1, $x6, $x11, $x12) = self::quarterRound($x1, $x6, $x11, $x12); # QUARTERROUND( x2, x7, x8, x13) list($x2, $x7, $x8, $x13) = self::quarterRound($x2, $x7, $x8, $x13); # QUARTERROUND( x3, x4, x9, x14) list($x3, $x4, $x9, $x14) = self::quarterRound($x3, $x4, $x9, $x14); } return self::store32_le((int) ($x0 & 0xffffffff)) . self::store32_le((int) ($x1 & 0xffffffff)) . self::store32_le((int) ($x2 & 0xffffffff)) . self::store32_le((int) ($x3 & 0xffffffff)) . self::store32_le((int) ($x12 & 0xffffffff)) . self::store32_le((int) ($x13 & 0xffffffff)) . self::store32_le((int) ($x14 & 0xffffffff)) . self::store32_le((int) ($x15 & 0xffffffff)); } } ChaCha20/IetfCtx.php 0000644 00000002452 14721742304 0010103 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_ChaCha20_IetfCtx', false)) { return; } /** * Class ParagonIE_Sodium_Core_ChaCha20_IetfCtx */ class ParagonIE_Sodium_Core_ChaCha20_IetfCtx extends ParagonIE_Sodium_Core_ChaCha20_Ctx { /** * ParagonIE_Sodium_Core_ChaCha20_IetfCtx constructor. * * @internal You should not use this directly from another application * * @param string $key ChaCha20 key. * @param string $iv Initialization Vector (a.k.a. nonce). * @param string $counter The initial counter value. * Defaults to 4 0x00 bytes. * @throws InvalidArgumentException * @throws TypeError */ public function __construct($key = '', $iv = '', $counter = '') { if (self::strlen($iv) !== 12) { throw new InvalidArgumentException('ChaCha20 expects a 96-bit nonce in IETF mode.'); } parent::__construct($key, self::substr($iv, 0, 8), $counter); if (!empty($counter)) { $this->container[12] = self::load_4(self::substr($counter, 0, 4)); } $this->container[13] = self::load_4(self::substr($iv, 0, 4)); $this->container[14] = self::load_4(self::substr($iv, 4, 4)); $this->container[15] = self::load_4(self::substr($iv, 8, 4)); } } ChaCha20/Ctx.php 0000644 00000007546 14721742304 0007304 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_ChaCha20_Ctx', false)) { return; } /** * Class ParagonIE_Sodium_Core_ChaCha20_Ctx */ class ParagonIE_Sodium_Core_ChaCha20_Ctx extends ParagonIE_Sodium_Core_Util implements ArrayAccess { /** * @var SplFixedArray internally, <int, int> */ protected $container; /** * ParagonIE_Sodium_Core_ChaCha20_Ctx constructor. * * @internal You should not use this directly from another application * * @param string $key ChaCha20 key. * @param string $iv Initialization Vector (a.k.a. nonce). * @param string $counter The initial counter value. * Defaults to 8 0x00 bytes. * @throws InvalidArgumentException * @throws TypeError */ public function __construct($key = '', $iv = '', $counter = '') { if (self::strlen($key) !== 32) { throw new InvalidArgumentException('ChaCha20 expects a 256-bit key.'); } if (self::strlen($iv) !== 8) { throw new InvalidArgumentException('ChaCha20 expects a 64-bit nonce.'); } $this->container = new SplFixedArray(16); /* "expand 32-byte k" as per ChaCha20 spec */ $this->container[0] = 0x61707865; $this->container[1] = 0x3320646e; $this->container[2] = 0x79622d32; $this->container[3] = 0x6b206574; $this->container[4] = self::load_4(self::substr($key, 0, 4)); $this->container[5] = self::load_4(self::substr($key, 4, 4)); $this->container[6] = self::load_4(self::substr($key, 8, 4)); $this->container[7] = self::load_4(self::substr($key, 12, 4)); $this->container[8] = self::load_4(self::substr($key, 16, 4)); $this->container[9] = self::load_4(self::substr($key, 20, 4)); $this->container[10] = self::load_4(self::substr($key, 24, 4)); $this->container[11] = self::load_4(self::substr($key, 28, 4)); if (empty($counter)) { $this->container[12] = 0; $this->container[13] = 0; } else { $this->container[12] = self::load_4(self::substr($counter, 0, 4)); $this->container[13] = self::load_4(self::substr($counter, 4, 4)); } $this->container[14] = self::load_4(self::substr($iv, 0, 4)); $this->container[15] = self::load_4(self::substr($iv, 4, 4)); } /** * @internal You should not use this directly from another application * * @param int $offset * @param int $value * @return void * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if (!is_int($offset)) { throw new InvalidArgumentException('Expected an integer'); } if (!is_int($value)) { throw new InvalidArgumentException('Expected an integer'); } $this->container[$offset] = $value; } /** * @internal You should not use this directly from another application * * @param int $offset * @return bool */ #[ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->container[$offset]); } /** * @internal You should not use this directly from another application * * @param int $offset * @return void * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->container[$offset]); } /** * @internal You should not use this directly from another application * * @param int $offset * @return mixed|null * @psalm-suppress MixedArrayOffset */ #[ReturnTypeWillChange] public function offsetGet($offset) { return isset($this->container[$offset]) ? $this->container[$offset] : null; } } Util.php 0000644 00000070373 14721742304 0006210 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Util', false)) { return; } /** * Class ParagonIE_Sodium_Core_Util */ abstract class ParagonIE_Sodium_Core_Util { const U32_MAX = 0xFFFFFFFF; /** * @param int $integer * @param int $size (16, 32, 64) * @return int */ public static function abs($integer, $size = 0) { /** @var int $realSize */ $realSize = (PHP_INT_SIZE << 3) - 1; if ($size) { --$size; } else { /** @var int $size */ $size = $realSize; } $negative = -(($integer >> $size) & 1); return (int) ( ($integer ^ $negative) + (($negative >> $realSize) & 1) ); } /** * @param string $a * @param string $b * @return string * @throws SodiumException */ public static function andStrings($a, $b) { /* Type checks: */ if (!is_string($a)) { throw new TypeError('Argument 1 must be a string'); } if (!is_string($b)) { throw new TypeError('Argument 2 must be a string'); } $len = self::strlen($a); if (self::strlen($b) !== $len) { throw new SodiumException('Both strings must be of equal length to combine with bitwise AND'); } return $a & $b; } /** * Convert a binary string into a hexadecimal string without cache-timing * leaks * * @internal You should not use this directly from another application * * @param string $binaryString (raw binary) * @return string * @throws TypeError */ public static function bin2hex($binaryString) { /* Type checks: */ if (!is_string($binaryString)) { throw new TypeError('Argument 1 must be a string, ' . gettype($binaryString) . ' given.'); } $hex = ''; $len = self::strlen($binaryString); for ($i = 0; $i < $len; ++$i) { /** @var array<int, int> $chunk */ $chunk = unpack('C', $binaryString[$i]); /** @var int $c */ $c = $chunk[1] & 0xf; /** @var int $b */ $b = $chunk[1] >> 4; $hex .= pack( 'CC', (87 + $b + ((($b - 10) >> 8) & ~38)), (87 + $c + ((($c - 10) >> 8) & ~38)) ); } return $hex; } /** * Convert a binary string into a hexadecimal string without cache-timing * leaks, returning uppercase letters (as per RFC 4648) * * @internal You should not use this directly from another application * * @param string $bin_string (raw binary) * @return string * @throws TypeError */ public static function bin2hexUpper($bin_string) { $hex = ''; $len = self::strlen($bin_string); for ($i = 0; $i < $len; ++$i) { /** @var array<int, int> $chunk */ $chunk = unpack('C', $bin_string[$i]); /** * Lower 16 bits * * @var int $c */ $c = $chunk[1] & 0xf; /** * Upper 16 bits * @var int $b */ $b = $chunk[1] >> 4; /** * Use pack() and binary operators to turn the two integers * into hexadecimal characters. We don't use chr() here, because * it uses a lookup table internally and we want to avoid * cache-timing side-channels. */ $hex .= pack( 'CC', (55 + $b + ((($b - 10) >> 8) & ~6)), (55 + $c + ((($c - 10) >> 8) & ~6)) ); } return $hex; } /** * Cache-timing-safe variant of ord() * * @internal You should not use this directly from another application * * @param string $chr * @return int * @throws SodiumException * @throws TypeError */ public static function chrToInt($chr) { /* Type checks: */ if (!is_string($chr)) { throw new TypeError('Argument 1 must be a string, ' . gettype($chr) . ' given.'); } if (self::strlen($chr) !== 1) { throw new SodiumException('chrToInt() expects a string that is exactly 1 character long'); } /** @var array<int, int> $chunk */ $chunk = unpack('C', $chr); return (int) ($chunk[1]); } /** * Compares two strings. * * @internal You should not use this directly from another application * * @param string $left * @param string $right * @param int $len * @return int * @throws SodiumException * @throws TypeError */ public static function compare($left, $right, $len = null) { $leftLen = self::strlen($left); $rightLen = self::strlen($right); if ($len === null) { $len = max($leftLen, $rightLen); $left = str_pad($left, $len, "\x00", STR_PAD_RIGHT); $right = str_pad($right, $len, "\x00", STR_PAD_RIGHT); } $gt = 0; $eq = 1; $i = $len; while ($i !== 0) { --$i; $gt |= ((self::chrToInt($right[$i]) - self::chrToInt($left[$i])) >> 8) & $eq; $eq &= ((self::chrToInt($right[$i]) ^ self::chrToInt($left[$i])) - 1) >> 8; } return ($gt + $gt + $eq) - 1; } /** * If a variable does not match a given type, throw a TypeError. * * @param mixed $mixedVar * @param string $type * @param int $argumentIndex * @throws TypeError * @throws SodiumException * @return void */ public static function declareScalarType(&$mixedVar = null, $type = 'void', $argumentIndex = 0) { if (func_num_args() === 0) { /* Tautology, by default */ return; } if (func_num_args() === 1) { throw new TypeError('Declared void, but passed a variable'); } $realType = strtolower(gettype($mixedVar)); $type = strtolower($type); switch ($type) { case 'null': if ($mixedVar !== null) { throw new TypeError('Argument ' . $argumentIndex . ' must be null, ' . $realType . ' given.'); } break; case 'integer': case 'int': $allow = array('int', 'integer'); if (!in_array($type, $allow)) { throw new TypeError('Argument ' . $argumentIndex . ' must be an integer, ' . $realType . ' given.'); } $mixedVar = (int) $mixedVar; break; case 'boolean': case 'bool': $allow = array('bool', 'boolean'); if (!in_array($type, $allow)) { throw new TypeError('Argument ' . $argumentIndex . ' must be a boolean, ' . $realType . ' given.'); } $mixedVar = (bool) $mixedVar; break; case 'string': if (!is_string($mixedVar)) { throw new TypeError('Argument ' . $argumentIndex . ' must be a string, ' . $realType . ' given.'); } $mixedVar = (string) $mixedVar; break; case 'decimal': case 'double': case 'float': $allow = array('decimal', 'double', 'float'); if (!in_array($type, $allow)) { throw new TypeError('Argument ' . $argumentIndex . ' must be a float, ' . $realType . ' given.'); } $mixedVar = (float) $mixedVar; break; case 'object': if (!is_object($mixedVar)) { throw new TypeError('Argument ' . $argumentIndex . ' must be an object, ' . $realType . ' given.'); } break; case 'array': if (!is_array($mixedVar)) { if (is_object($mixedVar)) { if ($mixedVar instanceof ArrayAccess) { return; } } throw new TypeError('Argument ' . $argumentIndex . ' must be an array, ' . $realType . ' given.'); } break; default: throw new SodiumException('Unknown type (' . $realType .') does not match expect type (' . $type . ')'); } } /** * Evaluate whether or not two strings are equal (in constant-time) * * @param string $left * @param string $right * @return bool * @throws SodiumException * @throws TypeError */ public static function hashEquals($left, $right) { /* Type checks: */ if (!is_string($left)) { throw new TypeError('Argument 1 must be a string, ' . gettype($left) . ' given.'); } if (!is_string($right)) { throw new TypeError('Argument 2 must be a string, ' . gettype($right) . ' given.'); } if (is_callable('hash_equals')) { return hash_equals($left, $right); } $d = 0; /** @var int $len */ $len = self::strlen($left); if ($len !== self::strlen($right)) { return false; } for ($i = 0; $i < $len; ++$i) { $d |= self::chrToInt($left[$i]) ^ self::chrToInt($right[$i]); } if ($d !== 0) { return false; } return $left === $right; } /** * Catch hash_update() failures and throw instead of silently proceeding * * @param HashContext|resource &$hs * @param string $data * @return void * @throws SodiumException * @psalm-suppress PossiblyInvalidArgument */ protected static function hash_update(&$hs, $data) { if (!hash_update($hs, $data)) { throw new SodiumException('hash_update() failed'); } } /** * Convert a hexadecimal string into a binary string without cache-timing * leaks * * @internal You should not use this directly from another application * * @param string $hexString * @param string $ignore * @param bool $strictPadding * @return string (raw binary) * @throws RangeException * @throws TypeError */ public static function hex2bin($hexString, $ignore = '', $strictPadding = false) { /* Type checks: */ if (!is_string($hexString)) { throw new TypeError('Argument 1 must be a string, ' . gettype($hexString) . ' given.'); } if (!is_string($ignore)) { throw new TypeError('Argument 2 must be a string, ' . gettype($hexString) . ' given.'); } $hex_pos = 0; $bin = ''; $c_acc = 0; $hex_len = self::strlen($hexString); $state = 0; if (($hex_len & 1) !== 0) { if ($strictPadding) { throw new RangeException( 'Expected an even number of hexadecimal characters' ); } else { $hexString = '0' . $hexString; ++$hex_len; } } $chunk = unpack('C*', $hexString); while ($hex_pos < $hex_len) { ++$hex_pos; /** @var int $c */ $c = $chunk[$hex_pos]; $c_num = $c ^ 48; $c_num0 = ($c_num - 10) >> 8; $c_alpha = ($c & ~32) - 55; $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8; if (($c_num0 | $c_alpha0) === 0) { if ($ignore && $state === 0 && strpos($ignore, self::intToChr($c)) !== false) { continue; } throw new RangeException( 'hex2bin() only expects hexadecimal characters' ); } $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0); if ($state === 0) { $c_acc = $c_val * 16; } else { $bin .= pack('C', $c_acc | $c_val); } $state ^= 1; } return $bin; } /** * Turn an array of integers into a string * * @internal You should not use this directly from another application * * @param array<int, int> $ints * @return string */ public static function intArrayToString(array $ints) { $args = $ints; foreach ($args as $i => $v) { $args[$i] = (int) ($v & 0xff); } array_unshift($args, str_repeat('C', count($ints))); return (string) (call_user_func_array('pack', $args)); } /** * Cache-timing-safe variant of ord() * * @internal You should not use this directly from another application * * @param int $int * @return string * @throws TypeError */ public static function intToChr($int) { return pack('C', $int); } /** * Load a 3 character substring into an integer * * @internal You should not use this directly from another application * * @param string $string * @return int * @throws RangeException * @throws TypeError */ public static function load_3($string) { /* Type checks: */ if (!is_string($string)) { throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.'); } /* Input validation: */ if (self::strlen($string) < 3) { throw new RangeException( 'String must be 3 bytes or more; ' . self::strlen($string) . ' given.' ); } /** @var array<int, int> $unpacked */ $unpacked = unpack('V', $string . "\0"); return (int) ($unpacked[1] & 0xffffff); } /** * Load a 4 character substring into an integer * * @internal You should not use this directly from another application * * @param string $string * @return int * @throws RangeException * @throws TypeError */ public static function load_4($string) { /* Type checks: */ if (!is_string($string)) { throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.'); } /* Input validation: */ if (self::strlen($string) < 4) { throw new RangeException( 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.' ); } /** @var array<int, int> $unpacked */ $unpacked = unpack('V', $string); return (int) $unpacked[1]; } /** * Load a 8 character substring into an integer * * @internal You should not use this directly from another application * * @param string $string * @return int * @throws RangeException * @throws SodiumException * @throws TypeError */ public static function load64_le($string) { /* Type checks: */ if (!is_string($string)) { throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.'); } /* Input validation: */ if (self::strlen($string) < 4) { throw new RangeException( 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.' ); } if (PHP_VERSION_ID >= 50603 && PHP_INT_SIZE === 8) { /** @var array<int, int> $unpacked */ $unpacked = unpack('P', $string); return (int) $unpacked[1]; } /** @var int $result */ $result = (self::chrToInt($string[0]) & 0xff); $result |= (self::chrToInt($string[1]) & 0xff) << 8; $result |= (self::chrToInt($string[2]) & 0xff) << 16; $result |= (self::chrToInt($string[3]) & 0xff) << 24; $result |= (self::chrToInt($string[4]) & 0xff) << 32; $result |= (self::chrToInt($string[5]) & 0xff) << 40; $result |= (self::chrToInt($string[6]) & 0xff) << 48; $result |= (self::chrToInt($string[7]) & 0xff) << 56; return (int) $result; } /** * @internal You should not use this directly from another application * * @param string $left * @param string $right * @return int * @throws SodiumException * @throws TypeError */ public static function memcmp($left, $right) { if (self::hashEquals($left, $right)) { return 0; } return -1; } /** * Multiply two integers in constant-time * * Micro-architecture timing side-channels caused by how your CPU * implements multiplication are best prevented by never using the * multiplication operators and ensuring that our code always takes * the same number of operations to complete, regardless of the values * of $a and $b. * * @internal You should not use this directly from another application * * @param int $a * @param int $b * @param int $size Limits the number of operations (useful for small, * constant operands) * @return int */ public static function mul($a, $b, $size = 0) { if (ParagonIE_Sodium_Compat::$fastMult) { return (int) ($a * $b); } static $defaultSize = null; /** @var int $defaultSize */ if (!$defaultSize) { /** @var int $defaultSize */ $defaultSize = (PHP_INT_SIZE << 3) - 1; } if ($size < 1) { /** @var int $size */ $size = $defaultSize; } /** @var int $size */ $c = 0; /** * Mask is either -1 or 0. * * -1 in binary looks like 0x1111 ... 1111 * 0 in binary looks like 0x0000 ... 0000 * * @var int */ $mask = -(($b >> ((int) $defaultSize)) & 1); /** * Ensure $b is a positive integer, without creating * a branching side-channel * * @var int $b */ $b = ($b & ~$mask) | ($mask & -$b); /** * Unless $size is provided: * * This loop always runs 32 times when PHP_INT_SIZE is 4. * This loop always runs 64 times when PHP_INT_SIZE is 8. */ for ($i = $size; $i >= 0; --$i) { $c += (int) ($a & -($b & 1)); $a <<= 1; $b >>= 1; } $c = (int) @($c & -1); /** * If $b was negative, we then apply the same value to $c here. * It doesn't matter much if $a was negative; the $c += above would * have produced a negative integer to begin with. But a negative $b * makes $b >>= 1 never return 0, so we would end up with incorrect * results. * * The end result is what we'd expect from integer multiplication. */ return (int) (($c & ~$mask) | ($mask & -$c)); } /** * Convert any arbitrary numbers into two 32-bit integers that represent * a 64-bit integer. * * @internal You should not use this directly from another application * * @param int|float $num * @return array<int, int> */ public static function numericTo64BitInteger($num) { $high = 0; /** @var int $low */ if (PHP_INT_SIZE === 4) { $low = (int) $num; } else { $low = $num & 0xffffffff; } if ((+(abs($num))) >= 1) { if ($num > 0) { /** @var int $high */ $high = min((+(floor($num/4294967296))), 4294967295); } else { /** @var int $high */ $high = ~~((+(ceil(($num - (+((~~($num)))))/4294967296)))); } } return array((int) $high, (int) $low); } /** * Store a 24-bit integer into a string, treating it as big-endian. * * @internal You should not use this directly from another application * * @param int $int * @return string * @throws TypeError */ public static function store_3($int) { /* Type checks: */ if (!is_int($int)) { if (is_numeric($int)) { $int = (int) $int; } else { throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.'); } } /** @var string $packed */ $packed = pack('N', $int); return self::substr($packed, 1, 3); } /** * Store a 32-bit integer into a string, treating it as little-endian. * * @internal You should not use this directly from another application * * @param int $int * @return string * @throws TypeError */ public static function store32_le($int) { /* Type checks: */ if (!is_int($int)) { if (is_numeric($int)) { $int = (int) $int; } else { throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.'); } } /** @var string $packed */ $packed = pack('V', $int); return $packed; } /** * Store a 32-bit integer into a string, treating it as big-endian. * * @internal You should not use this directly from another application * * @param int $int * @return string * @throws TypeError */ public static function store_4($int) { /* Type checks: */ if (!is_int($int)) { if (is_numeric($int)) { $int = (int) $int; } else { throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.'); } } /** @var string $packed */ $packed = pack('N', $int); return $packed; } /** * Stores a 64-bit integer as an string, treating it as little-endian. * * @internal You should not use this directly from another application * * @param int $int * @return string * @throws TypeError */ public static function store64_le($int) { /* Type checks: */ if (!is_int($int)) { if (is_numeric($int)) { $int = (int) $int; } else { throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.'); } } if (PHP_INT_SIZE === 8) { if (PHP_VERSION_ID >= 50603) { /** @var string $packed */ $packed = pack('P', $int); return $packed; } return self::intToChr($int & 0xff) . self::intToChr(($int >> 8) & 0xff) . self::intToChr(($int >> 16) & 0xff) . self::intToChr(($int >> 24) & 0xff) . self::intToChr(($int >> 32) & 0xff) . self::intToChr(($int >> 40) & 0xff) . self::intToChr(($int >> 48) & 0xff) . self::intToChr(($int >> 56) & 0xff); } if ($int > PHP_INT_MAX) { list($hiB, $int) = self::numericTo64BitInteger($int); } else { $hiB = 0; } return self::intToChr(($int ) & 0xff) . self::intToChr(($int >> 8) & 0xff) . self::intToChr(($int >> 16) & 0xff) . self::intToChr(($int >> 24) & 0xff) . self::intToChr($hiB & 0xff) . self::intToChr(($hiB >> 8) & 0xff) . self::intToChr(($hiB >> 16) & 0xff) . self::intToChr(($hiB >> 24) & 0xff); } /** * Safe string length * * @internal You should not use this directly from another application * * @ref mbstring.func_overload * * @param string $str * @return int * @throws TypeError */ public static function strlen($str) { /* Type checks: */ if (!is_string($str)) { throw new TypeError('String expected'); } return (int) ( self::isMbStringOverride() ? mb_strlen($str, '8bit') : strlen($str) ); } /** * Turn a string into an array of integers * * @internal You should not use this directly from another application * * @param string $string * @return array<int, int> * @throws TypeError */ public static function stringToIntArray($string) { if (!is_string($string)) { throw new TypeError('String expected'); } /** * @var array<int, int> */ $values = array_values( unpack('C*', $string) ); return $values; } /** * Safe substring * * @internal You should not use this directly from another application * * @ref mbstring.func_overload * * @param string $str * @param int $start * @param int $length * @return string * @throws TypeError */ public static function substr($str, $start = 0, $length = null) { /* Type checks: */ if (!is_string($str)) { throw new TypeError('String expected'); } if ($length === 0) { return ''; } if (self::isMbStringOverride()) { if (PHP_VERSION_ID < 50400 && $length === null) { $length = self::strlen($str); } $sub = (string) mb_substr($str, $start, $length, '8bit'); } elseif ($length === null) { $sub = (string) substr($str, $start); } else { $sub = (string) substr($str, $start, $length); } if ($sub !== '') { return $sub; } return ''; } /** * Compare a 16-character byte string in constant time. * * @internal You should not use this directly from another application * * @param string $a * @param string $b * @return bool * @throws SodiumException * @throws TypeError */ public static function verify_16($a, $b) { /* Type checks: */ if (!is_string($a)) { throw new TypeError('String expected'); } if (!is_string($b)) { throw new TypeError('String expected'); } return self::hashEquals( self::substr($a, 0, 16), self::substr($b, 0, 16) ); } /** * Compare a 32-character byte string in constant time. * * @internal You should not use this directly from another application * * @param string $a * @param string $b * @return bool * @throws SodiumException * @throws TypeError */ public static function verify_32($a, $b) { /* Type checks: */ if (!is_string($a)) { throw new TypeError('String expected'); } if (!is_string($b)) { throw new TypeError('String expected'); } return self::hashEquals( self::substr($a, 0, 32), self::substr($b, 0, 32) ); } /** * Calculate $a ^ $b for two strings. * * @internal You should not use this directly from another application * * @param string $a * @param string $b * @return string * @throws TypeError */ public static function xorStrings($a, $b) { /* Type checks: */ if (!is_string($a)) { throw new TypeError('Argument 1 must be a string'); } if (!is_string($b)) { throw new TypeError('Argument 2 must be a string'); } return (string) ($a ^ $b); } /** * Returns whether or not mbstring.func_overload is in effect. * * @internal You should not use this directly from another application * * Note: MB_OVERLOAD_STRING === 2, but we don't reference the constant * (for nuisance-free PHP 8 support) * * @return bool */ protected static function isMbStringOverride() { static $mbstring = null; if ($mbstring === null) { if (!defined('MB_OVERLOAD_STRING')) { $mbstring = false; return $mbstring; } $mbstring = extension_loaded('mbstring') && defined('MB_OVERLOAD_STRING') && ((int) (ini_get('mbstring.func_overload')) & 2); // MB_OVERLOAD_STRING === 2 } /** @var bool $mbstring */ return $mbstring; } } Ristretto255.php 0000644 00000052574 14721742304 0007531 0 ustar 00 <?php /** * Class ParagonIE_Sodium_Core_Ristretto255 */ class ParagonIE_Sodium_Core_Ristretto255 extends ParagonIE_Sodium_Core_Ed25519 { const crypto_core_ristretto255_HASHBYTES = 64; const HASH_SC_L = 48; const CORE_H2C_SHA256 = 1; const CORE_H2C_SHA512 = 2; /** * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @param int $b * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_cneg(ParagonIE_Sodium_Core_Curve25519_Fe $f, $b) { $negf = self::fe_neg($f); return self::fe_cmov($f, $negf, $b); } /** * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return ParagonIE_Sodium_Core_Curve25519_Fe * @throws SodiumException */ public static function fe_abs(ParagonIE_Sodium_Core_Curve25519_Fe $f) { return self::fe_cneg($f, self::fe_isnegative($f)); } /** * Returns 0 if this field element results in all NUL bytes. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return int * @throws SodiumException */ public static function fe_iszero(ParagonIE_Sodium_Core_Curve25519_Fe $f) { static $zero; if ($zero === null) { $zero = str_repeat("\x00", 32); } /** @var string $zero */ $str = self::fe_tobytes($f); $d = 0; for ($i = 0; $i < 32; ++$i) { $d |= self::chrToInt($str[$i]); } return (($d - 1) >> 31) & 1; } /** * @param ParagonIE_Sodium_Core_Curve25519_Fe $u * @param ParagonIE_Sodium_Core_Curve25519_Fe $v * @return array{x: ParagonIE_Sodium_Core_Curve25519_Fe, nonsquare: int} * * @throws SodiumException */ public static function ristretto255_sqrt_ratio_m1( ParagonIE_Sodium_Core_Curve25519_Fe $u, ParagonIE_Sodium_Core_Curve25519_Fe $v ) { $sqrtm1 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtm1); $v3 = self::fe_mul( self::fe_sq($v), $v ); /* v3 = v^3 */ $x = self::fe_mul( self::fe_mul( self::fe_sq($v3), $u ), $v ); /* x = uv^7 */ $x = self::fe_mul( self::fe_mul( self::fe_pow22523($x), /* x = (uv^7)^((q-5)/8) */ $v3 ), $u ); /* x = uv^3(uv^7)^((q-5)/8) */ $vxx = self::fe_mul( self::fe_sq($x), $v ); /* vx^2 */ $m_root_check = self::fe_sub($vxx, $u); /* vx^2-u */ $p_root_check = self::fe_add($vxx, $u); /* vx^2+u */ $f_root_check = self::fe_mul($u, $sqrtm1); /* u*sqrt(-1) */ $f_root_check = self::fe_add($vxx, $f_root_check); /* vx^2+u*sqrt(-1) */ $has_m_root = self::fe_iszero($m_root_check); $has_p_root = self::fe_iszero($p_root_check); $has_f_root = self::fe_iszero($f_root_check); $x_sqrtm1 = self::fe_mul($x, $sqrtm1); /* x*sqrt(-1) */ $x = self::fe_abs( self::fe_cmov($x, $x_sqrtm1, $has_p_root | $has_f_root) ); return array( 'x' => $x, 'nonsquare' => $has_m_root | $has_p_root ); } /** * @param string $s * @return int * @throws SodiumException */ public static function ristretto255_point_is_canonical($s) { $c = (self::chrToInt($s[31]) & 0x7f) ^ 0x7f; for ($i = 30; $i > 0; --$i) { $c |= self::chrToInt($s[$i]) ^ 0xff; } $c = ($c - 1) >> 8; $d = (0xed - 1 - self::chrToInt($s[0])) >> 8; $e = self::chrToInt($s[31]) >> 7; return 1 - ((($c & $d) | $e | self::chrToInt($s[0])) & 1); } /** * @param string $s * @param bool $skipCanonicalCheck * @return array{h: ParagonIE_Sodium_Core_Curve25519_Ge_P3, res: int} * @throws SodiumException */ public static function ristretto255_frombytes($s, $skipCanonicalCheck = false) { if (!$skipCanonicalCheck) { if (!self::ristretto255_point_is_canonical($s)) { throw new SodiumException('S is not canonical'); } } $s_ = self::fe_frombytes($s); $ss = self::fe_sq($s_); /* ss = s^2 */ $u1 = self::fe_sub(self::fe_1(), $ss); /* u1 = 1-ss */ $u1u1 = self::fe_sq($u1); /* u1u1 = u1^2 */ $u2 = self::fe_add(self::fe_1(), $ss); /* u2 = 1+ss */ $u2u2 = self::fe_sq($u2); /* u2u2 = u2^2 */ $v = self::fe_mul( ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$d), $u1u1 ); /* v = d*u1^2 */ $v = self::fe_neg($v); /* v = -d*u1^2 */ $v = self::fe_sub($v, $u2u2); /* v = -(d*u1^2)-u2^2 */ $v_u2u2 = self::fe_mul($v, $u2u2); /* v_u2u2 = v*u2^2 */ // fe25519_1(one); // notsquare = ristretto255_sqrt_ratio_m1(inv_sqrt, one, v_u2u2); $one = self::fe_1(); $result = self::ristretto255_sqrt_ratio_m1($one, $v_u2u2); $inv_sqrt = $result['x']; $notsquare = $result['nonsquare']; $h = new ParagonIE_Sodium_Core_Curve25519_Ge_P3(); $h->X = self::fe_mul($inv_sqrt, $u2); $h->Y = self::fe_mul(self::fe_mul($inv_sqrt, $h->X), $v); $h->X = self::fe_mul($h->X, $s_); $h->X = self::fe_abs( self::fe_add($h->X, $h->X) ); $h->Y = self::fe_mul($u1, $h->Y); $h->Z = self::fe_1(); $h->T = self::fe_mul($h->X, $h->Y); $res = - ((1 - $notsquare) | self::fe_isnegative($h->T) | self::fe_iszero($h->Y)); return array('h' => $h, 'res' => $res); } /** * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $h * @return string * @throws SodiumException */ public static function ristretto255_p3_tobytes(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $h) { $sqrtm1 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtm1); $invsqrtamd = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$invsqrtamd); $u1 = self::fe_add($h->Z, $h->Y); /* u1 = Z+Y */ $zmy = self::fe_sub($h->Z, $h->Y); /* zmy = Z-Y */ $u1 = self::fe_mul($u1, $zmy); /* u1 = (Z+Y)*(Z-Y) */ $u2 = self::fe_mul($h->X, $h->Y); /* u2 = X*Y */ $u1_u2u2 = self::fe_mul(self::fe_sq($u2), $u1); /* u1_u2u2 = u1*u2^2 */ $one = self::fe_1(); // fe25519_1(one); // (void) ristretto255_sqrt_ratio_m1(inv_sqrt, one, u1_u2u2); $result = self::ristretto255_sqrt_ratio_m1($one, $u1_u2u2); $inv_sqrt = $result['x']; $den1 = self::fe_mul($inv_sqrt, $u1); /* den1 = inv_sqrt*u1 */ $den2 = self::fe_mul($inv_sqrt, $u2); /* den2 = inv_sqrt*u2 */ $z_inv = self::fe_mul($h->T, self::fe_mul($den1, $den2)); /* z_inv = den1*den2*T */ $ix = self::fe_mul($h->X, $sqrtm1); /* ix = X*sqrt(-1) */ $iy = self::fe_mul($h->Y, $sqrtm1); /* iy = Y*sqrt(-1) */ $eden = self::fe_mul($den1, $invsqrtamd); $t_z_inv = self::fe_mul($h->T, $z_inv); /* t_z_inv = T*z_inv */ $rotate = self::fe_isnegative($t_z_inv); $x_ = self::fe_copy($h->X); $y_ = self::fe_copy($h->Y); $den_inv = self::fe_copy($den2); $x_ = self::fe_cmov($x_, $iy, $rotate); $y_ = self::fe_cmov($y_, $ix, $rotate); $den_inv = self::fe_cmov($den_inv, $eden, $rotate); $x_z_inv = self::fe_mul($x_, $z_inv); $y_ = self::fe_cneg($y_, self::fe_isnegative($x_z_inv)); // fe25519_sub(s_, h->Z, y_); // fe25519_mul(s_, den_inv, s_); // fe25519_abs(s_, s_); // fe25519_tobytes(s, s_); return self::fe_tobytes( self::fe_abs( self::fe_mul( $den_inv, self::fe_sub($h->Z, $y_) ) ) ); } /** * @param ParagonIE_Sodium_Core_Curve25519_Fe $t * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3 * * @throws SodiumException */ public static function ristretto255_elligator(ParagonIE_Sodium_Core_Curve25519_Fe $t) { $sqrtm1 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtm1); $onemsqd = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$onemsqd); $d = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$d); $sqdmone = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqdmone); $sqrtadm1 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtadm1); $one = self::fe_1(); $r = self::fe_mul($sqrtm1, self::fe_sq($t)); /* r = sqrt(-1)*t^2 */ $u = self::fe_mul(self::fe_add($r, $one), $onemsqd); /* u = (r+1)*(1-d^2) */ $c = self::fe_neg(self::fe_1()); /* c = -1 */ $rpd = self::fe_add($r, $d); /* rpd = r+d */ $v = self::fe_mul( self::fe_sub( $c, self::fe_mul($r, $d) ), $rpd ); /* v = (c-r*d)*(r+d) */ $result = self::ristretto255_sqrt_ratio_m1($u, $v); $s = $result['x']; $wasnt_square = 1 - $result['nonsquare']; $s_prime = self::fe_neg( self::fe_abs( self::fe_mul($s, $t) ) ); /* s_prime = -|s*t| */ $s = self::fe_cmov($s, $s_prime, $wasnt_square); $c = self::fe_cmov($c, $r, $wasnt_square); // fe25519_sub(n, r, one); /* n = r-1 */ // fe25519_mul(n, n, c); /* n = c*(r-1) */ // fe25519_mul(n, n, ed25519_sqdmone); /* n = c*(r-1)*(d-1)^2 */ // fe25519_sub(n, n, v); /* n = c*(r-1)*(d-1)^2-v */ $n = self::fe_sub( self::fe_mul( self::fe_mul( self::fe_sub($r, $one), $c ), $sqdmone ), $v ); /* n = c*(r-1)*(d-1)^2-v */ $w0 = self::fe_mul( self::fe_add($s, $s), $v ); /* w0 = 2s*v */ $w1 = self::fe_mul($n, $sqrtadm1); /* w1 = n*sqrt(ad-1) */ $ss = self::fe_sq($s); /* ss = s^2 */ $w2 = self::fe_sub($one, $ss); /* w2 = 1-s^2 */ $w3 = self::fe_add($one, $ss); /* w3 = 1+s^2 */ return new ParagonIE_Sodium_Core_Curve25519_Ge_P3( self::fe_mul($w0, $w3), self::fe_mul($w2, $w1), self::fe_mul($w1, $w3), self::fe_mul($w0, $w2) ); } /** * @param string $h * @return string * @throws SodiumException */ public static function ristretto255_from_hash($h) { if (self::strlen($h) !== 64) { throw new SodiumException('Hash must be 64 bytes'); } //fe25519_frombytes(r0, h); //fe25519_frombytes(r1, h + 32); $r0 = self::fe_frombytes(self::substr($h, 0, 32)); $r1 = self::fe_frombytes(self::substr($h, 32, 32)); //ristretto255_elligator(&p0, r0); //ristretto255_elligator(&p1, r1); $p0 = self::ristretto255_elligator($r0); $p1 = self::ristretto255_elligator($r1); //ge25519_p3_to_cached(&p1_cached, &p1); //ge25519_add_cached(&p_p1p1, &p0, &p1_cached); $p_p1p1 = self::ge_add( $p0, self::ge_p3_to_cached($p1) ); //ge25519_p1p1_to_p3(&p, &p_p1p1); //ristretto255_p3_tobytes(s, &p); return self::ristretto255_p3_tobytes( self::ge_p1p1_to_p3($p_p1p1) ); } /** * @param string $p * @return int * @throws SodiumException */ public static function is_valid_point($p) { $result = self::ristretto255_frombytes($p); if ($result['res'] !== 0) { return 0; } return 1; } /** * @param string $p * @param string $q * @return string * @throws SodiumException */ public static function ristretto255_add($p, $q) { $p_res = self::ristretto255_frombytes($p); $q_res = self::ristretto255_frombytes($q); if ($p_res['res'] !== 0 || $q_res['res'] !== 0) { throw new SodiumException('Could not add points'); } $p_p3 = $p_res['h']; $q_p3 = $q_res['h']; $q_cached = self::ge_p3_to_cached($q_p3); $r_p1p1 = self::ge_add($p_p3, $q_cached); $r_p3 = self::ge_p1p1_to_p3($r_p1p1); return self::ristretto255_p3_tobytes($r_p3); } /** * @param string $p * @param string $q * @return string * @throws SodiumException */ public static function ristretto255_sub($p, $q) { $p_res = self::ristretto255_frombytes($p); $q_res = self::ristretto255_frombytes($q); if ($p_res['res'] !== 0 || $q_res['res'] !== 0) { throw new SodiumException('Could not add points'); } $p_p3 = $p_res['h']; $q_p3 = $q_res['h']; $q_cached = self::ge_p3_to_cached($q_p3); $r_p1p1 = self::ge_sub($p_p3, $q_cached); $r_p3 = self::ge_p1p1_to_p3($r_p1p1); return self::ristretto255_p3_tobytes($r_p3); } /** * @param int $hLen * @param ?string $ctx * @param string $msg * @return string * @throws SodiumException * @psalm-suppress PossiblyInvalidArgument hash API */ protected static function h2c_string_to_hash_sha256($hLen, $ctx, $msg) { $h = array_fill(0, $hLen, 0); $ctx_len = !is_null($ctx) ? self::strlen($ctx) : 0; if ($hLen > 0xff) { throw new SodiumException('Hash must be less than 256 bytes'); } if ($ctx_len > 0xff) { $st = hash_init('sha256'); self::hash_update($st, "H2C-OVERSIZE-DST-"); self::hash_update($st, $ctx); $ctx = hash_final($st, true); $ctx_len = 32; } $t = array(0, $hLen, 0); $ux = str_repeat("\0", 64); $st = hash_init('sha256'); self::hash_update($st, $ux); self::hash_update($st, $msg); self::hash_update($st, self::intArrayToString($t)); self::hash_update($st, $ctx); self::hash_update($st, self::intToChr($ctx_len)); $u0 = hash_final($st, true); for ($i = 0; $i < $hLen; $i += 64) { $ux = self::xorStrings($ux, $u0); ++$t[2]; $st = hash_init('sha256'); self::hash_update($st, $ux); self::hash_update($st, self::intToChr($t[2])); self::hash_update($st, $ctx); self::hash_update($st, self::intToChr($ctx_len)); $ux = hash_final($st, true); $amount = min($hLen - $i, 64); for ($j = 0; $j < $amount; ++$j) { $h[$i + $j] = self::chrToInt($ux[$i]); } } return self::intArrayToString(array_slice($h, 0, $hLen)); } /** * @param int $hLen * @param ?string $ctx * @param string $msg * @return string * @throws SodiumException * @psalm-suppress PossiblyInvalidArgument hash API */ protected static function h2c_string_to_hash_sha512($hLen, $ctx, $msg) { $h = array_fill(0, $hLen, 0); $ctx_len = !is_null($ctx) ? self::strlen($ctx) : 0; if ($hLen > 0xff) { throw new SodiumException('Hash must be less than 256 bytes'); } if ($ctx_len > 0xff) { $st = hash_init('sha256'); self::hash_update($st, "H2C-OVERSIZE-DST-"); self::hash_update($st, $ctx); $ctx = hash_final($st, true); $ctx_len = 32; } $t = array(0, $hLen, 0); $ux = str_repeat("\0", 128); $st = hash_init('sha512'); self::hash_update($st, $ux); self::hash_update($st, $msg); self::hash_update($st, self::intArrayToString($t)); self::hash_update($st, $ctx); self::hash_update($st, self::intToChr($ctx_len)); $u0 = hash_final($st, true); for ($i = 0; $i < $hLen; $i += 128) { $ux = self::xorStrings($ux, $u0); ++$t[2]; $st = hash_init('sha512'); self::hash_update($st, $ux); self::hash_update($st, self::intToChr($t[2])); self::hash_update($st, $ctx); self::hash_update($st, self::intToChr($ctx_len)); $ux = hash_final($st, true); $amount = min($hLen - $i, 128); for ($j = 0; $j < $amount; ++$j) { $h[$i + $j] = self::chrToInt($ux[$i]); } } return self::intArrayToString(array_slice($h, 0, $hLen)); } /** * @param int $hLen * @param ?string $ctx * @param string $msg * @param int $hash_alg * @return string * @throws SodiumException */ public static function h2c_string_to_hash($hLen, $ctx, $msg, $hash_alg) { switch ($hash_alg) { case self::CORE_H2C_SHA256: return self::h2c_string_to_hash_sha256($hLen, $ctx, $msg); case self::CORE_H2C_SHA512: return self::h2c_string_to_hash_sha512($hLen, $ctx, $msg); default: throw new SodiumException('Invalid H2C hash algorithm'); } } /** * @param ?string $ctx * @param string $msg * @param int $hash_alg * @return string * @throws SodiumException */ protected static function _string_to_element($ctx, $msg, $hash_alg) { return self::ristretto255_from_hash( self::h2c_string_to_hash(self::crypto_core_ristretto255_HASHBYTES, $ctx, $msg, $hash_alg) ); } /** * @return string * @throws SodiumException * @throws Exception */ public static function ristretto255_random() { return self::ristretto255_from_hash( ParagonIE_Sodium_Compat::randombytes_buf(self::crypto_core_ristretto255_HASHBYTES) ); } /** * @return string * @throws SodiumException */ public static function ristretto255_scalar_random() { return self::scalar_random(); } /** * @param string $s * @return string * @throws SodiumException */ public static function ristretto255_scalar_complement($s) { return self::scalar_complement($s); } /** * @param string $s * @return string */ public static function ristretto255_scalar_invert($s) { return self::sc25519_invert($s); } /** * @param string $s * @return string * @throws SodiumException */ public static function ristretto255_scalar_negate($s) { return self::scalar_negate($s); } /** * @param string $x * @param string $y * @return string */ public static function ristretto255_scalar_add($x, $y) { return self::scalar_add($x, $y); } /** * @param string $x * @param string $y * @return string */ public static function ristretto255_scalar_sub($x, $y) { return self::scalar_sub($x, $y); } /** * @param string $x * @param string $y * @return string */ public static function ristretto255_scalar_mul($x, $y) { return self::sc25519_mul($x, $y); } /** * @param string $ctx * @param string $msg * @param int $hash_alg * @return string * @throws SodiumException */ public static function ristretto255_scalar_from_string($ctx, $msg, $hash_alg) { $h = array_fill(0, 64, 0); $h_be = self::stringToIntArray( self::h2c_string_to_hash( self::HASH_SC_L, $ctx, $msg, $hash_alg ) ); for ($i = 0; $i < self::HASH_SC_L; ++$i) { $h[$i] = $h_be[self::HASH_SC_L - 1 - $i]; } return self::ristretto255_scalar_reduce(self::intArrayToString($h)); } /** * @param string $s * @return string */ public static function ristretto255_scalar_reduce($s) { return self::sc_reduce($s); } /** * @param string $n * @param string $p * @return string * @throws SodiumException */ public static function scalarmult_ristretto255($n, $p) { if (self::strlen($n) !== 32) { throw new SodiumException('Scalar must be 32 bytes, ' . self::strlen($p) . ' given.'); } if (self::strlen($p) !== 32) { throw new SodiumException('Point must be 32 bytes, ' . self::strlen($p) . ' given.'); } $result = self::ristretto255_frombytes($p); if ($result['res'] !== 0) { throw new SodiumException('Could not multiply points'); } $P = $result['h']; $t = self::stringToIntArray($n); $t[31] &= 0x7f; $Q = self::ge_scalarmult(self::intArrayToString($t), $P); $q = self::ristretto255_p3_tobytes($Q); if (ParagonIE_Sodium_Compat::is_zero($q)) { throw new SodiumException('An unknown error has occurred'); } return $q; } /** * @param string $n * @return string * @throws SodiumException */ public static function scalarmult_ristretto255_base($n) { $t = self::stringToIntArray($n); $t[31] &= 0x7f; $Q = self::ge_scalarmult_base(self::intArrayToString($t)); $q = self::ristretto255_p3_tobytes($Q); if (ParagonIE_Sodium_Compat::is_zero($q)) { throw new SodiumException('An unknown error has occurred'); } return $q; } } Salsa20.php 0000644 00000020051 14721742304 0006464 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Salsa20', false)) { return; } /** * Class ParagonIE_Sodium_Core_Salsa20 */ abstract class ParagonIE_Sodium_Core_Salsa20 extends ParagonIE_Sodium_Core_Util { const ROUNDS = 20; /** * Calculate an salsa20 hash of a single block * * @internal You should not use this directly from another application * * @param string $in * @param string $k * @param string|null $c * @return string * @throws TypeError */ public static function core_salsa20($in, $k, $c = null) { if (self::strlen($k) < 32) { throw new RangeException('Key must be 32 bytes long'); } if ($c === null) { $j0 = $x0 = 0x61707865; $j5 = $x5 = 0x3320646e; $j10 = $x10 = 0x79622d32; $j15 = $x15 = 0x6b206574; } else { $j0 = $x0 = self::load_4(self::substr($c, 0, 4)); $j5 = $x5 = self::load_4(self::substr($c, 4, 4)); $j10 = $x10 = self::load_4(self::substr($c, 8, 4)); $j15 = $x15 = self::load_4(self::substr($c, 12, 4)); } $j1 = $x1 = self::load_4(self::substr($k, 0, 4)); $j2 = $x2 = self::load_4(self::substr($k, 4, 4)); $j3 = $x3 = self::load_4(self::substr($k, 8, 4)); $j4 = $x4 = self::load_4(self::substr($k, 12, 4)); $j6 = $x6 = self::load_4(self::substr($in, 0, 4)); $j7 = $x7 = self::load_4(self::substr($in, 4, 4)); $j8 = $x8 = self::load_4(self::substr($in, 8, 4)); $j9 = $x9 = self::load_4(self::substr($in, 12, 4)); $j11 = $x11 = self::load_4(self::substr($k, 16, 4)); $j12 = $x12 = self::load_4(self::substr($k, 20, 4)); $j13 = $x13 = self::load_4(self::substr($k, 24, 4)); $j14 = $x14 = self::load_4(self::substr($k, 28, 4)); for ($i = self::ROUNDS; $i > 0; $i -= 2) { $x4 ^= self::rotate($x0 + $x12, 7); $x8 ^= self::rotate($x4 + $x0, 9); $x12 ^= self::rotate($x8 + $x4, 13); $x0 ^= self::rotate($x12 + $x8, 18); $x9 ^= self::rotate($x5 + $x1, 7); $x13 ^= self::rotate($x9 + $x5, 9); $x1 ^= self::rotate($x13 + $x9, 13); $x5 ^= self::rotate($x1 + $x13, 18); $x14 ^= self::rotate($x10 + $x6, 7); $x2 ^= self::rotate($x14 + $x10, 9); $x6 ^= self::rotate($x2 + $x14, 13); $x10 ^= self::rotate($x6 + $x2, 18); $x3 ^= self::rotate($x15 + $x11, 7); $x7 ^= self::rotate($x3 + $x15, 9); $x11 ^= self::rotate($x7 + $x3, 13); $x15 ^= self::rotate($x11 + $x7, 18); $x1 ^= self::rotate($x0 + $x3, 7); $x2 ^= self::rotate($x1 + $x0, 9); $x3 ^= self::rotate($x2 + $x1, 13); $x0 ^= self::rotate($x3 + $x2, 18); $x6 ^= self::rotate($x5 + $x4, 7); $x7 ^= self::rotate($x6 + $x5, 9); $x4 ^= self::rotate($x7 + $x6, 13); $x5 ^= self::rotate($x4 + $x7, 18); $x11 ^= self::rotate($x10 + $x9, 7); $x8 ^= self::rotate($x11 + $x10, 9); $x9 ^= self::rotate($x8 + $x11, 13); $x10 ^= self::rotate($x9 + $x8, 18); $x12 ^= self::rotate($x15 + $x14, 7); $x13 ^= self::rotate($x12 + $x15, 9); $x14 ^= self::rotate($x13 + $x12, 13); $x15 ^= self::rotate($x14 + $x13, 18); } $x0 += $j0; $x1 += $j1; $x2 += $j2; $x3 += $j3; $x4 += $j4; $x5 += $j5; $x6 += $j6; $x7 += $j7; $x8 += $j8; $x9 += $j9; $x10 += $j10; $x11 += $j11; $x12 += $j12; $x13 += $j13; $x14 += $j14; $x15 += $j15; return self::store32_le($x0) . self::store32_le($x1) . self::store32_le($x2) . self::store32_le($x3) . self::store32_le($x4) . self::store32_le($x5) . self::store32_le($x6) . self::store32_le($x7) . self::store32_le($x8) . self::store32_le($x9) . self::store32_le($x10) . self::store32_le($x11) . self::store32_le($x12) . self::store32_le($x13) . self::store32_le($x14) . self::store32_le($x15); } /** * @internal You should not use this directly from another application * * @param int $len * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function salsa20($len, $nonce, $key) { if (self::strlen($key) !== 32) { throw new RangeException('Key must be 32 bytes long'); } $kcopy = '' . $key; $in = self::substr($nonce, 0, 8) . str_repeat("\0", 8); $c = ''; while ($len >= 64) { $c .= self::core_salsa20($in, $kcopy, null); $u = 1; // Internal counter. for ($i = 8; $i < 16; ++$i) { $u += self::chrToInt($in[$i]); $in[$i] = self::intToChr($u & 0xff); $u >>= 8; } $len -= 64; } if ($len > 0) { $c .= self::substr( self::core_salsa20($in, $kcopy, null), 0, $len ); } try { ParagonIE_Sodium_Compat::memzero($kcopy); } catch (SodiumException $ex) { $kcopy = null; } return $c; } /** * @internal You should not use this directly from another application * * @param string $m * @param string $n * @param int $ic * @param string $k * @return string * @throws SodiumException * @throws TypeError */ public static function salsa20_xor_ic($m, $n, $ic, $k) { $mlen = self::strlen($m); if ($mlen < 1) { return ''; } $kcopy = self::substr($k, 0, 32); $in = self::substr($n, 0, 8); // Initialize the counter $in .= ParagonIE_Sodium_Core_Util::store64_le($ic); $c = ''; while ($mlen >= 64) { $block = self::core_salsa20($in, $kcopy, null); $c .= self::xorStrings( self::substr($m, 0, 64), self::substr($block, 0, 64) ); $u = 1; for ($i = 8; $i < 16; ++$i) { $u += self::chrToInt($in[$i]); $in[$i] = self::intToChr($u & 0xff); $u >>= 8; } $mlen -= 64; $m = self::substr($m, 64); } if ($mlen) { $block = self::core_salsa20($in, $kcopy, null); $c .= self::xorStrings( self::substr($m, 0, $mlen), self::substr($block, 0, $mlen) ); } try { ParagonIE_Sodium_Compat::memzero($block); ParagonIE_Sodium_Compat::memzero($kcopy); } catch (SodiumException $ex) { $block = null; $kcopy = null; } return $c; } /** * @internal You should not use this directly from another application * * @param string $message * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function salsa20_xor($message, $nonce, $key) { return self::xorStrings( $message, self::salsa20( self::strlen($message), $nonce, $key ) ); } /** * @internal You should not use this directly from another application * * @param int $u * @param int $c * @return int */ public static function rotate($u, $c) { $u &= 0xffffffff; $c %= 32; return (int) (0xffffffff & ( ($u << $c) | ($u >> (32 - $c)) ) ); } } AES.php 0000644 00000037015 14721742304 0005677 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_AES', false)) { return; } /** * Bitsliced implementation of the AES block cipher. * * Based on the implementation provided by BearSSL. * * @internal This should only be used by sodium_compat */ class ParagonIE_Sodium_Core_AES extends ParagonIE_Sodium_Core_Util { /** * @var int[] AES round constants */ private static $Rcon = array( 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36 ); /** * Mutates the values of $q! * * @param ParagonIE_Sodium_Core_AES_Block $q * @return void */ public static function sbox(ParagonIE_Sodium_Core_AES_Block $q) { /** * @var int $x0 * @var int $x1 * @var int $x2 * @var int $x3 * @var int $x4 * @var int $x5 * @var int $x6 * @var int $x7 */ $x0 = $q[7] & self::U32_MAX; $x1 = $q[6] & self::U32_MAX; $x2 = $q[5] & self::U32_MAX; $x3 = $q[4] & self::U32_MAX; $x4 = $q[3] & self::U32_MAX; $x5 = $q[2] & self::U32_MAX; $x6 = $q[1] & self::U32_MAX; $x7 = $q[0] & self::U32_MAX; $y14 = $x3 ^ $x5; $y13 = $x0 ^ $x6; $y9 = $x0 ^ $x3; $y8 = $x0 ^ $x5; $t0 = $x1 ^ $x2; $y1 = $t0 ^ $x7; $y4 = $y1 ^ $x3; $y12 = $y13 ^ $y14; $y2 = $y1 ^ $x0; $y5 = $y1 ^ $x6; $y3 = $y5 ^ $y8; $t1 = $x4 ^ $y12; $y15 = $t1 ^ $x5; $y20 = $t1 ^ $x1; $y6 = $y15 ^ $x7; $y10 = $y15 ^ $t0; $y11 = $y20 ^ $y9; $y7 = $x7 ^ $y11; $y17 = $y10 ^ $y11; $y19 = $y10 ^ $y8; $y16 = $t0 ^ $y11; $y21 = $y13 ^ $y16; $y18 = $x0 ^ $y16; /* * Non-linear section. */ $t2 = $y12 & $y15; $t3 = $y3 & $y6; $t4 = $t3 ^ $t2; $t5 = $y4 & $x7; $t6 = $t5 ^ $t2; $t7 = $y13 & $y16; $t8 = $y5 & $y1; $t9 = $t8 ^ $t7; $t10 = $y2 & $y7; $t11 = $t10 ^ $t7; $t12 = $y9 & $y11; $t13 = $y14 & $y17; $t14 = $t13 ^ $t12; $t15 = $y8 & $y10; $t16 = $t15 ^ $t12; $t17 = $t4 ^ $t14; $t18 = $t6 ^ $t16; $t19 = $t9 ^ $t14; $t20 = $t11 ^ $t16; $t21 = $t17 ^ $y20; $t22 = $t18 ^ $y19; $t23 = $t19 ^ $y21; $t24 = $t20 ^ $y18; $t25 = $t21 ^ $t22; $t26 = $t21 & $t23; $t27 = $t24 ^ $t26; $t28 = $t25 & $t27; $t29 = $t28 ^ $t22; $t30 = $t23 ^ $t24; $t31 = $t22 ^ $t26; $t32 = $t31 & $t30; $t33 = $t32 ^ $t24; $t34 = $t23 ^ $t33; $t35 = $t27 ^ $t33; $t36 = $t24 & $t35; $t37 = $t36 ^ $t34; $t38 = $t27 ^ $t36; $t39 = $t29 & $t38; $t40 = $t25 ^ $t39; $t41 = $t40 ^ $t37; $t42 = $t29 ^ $t33; $t43 = $t29 ^ $t40; $t44 = $t33 ^ $t37; $t45 = $t42 ^ $t41; $z0 = $t44 & $y15; $z1 = $t37 & $y6; $z2 = $t33 & $x7; $z3 = $t43 & $y16; $z4 = $t40 & $y1; $z5 = $t29 & $y7; $z6 = $t42 & $y11; $z7 = $t45 & $y17; $z8 = $t41 & $y10; $z9 = $t44 & $y12; $z10 = $t37 & $y3; $z11 = $t33 & $y4; $z12 = $t43 & $y13; $z13 = $t40 & $y5; $z14 = $t29 & $y2; $z15 = $t42 & $y9; $z16 = $t45 & $y14; $z17 = $t41 & $y8; /* * Bottom linear transformation. */ $t46 = $z15 ^ $z16; $t47 = $z10 ^ $z11; $t48 = $z5 ^ $z13; $t49 = $z9 ^ $z10; $t50 = $z2 ^ $z12; $t51 = $z2 ^ $z5; $t52 = $z7 ^ $z8; $t53 = $z0 ^ $z3; $t54 = $z6 ^ $z7; $t55 = $z16 ^ $z17; $t56 = $z12 ^ $t48; $t57 = $t50 ^ $t53; $t58 = $z4 ^ $t46; $t59 = $z3 ^ $t54; $t60 = $t46 ^ $t57; $t61 = $z14 ^ $t57; $t62 = $t52 ^ $t58; $t63 = $t49 ^ $t58; $t64 = $z4 ^ $t59; $t65 = $t61 ^ $t62; $t66 = $z1 ^ $t63; $s0 = $t59 ^ $t63; $s6 = $t56 ^ ~$t62; $s7 = $t48 ^ ~$t60; $t67 = $t64 ^ $t65; $s3 = $t53 ^ $t66; $s4 = $t51 ^ $t66; $s5 = $t47 ^ $t65; $s1 = $t64 ^ ~$s3; $s2 = $t55 ^ ~$t67; $q[7] = $s0 & self::U32_MAX; $q[6] = $s1 & self::U32_MAX; $q[5] = $s2 & self::U32_MAX; $q[4] = $s3 & self::U32_MAX; $q[3] = $s4 & self::U32_MAX; $q[2] = $s5 & self::U32_MAX; $q[1] = $s6 & self::U32_MAX; $q[0] = $s7 & self::U32_MAX; } /** * Mutates the values of $q! * * @param ParagonIE_Sodium_Core_AES_Block $q * @return void */ public static function invSbox(ParagonIE_Sodium_Core_AES_Block $q) { self::processInversion($q); self::sbox($q); self::processInversion($q); } /** * This is some boilerplate code needed to invert an S-box. Rather than repeat the code * twice, I moved it to a protected method. * * Mutates $q * * @param ParagonIE_Sodium_Core_AES_Block $q * @return void */ protected static function processInversion(ParagonIE_Sodium_Core_AES_Block $q) { $q0 = (~$q[0]) & self::U32_MAX; $q1 = (~$q[1]) & self::U32_MAX; $q2 = $q[2] & self::U32_MAX; $q3 = $q[3] & self::U32_MAX; $q4 = $q[4] & self::U32_MAX; $q5 = (~$q[5]) & self::U32_MAX; $q6 = (~$q[6]) & self::U32_MAX; $q7 = $q[7] & self::U32_MAX; $q[7] = ($q1 ^ $q4 ^ $q6) & self::U32_MAX; $q[6] = ($q0 ^ $q3 ^ $q5) & self::U32_MAX; $q[5] = ($q7 ^ $q2 ^ $q4) & self::U32_MAX; $q[4] = ($q6 ^ $q1 ^ $q3) & self::U32_MAX; $q[3] = ($q5 ^ $q0 ^ $q2) & self::U32_MAX; $q[2] = ($q4 ^ $q7 ^ $q1) & self::U32_MAX; $q[1] = ($q3 ^ $q6 ^ $q0) & self::U32_MAX; $q[0] = ($q2 ^ $q5 ^ $q7) & self::U32_MAX; } /** * @param int $x * @return int */ public static function subWord($x) { $q = ParagonIE_Sodium_Core_AES_Block::fromArray( array($x, $x, $x, $x, $x, $x, $x, $x) ); $q->orthogonalize(); self::sbox($q); $q->orthogonalize(); return $q[0] & self::U32_MAX; } /** * Calculate the key schedule from a given random key * * @param string $key * @return ParagonIE_Sodium_Core_AES_KeySchedule * @throws SodiumException */ public static function keySchedule($key) { $key_len = self::strlen($key); switch ($key_len) { case 16: $num_rounds = 10; break; case 24: $num_rounds = 12; break; case 32: $num_rounds = 14; break; default: throw new SodiumException('Invalid key length: ' . $key_len); } $skey = array(); $comp_skey = array(); $nk = $key_len >> 2; $nkf = ($num_rounds + 1) << 2; $tmp = 0; for ($i = 0; $i < $nk; ++$i) { $tmp = self::load_4(self::substr($key, $i << 2, 4)); $skey[($i << 1)] = $tmp; $skey[($i << 1) + 1] = $tmp; } for ($i = $nk, $j = 0, $k = 0; $i < $nkf; ++$i) { if ($j === 0) { $tmp = (($tmp & 0xff) << 24) | ($tmp >> 8); $tmp = (self::subWord($tmp) ^ self::$Rcon[$k]) & self::U32_MAX; } elseif ($nk > 6 && $j === 4) { $tmp = self::subWord($tmp); } $tmp ^= $skey[($i - $nk) << 1]; $skey[($i << 1)] = $tmp & self::U32_MAX; $skey[($i << 1) + 1] = $tmp & self::U32_MAX; if (++$j === $nk) { /** @psalm-suppress LoopInvalidation */ $j = 0; ++$k; } } for ($i = 0; $i < $nkf; $i += 4) { $q = ParagonIE_Sodium_Core_AES_Block::fromArray( array_slice($skey, $i << 1, 8) ); $q->orthogonalize(); // We have to overwrite $skey since we're not using C pointers like BearSSL did for ($j = 0; $j < 8; ++$j) { $skey[($i << 1) + $j] = $q[$j]; } } for ($i = 0, $j = 0; $i < $nkf; ++$i, $j += 2) { $comp_skey[$i] = ($skey[$j] & 0x55555555) | ($skey[$j + 1] & 0xAAAAAAAA); } return new ParagonIE_Sodium_Core_AES_KeySchedule($comp_skey, $num_rounds); } /** * Mutates $q * * @param ParagonIE_Sodium_Core_AES_KeySchedule $skey * @param ParagonIE_Sodium_Core_AES_Block $q * @param int $offset * @return void */ public static function addRoundKey( ParagonIE_Sodium_Core_AES_Block $q, ParagonIE_Sodium_Core_AES_KeySchedule $skey, $offset = 0 ) { $block = $skey->getRoundKey($offset); for ($j = 0; $j < 8; ++$j) { $q[$j] = ($q[$j] ^ $block[$j]) & ParagonIE_Sodium_Core_Util::U32_MAX; } } /** * This mainly exists for testing, as we need the round key features for AEGIS. * * @param string $message * @param string $key * @return string * @throws SodiumException */ public static function decryptBlockECB($message, $key) { if (self::strlen($message) !== 16) { throw new SodiumException('decryptBlockECB() expects a 16 byte message'); } $skey = self::keySchedule($key)->expand(); $q = ParagonIE_Sodium_Core_AES_Block::init(); $q[0] = self::load_4(self::substr($message, 0, 4)); $q[2] = self::load_4(self::substr($message, 4, 4)); $q[4] = self::load_4(self::substr($message, 8, 4)); $q[6] = self::load_4(self::substr($message, 12, 4)); $q->orthogonalize(); self::bitsliceDecryptBlock($skey, $q); $q->orthogonalize(); return self::store32_le($q[0]) . self::store32_le($q[2]) . self::store32_le($q[4]) . self::store32_le($q[6]); } /** * This mainly exists for testing, as we need the round key features for AEGIS. * * @param string $message * @param string $key * @return string * @throws SodiumException */ public static function encryptBlockECB($message, $key) { if (self::strlen($message) !== 16) { throw new SodiumException('encryptBlockECB() expects a 16 byte message'); } $comp_skey = self::keySchedule($key); $skey = $comp_skey->expand(); $q = ParagonIE_Sodium_Core_AES_Block::init(); $q[0] = self::load_4(self::substr($message, 0, 4)); $q[2] = self::load_4(self::substr($message, 4, 4)); $q[4] = self::load_4(self::substr($message, 8, 4)); $q[6] = self::load_4(self::substr($message, 12, 4)); $q->orthogonalize(); self::bitsliceEncryptBlock($skey, $q); $q->orthogonalize(); return self::store32_le($q[0]) . self::store32_le($q[2]) . self::store32_le($q[4]) . self::store32_le($q[6]); } /** * Mutates $q * * @param ParagonIE_Sodium_Core_AES_Expanded $skey * @param ParagonIE_Sodium_Core_AES_Block $q * @return void */ public static function bitsliceEncryptBlock( ParagonIE_Sodium_Core_AES_Expanded $skey, ParagonIE_Sodium_Core_AES_Block $q ) { self::addRoundKey($q, $skey); for ($u = 1; $u < $skey->getNumRounds(); ++$u) { self::sbox($q); $q->shiftRows(); $q->mixColumns(); self::addRoundKey($q, $skey, ($u << 3)); } self::sbox($q); $q->shiftRows(); self::addRoundKey($q, $skey, ($skey->getNumRounds() << 3)); } /** * @param string $x * @param string $y * @return string */ public static function aesRound($x, $y) { $q = ParagonIE_Sodium_Core_AES_Block::init(); $q[0] = self::load_4(self::substr($x, 0, 4)); $q[2] = self::load_4(self::substr($x, 4, 4)); $q[4] = self::load_4(self::substr($x, 8, 4)); $q[6] = self::load_4(self::substr($x, 12, 4)); $rk = ParagonIE_Sodium_Core_AES_Block::init(); $rk[0] = $rk[1] = self::load_4(self::substr($y, 0, 4)); $rk[2] = $rk[3] = self::load_4(self::substr($y, 4, 4)); $rk[4] = $rk[5] = self::load_4(self::substr($y, 8, 4)); $rk[6] = $rk[7] = self::load_4(self::substr($y, 12, 4)); $q->orthogonalize(); self::sbox($q); $q->shiftRows(); $q->mixColumns(); $q->orthogonalize(); // add round key without key schedule: for ($i = 0; $i < 8; ++$i) { $q[$i] ^= $rk[$i]; } return self::store32_le($q[0]) . self::store32_le($q[2]) . self::store32_le($q[4]) . self::store32_le($q[6]); } /** * Process two AES blocks in one shot. * * @param string $b0 First AES block * @param string $rk0 First round key * @param string $b1 Second AES block * @param string $rk1 Second round key * @return string[] */ public static function doubleRound($b0, $rk0, $b1, $rk1) { $q = ParagonIE_Sodium_Core_AES_Block::init(); // First block $q[0] = self::load_4(self::substr($b0, 0, 4)); $q[2] = self::load_4(self::substr($b0, 4, 4)); $q[4] = self::load_4(self::substr($b0, 8, 4)); $q[6] = self::load_4(self::substr($b0, 12, 4)); // Second block $q[1] = self::load_4(self::substr($b1, 0, 4)); $q[3] = self::load_4(self::substr($b1, 4, 4)); $q[5] = self::load_4(self::substr($b1, 8, 4)); $q[7] = self::load_4(self::substr($b1, 12, 4));; $rk = ParagonIE_Sodium_Core_AES_Block::init(); // First round key $rk[0] = self::load_4(self::substr($rk0, 0, 4)); $rk[2] = self::load_4(self::substr($rk0, 4, 4)); $rk[4] = self::load_4(self::substr($rk0, 8, 4)); $rk[6] = self::load_4(self::substr($rk0, 12, 4)); // Second round key $rk[1] = self::load_4(self::substr($rk1, 0, 4)); $rk[3] = self::load_4(self::substr($rk1, 4, 4)); $rk[5] = self::load_4(self::substr($rk1, 8, 4)); $rk[7] = self::load_4(self::substr($rk1, 12, 4)); $q->orthogonalize(); self::sbox($q); $q->shiftRows(); $q->mixColumns(); $q->orthogonalize(); // add round key without key schedule: for ($i = 0; $i < 8; ++$i) { $q[$i] ^= $rk[$i]; } return array( self::store32_le($q[0]) . self::store32_le($q[2]) . self::store32_le($q[4]) . self::store32_le($q[6]), self::store32_le($q[1]) . self::store32_le($q[3]) . self::store32_le($q[5]) . self::store32_le($q[7]), ); } /** * @param ParagonIE_Sodium_Core_AES_Expanded $skey * @param ParagonIE_Sodium_Core_AES_Block $q * @return void */ public static function bitsliceDecryptBlock( ParagonIE_Sodium_Core_AES_Expanded $skey, ParagonIE_Sodium_Core_AES_Block $q ) { self::addRoundKey($q, $skey, ($skey->getNumRounds() << 3)); for ($u = $skey->getNumRounds() - 1; $u > 0; --$u) { $q->inverseShiftRows(); self::invSbox($q); self::addRoundKey($q, $skey, ($u << 3)); $q->inverseMixColumns(); } $q->inverseShiftRows(); self::invSbox($q); self::addRoundKey($q, $skey, ($u << 3)); } } Curve25519.php 0000644 00000426446 14721742304 0006773 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Curve25519', false)) { return; } /** * Class ParagonIE_Sodium_Core_Curve25519 * * Implements Curve25519 core functions * * Based on the ref10 curve25519 code provided by libsodium * * @ref https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c */ abstract class ParagonIE_Sodium_Core_Curve25519 extends ParagonIE_Sodium_Core_Curve25519_H { /** * Get a field element of size 10 with a value of 0 * * @internal You should not use this directly from another application * * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_0() { return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray( array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) ); } /** * Get a field element of size 10 with a value of 1 * * @internal You should not use this directly from another application * * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_1() { return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray( array(1, 0, 0, 0, 0, 0, 0, 0, 0, 0) ); } /** * Add two field elements. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @param ParagonIE_Sodium_Core_Curve25519_Fe $g * @return ParagonIE_Sodium_Core_Curve25519_Fe * @psalm-suppress MixedAssignment * @psalm-suppress MixedOperand */ public static function fe_add( ParagonIE_Sodium_Core_Curve25519_Fe $f, ParagonIE_Sodium_Core_Curve25519_Fe $g ) { /** @var array<int, int> $arr */ $arr = array(); for ($i = 0; $i < 10; ++$i) { $arr[$i] = (int) ($f[$i] + $g[$i]); } return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($arr); } /** * Constant-time conditional move. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @param ParagonIE_Sodium_Core_Curve25519_Fe $g * @param int $b * @return ParagonIE_Sodium_Core_Curve25519_Fe * @psalm-suppress MixedAssignment */ public static function fe_cmov( ParagonIE_Sodium_Core_Curve25519_Fe $f, ParagonIE_Sodium_Core_Curve25519_Fe $g, $b = 0 ) { /** @var array<int, int> $h */ $h = array(); $b *= -1; for ($i = 0; $i < 10; ++$i) { $x = (($f[$i] ^ $g[$i]) & $b); $h[$i] = ($f[$i]) ^ $x; } return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($h); } /** * Create a copy of a field element. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_copy(ParagonIE_Sodium_Core_Curve25519_Fe $f) { $h = clone $f; return $h; } /** * Give: 32-byte string. * Receive: A field element object to use for internal calculations. * * @internal You should not use this directly from another application * * @param string $s * @return ParagonIE_Sodium_Core_Curve25519_Fe * @throws RangeException * @throws TypeError */ public static function fe_frombytes($s) { if (self::strlen($s) !== 32) { throw new RangeException('Expected a 32-byte string.'); } $h0 = self::load_4($s); $h1 = self::load_3(self::substr($s, 4, 3)) << 6; $h2 = self::load_3(self::substr($s, 7, 3)) << 5; $h3 = self::load_3(self::substr($s, 10, 3)) << 3; $h4 = self::load_3(self::substr($s, 13, 3)) << 2; $h5 = self::load_4(self::substr($s, 16, 4)); $h6 = self::load_3(self::substr($s, 20, 3)) << 7; $h7 = self::load_3(self::substr($s, 23, 3)) << 5; $h8 = self::load_3(self::substr($s, 26, 3)) << 4; $h9 = (self::load_3(self::substr($s, 29, 3)) & 8388607) << 2; $carry9 = ($h9 + (1 << 24)) >> 25; $h0 += self::mul($carry9, 19, 5); $h9 -= $carry9 << 25; $carry1 = ($h1 + (1 << 24)) >> 25; $h2 += $carry1; $h1 -= $carry1 << 25; $carry3 = ($h3 + (1 << 24)) >> 25; $h4 += $carry3; $h3 -= $carry3 << 25; $carry5 = ($h5 + (1 << 24)) >> 25; $h6 += $carry5; $h5 -= $carry5 << 25; $carry7 = ($h7 + (1 << 24)) >> 25; $h8 += $carry7; $h7 -= $carry7 << 25; $carry0 = ($h0 + (1 << 25)) >> 26; $h1 += $carry0; $h0 -= $carry0 << 26; $carry2 = ($h2 + (1 << 25)) >> 26; $h3 += $carry2; $h2 -= $carry2 << 26; $carry4 = ($h4 + (1 << 25)) >> 26; $h5 += $carry4; $h4 -= $carry4 << 26; $carry6 = ($h6 + (1 << 25)) >> 26; $h7 += $carry6; $h6 -= $carry6 << 26; $carry8 = ($h8 + (1 << 25)) >> 26; $h9 += $carry8; $h8 -= $carry8 << 26; return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray( array( (int) $h0, (int) $h1, (int) $h2, (int) $h3, (int) $h4, (int) $h5, (int) $h6, (int) $h7, (int) $h8, (int) $h9 ) ); } /** * Convert a field element to a byte string. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $h * @return string */ public static function fe_tobytes(ParagonIE_Sodium_Core_Curve25519_Fe $h) { $h0 = (int) $h[0]; $h1 = (int) $h[1]; $h2 = (int) $h[2]; $h3 = (int) $h[3]; $h4 = (int) $h[4]; $h5 = (int) $h[5]; $h6 = (int) $h[6]; $h7 = (int) $h[7]; $h8 = (int) $h[8]; $h9 = (int) $h[9]; $q = (self::mul($h9, 19, 5) + (1 << 24)) >> 25; $q = ($h0 + $q) >> 26; $q = ($h1 + $q) >> 25; $q = ($h2 + $q) >> 26; $q = ($h3 + $q) >> 25; $q = ($h4 + $q) >> 26; $q = ($h5 + $q) >> 25; $q = ($h6 + $q) >> 26; $q = ($h7 + $q) >> 25; $q = ($h8 + $q) >> 26; $q = ($h9 + $q) >> 25; $h0 += self::mul($q, 19, 5); $carry0 = $h0 >> 26; $h1 += $carry0; $h0 -= $carry0 << 26; $carry1 = $h1 >> 25; $h2 += $carry1; $h1 -= $carry1 << 25; $carry2 = $h2 >> 26; $h3 += $carry2; $h2 -= $carry2 << 26; $carry3 = $h3 >> 25; $h4 += $carry3; $h3 -= $carry3 << 25; $carry4 = $h4 >> 26; $h5 += $carry4; $h4 -= $carry4 << 26; $carry5 = $h5 >> 25; $h6 += $carry5; $h5 -= $carry5 << 25; $carry6 = $h6 >> 26; $h7 += $carry6; $h6 -= $carry6 << 26; $carry7 = $h7 >> 25; $h8 += $carry7; $h7 -= $carry7 << 25; $carry8 = $h8 >> 26; $h9 += $carry8; $h8 -= $carry8 << 26; $carry9 = $h9 >> 25; $h9 -= $carry9 << 25; /** * @var array<int, int> */ $s = array( (int) (($h0 >> 0) & 0xff), (int) (($h0 >> 8) & 0xff), (int) (($h0 >> 16) & 0xff), (int) ((($h0 >> 24) | ($h1 << 2)) & 0xff), (int) (($h1 >> 6) & 0xff), (int) (($h1 >> 14) & 0xff), (int) ((($h1 >> 22) | ($h2 << 3)) & 0xff), (int) (($h2 >> 5) & 0xff), (int) (($h2 >> 13) & 0xff), (int) ((($h2 >> 21) | ($h3 << 5)) & 0xff), (int) (($h3 >> 3) & 0xff), (int) (($h3 >> 11) & 0xff), (int) ((($h3 >> 19) | ($h4 << 6)) & 0xff), (int) (($h4 >> 2) & 0xff), (int) (($h4 >> 10) & 0xff), (int) (($h4 >> 18) & 0xff), (int) (($h5 >> 0) & 0xff), (int) (($h5 >> 8) & 0xff), (int) (($h5 >> 16) & 0xff), (int) ((($h5 >> 24) | ($h6 << 1)) & 0xff), (int) (($h6 >> 7) & 0xff), (int) (($h6 >> 15) & 0xff), (int) ((($h6 >> 23) | ($h7 << 3)) & 0xff), (int) (($h7 >> 5) & 0xff), (int) (($h7 >> 13) & 0xff), (int) ((($h7 >> 21) | ($h8 << 4)) & 0xff), (int) (($h8 >> 4) & 0xff), (int) (($h8 >> 12) & 0xff), (int) ((($h8 >> 20) | ($h9 << 6)) & 0xff), (int) (($h9 >> 2) & 0xff), (int) (($h9 >> 10) & 0xff), (int) (($h9 >> 18) & 0xff) ); return self::intArrayToString($s); } /** * Is a field element negative? (1 = yes, 0 = no. Used in calculations.) * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return int * @throws SodiumException * @throws TypeError */ public static function fe_isnegative(ParagonIE_Sodium_Core_Curve25519_Fe $f) { $str = self::fe_tobytes($f); return (int) (self::chrToInt($str[0]) & 1); } /** * Returns 0 if this field element results in all NUL bytes. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return bool * @throws SodiumException * @throws TypeError */ public static function fe_isnonzero(ParagonIE_Sodium_Core_Curve25519_Fe $f) { static $zero; if ($zero === null) { $zero = str_repeat("\x00", 32); } /** @var string $zero */ /** @var string $str */ $str = self::fe_tobytes($f); return !self::verify_32($str, (string) $zero); } /** * Multiply two field elements * * h = f * g * * @internal You should not use this directly from another application * * @security Is multiplication a source of timing leaks? If so, can we do * anything to prevent that from happening? * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @param ParagonIE_Sodium_Core_Curve25519_Fe $g * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_mul( ParagonIE_Sodium_Core_Curve25519_Fe $f, ParagonIE_Sodium_Core_Curve25519_Fe $g ) { // Ensure limbs aren't oversized. $f = self::fe_normalize($f); $g = self::fe_normalize($g); $f0 = $f[0]; $f1 = $f[1]; $f2 = $f[2]; $f3 = $f[3]; $f4 = $f[4]; $f5 = $f[5]; $f6 = $f[6]; $f7 = $f[7]; $f8 = $f[8]; $f9 = $f[9]; $g0 = $g[0]; $g1 = $g[1]; $g2 = $g[2]; $g3 = $g[3]; $g4 = $g[4]; $g5 = $g[5]; $g6 = $g[6]; $g7 = $g[7]; $g8 = $g[8]; $g9 = $g[9]; $g1_19 = self::mul($g1, 19, 5); $g2_19 = self::mul($g2, 19, 5); $g3_19 = self::mul($g3, 19, 5); $g4_19 = self::mul($g4, 19, 5); $g5_19 = self::mul($g5, 19, 5); $g6_19 = self::mul($g6, 19, 5); $g7_19 = self::mul($g7, 19, 5); $g8_19 = self::mul($g8, 19, 5); $g9_19 = self::mul($g9, 19, 5); $f1_2 = $f1 << 1; $f3_2 = $f3 << 1; $f5_2 = $f5 << 1; $f7_2 = $f7 << 1; $f9_2 = $f9 << 1; $f0g0 = self::mul($f0, $g0, 26); $f0g1 = self::mul($f0, $g1, 25); $f0g2 = self::mul($f0, $g2, 26); $f0g3 = self::mul($f0, $g3, 25); $f0g4 = self::mul($f0, $g4, 26); $f0g5 = self::mul($f0, $g5, 25); $f0g6 = self::mul($f0, $g6, 26); $f0g7 = self::mul($f0, $g7, 25); $f0g8 = self::mul($f0, $g8, 26); $f0g9 = self::mul($f0, $g9, 26); $f1g0 = self::mul($f1, $g0, 26); $f1g1_2 = self::mul($f1_2, $g1, 25); $f1g2 = self::mul($f1, $g2, 26); $f1g3_2 = self::mul($f1_2, $g3, 25); $f1g4 = self::mul($f1, $g4, 26); $f1g5_2 = self::mul($f1_2, $g5, 25); $f1g6 = self::mul($f1, $g6, 26); $f1g7_2 = self::mul($f1_2, $g7, 25); $f1g8 = self::mul($f1, $g8, 26); $f1g9_38 = self::mul($g9_19, $f1_2, 26); $f2g0 = self::mul($f2, $g0, 26); $f2g1 = self::mul($f2, $g1, 25); $f2g2 = self::mul($f2, $g2, 26); $f2g3 = self::mul($f2, $g3, 25); $f2g4 = self::mul($f2, $g4, 26); $f2g5 = self::mul($f2, $g5, 25); $f2g6 = self::mul($f2, $g6, 26); $f2g7 = self::mul($f2, $g7, 25); $f2g8_19 = self::mul($g8_19, $f2, 26); $f2g9_19 = self::mul($g9_19, $f2, 26); $f3g0 = self::mul($f3, $g0, 26); $f3g1_2 = self::mul($f3_2, $g1, 25); $f3g2 = self::mul($f3, $g2, 26); $f3g3_2 = self::mul($f3_2, $g3, 25); $f3g4 = self::mul($f3, $g4, 26); $f3g5_2 = self::mul($f3_2, $g5, 25); $f3g6 = self::mul($f3, $g6, 26); $f3g7_38 = self::mul($g7_19, $f3_2, 26); $f3g8_19 = self::mul($g8_19, $f3, 25); $f3g9_38 = self::mul($g9_19, $f3_2, 26); $f4g0 = self::mul($f4, $g0, 26); $f4g1 = self::mul($f4, $g1, 25); $f4g2 = self::mul($f4, $g2, 26); $f4g3 = self::mul($f4, $g3, 25); $f4g4 = self::mul($f4, $g4, 26); $f4g5 = self::mul($f4, $g5, 25); $f4g6_19 = self::mul($g6_19, $f4, 26); $f4g7_19 = self::mul($g7_19, $f4, 26); $f4g8_19 = self::mul($g8_19, $f4, 26); $f4g9_19 = self::mul($g9_19, $f4, 26); $f5g0 = self::mul($f5, $g0, 26); $f5g1_2 = self::mul($f5_2, $g1, 25); $f5g2 = self::mul($f5, $g2, 26); $f5g3_2 = self::mul($f5_2, $g3, 25); $f5g4 = self::mul($f5, $g4, 26); $f5g5_38 = self::mul($g5_19, $f5_2, 26); $f5g6_19 = self::mul($g6_19, $f5, 25); $f5g7_38 = self::mul($g7_19, $f5_2, 26); $f5g8_19 = self::mul($g8_19, $f5, 25); $f5g9_38 = self::mul($g9_19, $f5_2, 26); $f6g0 = self::mul($f6, $g0, 26); $f6g1 = self::mul($f6, $g1, 25); $f6g2 = self::mul($f6, $g2, 26); $f6g3 = self::mul($f6, $g3, 25); $f6g4_19 = self::mul($g4_19, $f6, 26); $f6g5_19 = self::mul($g5_19, $f6, 26); $f6g6_19 = self::mul($g6_19, $f6, 26); $f6g7_19 = self::mul($g7_19, $f6, 26); $f6g8_19 = self::mul($g8_19, $f6, 26); $f6g9_19 = self::mul($g9_19, $f6, 26); $f7g0 = self::mul($f7, $g0, 26); $f7g1_2 = self::mul($f7_2, $g1, 25); $f7g2 = self::mul($f7, $g2, 26); $f7g3_38 = self::mul($g3_19, $f7_2, 26); $f7g4_19 = self::mul($g4_19, $f7, 26); $f7g5_38 = self::mul($g5_19, $f7_2, 26); $f7g6_19 = self::mul($g6_19, $f7, 25); $f7g7_38 = self::mul($g7_19, $f7_2, 26); $f7g8_19 = self::mul($g8_19, $f7, 25); $f7g9_38 = self::mul($g9_19,$f7_2, 26); $f8g0 = self::mul($f8, $g0, 26); $f8g1 = self::mul($f8, $g1, 25); $f8g2_19 = self::mul($g2_19, $f8, 26); $f8g3_19 = self::mul($g3_19, $f8, 26); $f8g4_19 = self::mul($g4_19, $f8, 26); $f8g5_19 = self::mul($g5_19, $f8, 26); $f8g6_19 = self::mul($g6_19, $f8, 26); $f8g7_19 = self::mul($g7_19, $f8, 26); $f8g8_19 = self::mul($g8_19, $f8, 26); $f8g9_19 = self::mul($g9_19, $f8, 26); $f9g0 = self::mul($f9, $g0, 26); $f9g1_38 = self::mul($g1_19, $f9_2, 26); $f9g2_19 = self::mul($g2_19, $f9, 25); $f9g3_38 = self::mul($g3_19, $f9_2, 26); $f9g4_19 = self::mul($g4_19, $f9, 25); $f9g5_38 = self::mul($g5_19, $f9_2, 26); $f9g6_19 = self::mul($g6_19, $f9, 25); $f9g7_38 = self::mul($g7_19, $f9_2, 26); $f9g8_19 = self::mul($g8_19, $f9, 25); $f9g9_38 = self::mul($g9_19, $f9_2, 26); $h0 = $f0g0 + $f1g9_38 + $f2g8_19 + $f3g7_38 + $f4g6_19 + $f5g5_38 + $f6g4_19 + $f7g3_38 + $f8g2_19 + $f9g1_38; $h1 = $f0g1 + $f1g0 + $f2g9_19 + $f3g8_19 + $f4g7_19 + $f5g6_19 + $f6g5_19 + $f7g4_19 + $f8g3_19 + $f9g2_19; $h2 = $f0g2 + $f1g1_2 + $f2g0 + $f3g9_38 + $f4g8_19 + $f5g7_38 + $f6g6_19 + $f7g5_38 + $f8g4_19 + $f9g3_38; $h3 = $f0g3 + $f1g2 + $f2g1 + $f3g0 + $f4g9_19 + $f5g8_19 + $f6g7_19 + $f7g6_19 + $f8g5_19 + $f9g4_19; $h4 = $f0g4 + $f1g3_2 + $f2g2 + $f3g1_2 + $f4g0 + $f5g9_38 + $f6g8_19 + $f7g7_38 + $f8g6_19 + $f9g5_38; $h5 = $f0g5 + $f1g4 + $f2g3 + $f3g2 + $f4g1 + $f5g0 + $f6g9_19 + $f7g8_19 + $f8g7_19 + $f9g6_19; $h6 = $f0g6 + $f1g5_2 + $f2g4 + $f3g3_2 + $f4g2 + $f5g1_2 + $f6g0 + $f7g9_38 + $f8g8_19 + $f9g7_38; $h7 = $f0g7 + $f1g6 + $f2g5 + $f3g4 + $f4g3 + $f5g2 + $f6g1 + $f7g0 + $f8g9_19 + $f9g8_19; $h8 = $f0g8 + $f1g7_2 + $f2g6 + $f3g5_2 + $f4g4 + $f5g3_2 + $f6g2 + $f7g1_2 + $f8g0 + $f9g9_38; $h9 = $f0g9 + $f1g8 + $f2g7 + $f3g6 + $f4g5 + $f5g4 + $f6g3 + $f7g2 + $f8g1 + $f9g0 ; $carry0 = ($h0 + (1 << 25)) >> 26; $h1 += $carry0; $h0 -= $carry0 << 26; $carry4 = ($h4 + (1 << 25)) >> 26; $h5 += $carry4; $h4 -= $carry4 << 26; $carry1 = ($h1 + (1 << 24)) >> 25; $h2 += $carry1; $h1 -= $carry1 << 25; $carry5 = ($h5 + (1 << 24)) >> 25; $h6 += $carry5; $h5 -= $carry5 << 25; $carry2 = ($h2 + (1 << 25)) >> 26; $h3 += $carry2; $h2 -= $carry2 << 26; $carry6 = ($h6 + (1 << 25)) >> 26; $h7 += $carry6; $h6 -= $carry6 << 26; $carry3 = ($h3 + (1 << 24)) >> 25; $h4 += $carry3; $h3 -= $carry3 << 25; $carry7 = ($h7 + (1 << 24)) >> 25; $h8 += $carry7; $h7 -= $carry7 << 25; $carry4 = ($h4 + (1 << 25)) >> 26; $h5 += $carry4; $h4 -= $carry4 << 26; $carry8 = ($h8 + (1 << 25)) >> 26; $h9 += $carry8; $h8 -= $carry8 << 26; $carry9 = ($h9 + (1 << 24)) >> 25; $h0 += self::mul($carry9, 19, 5); $h9 -= $carry9 << 25; $carry0 = ($h0 + (1 << 25)) >> 26; $h1 += $carry0; $h0 -= $carry0 << 26; return self::fe_normalize( ParagonIE_Sodium_Core_Curve25519_Fe::fromArray( array( (int) $h0, (int) $h1, (int) $h2, (int) $h3, (int) $h4, (int) $h5, (int) $h6, (int) $h7, (int) $h8, (int) $h9 ) ) ); } /** * Get the negative values for each piece of the field element. * * h = -f * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return ParagonIE_Sodium_Core_Curve25519_Fe * @psalm-suppress MixedAssignment */ public static function fe_neg(ParagonIE_Sodium_Core_Curve25519_Fe $f) { $h = new ParagonIE_Sodium_Core_Curve25519_Fe(); for ($i = 0; $i < 10; ++$i) { $h[$i] = -$f[$i]; } return self::fe_normalize($h); } /** * Square a field element * * h = f * f * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_sq(ParagonIE_Sodium_Core_Curve25519_Fe $f) { $f = self::fe_normalize($f); $f0 = (int) $f[0]; $f1 = (int) $f[1]; $f2 = (int) $f[2]; $f3 = (int) $f[3]; $f4 = (int) $f[4]; $f5 = (int) $f[5]; $f6 = (int) $f[6]; $f7 = (int) $f[7]; $f8 = (int) $f[8]; $f9 = (int) $f[9]; $f0_2 = $f0 << 1; $f1_2 = $f1 << 1; $f2_2 = $f2 << 1; $f3_2 = $f3 << 1; $f4_2 = $f4 << 1; $f5_2 = $f5 << 1; $f6_2 = $f6 << 1; $f7_2 = $f7 << 1; $f5_38 = self::mul($f5, 38, 6); $f6_19 = self::mul($f6, 19, 5); $f7_38 = self::mul($f7, 38, 6); $f8_19 = self::mul($f8, 19, 5); $f9_38 = self::mul($f9, 38, 6); $f0f0 = self::mul($f0, $f0, 26); $f0f1_2 = self::mul($f0_2, $f1, 26); $f0f2_2 = self::mul($f0_2, $f2, 26); $f0f3_2 = self::mul($f0_2, $f3, 26); $f0f4_2 = self::mul($f0_2, $f4, 26); $f0f5_2 = self::mul($f0_2, $f5, 26); $f0f6_2 = self::mul($f0_2, $f6, 26); $f0f7_2 = self::mul($f0_2, $f7, 26); $f0f8_2 = self::mul($f0_2, $f8, 26); $f0f9_2 = self::mul($f0_2, $f9, 26); $f1f1_2 = self::mul($f1_2, $f1, 26); $f1f2_2 = self::mul($f1_2, $f2, 26); $f1f3_4 = self::mul($f1_2, $f3_2, 26); $f1f4_2 = self::mul($f1_2, $f4, 26); $f1f5_4 = self::mul($f1_2, $f5_2, 26); $f1f6_2 = self::mul($f1_2, $f6, 26); $f1f7_4 = self::mul($f1_2, $f7_2, 26); $f1f8_2 = self::mul($f1_2, $f8, 26); $f1f9_76 = self::mul($f9_38, $f1_2, 27); $f2f2 = self::mul($f2, $f2, 27); $f2f3_2 = self::mul($f2_2, $f3, 27); $f2f4_2 = self::mul($f2_2, $f4, 27); $f2f5_2 = self::mul($f2_2, $f5, 27); $f2f6_2 = self::mul($f2_2, $f6, 27); $f2f7_2 = self::mul($f2_2, $f7, 27); $f2f8_38 = self::mul($f8_19, $f2_2, 27); $f2f9_38 = self::mul($f9_38, $f2, 26); $f3f3_2 = self::mul($f3_2, $f3, 26); $f3f4_2 = self::mul($f3_2, $f4, 26); $f3f5_4 = self::mul($f3_2, $f5_2, 26); $f3f6_2 = self::mul($f3_2, $f6, 26); $f3f7_76 = self::mul($f7_38, $f3_2, 26); $f3f8_38 = self::mul($f8_19, $f3_2, 26); $f3f9_76 = self::mul($f9_38, $f3_2, 26); $f4f4 = self::mul($f4, $f4, 26); $f4f5_2 = self::mul($f4_2, $f5, 26); $f4f6_38 = self::mul($f6_19, $f4_2, 27); $f4f7_38 = self::mul($f7_38, $f4, 26); $f4f8_38 = self::mul($f8_19, $f4_2, 27); $f4f9_38 = self::mul($f9_38, $f4, 26); $f5f5_38 = self::mul($f5_38, $f5, 26); $f5f6_38 = self::mul($f6_19, $f5_2, 26); $f5f7_76 = self::mul($f7_38, $f5_2, 26); $f5f8_38 = self::mul($f8_19, $f5_2, 26); $f5f9_76 = self::mul($f9_38, $f5_2, 26); $f6f6_19 = self::mul($f6_19, $f6, 26); $f6f7_38 = self::mul($f7_38, $f6, 26); $f6f8_38 = self::mul($f8_19, $f6_2, 27); $f6f9_38 = self::mul($f9_38, $f6, 26); $f7f7_38 = self::mul($f7_38, $f7, 26); $f7f8_38 = self::mul($f8_19, $f7_2, 26); $f7f9_76 = self::mul($f9_38, $f7_2, 26); $f8f8_19 = self::mul($f8_19, $f8, 26); $f8f9_38 = self::mul($f9_38, $f8, 26); $f9f9_38 = self::mul($f9_38, $f9, 26); $h0 = $f0f0 + $f1f9_76 + $f2f8_38 + $f3f7_76 + $f4f6_38 + $f5f5_38; $h1 = $f0f1_2 + $f2f9_38 + $f3f8_38 + $f4f7_38 + $f5f6_38; $h2 = $f0f2_2 + $f1f1_2 + $f3f9_76 + $f4f8_38 + $f5f7_76 + $f6f6_19; $h3 = $f0f3_2 + $f1f2_2 + $f4f9_38 + $f5f8_38 + $f6f7_38; $h4 = $f0f4_2 + $f1f3_4 + $f2f2 + $f5f9_76 + $f6f8_38 + $f7f7_38; $h5 = $f0f5_2 + $f1f4_2 + $f2f3_2 + $f6f9_38 + $f7f8_38; $h6 = $f0f6_2 + $f1f5_4 + $f2f4_2 + $f3f3_2 + $f7f9_76 + $f8f8_19; $h7 = $f0f7_2 + $f1f6_2 + $f2f5_2 + $f3f4_2 + $f8f9_38; $h8 = $f0f8_2 + $f1f7_4 + $f2f6_2 + $f3f5_4 + $f4f4 + $f9f9_38; $h9 = $f0f9_2 + $f1f8_2 + $f2f7_2 + $f3f6_2 + $f4f5_2; $carry0 = ($h0 + (1 << 25)) >> 26; $h1 += $carry0; $h0 -= $carry0 << 26; $carry4 = ($h4 + (1 << 25)) >> 26; $h5 += $carry4; $h4 -= $carry4 << 26; $carry1 = ($h1 + (1 << 24)) >> 25; $h2 += $carry1; $h1 -= $carry1 << 25; $carry5 = ($h5 + (1 << 24)) >> 25; $h6 += $carry5; $h5 -= $carry5 << 25; $carry2 = ($h2 + (1 << 25)) >> 26; $h3 += $carry2; $h2 -= $carry2 << 26; $carry6 = ($h6 + (1 << 25)) >> 26; $h7 += $carry6; $h6 -= $carry6 << 26; $carry3 = ($h3 + (1 << 24)) >> 25; $h4 += $carry3; $h3 -= $carry3 << 25; $carry7 = ($h7 + (1 << 24)) >> 25; $h8 += $carry7; $h7 -= $carry7 << 25; $carry4 = ($h4 + (1 << 25)) >> 26; $h5 += $carry4; $h4 -= $carry4 << 26; $carry8 = ($h8 + (1 << 25)) >> 26; $h9 += $carry8; $h8 -= $carry8 << 26; $carry9 = ($h9 + (1 << 24)) >> 25; $h0 += self::mul($carry9, 19, 5); $h9 -= $carry9 << 25; $carry0 = ($h0 + (1 << 25)) >> 26; $h1 += $carry0; $h0 -= $carry0 << 26; return self::fe_normalize( ParagonIE_Sodium_Core_Curve25519_Fe::fromArray( array( (int) $h0, (int) $h1, (int) $h2, (int) $h3, (int) $h4, (int) $h5, (int) $h6, (int) $h7, (int) $h8, (int) $h9 ) ) ); } /** * Square and double a field element * * h = 2 * f * f * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_sq2(ParagonIE_Sodium_Core_Curve25519_Fe $f) { $f = self::fe_normalize($f); $f0 = (int) $f[0]; $f1 = (int) $f[1]; $f2 = (int) $f[2]; $f3 = (int) $f[3]; $f4 = (int) $f[4]; $f5 = (int) $f[5]; $f6 = (int) $f[6]; $f7 = (int) $f[7]; $f8 = (int) $f[8]; $f9 = (int) $f[9]; $f0_2 = $f0 << 1; $f1_2 = $f1 << 1; $f2_2 = $f2 << 1; $f3_2 = $f3 << 1; $f4_2 = $f4 << 1; $f5_2 = $f5 << 1; $f6_2 = $f6 << 1; $f7_2 = $f7 << 1; $f5_38 = self::mul($f5, 38, 6); /* 1.959375*2^30 */ $f6_19 = self::mul($f6, 19, 5); /* 1.959375*2^30 */ $f7_38 = self::mul($f7, 38, 6); /* 1.959375*2^30 */ $f8_19 = self::mul($f8, 19, 5); /* 1.959375*2^30 */ $f9_38 = self::mul($f9, 38, 6); /* 1.959375*2^30 */ $f0f0 = self::mul($f0, $f0, 24); $f0f1_2 = self::mul($f0_2, $f1, 24); $f0f2_2 = self::mul($f0_2, $f2, 24); $f0f3_2 = self::mul($f0_2, $f3, 24); $f0f4_2 = self::mul($f0_2, $f4, 24); $f0f5_2 = self::mul($f0_2, $f5, 24); $f0f6_2 = self::mul($f0_2, $f6, 24); $f0f7_2 = self::mul($f0_2, $f7, 24); $f0f8_2 = self::mul($f0_2, $f8, 24); $f0f9_2 = self::mul($f0_2, $f9, 24); $f1f1_2 = self::mul($f1_2, $f1, 24); $f1f2_2 = self::mul($f1_2, $f2, 24); $f1f3_4 = self::mul($f1_2, $f3_2, 24); $f1f4_2 = self::mul($f1_2, $f4, 24); $f1f5_4 = self::mul($f1_2, $f5_2, 24); $f1f6_2 = self::mul($f1_2, $f6, 24); $f1f7_4 = self::mul($f1_2, $f7_2, 24); $f1f8_2 = self::mul($f1_2, $f8, 24); $f1f9_76 = self::mul($f9_38, $f1_2, 24); $f2f2 = self::mul($f2, $f2, 24); $f2f3_2 = self::mul($f2_2, $f3, 24); $f2f4_2 = self::mul($f2_2, $f4, 24); $f2f5_2 = self::mul($f2_2, $f5, 24); $f2f6_2 = self::mul($f2_2, $f6, 24); $f2f7_2 = self::mul($f2_2, $f7, 24); $f2f8_38 = self::mul($f8_19, $f2_2, 25); $f2f9_38 = self::mul($f9_38, $f2, 24); $f3f3_2 = self::mul($f3_2, $f3, 24); $f3f4_2 = self::mul($f3_2, $f4, 24); $f3f5_4 = self::mul($f3_2, $f5_2, 24); $f3f6_2 = self::mul($f3_2, $f6, 24); $f3f7_76 = self::mul($f7_38, $f3_2, 24); $f3f8_38 = self::mul($f8_19, $f3_2, 24); $f3f9_76 = self::mul($f9_38, $f3_2, 24); $f4f4 = self::mul($f4, $f4, 24); $f4f5_2 = self::mul($f4_2, $f5, 24); $f4f6_38 = self::mul($f6_19, $f4_2, 25); $f4f7_38 = self::mul($f7_38, $f4, 24); $f4f8_38 = self::mul($f8_19, $f4_2, 25); $f4f9_38 = self::mul($f9_38, $f4, 24); $f5f5_38 = self::mul($f5_38, $f5, 24); $f5f6_38 = self::mul($f6_19, $f5_2, 24); $f5f7_76 = self::mul($f7_38, $f5_2, 24); $f5f8_38 = self::mul($f8_19, $f5_2, 24); $f5f9_76 = self::mul($f9_38, $f5_2, 24); $f6f6_19 = self::mul($f6_19, $f6, 24); $f6f7_38 = self::mul($f7_38, $f6, 24); $f6f8_38 = self::mul($f8_19, $f6_2, 25); $f6f9_38 = self::mul($f9_38, $f6, 24); $f7f7_38 = self::mul($f7_38, $f7, 24); $f7f8_38 = self::mul($f8_19, $f7_2, 24); $f7f9_76 = self::mul($f9_38, $f7_2, 24); $f8f8_19 = self::mul($f8_19, $f8, 24); $f8f9_38 = self::mul($f9_38, $f8, 24); $f9f9_38 = self::mul($f9_38, $f9, 24); $h0 = (int) ($f0f0 + $f1f9_76 + $f2f8_38 + $f3f7_76 + $f4f6_38 + $f5f5_38) << 1; $h1 = (int) ($f0f1_2 + $f2f9_38 + $f3f8_38 + $f4f7_38 + $f5f6_38) << 1; $h2 = (int) ($f0f2_2 + $f1f1_2 + $f3f9_76 + $f4f8_38 + $f5f7_76 + $f6f6_19) << 1; $h3 = (int) ($f0f3_2 + $f1f2_2 + $f4f9_38 + $f5f8_38 + $f6f7_38) << 1; $h4 = (int) ($f0f4_2 + $f1f3_4 + $f2f2 + $f5f9_76 + $f6f8_38 + $f7f7_38) << 1; $h5 = (int) ($f0f5_2 + $f1f4_2 + $f2f3_2 + $f6f9_38 + $f7f8_38) << 1; $h6 = (int) ($f0f6_2 + $f1f5_4 + $f2f4_2 + $f3f3_2 + $f7f9_76 + $f8f8_19) << 1; $h7 = (int) ($f0f7_2 + $f1f6_2 + $f2f5_2 + $f3f4_2 + $f8f9_38) << 1; $h8 = (int) ($f0f8_2 + $f1f7_4 + $f2f6_2 + $f3f5_4 + $f4f4 + $f9f9_38) << 1; $h9 = (int) ($f0f9_2 + $f1f8_2 + $f2f7_2 + $f3f6_2 + $f4f5_2) << 1; $carry0 = ($h0 + (1 << 25)) >> 26; $h1 += $carry0; $h0 -= $carry0 << 26; $carry4 = ($h4 + (1 << 25)) >> 26; $h5 += $carry4; $h4 -= $carry4 << 26; $carry1 = ($h1 + (1 << 24)) >> 25; $h2 += $carry1; $h1 -= $carry1 << 25; $carry5 = ($h5 + (1 << 24)) >> 25; $h6 += $carry5; $h5 -= $carry5 << 25; $carry2 = ($h2 + (1 << 25)) >> 26; $h3 += $carry2; $h2 -= $carry2 << 26; $carry6 = ($h6 + (1 << 25)) >> 26; $h7 += $carry6; $h6 -= $carry6 << 26; $carry3 = ($h3 + (1 << 24)) >> 25; $h4 += $carry3; $h3 -= $carry3 << 25; $carry7 = ($h7 + (1 << 24)) >> 25; $h8 += $carry7; $h7 -= $carry7 << 25; $carry4 = ($h4 + (1 << 25)) >> 26; $h5 += $carry4; $h4 -= $carry4 << 26; $carry8 = ($h8 + (1 << 25)) >> 26; $h9 += $carry8; $h8 -= $carry8 << 26; $carry9 = ($h9 + (1 << 24)) >> 25; $h0 += self::mul($carry9, 19, 5); $h9 -= $carry9 << 25; $carry0 = ($h0 + (1 << 25)) >> 26; $h1 += $carry0; $h0 -= $carry0 << 26; return self::fe_normalize( ParagonIE_Sodium_Core_Curve25519_Fe::fromArray( array( (int) $h0, (int) $h1, (int) $h2, (int) $h3, (int) $h4, (int) $h5, (int) $h6, (int) $h7, (int) $h8, (int) $h9 ) ) ); } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $Z * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_invert(ParagonIE_Sodium_Core_Curve25519_Fe $Z) { $z = clone $Z; $t0 = self::fe_sq($z); $t1 = self::fe_sq($t0); $t1 = self::fe_sq($t1); $t1 = self::fe_mul($z, $t1); $t0 = self::fe_mul($t0, $t1); $t2 = self::fe_sq($t0); $t1 = self::fe_mul($t1, $t2); $t2 = self::fe_sq($t1); for ($i = 1; $i < 5; ++$i) { $t2 = self::fe_sq($t2); } $t1 = self::fe_mul($t2, $t1); $t2 = self::fe_sq($t1); for ($i = 1; $i < 10; ++$i) { $t2 = self::fe_sq($t2); } $t2 = self::fe_mul($t2, $t1); $t3 = self::fe_sq($t2); for ($i = 1; $i < 20; ++$i) { $t3 = self::fe_sq($t3); } $t2 = self::fe_mul($t3, $t2); $t2 = self::fe_sq($t2); for ($i = 1; $i < 10; ++$i) { $t2 = self::fe_sq($t2); } $t1 = self::fe_mul($t2, $t1); $t2 = self::fe_sq($t1); for ($i = 1; $i < 50; ++$i) { $t2 = self::fe_sq($t2); } $t2 = self::fe_mul($t2, $t1); $t3 = self::fe_sq($t2); for ($i = 1; $i < 100; ++$i) { $t3 = self::fe_sq($t3); } $t2 = self::fe_mul($t3, $t2); $t2 = self::fe_sq($t2); for ($i = 1; $i < 50; ++$i) { $t2 = self::fe_sq($t2); } $t1 = self::fe_mul($t2, $t1); $t1 = self::fe_sq($t1); for ($i = 1; $i < 5; ++$i) { $t1 = self::fe_sq($t1); } return self::fe_mul($t1, $t0); } /** * @internal You should not use this directly from another application * * @ref https://github.com/jedisct1/libsodium/blob/68564326e1e9dc57ef03746f85734232d20ca6fb/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c#L1054-L1106 * * @param ParagonIE_Sodium_Core_Curve25519_Fe $z * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_pow22523(ParagonIE_Sodium_Core_Curve25519_Fe $z) { $z = self::fe_normalize($z); # fe_sq(t0, z); # fe_sq(t1, t0); # fe_sq(t1, t1); # fe_mul(t1, z, t1); # fe_mul(t0, t0, t1); # fe_sq(t0, t0); # fe_mul(t0, t1, t0); # fe_sq(t1, t0); $t0 = self::fe_sq($z); $t1 = self::fe_sq($t0); $t1 = self::fe_sq($t1); $t1 = self::fe_mul($z, $t1); $t0 = self::fe_mul($t0, $t1); $t0 = self::fe_sq($t0); $t0 = self::fe_mul($t1, $t0); $t1 = self::fe_sq($t0); # for (i = 1; i < 5; ++i) { # fe_sq(t1, t1); # } for ($i = 1; $i < 5; ++$i) { $t1 = self::fe_sq($t1); } # fe_mul(t0, t1, t0); # fe_sq(t1, t0); $t0 = self::fe_mul($t1, $t0); $t1 = self::fe_sq($t0); # for (i = 1; i < 10; ++i) { # fe_sq(t1, t1); # } for ($i = 1; $i < 10; ++$i) { $t1 = self::fe_sq($t1); } # fe_mul(t1, t1, t0); # fe_sq(t2, t1); $t1 = self::fe_mul($t1, $t0); $t2 = self::fe_sq($t1); # for (i = 1; i < 20; ++i) { # fe_sq(t2, t2); # } for ($i = 1; $i < 20; ++$i) { $t2 = self::fe_sq($t2); } # fe_mul(t1, t2, t1); # fe_sq(t1, t1); $t1 = self::fe_mul($t2, $t1); $t1 = self::fe_sq($t1); # for (i = 1; i < 10; ++i) { # fe_sq(t1, t1); # } for ($i = 1; $i < 10; ++$i) { $t1 = self::fe_sq($t1); } # fe_mul(t0, t1, t0); # fe_sq(t1, t0); $t0 = self::fe_mul($t1, $t0); $t1 = self::fe_sq($t0); # for (i = 1; i < 50; ++i) { # fe_sq(t1, t1); # } for ($i = 1; $i < 50; ++$i) { $t1 = self::fe_sq($t1); } # fe_mul(t1, t1, t0); # fe_sq(t2, t1); $t1 = self::fe_mul($t1, $t0); $t2 = self::fe_sq($t1); # for (i = 1; i < 100; ++i) { # fe_sq(t2, t2); # } for ($i = 1; $i < 100; ++$i) { $t2 = self::fe_sq($t2); } # fe_mul(t1, t2, t1); # fe_sq(t1, t1); $t1 = self::fe_mul($t2, $t1); $t1 = self::fe_sq($t1); # for (i = 1; i < 50; ++i) { # fe_sq(t1, t1); # } for ($i = 1; $i < 50; ++$i) { $t1 = self::fe_sq($t1); } # fe_mul(t0, t1, t0); # fe_sq(t0, t0); # fe_sq(t0, t0); # fe_mul(out, t0, z); $t0 = self::fe_mul($t1, $t0); $t0 = self::fe_sq($t0); $t0 = self::fe_sq($t0); return self::fe_mul($t0, $z); } /** * Subtract two field elements. * * h = f - g * * Preconditions: * |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. * |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. * * Postconditions: * |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @param ParagonIE_Sodium_Core_Curve25519_Fe $g * @return ParagonIE_Sodium_Core_Curve25519_Fe * @psalm-suppress MixedOperand */ public static function fe_sub(ParagonIE_Sodium_Core_Curve25519_Fe $f, ParagonIE_Sodium_Core_Curve25519_Fe $g) { return self::fe_normalize( ParagonIE_Sodium_Core_Curve25519_Fe::fromArray( array( (int) ($f[0] - $g[0]), (int) ($f[1] - $g[1]), (int) ($f[2] - $g[2]), (int) ($f[3] - $g[3]), (int) ($f[4] - $g[4]), (int) ($f[5] - $g[5]), (int) ($f[6] - $g[6]), (int) ($f[7] - $g[7]), (int) ($f[8] - $g[8]), (int) ($f[9] - $g[9]) ) ) ); } /** * Add two group elements. * * r = p + q * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p * @param ParagonIE_Sodium_Core_Curve25519_Ge_Cached $q * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 */ public static function ge_add( ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p, ParagonIE_Sodium_Core_Curve25519_Ge_Cached $q ) { $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P1p1(); $r->X = self::fe_add($p->Y, $p->X); $r->Y = self::fe_sub($p->Y, $p->X); $r->Z = self::fe_mul($r->X, $q->YplusX); $r->Y = self::fe_mul($r->Y, $q->YminusX); $r->T = self::fe_mul($q->T2d, $p->T); $r->X = self::fe_mul($p->Z, $q->Z); $t0 = self::fe_add($r->X, $r->X); $r->X = self::fe_sub($r->Z, $r->Y); $r->Y = self::fe_add($r->Z, $r->Y); $r->Z = self::fe_add($t0, $r->T); $r->T = self::fe_sub($t0, $r->T); return $r; } /** * @internal You should not use this directly from another application * * @ref https://github.com/jedisct1/libsodium/blob/157c4a80c13b117608aeae12178b2d38825f9f8f/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c#L1185-L1215 * @param string $a * @return array<int, mixed> * @throws SodiumException * @throws TypeError */ public static function slide($a) { if (self::strlen($a) < 256) { if (self::strlen($a) < 16) { $a = str_pad($a, 256, '0', STR_PAD_RIGHT); } } /** @var array<int, int> $r */ $r = array(); /** @var int $i */ for ($i = 0; $i < 256; ++$i) { $r[$i] = (int) ( 1 & ( self::chrToInt($a[(int) ($i >> 3)]) >> ($i & 7) ) ); } for ($i = 0;$i < 256;++$i) { if ($r[$i]) { for ($b = 1;$b <= 6 && $i + $b < 256;++$b) { if ($r[$i + $b]) { if ($r[$i] + ($r[$i + $b] << $b) <= 15) { $r[$i] += $r[$i + $b] << $b; $r[$i + $b] = 0; } elseif ($r[$i] - ($r[$i + $b] << $b) >= -15) { $r[$i] -= $r[$i + $b] << $b; for ($k = $i + $b; $k < 256; ++$k) { if (!$r[$k]) { $r[$k] = 1; break; } $r[$k] = 0; } } else { break; } } } } } return $r; } /** * @internal You should not use this directly from another application * * @param string $s * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3 * @throws SodiumException * @throws TypeError */ public static function ge_frombytes_negate_vartime($s) { static $d = null; if (!$d) { $d = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$d); } # fe_frombytes(h->Y,s); # fe_1(h->Z); $h = new ParagonIE_Sodium_Core_Curve25519_Ge_P3( self::fe_0(), self::fe_frombytes($s), self::fe_1() ); # fe_sq(u,h->Y); # fe_mul(v,u,d); # fe_sub(u,u,h->Z); /* u = y^2-1 */ # fe_add(v,v,h->Z); /* v = dy^2+1 */ $u = self::fe_sq($h->Y); /** @var ParagonIE_Sodium_Core_Curve25519_Fe $d */ $v = self::fe_mul($u, $d); $u = self::fe_sub($u, $h->Z); /* u = y^2 - 1 */ $v = self::fe_add($v, $h->Z); /* v = dy^2 + 1 */ # fe_sq(v3,v); # fe_mul(v3,v3,v); /* v3 = v^3 */ # fe_sq(h->X,v3); # fe_mul(h->X,h->X,v); # fe_mul(h->X,h->X,u); /* x = uv^7 */ $v3 = self::fe_sq($v); $v3 = self::fe_mul($v3, $v); /* v3 = v^3 */ $h->X = self::fe_sq($v3); $h->X = self::fe_mul($h->X, $v); $h->X = self::fe_mul($h->X, $u); /* x = uv^7 */ # fe_pow22523(h->X,h->X); /* x = (uv^7)^((q-5)/8) */ # fe_mul(h->X,h->X,v3); # fe_mul(h->X,h->X,u); /* x = uv^3(uv^7)^((q-5)/8) */ $h->X = self::fe_pow22523($h->X); /* x = (uv^7)^((q-5)/8) */ $h->X = self::fe_mul($h->X, $v3); $h->X = self::fe_mul($h->X, $u); /* x = uv^3(uv^7)^((q-5)/8) */ # fe_sq(vxx,h->X); # fe_mul(vxx,vxx,v); # fe_sub(check,vxx,u); /* vx^2-u */ $vxx = self::fe_sq($h->X); $vxx = self::fe_mul($vxx, $v); $check = self::fe_sub($vxx, $u); /* vx^2 - u */ # if (fe_isnonzero(check)) { # fe_add(check,vxx,u); /* vx^2+u */ # if (fe_isnonzero(check)) { # return -1; # } # fe_mul(h->X,h->X,sqrtm1); # } if (self::fe_isnonzero($check)) { $check = self::fe_add($vxx, $u); /* vx^2 + u */ if (self::fe_isnonzero($check)) { throw new RangeException('Internal check failed.'); } $h->X = self::fe_mul( $h->X, ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtm1) ); } # if (fe_isnegative(h->X) == (s[31] >> 7)) { # fe_neg(h->X,h->X); # } $i = self::chrToInt($s[31]); if (self::fe_isnegative($h->X) === ($i >> 7)) { $h->X = self::fe_neg($h->X); } # fe_mul(h->T,h->X,h->Y); $h->T = self::fe_mul($h->X, $h->Y); return $h; } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $R * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p * @param ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $q * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 */ public static function ge_madd( ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $R, ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p, ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $q ) { $r = clone $R; $r->X = self::fe_add($p->Y, $p->X); $r->Y = self::fe_sub($p->Y, $p->X); $r->Z = self::fe_mul($r->X, $q->yplusx); $r->Y = self::fe_mul($r->Y, $q->yminusx); $r->T = self::fe_mul($q->xy2d, $p->T); $t0 = self::fe_add(clone $p->Z, clone $p->Z); $r->X = self::fe_sub($r->Z, $r->Y); $r->Y = self::fe_add($r->Z, $r->Y); $r->Z = self::fe_add($t0, $r->T); $r->T = self::fe_sub($t0, $r->T); return $r; } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $R * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p * @param ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $q * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 */ public static function ge_msub( ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $R, ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p, ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $q ) { $r = clone $R; $r->X = self::fe_add($p->Y, $p->X); $r->Y = self::fe_sub($p->Y, $p->X); $r->Z = self::fe_mul($r->X, $q->yminusx); $r->Y = self::fe_mul($r->Y, $q->yplusx); $r->T = self::fe_mul($q->xy2d, $p->T); $t0 = self::fe_add($p->Z, $p->Z); $r->X = self::fe_sub($r->Z, $r->Y); $r->Y = self::fe_add($r->Z, $r->Y); $r->Z = self::fe_sub($t0, $r->T); $r->T = self::fe_add($t0, $r->T); return $r; } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $p * @return ParagonIE_Sodium_Core_Curve25519_Ge_P2 */ public static function ge_p1p1_to_p2(ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $p) { $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P2(); $r->X = self::fe_mul($p->X, $p->T); $r->Y = self::fe_mul($p->Y, $p->Z); $r->Z = self::fe_mul($p->Z, $p->T); return $r; } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $p * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3 */ public static function ge_p1p1_to_p3(ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $p) { $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P3(); $r->X = self::fe_mul($p->X, $p->T); $r->Y = self::fe_mul($p->Y, $p->Z); $r->Z = self::fe_mul($p->Z, $p->T); $r->T = self::fe_mul($p->X, $p->Y); return $r; } /** * @internal You should not use this directly from another application * * @return ParagonIE_Sodium_Core_Curve25519_Ge_P2 */ public static function ge_p2_0() { return new ParagonIE_Sodium_Core_Curve25519_Ge_P2( self::fe_0(), self::fe_1(), self::fe_1() ); } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P2 $p * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 */ public static function ge_p2_dbl(ParagonIE_Sodium_Core_Curve25519_Ge_P2 $p) { $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P1p1(); $r->X = self::fe_sq($p->X); $r->Z = self::fe_sq($p->Y); $r->T = self::fe_sq2($p->Z); $r->Y = self::fe_add($p->X, $p->Y); $t0 = self::fe_sq($r->Y); $r->Y = self::fe_add($r->Z, $r->X); $r->Z = self::fe_sub($r->Z, $r->X); $r->X = self::fe_sub($t0, $r->Y); $r->T = self::fe_sub($r->T, $r->Z); return $r; } /** * @internal You should not use this directly from another application * * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3 */ public static function ge_p3_0() { return new ParagonIE_Sodium_Core_Curve25519_Ge_P3( self::fe_0(), self::fe_1(), self::fe_1(), self::fe_0() ); } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p * @return ParagonIE_Sodium_Core_Curve25519_Ge_Cached */ public static function ge_p3_to_cached(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p) { static $d2 = null; if ($d2 === null) { $d2 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$d2); } /** @var ParagonIE_Sodium_Core_Curve25519_Fe $d2 */ $r = new ParagonIE_Sodium_Core_Curve25519_Ge_Cached(); $r->YplusX = self::fe_add($p->Y, $p->X); $r->YminusX = self::fe_sub($p->Y, $p->X); $r->Z = self::fe_copy($p->Z); $r->T2d = self::fe_mul($p->T, $d2); return $r; } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p * @return ParagonIE_Sodium_Core_Curve25519_Ge_P2 */ public static function ge_p3_to_p2(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p) { return new ParagonIE_Sodium_Core_Curve25519_Ge_P2( self::fe_copy($p->X), self::fe_copy($p->Y), self::fe_copy($p->Z) ); } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $h * @return string * @throws SodiumException * @throws TypeError */ public static function ge_p3_tobytes(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $h) { $recip = self::fe_invert($h->Z); $x = self::fe_mul($h->X, $recip); $y = self::fe_mul($h->Y, $recip); $s = self::fe_tobytes($y); $s[31] = self::intToChr( self::chrToInt($s[31]) ^ (self::fe_isnegative($x) << 7) ); return $s; } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 */ public static function ge_p3_dbl(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p) { $q = self::ge_p3_to_p2($p); return self::ge_p2_dbl($q); } /** * @return ParagonIE_Sodium_Core_Curve25519_Ge_Precomp */ public static function ge_precomp_0() { return new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp( self::fe_1(), self::fe_1(), self::fe_0() ); } /** * @internal You should not use this directly from another application * * @param int $b * @param int $c * @return int */ public static function equal($b, $c) { return (int) ((($b ^ $c) - 1) >> 31) & 1; } /** * @internal You should not use this directly from another application * * @param int|string $char * @return int (1 = yes, 0 = no) * @throws SodiumException * @throws TypeError */ public static function negative($char) { if (is_int($char)) { return ($char >> 63) & 1; } $x = self::chrToInt(self::substr($char, 0, 1)); return (int) ($x >> 63); } /** * Conditional move * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $t * @param ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $u * @param int $b * @return ParagonIE_Sodium_Core_Curve25519_Ge_Precomp */ public static function cmov( ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $t, ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $u, $b ) { if (!is_int($b)) { throw new InvalidArgumentException('Expected an integer.'); } return new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp( self::fe_cmov($t->yplusx, $u->yplusx, $b), self::fe_cmov($t->yminusx, $u->yminusx, $b), self::fe_cmov($t->xy2d, $u->xy2d, $b) ); } /** * @param ParagonIE_Sodium_Core_Curve25519_Ge_Cached $t * @param ParagonIE_Sodium_Core_Curve25519_Ge_Cached $u * @param int $b * @return ParagonIE_Sodium_Core_Curve25519_Ge_Cached */ public static function ge_cmov_cached( ParagonIE_Sodium_Core_Curve25519_Ge_Cached $t, ParagonIE_Sodium_Core_Curve25519_Ge_Cached $u, $b ) { $b &= 1; $ret = new ParagonIE_Sodium_Core_Curve25519_Ge_Cached(); $ret->YplusX = self::fe_cmov($t->YplusX, $u->YplusX, $b); $ret->YminusX = self::fe_cmov($t->YminusX, $u->YminusX, $b); $ret->Z = self::fe_cmov($t->Z, $u->Z, $b); $ret->T2d = self::fe_cmov($t->T2d, $u->T2d, $b); return $ret; } /** * @param ParagonIE_Sodium_Core_Curve25519_Ge_Cached[] $cached * @param int $b * @return ParagonIE_Sodium_Core_Curve25519_Ge_Cached * @throws SodiumException */ public static function ge_cmov8_cached(array $cached, $b) { // const unsigned char bnegative = negative(b); // const unsigned char babs = b - (((-bnegative) & b) * ((signed char) 1 << 1)); $bnegative = self::negative($b); $babs = $b - (((-$bnegative) & $b) << 1); // ge25519_cached_0(t); $t = new ParagonIE_Sodium_Core_Curve25519_Ge_Cached( self::fe_1(), self::fe_1(), self::fe_1(), self::fe_0() ); // ge25519_cmov_cached(t, &cached[0], equal(babs, 1)); // ge25519_cmov_cached(t, &cached[1], equal(babs, 2)); // ge25519_cmov_cached(t, &cached[2], equal(babs, 3)); // ge25519_cmov_cached(t, &cached[3], equal(babs, 4)); // ge25519_cmov_cached(t, &cached[4], equal(babs, 5)); // ge25519_cmov_cached(t, &cached[5], equal(babs, 6)); // ge25519_cmov_cached(t, &cached[6], equal(babs, 7)); // ge25519_cmov_cached(t, &cached[7], equal(babs, 8)); for ($x = 0; $x < 8; ++$x) { $t = self::ge_cmov_cached($t, $cached[$x], self::equal($babs, $x + 1)); } // fe25519_copy(minust.YplusX, t->YminusX); // fe25519_copy(minust.YminusX, t->YplusX); // fe25519_copy(minust.Z, t->Z); // fe25519_neg(minust.T2d, t->T2d); $minust = new ParagonIE_Sodium_Core_Curve25519_Ge_Cached( self::fe_copy($t->YminusX), self::fe_copy($t->YplusX), self::fe_copy($t->Z), self::fe_neg($t->T2d) ); return self::ge_cmov_cached($t, $minust, $bnegative); } /** * @internal You should not use this directly from another application * * @param int $pos * @param int $b * @return ParagonIE_Sodium_Core_Curve25519_Ge_Precomp * @throws SodiumException * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedArrayOffset */ public static function ge_select($pos = 0, $b = 0) { static $base = null; if ($base === null) { $base = array(); /** @var int $i */ foreach (self::$base as $i => $bas) { for ($j = 0; $j < 8; ++$j) { $base[$i][$j] = new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp( ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($bas[$j][0]), ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($bas[$j][1]), ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($bas[$j][2]) ); } } } /** @var array<int, array<int, ParagonIE_Sodium_Core_Curve25519_Ge_Precomp>> $base */ if (!is_int($pos)) { throw new InvalidArgumentException('Position must be an integer'); } if ($pos < 0 || $pos > 31) { throw new RangeException('Position is out of range [0, 31]'); } $bnegative = self::negative($b); $babs = $b - (((-$bnegative) & $b) << 1); $t = self::ge_precomp_0(); for ($i = 0; $i < 8; ++$i) { $t = self::cmov( $t, $base[$pos][$i], self::equal($babs, $i + 1) ); } $minusT = new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp( self::fe_copy($t->yminusx), self::fe_copy($t->yplusx), self::fe_neg($t->xy2d) ); return self::cmov($t, $minusT, $bnegative); } /** * Subtract two group elements. * * r = p - q * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p * @param ParagonIE_Sodium_Core_Curve25519_Ge_Cached $q * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 */ public static function ge_sub( ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p, ParagonIE_Sodium_Core_Curve25519_Ge_Cached $q ) { $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P1p1(); $r->X = self::fe_add($p->Y, $p->X); $r->Y = self::fe_sub($p->Y, $p->X); $r->Z = self::fe_mul($r->X, $q->YminusX); $r->Y = self::fe_mul($r->Y, $q->YplusX); $r->T = self::fe_mul($q->T2d, $p->T); $r->X = self::fe_mul($p->Z, $q->Z); $t0 = self::fe_add($r->X, $r->X); $r->X = self::fe_sub($r->Z, $r->Y); $r->Y = self::fe_add($r->Z, $r->Y); $r->Z = self::fe_sub($t0, $r->T); $r->T = self::fe_add($t0, $r->T); return $r; } /** * Convert a group element to a byte string. * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P2 $h * @return string * @throws SodiumException * @throws TypeError */ public static function ge_tobytes(ParagonIE_Sodium_Core_Curve25519_Ge_P2 $h) { $recip = self::fe_invert($h->Z); $x = self::fe_mul($h->X, $recip); $y = self::fe_mul($h->Y, $recip); $s = self::fe_tobytes($y); $s[31] = self::intToChr( self::chrToInt($s[31]) ^ (self::fe_isnegative($x) << 7) ); return $s; } /** * @internal You should not use this directly from another application * * @param string $a * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A * @param string $b * @return ParagonIE_Sodium_Core_Curve25519_Ge_P2 * @throws SodiumException * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress MixedArrayAccess */ public static function ge_double_scalarmult_vartime( $a, ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A, $b ) { /** @var array<int, ParagonIE_Sodium_Core_Curve25519_Ge_Cached> $Ai */ $Ai = array(); /** @var array<int, ParagonIE_Sodium_Core_Curve25519_Ge_Precomp> $Bi */ static $Bi = array(); if (!$Bi) { for ($i = 0; $i < 8; ++$i) { $Bi[$i] = new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp( ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$base2[$i][0]), ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$base2[$i][1]), ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$base2[$i][2]) ); } } for ($i = 0; $i < 8; ++$i) { $Ai[$i] = new ParagonIE_Sodium_Core_Curve25519_Ge_Cached( self::fe_0(), self::fe_0(), self::fe_0(), self::fe_0() ); } # slide(aslide,a); # slide(bslide,b); /** @var array<int, int> $aslide */ $aslide = self::slide($a); /** @var array<int, int> $bslide */ $bslide = self::slide($b); # ge_p3_to_cached(&Ai[0],A); # ge_p3_dbl(&t,A); ge_p1p1_to_p3(&A2,&t); $Ai[0] = self::ge_p3_to_cached($A); $t = self::ge_p3_dbl($A); $A2 = self::ge_p1p1_to_p3($t); # ge_add(&t,&A2,&Ai[0]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[1],&u); # ge_add(&t,&A2,&Ai[1]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[2],&u); # ge_add(&t,&A2,&Ai[2]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[3],&u); # ge_add(&t,&A2,&Ai[3]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[4],&u); # ge_add(&t,&A2,&Ai[4]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[5],&u); # ge_add(&t,&A2,&Ai[5]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[6],&u); # ge_add(&t,&A2,&Ai[6]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[7],&u); for ($i = 0; $i < 7; ++$i) { $t = self::ge_add($A2, $Ai[$i]); $u = self::ge_p1p1_to_p3($t); $Ai[$i + 1] = self::ge_p3_to_cached($u); } # ge_p2_0(r); $r = self::ge_p2_0(); # for (i = 255;i >= 0;--i) { # if (aslide[i] || bslide[i]) break; # } $i = 255; for (; $i >= 0; --$i) { if ($aslide[$i] || $bslide[$i]) { break; } } # for (;i >= 0;--i) { for (; $i >= 0; --$i) { # ge_p2_dbl(&t,r); $t = self::ge_p2_dbl($r); # if (aslide[i] > 0) { if ($aslide[$i] > 0) { # ge_p1p1_to_p3(&u,&t); # ge_add(&t,&u,&Ai[aslide[i]/2]); $u = self::ge_p1p1_to_p3($t); $t = self::ge_add( $u, $Ai[(int) floor($aslide[$i] / 2)] ); # } else if (aslide[i] < 0) { } elseif ($aslide[$i] < 0) { # ge_p1p1_to_p3(&u,&t); # ge_sub(&t,&u,&Ai[(-aslide[i])/2]); $u = self::ge_p1p1_to_p3($t); $t = self::ge_sub( $u, $Ai[(int) floor(-$aslide[$i] / 2)] ); } # if (bslide[i] > 0) { if ($bslide[$i] > 0) { /** @var int $index */ $index = (int) floor($bslide[$i] / 2); # ge_p1p1_to_p3(&u,&t); # ge_madd(&t,&u,&Bi[bslide[i]/2]); $u = self::ge_p1p1_to_p3($t); $t = self::ge_madd($t, $u, $Bi[$index]); # } else if (bslide[i] < 0) { } elseif ($bslide[$i] < 0) { /** @var int $index */ $index = (int) floor(-$bslide[$i] / 2); # ge_p1p1_to_p3(&u,&t); # ge_msub(&t,&u,&Bi[(-bslide[i])/2]); $u = self::ge_p1p1_to_p3($t); $t = self::ge_msub($t, $u, $Bi[$index]); } # ge_p1p1_to_p2(r,&t); $r = self::ge_p1p1_to_p2($t); } return $r; } /** * @internal You should not use this directly from another application * * @param string $a * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3 * @throws SodiumException * @throws TypeError * @psalm-suppress MixedAssignment * @psalm-suppress MixedOperand */ public static function ge_scalarmult($a, $p) { $e = array_fill(0, 64, 0); /** @var ParagonIE_Sodium_Core_Curve25519_Ge_Cached[] $pi */ $pi = array(); // ge25519_p3_to_cached(&pi[1 - 1], p); /* p */ $pi[0] = self::ge_p3_to_cached($p); // ge25519_p3_dbl(&t2, p); // ge25519_p1p1_to_p3(&p2, &t2); // ge25519_p3_to_cached(&pi[2 - 1], &p2); /* 2p = 2*p */ $t2 = self::ge_p3_dbl($p); $p2 = self::ge_p1p1_to_p3($t2); $pi[1] = self::ge_p3_to_cached($p2); // ge25519_add_cached(&t3, p, &pi[2 - 1]); // ge25519_p1p1_to_p3(&p3, &t3); // ge25519_p3_to_cached(&pi[3 - 1], &p3); /* 3p = 2p+p */ $t3 = self::ge_add($p, $pi[1]); $p3 = self::ge_p1p1_to_p3($t3); $pi[2] = self::ge_p3_to_cached($p3); // ge25519_p3_dbl(&t4, &p2); // ge25519_p1p1_to_p3(&p4, &t4); // ge25519_p3_to_cached(&pi[4 - 1], &p4); /* 4p = 2*2p */ $t4 = self::ge_p3_dbl($p2); $p4 = self::ge_p1p1_to_p3($t4); $pi[3] = self::ge_p3_to_cached($p4); // ge25519_add_cached(&t5, p, &pi[4 - 1]); // ge25519_p1p1_to_p3(&p5, &t5); // ge25519_p3_to_cached(&pi[5 - 1], &p5); /* 5p = 4p+p */ $t5 = self::ge_add($p, $pi[3]); $p5 = self::ge_p1p1_to_p3($t5); $pi[4] = self::ge_p3_to_cached($p5); // ge25519_p3_dbl(&t6, &p3); // ge25519_p1p1_to_p3(&p6, &t6); // ge25519_p3_to_cached(&pi[6 - 1], &p6); /* 6p = 2*3p */ $t6 = self::ge_p3_dbl($p3); $p6 = self::ge_p1p1_to_p3($t6); $pi[5] = self::ge_p3_to_cached($p6); // ge25519_add_cached(&t7, p, &pi[6 - 1]); // ge25519_p1p1_to_p3(&p7, &t7); // ge25519_p3_to_cached(&pi[7 - 1], &p7); /* 7p = 6p+p */ $t7 = self::ge_add($p, $pi[5]); $p7 = self::ge_p1p1_to_p3($t7); $pi[6] = self::ge_p3_to_cached($p7); // ge25519_p3_dbl(&t8, &p4); // ge25519_p1p1_to_p3(&p8, &t8); // ge25519_p3_to_cached(&pi[8 - 1], &p8); /* 8p = 2*4p */ $t8 = self::ge_p3_dbl($p4); $p8 = self::ge_p1p1_to_p3($t8); $pi[7] = self::ge_p3_to_cached($p8); // for (i = 0; i < 32; ++i) { // e[2 * i + 0] = (a[i] >> 0) & 15; // e[2 * i + 1] = (a[i] >> 4) & 15; // } for ($i = 0; $i < 32; ++$i) { $e[($i << 1) ] = self::chrToInt($a[$i]) & 15; $e[($i << 1) + 1] = (self::chrToInt($a[$i]) >> 4) & 15; } // /* each e[i] is between 0 and 15 */ // /* e[63] is between 0 and 7 */ // carry = 0; // for (i = 0; i < 63; ++i) { // e[i] += carry; // carry = e[i] + 8; // carry >>= 4; // e[i] -= carry * ((signed char) 1 << 4); // } $carry = 0; for ($i = 0; $i < 63; ++$i) { $e[$i] += $carry; $carry = $e[$i] + 8; $carry >>= 4; $e[$i] -= $carry << 4; } // e[63] += carry; // /* each e[i] is between -8 and 8 */ $e[63] += $carry; // ge25519_p3_0(h); $h = self::ge_p3_0(); // for (i = 63; i != 0; i--) { for ($i = 63; $i != 0; --$i) { // ge25519_cmov8_cached(&t, pi, e[i]); $t = self::ge_cmov8_cached($pi, $e[$i]); // ge25519_add_cached(&r, h, &t); $r = self::ge_add($h, $t); // ge25519_p1p1_to_p2(&s, &r); // ge25519_p2_dbl(&r, &s); // ge25519_p1p1_to_p2(&s, &r); // ge25519_p2_dbl(&r, &s); // ge25519_p1p1_to_p2(&s, &r); // ge25519_p2_dbl(&r, &s); // ge25519_p1p1_to_p2(&s, &r); // ge25519_p2_dbl(&r, &s); $s = self::ge_p1p1_to_p2($r); $r = self::ge_p2_dbl($s); $s = self::ge_p1p1_to_p2($r); $r = self::ge_p2_dbl($s); $s = self::ge_p1p1_to_p2($r); $r = self::ge_p2_dbl($s); $s = self::ge_p1p1_to_p2($r); $r = self::ge_p2_dbl($s); // ge25519_p1p1_to_p3(h, &r); /* *16 */ $h = self::ge_p1p1_to_p3($r); /* *16 */ } // ge25519_cmov8_cached(&t, pi, e[i]); // ge25519_add_cached(&r, h, &t); // ge25519_p1p1_to_p3(h, &r); $t = self::ge_cmov8_cached($pi, $e[0]); $r = self::ge_add($h, $t); return self::ge_p1p1_to_p3($r); } /** * @internal You should not use this directly from another application * * @param string $a * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3 * @throws SodiumException * @throws TypeError * @psalm-suppress MixedAssignment * @psalm-suppress MixedOperand */ public static function ge_scalarmult_base($a) { /** @var array<int, int> $e */ $e = array(); $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P1p1(); for ($i = 0; $i < 32; ++$i) { $dbl = (int) $i << 1; $e[$dbl] = (int) self::chrToInt($a[$i]) & 15; $e[$dbl + 1] = (int) (self::chrToInt($a[$i]) >> 4) & 15; } $carry = 0; for ($i = 0; $i < 63; ++$i) { $e[$i] += $carry; $carry = $e[$i] + 8; $carry >>= 4; $e[$i] -= $carry << 4; } $e[63] += (int) $carry; $h = self::ge_p3_0(); for ($i = 1; $i < 64; $i += 2) { $t = self::ge_select((int) floor($i / 2), (int) $e[$i]); $r = self::ge_madd($r, $h, $t); $h = self::ge_p1p1_to_p3($r); } $r = self::ge_p3_dbl($h); $s = self::ge_p1p1_to_p2($r); $r = self::ge_p2_dbl($s); $s = self::ge_p1p1_to_p2($r); $r = self::ge_p2_dbl($s); $s = self::ge_p1p1_to_p2($r); $r = self::ge_p2_dbl($s); $h = self::ge_p1p1_to_p3($r); for ($i = 0; $i < 64; $i += 2) { $t = self::ge_select($i >> 1, (int) $e[$i]); $r = self::ge_madd($r, $h, $t); $h = self::ge_p1p1_to_p3($r); } return $h; } /** * Calculates (ab + c) mod l * where l = 2^252 + 27742317777372353535851937790883648493 * * @internal You should not use this directly from another application * * @param string $a * @param string $b * @param string $c * @return string * @throws TypeError */ public static function sc_muladd($a, $b, $c) { $a0 = 2097151 & self::load_3(self::substr($a, 0, 3)); $a1 = 2097151 & (self::load_4(self::substr($a, 2, 4)) >> 5); $a2 = 2097151 & (self::load_3(self::substr($a, 5, 3)) >> 2); $a3 = 2097151 & (self::load_4(self::substr($a, 7, 4)) >> 7); $a4 = 2097151 & (self::load_4(self::substr($a, 10, 4)) >> 4); $a5 = 2097151 & (self::load_3(self::substr($a, 13, 3)) >> 1); $a6 = 2097151 & (self::load_4(self::substr($a, 15, 4)) >> 6); $a7 = 2097151 & (self::load_3(self::substr($a, 18, 3)) >> 3); $a8 = 2097151 & self::load_3(self::substr($a, 21, 3)); $a9 = 2097151 & (self::load_4(self::substr($a, 23, 4)) >> 5); $a10 = 2097151 & (self::load_3(self::substr($a, 26, 3)) >> 2); $a11 = (self::load_4(self::substr($a, 28, 4)) >> 7); $b0 = 2097151 & self::load_3(self::substr($b, 0, 3)); $b1 = 2097151 & (self::load_4(self::substr($b, 2, 4)) >> 5); $b2 = 2097151 & (self::load_3(self::substr($b, 5, 3)) >> 2); $b3 = 2097151 & (self::load_4(self::substr($b, 7, 4)) >> 7); $b4 = 2097151 & (self::load_4(self::substr($b, 10, 4)) >> 4); $b5 = 2097151 & (self::load_3(self::substr($b, 13, 3)) >> 1); $b6 = 2097151 & (self::load_4(self::substr($b, 15, 4)) >> 6); $b7 = 2097151 & (self::load_3(self::substr($b, 18, 3)) >> 3); $b8 = 2097151 & self::load_3(self::substr($b, 21, 3)); $b9 = 2097151 & (self::load_4(self::substr($b, 23, 4)) >> 5); $b10 = 2097151 & (self::load_3(self::substr($b, 26, 3)) >> 2); $b11 = (self::load_4(self::substr($b, 28, 4)) >> 7); $c0 = 2097151 & self::load_3(self::substr($c, 0, 3)); $c1 = 2097151 & (self::load_4(self::substr($c, 2, 4)) >> 5); $c2 = 2097151 & (self::load_3(self::substr($c, 5, 3)) >> 2); $c3 = 2097151 & (self::load_4(self::substr($c, 7, 4)) >> 7); $c4 = 2097151 & (self::load_4(self::substr($c, 10, 4)) >> 4); $c5 = 2097151 & (self::load_3(self::substr($c, 13, 3)) >> 1); $c6 = 2097151 & (self::load_4(self::substr($c, 15, 4)) >> 6); $c7 = 2097151 & (self::load_3(self::substr($c, 18, 3)) >> 3); $c8 = 2097151 & self::load_3(self::substr($c, 21, 3)); $c9 = 2097151 & (self::load_4(self::substr($c, 23, 4)) >> 5); $c10 = 2097151 & (self::load_3(self::substr($c, 26, 3)) >> 2); $c11 = (self::load_4(self::substr($c, 28, 4)) >> 7); /* Can't really avoid the pyramid here: */ $s0 = $c0 + self::mul($a0, $b0, 24); $s1 = $c1 + self::mul($a0, $b1, 24) + self::mul($a1, $b0, 24); $s2 = $c2 + self::mul($a0, $b2, 24) + self::mul($a1, $b1, 24) + self::mul($a2, $b0, 24); $s3 = $c3 + self::mul($a0, $b3, 24) + self::mul($a1, $b2, 24) + self::mul($a2, $b1, 24) + self::mul($a3, $b0, 24); $s4 = $c4 + self::mul($a0, $b4, 24) + self::mul($a1, $b3, 24) + self::mul($a2, $b2, 24) + self::mul($a3, $b1, 24) + self::mul($a4, $b0, 24); $s5 = $c5 + self::mul($a0, $b5, 24) + self::mul($a1, $b4, 24) + self::mul($a2, $b3, 24) + self::mul($a3, $b2, 24) + self::mul($a4, $b1, 24) + self::mul($a5, $b0, 24); $s6 = $c6 + self::mul($a0, $b6, 24) + self::mul($a1, $b5, 24) + self::mul($a2, $b4, 24) + self::mul($a3, $b3, 24) + self::mul($a4, $b2, 24) + self::mul($a5, $b1, 24) + self::mul($a6, $b0, 24); $s7 = $c7 + self::mul($a0, $b7, 24) + self::mul($a1, $b6, 24) + self::mul($a2, $b5, 24) + self::mul($a3, $b4, 24) + self::mul($a4, $b3, 24) + self::mul($a5, $b2, 24) + self::mul($a6, $b1, 24) + self::mul($a7, $b0, 24); $s8 = $c8 + self::mul($a0, $b8, 24) + self::mul($a1, $b7, 24) + self::mul($a2, $b6, 24) + self::mul($a3, $b5, 24) + self::mul($a4, $b4, 24) + self::mul($a5, $b3, 24) + self::mul($a6, $b2, 24) + self::mul($a7, $b1, 24) + self::mul($a8, $b0, 24); $s9 = $c9 + self::mul($a0, $b9, 24) + self::mul($a1, $b8, 24) + self::mul($a2, $b7, 24) + self::mul($a3, $b6, 24) + self::mul($a4, $b5, 24) + self::mul($a5, $b4, 24) + self::mul($a6, $b3, 24) + self::mul($a7, $b2, 24) + self::mul($a8, $b1, 24) + self::mul($a9, $b0, 24); $s10 = $c10 + self::mul($a0, $b10, 24) + self::mul($a1, $b9, 24) + self::mul($a2, $b8, 24) + self::mul($a3, $b7, 24) + self::mul($a4, $b6, 24) + self::mul($a5, $b5, 24) + self::mul($a6, $b4, 24) + self::mul($a7, $b3, 24) + self::mul($a8, $b2, 24) + self::mul($a9, $b1, 24) + self::mul($a10, $b0, 24); $s11 = $c11 + self::mul($a0, $b11, 24) + self::mul($a1, $b10, 24) + self::mul($a2, $b9, 24) + self::mul($a3, $b8, 24) + self::mul($a4, $b7, 24) + self::mul($a5, $b6, 24) + self::mul($a6, $b5, 24) + self::mul($a7, $b4, 24) + self::mul($a8, $b3, 24) + self::mul($a9, $b2, 24) + self::mul($a10, $b1, 24) + self::mul($a11, $b0, 24); $s12 = self::mul($a1, $b11, 24) + self::mul($a2, $b10, 24) + self::mul($a3, $b9, 24) + self::mul($a4, $b8, 24) + self::mul($a5, $b7, 24) + self::mul($a6, $b6, 24) + self::mul($a7, $b5, 24) + self::mul($a8, $b4, 24) + self::mul($a9, $b3, 24) + self::mul($a10, $b2, 24) + self::mul($a11, $b1, 24); $s13 = self::mul($a2, $b11, 24) + self::mul($a3, $b10, 24) + self::mul($a4, $b9, 24) + self::mul($a5, $b8, 24) + self::mul($a6, $b7, 24) + self::mul($a7, $b6, 24) + self::mul($a8, $b5, 24) + self::mul($a9, $b4, 24) + self::mul($a10, $b3, 24) + self::mul($a11, $b2, 24); $s14 = self::mul($a3, $b11, 24) + self::mul($a4, $b10, 24) + self::mul($a5, $b9, 24) + self::mul($a6, $b8, 24) + self::mul($a7, $b7, 24) + self::mul($a8, $b6, 24) + self::mul($a9, $b5, 24) + self::mul($a10, $b4, 24) + self::mul($a11, $b3, 24); $s15 = self::mul($a4, $b11, 24) + self::mul($a5, $b10, 24) + self::mul($a6, $b9, 24) + self::mul($a7, $b8, 24) + self::mul($a8, $b7, 24) + self::mul($a9, $b6, 24) + self::mul($a10, $b5, 24) + self::mul($a11, $b4, 24); $s16 = self::mul($a5, $b11, 24) + self::mul($a6, $b10, 24) + self::mul($a7, $b9, 24) + self::mul($a8, $b8, 24) + self::mul($a9, $b7, 24) + self::mul($a10, $b6, 24) + self::mul($a11, $b5, 24); $s17 = self::mul($a6, $b11, 24) + self::mul($a7, $b10, 24) + self::mul($a8, $b9, 24) + self::mul($a9, $b8, 24) + self::mul($a10, $b7, 24) + self::mul($a11, $b6, 24); $s18 = self::mul($a7, $b11, 24) + self::mul($a8, $b10, 24) + self::mul($a9, $b9, 24) + self::mul($a10, $b8, 24) + self::mul($a11, $b7, 24); $s19 = self::mul($a8, $b11, 24) + self::mul($a9, $b10, 24) + self::mul($a10, $b9, 24) + self::mul($a11, $b8, 24); $s20 = self::mul($a9, $b11, 24) + self::mul($a10, $b10, 24) + self::mul($a11, $b9, 24); $s21 = self::mul($a10, $b11, 24) + self::mul($a11, $b10, 24); $s22 = self::mul($a11, $b11, 24); $s23 = 0; $carry0 = ($s0 + (1 << 20)) >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; $carry2 = ($s2 + (1 << 20)) >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; $carry4 = ($s4 + (1 << 20)) >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; $carry6 = ($s6 + (1 << 20)) >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry8 = ($s8 + (1 << 20)) >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry10 = ($s10 + (1 << 20)) >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; $carry12 = ($s12 + (1 << 20)) >> 21; $s13 += $carry12; $s12 -= $carry12 << 21; $carry14 = ($s14 + (1 << 20)) >> 21; $s15 += $carry14; $s14 -= $carry14 << 21; $carry16 = ($s16 + (1 << 20)) >> 21; $s17 += $carry16; $s16 -= $carry16 << 21; $carry18 = ($s18 + (1 << 20)) >> 21; $s19 += $carry18; $s18 -= $carry18 << 21; $carry20 = ($s20 + (1 << 20)) >> 21; $s21 += $carry20; $s20 -= $carry20 << 21; $carry22 = ($s22 + (1 << 20)) >> 21; $s23 += $carry22; $s22 -= $carry22 << 21; $carry1 = ($s1 + (1 << 20)) >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; $carry3 = ($s3 + (1 << 20)) >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; $carry5 = ($s5 + (1 << 20)) >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; $carry7 = ($s7 + (1 << 20)) >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry9 = ($s9 + (1 << 20)) >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry11 = ($s11 + (1 << 20)) >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; $carry13 = ($s13 + (1 << 20)) >> 21; $s14 += $carry13; $s13 -= $carry13 << 21; $carry15 = ($s15 + (1 << 20)) >> 21; $s16 += $carry15; $s15 -= $carry15 << 21; $carry17 = ($s17 + (1 << 20)) >> 21; $s18 += $carry17; $s17 -= $carry17 << 21; $carry19 = ($s19 + (1 << 20)) >> 21; $s20 += $carry19; $s19 -= $carry19 << 21; $carry21 = ($s21 + (1 << 20)) >> 21; $s22 += $carry21; $s21 -= $carry21 << 21; $s11 += self::mul($s23, 666643, 20); $s12 += self::mul($s23, 470296, 19); $s13 += self::mul($s23, 654183, 20); $s14 -= self::mul($s23, 997805, 20); $s15 += self::mul($s23, 136657, 18); $s16 -= self::mul($s23, 683901, 20); $s10 += self::mul($s22, 666643, 20); $s11 += self::mul($s22, 470296, 19); $s12 += self::mul($s22, 654183, 20); $s13 -= self::mul($s22, 997805, 20); $s14 += self::mul($s22, 136657, 18); $s15 -= self::mul($s22, 683901, 20); $s9 += self::mul($s21, 666643, 20); $s10 += self::mul($s21, 470296, 19); $s11 += self::mul($s21, 654183, 20); $s12 -= self::mul($s21, 997805, 20); $s13 += self::mul($s21, 136657, 18); $s14 -= self::mul($s21, 683901, 20); $s8 += self::mul($s20, 666643, 20); $s9 += self::mul($s20, 470296, 19); $s10 += self::mul($s20, 654183, 20); $s11 -= self::mul($s20, 997805, 20); $s12 += self::mul($s20, 136657, 18); $s13 -= self::mul($s20, 683901, 20); $s7 += self::mul($s19, 666643, 20); $s8 += self::mul($s19, 470296, 19); $s9 += self::mul($s19, 654183, 20); $s10 -= self::mul($s19, 997805, 20); $s11 += self::mul($s19, 136657, 18); $s12 -= self::mul($s19, 683901, 20); $s6 += self::mul($s18, 666643, 20); $s7 += self::mul($s18, 470296, 19); $s8 += self::mul($s18, 654183, 20); $s9 -= self::mul($s18, 997805, 20); $s10 += self::mul($s18, 136657, 18); $s11 -= self::mul($s18, 683901, 20); $carry6 = ($s6 + (1 << 20)) >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry8 = ($s8 + (1 << 20)) >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry10 = ($s10 + (1 << 20)) >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; $carry12 = ($s12 + (1 << 20)) >> 21; $s13 += $carry12; $s12 -= $carry12 << 21; $carry14 = ($s14 + (1 << 20)) >> 21; $s15 += $carry14; $s14 -= $carry14 << 21; $carry16 = ($s16 + (1 << 20)) >> 21; $s17 += $carry16; $s16 -= $carry16 << 21; $carry7 = ($s7 + (1 << 20)) >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry9 = ($s9 + (1 << 20)) >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry11 = ($s11 + (1 << 20)) >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; $carry13 = ($s13 + (1 << 20)) >> 21; $s14 += $carry13; $s13 -= $carry13 << 21; $carry15 = ($s15 + (1 << 20)) >> 21; $s16 += $carry15; $s15 -= $carry15 << 21; $s5 += self::mul($s17, 666643, 20); $s6 += self::mul($s17, 470296, 19); $s7 += self::mul($s17, 654183, 20); $s8 -= self::mul($s17, 997805, 20); $s9 += self::mul($s17, 136657, 18); $s10 -= self::mul($s17, 683901, 20); $s4 += self::mul($s16, 666643, 20); $s5 += self::mul($s16, 470296, 19); $s6 += self::mul($s16, 654183, 20); $s7 -= self::mul($s16, 997805, 20); $s8 += self::mul($s16, 136657, 18); $s9 -= self::mul($s16, 683901, 20); $s3 += self::mul($s15, 666643, 20); $s4 += self::mul($s15, 470296, 19); $s5 += self::mul($s15, 654183, 20); $s6 -= self::mul($s15, 997805, 20); $s7 += self::mul($s15, 136657, 18); $s8 -= self::mul($s15, 683901, 20); $s2 += self::mul($s14, 666643, 20); $s3 += self::mul($s14, 470296, 19); $s4 += self::mul($s14, 654183, 20); $s5 -= self::mul($s14, 997805, 20); $s6 += self::mul($s14, 136657, 18); $s7 -= self::mul($s14, 683901, 20); $s1 += self::mul($s13, 666643, 20); $s2 += self::mul($s13, 470296, 19); $s3 += self::mul($s13, 654183, 20); $s4 -= self::mul($s13, 997805, 20); $s5 += self::mul($s13, 136657, 18); $s6 -= self::mul($s13, 683901, 20); $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); $s12 = 0; $carry0 = ($s0 + (1 << 20)) >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; $carry2 = ($s2 + (1 << 20)) >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; $carry4 = ($s4 + (1 << 20)) >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; $carry6 = ($s6 + (1 << 20)) >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry8 = ($s8 + (1 << 20)) >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry10 = ($s10 + (1 << 20)) >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; $carry1 = ($s1 + (1 << 20)) >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; $carry3 = ($s3 + (1 << 20)) >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; $carry5 = ($s5 + (1 << 20)) >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; $carry7 = ($s7 + (1 << 20)) >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry9 = ($s9 + (1 << 20)) >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry11 = ($s11 + (1 << 20)) >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); $s12 = 0; $carry0 = $s0 >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; $carry1 = $s1 >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; $carry2 = $s2 >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; $carry3 = $s3 >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; $carry4 = $s4 >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; $carry5 = $s5 >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; $carry6 = $s6 >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry7 = $s7 >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry8 = $s8 >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry9 = $s9 >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry10 = $s10 >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; $carry11 = $s11 >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); $carry0 = $s0 >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; $carry1 = $s1 >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; $carry2 = $s2 >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; $carry3 = $s3 >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; $carry4 = $s4 >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; $carry5 = $s5 >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; $carry6 = $s6 >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry7 = $s7 >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry8 = $s8 >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry9 = $s9 >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry10 = $s10 >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; /** * @var array<int, int> */ $arr = array( (int) (0xff & ($s0 >> 0)), (int) (0xff & ($s0 >> 8)), (int) (0xff & (($s0 >> 16) | $s1 << 5)), (int) (0xff & ($s1 >> 3)), (int) (0xff & ($s1 >> 11)), (int) (0xff & (($s1 >> 19) | $s2 << 2)), (int) (0xff & ($s2 >> 6)), (int) (0xff & (($s2 >> 14) | $s3 << 7)), (int) (0xff & ($s3 >> 1)), (int) (0xff & ($s3 >> 9)), (int) (0xff & (($s3 >> 17) | $s4 << 4)), (int) (0xff & ($s4 >> 4)), (int) (0xff & ($s4 >> 12)), (int) (0xff & (($s4 >> 20) | $s5 << 1)), (int) (0xff & ($s5 >> 7)), (int) (0xff & (($s5 >> 15) | $s6 << 6)), (int) (0xff & ($s6 >> 2)), (int) (0xff & ($s6 >> 10)), (int) (0xff & (($s6 >> 18) | $s7 << 3)), (int) (0xff & ($s7 >> 5)), (int) (0xff & ($s7 >> 13)), (int) (0xff & ($s8 >> 0)), (int) (0xff & ($s8 >> 8)), (int) (0xff & (($s8 >> 16) | $s9 << 5)), (int) (0xff & ($s9 >> 3)), (int) (0xff & ($s9 >> 11)), (int) (0xff & (($s9 >> 19) | $s10 << 2)), (int) (0xff & ($s10 >> 6)), (int) (0xff & (($s10 >> 14) | $s11 << 7)), (int) (0xff & ($s11 >> 1)), (int) (0xff & ($s11 >> 9)), 0xff & ($s11 >> 17) ); return self::intArrayToString($arr); } /** * @internal You should not use this directly from another application * * @param string $s * @return string * @throws TypeError */ public static function sc_reduce($s) { $s0 = 2097151 & self::load_3(self::substr($s, 0, 3)); $s1 = 2097151 & (self::load_4(self::substr($s, 2, 4)) >> 5); $s2 = 2097151 & (self::load_3(self::substr($s, 5, 3)) >> 2); $s3 = 2097151 & (self::load_4(self::substr($s, 7, 4)) >> 7); $s4 = 2097151 & (self::load_4(self::substr($s, 10, 4)) >> 4); $s5 = 2097151 & (self::load_3(self::substr($s, 13, 3)) >> 1); $s6 = 2097151 & (self::load_4(self::substr($s, 15, 4)) >> 6); $s7 = 2097151 & (self::load_3(self::substr($s, 18, 4)) >> 3); $s8 = 2097151 & self::load_3(self::substr($s, 21, 3)); $s9 = 2097151 & (self::load_4(self::substr($s, 23, 4)) >> 5); $s10 = 2097151 & (self::load_3(self::substr($s, 26, 3)) >> 2); $s11 = 2097151 & (self::load_4(self::substr($s, 28, 4)) >> 7); $s12 = 2097151 & (self::load_4(self::substr($s, 31, 4)) >> 4); $s13 = 2097151 & (self::load_3(self::substr($s, 34, 3)) >> 1); $s14 = 2097151 & (self::load_4(self::substr($s, 36, 4)) >> 6); $s15 = 2097151 & (self::load_3(self::substr($s, 39, 4)) >> 3); $s16 = 2097151 & self::load_3(self::substr($s, 42, 3)); $s17 = 2097151 & (self::load_4(self::substr($s, 44, 4)) >> 5); $s18 = 2097151 & (self::load_3(self::substr($s, 47, 3)) >> 2); $s19 = 2097151 & (self::load_4(self::substr($s, 49, 4)) >> 7); $s20 = 2097151 & (self::load_4(self::substr($s, 52, 4)) >> 4); $s21 = 2097151 & (self::load_3(self::substr($s, 55, 3)) >> 1); $s22 = 2097151 & (self::load_4(self::substr($s, 57, 4)) >> 6); $s23 = 0x1fffffff & (self::load_4(self::substr($s, 60, 4)) >> 3); $s11 += self::mul($s23, 666643, 20); $s12 += self::mul($s23, 470296, 19); $s13 += self::mul($s23, 654183, 20); $s14 -= self::mul($s23, 997805, 20); $s15 += self::mul($s23, 136657, 18); $s16 -= self::mul($s23, 683901, 20); $s10 += self::mul($s22, 666643, 20); $s11 += self::mul($s22, 470296, 19); $s12 += self::mul($s22, 654183, 20); $s13 -= self::mul($s22, 997805, 20); $s14 += self::mul($s22, 136657, 18); $s15 -= self::mul($s22, 683901, 20); $s9 += self::mul($s21, 666643, 20); $s10 += self::mul($s21, 470296, 19); $s11 += self::mul($s21, 654183, 20); $s12 -= self::mul($s21, 997805, 20); $s13 += self::mul($s21, 136657, 18); $s14 -= self::mul($s21, 683901, 20); $s8 += self::mul($s20, 666643, 20); $s9 += self::mul($s20, 470296, 19); $s10 += self::mul($s20, 654183, 20); $s11 -= self::mul($s20, 997805, 20); $s12 += self::mul($s20, 136657, 18); $s13 -= self::mul($s20, 683901, 20); $s7 += self::mul($s19, 666643, 20); $s8 += self::mul($s19, 470296, 19); $s9 += self::mul($s19, 654183, 20); $s10 -= self::mul($s19, 997805, 20); $s11 += self::mul($s19, 136657, 18); $s12 -= self::mul($s19, 683901, 20); $s6 += self::mul($s18, 666643, 20); $s7 += self::mul($s18, 470296, 19); $s8 += self::mul($s18, 654183, 20); $s9 -= self::mul($s18, 997805, 20); $s10 += self::mul($s18, 136657, 18); $s11 -= self::mul($s18, 683901, 20); $carry6 = ($s6 + (1 << 20)) >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry8 = ($s8 + (1 << 20)) >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry10 = ($s10 + (1 << 20)) >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; $carry12 = ($s12 + (1 << 20)) >> 21; $s13 += $carry12; $s12 -= $carry12 << 21; $carry14 = ($s14 + (1 << 20)) >> 21; $s15 += $carry14; $s14 -= $carry14 << 21; $carry16 = ($s16 + (1 << 20)) >> 21; $s17 += $carry16; $s16 -= $carry16 << 21; $carry7 = ($s7 + (1 << 20)) >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry9 = ($s9 + (1 << 20)) >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry11 = ($s11 + (1 << 20)) >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; $carry13 = ($s13 + (1 << 20)) >> 21; $s14 += $carry13; $s13 -= $carry13 << 21; $carry15 = ($s15 + (1 << 20)) >> 21; $s16 += $carry15; $s15 -= $carry15 << 21; $s5 += self::mul($s17, 666643, 20); $s6 += self::mul($s17, 470296, 19); $s7 += self::mul($s17, 654183, 20); $s8 -= self::mul($s17, 997805, 20); $s9 += self::mul($s17, 136657, 18); $s10 -= self::mul($s17, 683901, 20); $s4 += self::mul($s16, 666643, 20); $s5 += self::mul($s16, 470296, 19); $s6 += self::mul($s16, 654183, 20); $s7 -= self::mul($s16, 997805, 20); $s8 += self::mul($s16, 136657, 18); $s9 -= self::mul($s16, 683901, 20); $s3 += self::mul($s15, 666643, 20); $s4 += self::mul($s15, 470296, 19); $s5 += self::mul($s15, 654183, 20); $s6 -= self::mul($s15, 997805, 20); $s7 += self::mul($s15, 136657, 18); $s8 -= self::mul($s15, 683901, 20); $s2 += self::mul($s14, 666643, 20); $s3 += self::mul($s14, 470296, 19); $s4 += self::mul($s14, 654183, 20); $s5 -= self::mul($s14, 997805, 20); $s6 += self::mul($s14, 136657, 18); $s7 -= self::mul($s14, 683901, 20); $s1 += self::mul($s13, 666643, 20); $s2 += self::mul($s13, 470296, 19); $s3 += self::mul($s13, 654183, 20); $s4 -= self::mul($s13, 997805, 20); $s5 += self::mul($s13, 136657, 18); $s6 -= self::mul($s13, 683901, 20); $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); $s12 = 0; $carry0 = ($s0 + (1 << 20)) >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; $carry2 = ($s2 + (1 << 20)) >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; $carry4 = ($s4 + (1 << 20)) >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; $carry6 = ($s6 + (1 << 20)) >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry8 = ($s8 + (1 << 20)) >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry10 = ($s10 + (1 << 20)) >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; $carry1 = ($s1 + (1 << 20)) >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; $carry3 = ($s3 + (1 << 20)) >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; $carry5 = ($s5 + (1 << 20)) >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; $carry7 = ($s7 + (1 << 20)) >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry9 = ($s9 + (1 << 20)) >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry11 = ($s11 + (1 << 20)) >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); $s12 = 0; $carry0 = $s0 >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; $carry1 = $s1 >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; $carry2 = $s2 >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; $carry3 = $s3 >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; $carry4 = $s4 >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; $carry5 = $s5 >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; $carry6 = $s6 >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry7 = $s7 >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry8 = $s8 >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry9 = $s9 >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry10 = $s10 >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; $carry11 = $s11 >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); $carry0 = $s0 >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; $carry1 = $s1 >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; $carry2 = $s2 >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; $carry3 = $s3 >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; $carry4 = $s4 >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; $carry5 = $s5 >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; $carry6 = $s6 >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; $carry7 = $s7 >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; $carry8 = $s8 >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; $carry9 = $s9 >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; $carry10 = $s10 >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; /** * @var array<int, int> */ $arr = array( (int) ($s0 >> 0), (int) ($s0 >> 8), (int) (($s0 >> 16) | $s1 << 5), (int) ($s1 >> 3), (int) ($s1 >> 11), (int) (($s1 >> 19) | $s2 << 2), (int) ($s2 >> 6), (int) (($s2 >> 14) | $s3 << 7), (int) ($s3 >> 1), (int) ($s3 >> 9), (int) (($s3 >> 17) | $s4 << 4), (int) ($s4 >> 4), (int) ($s4 >> 12), (int) (($s4 >> 20) | $s5 << 1), (int) ($s5 >> 7), (int) (($s5 >> 15) | $s6 << 6), (int) ($s6 >> 2), (int) ($s6 >> 10), (int) (($s6 >> 18) | $s7 << 3), (int) ($s7 >> 5), (int) ($s7 >> 13), (int) ($s8 >> 0), (int) ($s8 >> 8), (int) (($s8 >> 16) | $s9 << 5), (int) ($s9 >> 3), (int) ($s9 >> 11), (int) (($s9 >> 19) | $s10 << 2), (int) ($s10 >> 6), (int) (($s10 >> 14) | $s11 << 7), (int) ($s11 >> 1), (int) ($s11 >> 9), (int) $s11 >> 17 ); return self::intArrayToString($arr); } /** * multiply by the order of the main subgroup l = 2^252+27742317777372353535851937790883648493 * * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3 */ public static function ge_mul_l(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A) { $aslide = array( 13, 0, 0, 0, 0, -1, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, -13, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, -13, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, -13, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 3, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ); /** @var array<int, ParagonIE_Sodium_Core_Curve25519_Ge_Cached> $Ai size 8 */ $Ai = array(); # ge_p3_to_cached(&Ai[0], A); $Ai[0] = self::ge_p3_to_cached($A); # ge_p3_dbl(&t, A); $t = self::ge_p3_dbl($A); # ge_p1p1_to_p3(&A2, &t); $A2 = self::ge_p1p1_to_p3($t); for ($i = 1; $i < 8; ++$i) { # ge_add(&t, &A2, &Ai[0]); $t = self::ge_add($A2, $Ai[$i - 1]); # ge_p1p1_to_p3(&u, &t); $u = self::ge_p1p1_to_p3($t); # ge_p3_to_cached(&Ai[i], &u); $Ai[$i] = self::ge_p3_to_cached($u); } $r = self::ge_p3_0(); for ($i = 252; $i >= 0; --$i) { $t = self::ge_p3_dbl($r); if ($aslide[$i] > 0) { # ge_p1p1_to_p3(&u, &t); $u = self::ge_p1p1_to_p3($t); # ge_add(&t, &u, &Ai[aslide[i] / 2]); $t = self::ge_add($u, $Ai[(int)($aslide[$i] / 2)]); } elseif ($aslide[$i] < 0) { # ge_p1p1_to_p3(&u, &t); $u = self::ge_p1p1_to_p3($t); # ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]); $t = self::ge_sub($u, $Ai[(int)(-$aslide[$i] / 2)]); } } # ge_p1p1_to_p3(r, &t); return self::ge_p1p1_to_p3($t); } /** * @param string $a * @param string $b * @return string */ public static function sc25519_mul($a, $b) { // int64_t a0 = 2097151 & load_3(a); // int64_t a1 = 2097151 & (load_4(a + 2) >> 5); // int64_t a2 = 2097151 & (load_3(a + 5) >> 2); // int64_t a3 = 2097151 & (load_4(a + 7) >> 7); // int64_t a4 = 2097151 & (load_4(a + 10) >> 4); // int64_t a5 = 2097151 & (load_3(a + 13) >> 1); // int64_t a6 = 2097151 & (load_4(a + 15) >> 6); // int64_t a7 = 2097151 & (load_3(a + 18) >> 3); // int64_t a8 = 2097151 & load_3(a + 21); // int64_t a9 = 2097151 & (load_4(a + 23) >> 5); // int64_t a10 = 2097151 & (load_3(a + 26) >> 2); // int64_t a11 = (load_4(a + 28) >> 7); $a0 = 2097151 & self::load_3(self::substr($a, 0, 3)); $a1 = 2097151 & (self::load_4(self::substr($a, 2, 4)) >> 5); $a2 = 2097151 & (self::load_3(self::substr($a, 5, 3)) >> 2); $a3 = 2097151 & (self::load_4(self::substr($a, 7, 4)) >> 7); $a4 = 2097151 & (self::load_4(self::substr($a, 10, 4)) >> 4); $a5 = 2097151 & (self::load_3(self::substr($a, 13, 3)) >> 1); $a6 = 2097151 & (self::load_4(self::substr($a, 15, 4)) >> 6); $a7 = 2097151 & (self::load_3(self::substr($a, 18, 3)) >> 3); $a8 = 2097151 & self::load_3(self::substr($a, 21, 3)); $a9 = 2097151 & (self::load_4(self::substr($a, 23, 4)) >> 5); $a10 = 2097151 & (self::load_3(self::substr($a, 26, 3)) >> 2); $a11 = (self::load_4(self::substr($a, 28, 4)) >> 7); // int64_t b0 = 2097151 & load_3(b); // int64_t b1 = 2097151 & (load_4(b + 2) >> 5); // int64_t b2 = 2097151 & (load_3(b + 5) >> 2); // int64_t b3 = 2097151 & (load_4(b + 7) >> 7); // int64_t b4 = 2097151 & (load_4(b + 10) >> 4); // int64_t b5 = 2097151 & (load_3(b + 13) >> 1); // int64_t b6 = 2097151 & (load_4(b + 15) >> 6); // int64_t b7 = 2097151 & (load_3(b + 18) >> 3); // int64_t b8 = 2097151 & load_3(b + 21); // int64_t b9 = 2097151 & (load_4(b + 23) >> 5); // int64_t b10 = 2097151 & (load_3(b + 26) >> 2); // int64_t b11 = (load_4(b + 28) >> 7); $b0 = 2097151 & self::load_3(self::substr($b, 0, 3)); $b1 = 2097151 & (self::load_4(self::substr($b, 2, 4)) >> 5); $b2 = 2097151 & (self::load_3(self::substr($b, 5, 3)) >> 2); $b3 = 2097151 & (self::load_4(self::substr($b, 7, 4)) >> 7); $b4 = 2097151 & (self::load_4(self::substr($b, 10, 4)) >> 4); $b5 = 2097151 & (self::load_3(self::substr($b, 13, 3)) >> 1); $b6 = 2097151 & (self::load_4(self::substr($b, 15, 4)) >> 6); $b7 = 2097151 & (self::load_3(self::substr($b, 18, 3)) >> 3); $b8 = 2097151 & self::load_3(self::substr($b, 21, 3)); $b9 = 2097151 & (self::load_4(self::substr($b, 23, 4)) >> 5); $b10 = 2097151 & (self::load_3(self::substr($b, 26, 3)) >> 2); $b11 = (self::load_4(self::substr($b, 28, 4)) >> 7); // s0 = a0 * b0; // s1 = a0 * b1 + a1 * b0; // s2 = a0 * b2 + a1 * b1 + a2 * b0; // s3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; // s4 = a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; // s5 = a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; // s6 = a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; // s7 = a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + // a6 * b1 + a7 * b0; // s8 = a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + // a6 * b2 + a7 * b1 + a8 * b0; // s9 = a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + // a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0; // s10 = a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + // a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0; // s11 = a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + // a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; // s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + // a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1; // s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + // a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2; // s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + // a9 * b5 + a10 * b4 + a11 * b3; // s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + // a10 * b5 + a11 * b4; // s16 = // a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; // s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; // s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; // s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; // s20 = a9 * b11 + a10 * b10 + a11 * b9; // s21 = a10 * b11 + a11 * b10; // s22 = a11 * b11; // s23 = 0; $s0 = self::mul($a0, $b0, 22); $s1 = self::mul($a0, $b1, 22) + self::mul($a1, $b0, 22); $s2 = self::mul($a0, $b2, 22) + self::mul($a1, $b1, 22) + self::mul($a2, $b0, 22); $s3 = self::mul($a0, $b3, 22) + self::mul($a1, $b2, 22) + self::mul($a2, $b1, 22) + self::mul($a3, $b0, 22); $s4 = self::mul($a0, $b4, 22) + self::mul($a1, $b3, 22) + self::mul($a2, $b2, 22) + self::mul($a3, $b1, 22) + self::mul($a4, $b0, 22); $s5 = self::mul($a0, $b5, 22) + self::mul($a1, $b4, 22) + self::mul($a2, $b3, 22) + self::mul($a3, $b2, 22) + self::mul($a4, $b1, 22) + self::mul($a5, $b0, 22); $s6 = self::mul($a0, $b6, 22) + self::mul($a1, $b5, 22) + self::mul($a2, $b4, 22) + self::mul($a3, $b3, 22) + self::mul($a4, $b2, 22) + self::mul($a5, $b1, 22) + self::mul($a6, $b0, 22); $s7 = self::mul($a0, $b7, 22) + self::mul($a1, $b6, 22) + self::mul($a2, $b5, 22) + self::mul($a3, $b4, 22) + self::mul($a4, $b3, 22) + self::mul($a5, $b2, 22) + self::mul($a6, $b1, 22) + self::mul($a7, $b0, 22); $s8 = self::mul($a0, $b8, 22) + self::mul($a1, $b7, 22) + self::mul($a2, $b6, 22) + self::mul($a3, $b5, 22) + self::mul($a4, $b4, 22) + self::mul($a5, $b3, 22) + self::mul($a6, $b2, 22) + self::mul($a7, $b1, 22) + self::mul($a8, $b0, 22); $s9 = self::mul($a0, $b9, 22) + self::mul($a1, $b8, 22) + self::mul($a2, $b7, 22) + self::mul($a3, $b6, 22) + self::mul($a4, $b5, 22) + self::mul($a5, $b4, 22) + self::mul($a6, $b3, 22) + self::mul($a7, $b2, 22) + self::mul($a8, $b1, 22) + self::mul($a9, $b0, 22); $s10 = self::mul($a0, $b10, 22) + self::mul($a1, $b9, 22) + self::mul($a2, $b8, 22) + self::mul($a3, $b7, 22) + self::mul($a4, $b6, 22) + self::mul($a5, $b5, 22) + self::mul($a6, $b4, 22) + self::mul($a7, $b3, 22) + self::mul($a8, $b2, 22) + self::mul($a9, $b1, 22) + self::mul($a10, $b0, 22); $s11 = self::mul($a0, $b11, 22) + self::mul($a1, $b10, 22) + self::mul($a2, $b9, 22) + self::mul($a3, $b8, 22) + self::mul($a4, $b7, 22) + self::mul($a5, $b6, 22) + self::mul($a6, $b5, 22) + self::mul($a7, $b4, 22) + self::mul($a8, $b3, 22) + self::mul($a9, $b2, 22) + self::mul($a10, $b1, 22) + self::mul($a11, $b0, 22); $s12 = self::mul($a1, $b11, 22) + self::mul($a2, $b10, 22) + self::mul($a3, $b9, 22) + self::mul($a4, $b8, 22) + self::mul($a5, $b7, 22) + self::mul($a6, $b6, 22) + self::mul($a7, $b5, 22) + self::mul($a8, $b4, 22) + self::mul($a9, $b3, 22) + self::mul($a10, $b2, 22) + self::mul($a11, $b1, 22); $s13 = self::mul($a2, $b11, 22) + self::mul($a3, $b10, 22) + self::mul($a4, $b9, 22) + self::mul($a5, $b8, 22) + self::mul($a6, $b7, 22) + self::mul($a7, $b6, 22) + self::mul($a8, $b5, 22) + self::mul($a9, $b4, 22) + self::mul($a10, $b3, 22) + self::mul($a11, $b2, 22); $s14 = self::mul($a3, $b11, 22) + self::mul($a4, $b10, 22) + self::mul($a5, $b9, 22) + self::mul($a6, $b8, 22) + self::mul($a7, $b7, 22) + self::mul($a8, $b6, 22) + self::mul($a9, $b5, 22) + self::mul($a10, $b4, 22) + self::mul($a11, $b3, 22); $s15 = self::mul($a4, $b11, 22) + self::mul($a5, $b10, 22) + self::mul($a6, $b9, 22) + self::mul($a7, $b8, 22) + self::mul($a8, $b7, 22) + self::mul($a9, $b6, 22) + self::mul($a10, $b5, 22) + self::mul($a11, $b4, 22); $s16 = self::mul($a5, $b11, 22) + self::mul($a6, $b10, 22) + self::mul($a7, $b9, 22) + self::mul($a8, $b8, 22) + self::mul($a9, $b7, 22) + self::mul($a10, $b6, 22) + self::mul($a11, $b5, 22); $s17 = self::mul($a6, $b11, 22) + self::mul($a7, $b10, 22) + self::mul($a8, $b9, 22) + self::mul($a9, $b8, 22) + self::mul($a10, $b7, 22) + self::mul($a11, $b6, 22); $s18 = self::mul($a7, $b11, 22) + self::mul($a8, $b10, 22) + self::mul($a9, $b9, 22) + self::mul($a10, $b8, 22) + self::mul($a11, $b7, 22); $s19 = self::mul($a8, $b11, 22) + self::mul($a9, $b10, 22) + self::mul($a10, $b9, 22) + self::mul($a11, $b8, 22); $s20 = self::mul($a9, $b11, 22) + self::mul($a10, $b10, 22) + self::mul($a11, $b9, 22); $s21 = self::mul($a10, $b11, 22) + self::mul($a11, $b10, 22); $s22 = self::mul($a11, $b11, 22); $s23 = 0; // carry0 = (s0 + (int64_t) (1L << 20)) >> 21; // s1 += carry0; // s0 -= carry0 * ((uint64_t) 1L << 21); $carry0 = ($s0 + (1 << 20)) >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; // carry2 = (s2 + (int64_t) (1L << 20)) >> 21; // s3 += carry2; // s2 -= carry2 * ((uint64_t) 1L << 21); $carry2 = ($s2 + (1 << 20)) >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; // carry4 = (s4 + (int64_t) (1L << 20)) >> 21; // s5 += carry4; // s4 -= carry4 * ((uint64_t) 1L << 21); $carry4 = ($s4 + (1 << 20)) >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; // carry6 = (s6 + (int64_t) (1L << 20)) >> 21; // s7 += carry6; // s6 -= carry6 * ((uint64_t) 1L << 21); $carry6 = ($s6 + (1 << 20)) >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; // carry8 = (s8 + (int64_t) (1L << 20)) >> 21; // s9 += carry8; // s8 -= carry8 * ((uint64_t) 1L << 21); $carry8 = ($s8 + (1 << 20)) >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; // carry10 = (s10 + (int64_t) (1L << 20)) >> 21; // s11 += carry10; // s10 -= carry10 * ((uint64_t) 1L << 21); $carry10 = ($s10 + (1 << 20)) >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; // carry12 = (s12 + (int64_t) (1L << 20)) >> 21; // s13 += carry12; // s12 -= carry12 * ((uint64_t) 1L << 21); $carry12 = ($s12 + (1 << 20)) >> 21; $s13 += $carry12; $s12 -= $carry12 << 21; // carry14 = (s14 + (int64_t) (1L << 20)) >> 21; // s15 += carry14; // s14 -= carry14 * ((uint64_t) 1L << 21); $carry14 = ($s14 + (1 << 20)) >> 21; $s15 += $carry14; $s14 -= $carry14 << 21; // carry16 = (s16 + (int64_t) (1L << 20)) >> 21; // s17 += carry16; // s16 -= carry16 * ((uint64_t) 1L << 21); $carry16 = ($s16 + (1 << 20)) >> 21; $s17 += $carry16; $s16 -= $carry16 << 21; // carry18 = (s18 + (int64_t) (1L << 20)) >> 21; // s19 += carry18; // s18 -= carry18 * ((uint64_t) 1L << 21); $carry18 = ($s18 + (1 << 20)) >> 21; $s19 += $carry18; $s18 -= $carry18 << 21; // carry20 = (s20 + (int64_t) (1L << 20)) >> 21; // s21 += carry20; // s20 -= carry20 * ((uint64_t) 1L << 21); $carry20 = ($s20 + (1 << 20)) >> 21; $s21 += $carry20; $s20 -= $carry20 << 21; // carry22 = (s22 + (int64_t) (1L << 20)) >> 21; // s23 += carry22; // s22 -= carry22 * ((uint64_t) 1L << 21); $carry22 = ($s22 + (1 << 20)) >> 21; $s23 += $carry22; $s22 -= $carry22 << 21; // carry1 = (s1 + (int64_t) (1L << 20)) >> 21; // s2 += carry1; // s1 -= carry1 * ((uint64_t) 1L << 21); $carry1 = ($s1 + (1 << 20)) >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; // carry3 = (s3 + (int64_t) (1L << 20)) >> 21; // s4 += carry3; // s3 -= carry3 * ((uint64_t) 1L << 21); $carry3 = ($s3 + (1 << 20)) >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; // carry5 = (s5 + (int64_t) (1L << 20)) >> 21; // s6 += carry5; // s5 -= carry5 * ((uint64_t) 1L << 21); $carry5 = ($s5 + (1 << 20)) >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; // carry7 = (s7 + (int64_t) (1L << 20)) >> 21; // s8 += carry7; // s7 -= carry7 * ((uint64_t) 1L << 21); $carry7 = ($s7 + (1 << 20)) >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; // carry9 = (s9 + (int64_t) (1L << 20)) >> 21; // s10 += carry9; // s9 -= carry9 * ((uint64_t) 1L << 21); $carry9 = ($s9 + (1 << 20)) >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; // carry11 = (s11 + (int64_t) (1L << 20)) >> 21; // s12 += carry11; // s11 -= carry11 * ((uint64_t) 1L << 21); $carry11 = ($s11 + (1 << 20)) >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; // carry13 = (s13 + (int64_t) (1L << 20)) >> 21; // s14 += carry13; // s13 -= carry13 * ((uint64_t) 1L << 21); $carry13 = ($s13 + (1 << 20)) >> 21; $s14 += $carry13; $s13 -= $carry13 << 21; // carry15 = (s15 + (int64_t) (1L << 20)) >> 21; // s16 += carry15; // s15 -= carry15 * ((uint64_t) 1L << 21); $carry15 = ($s15 + (1 << 20)) >> 21; $s16 += $carry15; $s15 -= $carry15 << 21; // carry17 = (s17 + (int64_t) (1L << 20)) >> 21; // s18 += carry17; // s17 -= carry17 * ((uint64_t) 1L << 21); $carry17 = ($s17 + (1 << 20)) >> 21; $s18 += $carry17; $s17 -= $carry17 << 21; // carry19 = (s19 + (int64_t) (1L << 20)) >> 21; // s20 += carry19; // s19 -= carry19 * ((uint64_t) 1L << 21); $carry19 = ($s19 + (1 << 20)) >> 21; $s20 += $carry19; $s19 -= $carry19 << 21; // carry21 = (s21 + (int64_t) (1L << 20)) >> 21; // s22 += carry21; // s21 -= carry21 * ((uint64_t) 1L << 21); $carry21 = ($s21 + (1 << 20)) >> 21; $s22 += $carry21; $s21 -= $carry21 << 21; // s11 += s23 * 666643; // s12 += s23 * 470296; // s13 += s23 * 654183; // s14 -= s23 * 997805; // s15 += s23 * 136657; // s16 -= s23 * 683901; $s11 += self::mul($s23, 666643, 20); $s12 += self::mul($s23, 470296, 19); $s13 += self::mul($s23, 654183, 20); $s14 -= self::mul($s23, 997805, 20); $s15 += self::mul($s23, 136657, 18); $s16 -= self::mul($s23, 683901, 20); // s10 += s22 * 666643; // s11 += s22 * 470296; // s12 += s22 * 654183; // s13 -= s22 * 997805; // s14 += s22 * 136657; // s15 -= s22 * 683901; $s10 += self::mul($s22, 666643, 20); $s11 += self::mul($s22, 470296, 19); $s12 += self::mul($s22, 654183, 20); $s13 -= self::mul($s22, 997805, 20); $s14 += self::mul($s22, 136657, 18); $s15 -= self::mul($s22, 683901, 20); // s9 += s21 * 666643; // s10 += s21 * 470296; // s11 += s21 * 654183; // s12 -= s21 * 997805; // s13 += s21 * 136657; // s14 -= s21 * 683901; $s9 += self::mul($s21, 666643, 20); $s10 += self::mul($s21, 470296, 19); $s11 += self::mul($s21, 654183, 20); $s12 -= self::mul($s21, 997805, 20); $s13 += self::mul($s21, 136657, 18); $s14 -= self::mul($s21, 683901, 20); // s8 += s20 * 666643; // s9 += s20 * 470296; // s10 += s20 * 654183; // s11 -= s20 * 997805; // s12 += s20 * 136657; // s13 -= s20 * 683901; $s8 += self::mul($s20, 666643, 20); $s9 += self::mul($s20, 470296, 19); $s10 += self::mul($s20, 654183, 20); $s11 -= self::mul($s20, 997805, 20); $s12 += self::mul($s20, 136657, 18); $s13 -= self::mul($s20, 683901, 20); // s7 += s19 * 666643; // s8 += s19 * 470296; // s9 += s19 * 654183; // s10 -= s19 * 997805; // s11 += s19 * 136657; // s12 -= s19 * 683901; $s7 += self::mul($s19, 666643, 20); $s8 += self::mul($s19, 470296, 19); $s9 += self::mul($s19, 654183, 20); $s10 -= self::mul($s19, 997805, 20); $s11 += self::mul($s19, 136657, 18); $s12 -= self::mul($s19, 683901, 20); // s6 += s18 * 666643; // s7 += s18 * 470296; // s8 += s18 * 654183; // s9 -= s18 * 997805; // s10 += s18 * 136657; // s11 -= s18 * 683901; $s6 += self::mul($s18, 666643, 20); $s7 += self::mul($s18, 470296, 19); $s8 += self::mul($s18, 654183, 20); $s9 -= self::mul($s18, 997805, 20); $s10 += self::mul($s18, 136657, 18); $s11 -= self::mul($s18, 683901, 20); // carry6 = (s6 + (int64_t) (1L << 20)) >> 21; // s7 += carry6; // s6 -= carry6 * ((uint64_t) 1L << 21); $carry6 = ($s6 + (1 << 20)) >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; // carry8 = (s8 + (int64_t) (1L << 20)) >> 21; // s9 += carry8; // s8 -= carry8 * ((uint64_t) 1L << 21); $carry8 = ($s8 + (1 << 20)) >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; // carry10 = (s10 + (int64_t) (1L << 20)) >> 21; // s11 += carry10; // s10 -= carry10 * ((uint64_t) 1L << 21); $carry10 = ($s10 + (1 << 20)) >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; // carry12 = (s12 + (int64_t) (1L << 20)) >> 21; // s13 += carry12; // s12 -= carry12 * ((uint64_t) 1L << 21); $carry12 = ($s12 + (1 << 20)) >> 21; $s13 += $carry12; $s12 -= $carry12 << 21; // carry14 = (s14 + (int64_t) (1L << 20)) >> 21; // s15 += carry14; // s14 -= carry14 * ((uint64_t) 1L << 21); $carry14 = ($s14 + (1 << 20)) >> 21; $s15 += $carry14; $s14 -= $carry14 << 21; // carry16 = (s16 + (int64_t) (1L << 20)) >> 21; // s17 += carry16; // s16 -= carry16 * ((uint64_t) 1L << 21); $carry16 = ($s16 + (1 << 20)) >> 21; $s17 += $carry16; $s16 -= $carry16 << 21; // carry7 = (s7 + (int64_t) (1L << 20)) >> 21; // s8 += carry7; // s7 -= carry7 * ((uint64_t) 1L << 21); $carry7 = ($s7 + (1 << 20)) >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; // carry9 = (s9 + (int64_t) (1L << 20)) >> 21; // s10 += carry9; // s9 -= carry9 * ((uint64_t) 1L << 21); $carry9 = ($s9 + (1 << 20)) >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; // carry11 = (s11 + (int64_t) (1L << 20)) >> 21; // s12 += carry11; // s11 -= carry11 * ((uint64_t) 1L << 21); $carry11 = ($s11 + (1 << 20)) >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; // carry13 = (s13 + (int64_t) (1L << 20)) >> 21; // s14 += carry13; // s13 -= carry13 * ((uint64_t) 1L << 21); $carry13 = ($s13 + (1 << 20)) >> 21; $s14 += $carry13; $s13 -= $carry13 << 21; // carry15 = (s15 + (int64_t) (1L << 20)) >> 21; // s16 += carry15; // s15 -= carry15 * ((uint64_t) 1L << 21); $carry15 = ($s15 + (1 << 20)) >> 21; $s16 += $carry15; $s15 -= $carry15 << 21; // s5 += s17 * 666643; // s6 += s17 * 470296; // s7 += s17 * 654183; // s8 -= s17 * 997805; // s9 += s17 * 136657; // s10 -= s17 * 683901; $s5 += self::mul($s17, 666643, 20); $s6 += self::mul($s17, 470296, 19); $s7 += self::mul($s17, 654183, 20); $s8 -= self::mul($s17, 997805, 20); $s9 += self::mul($s17, 136657, 18); $s10 -= self::mul($s17, 683901, 20); // s4 += s16 * 666643; // s5 += s16 * 470296; // s6 += s16 * 654183; // s7 -= s16 * 997805; // s8 += s16 * 136657; // s9 -= s16 * 683901; $s4 += self::mul($s16, 666643, 20); $s5 += self::mul($s16, 470296, 19); $s6 += self::mul($s16, 654183, 20); $s7 -= self::mul($s16, 997805, 20); $s8 += self::mul($s16, 136657, 18); $s9 -= self::mul($s16, 683901, 20); // s3 += s15 * 666643; // s4 += s15 * 470296; // s5 += s15 * 654183; // s6 -= s15 * 997805; // s7 += s15 * 136657; // s8 -= s15 * 683901; $s3 += self::mul($s15, 666643, 20); $s4 += self::mul($s15, 470296, 19); $s5 += self::mul($s15, 654183, 20); $s6 -= self::mul($s15, 997805, 20); $s7 += self::mul($s15, 136657, 18); $s8 -= self::mul($s15, 683901, 20); // s2 += s14 * 666643; // s3 += s14 * 470296; // s4 += s14 * 654183; // s5 -= s14 * 997805; // s6 += s14 * 136657; // s7 -= s14 * 683901; $s2 += self::mul($s14, 666643, 20); $s3 += self::mul($s14, 470296, 19); $s4 += self::mul($s14, 654183, 20); $s5 -= self::mul($s14, 997805, 20); $s6 += self::mul($s14, 136657, 18); $s7 -= self::mul($s14, 683901, 20); // s1 += s13 * 666643; // s2 += s13 * 470296; // s3 += s13 * 654183; // s4 -= s13 * 997805; // s5 += s13 * 136657; // s6 -= s13 * 683901; $s1 += self::mul($s13, 666643, 20); $s2 += self::mul($s13, 470296, 19); $s3 += self::mul($s13, 654183, 20); $s4 -= self::mul($s13, 997805, 20); $s5 += self::mul($s13, 136657, 18); $s6 -= self::mul($s13, 683901, 20); // s0 += s12 * 666643; // s1 += s12 * 470296; // s2 += s12 * 654183; // s3 -= s12 * 997805; // s4 += s12 * 136657; // s5 -= s12 * 683901; // s12 = 0; $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); $s12 = 0; // carry0 = (s0 + (int64_t) (1L << 20)) >> 21; // s1 += carry0; // s0 -= carry0 * ((uint64_t) 1L << 21); $carry0 = ($s0 + (1 << 20)) >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; // carry2 = (s2 + (int64_t) (1L << 20)) >> 21; // s3 += carry2; // s2 -= carry2 * ((uint64_t) 1L << 21); $carry2 = ($s2 + (1 << 20)) >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; // carry4 = (s4 + (int64_t) (1L << 20)) >> 21; // s5 += carry4; // s4 -= carry4 * ((uint64_t) 1L << 21); $carry4 = ($s4 + (1 << 20)) >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; // carry6 = (s6 + (int64_t) (1L << 20)) >> 21; // s7 += carry6; // s6 -= carry6 * ((uint64_t) 1L << 21); $carry6 = ($s6 + (1 << 20)) >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; // carry8 = (s8 + (int64_t) (1L << 20)) >> 21; // s9 += carry8; // s8 -= carry8 * ((uint64_t) 1L << 21); $carry8 = ($s8 + (1 << 20)) >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; // carry10 = (s10 + (int64_t) (1L << 20)) >> 21; // s11 += carry10; // s10 -= carry10 * ((uint64_t) 1L << 21); $carry10 = ($s10 + (1 << 20)) >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; // carry1 = (s1 + (int64_t) (1L << 20)) >> 21; // s2 += carry1; // s1 -= carry1 * ((uint64_t) 1L << 21); $carry1 = ($s1 + (1 << 20)) >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; // carry3 = (s3 + (int64_t) (1L << 20)) >> 21; // s4 += carry3; // s3 -= carry3 * ((uint64_t) 1L << 21); $carry3 = ($s3 + (1 << 20)) >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; // carry5 = (s5 + (int64_t) (1L << 20)) >> 21; // s6 += carry5; // s5 -= carry5 * ((uint64_t) 1L << 21); $carry5 = ($s5 + (1 << 20)) >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; // carry7 = (s7 + (int64_t) (1L << 20)) >> 21; // s8 += carry7; // s7 -= carry7 * ((uint64_t) 1L << 21); $carry7 = ($s7 + (1 << 20)) >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; // carry9 = (s9 + (int64_t) (1L << 20)) >> 21; // s10 += carry9; // s9 -= carry9 * ((uint64_t) 1L << 21); $carry9 = ($s9 + (1 << 20)) >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; // carry11 = (s11 + (int64_t) (1L << 20)) >> 21; // s12 += carry11; // s11 -= carry11 * ((uint64_t) 1L << 21); $carry11 = ($s11 + (1 << 20)) >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; // s0 += s12 * 666643; // s1 += s12 * 470296; // s2 += s12 * 654183; // s3 -= s12 * 997805; // s4 += s12 * 136657; // s5 -= s12 * 683901; // s12 = 0; $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); $s12 = 0; // carry0 = s0 >> 21; // s1 += carry0; // s0 -= carry0 * ((uint64_t) 1L << 21); $carry0 = $s0 >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; // carry1 = s1 >> 21; // s2 += carry1; // s1 -= carry1 * ((uint64_t) 1L << 21); $carry1 = $s1 >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; // carry2 = s2 >> 21; // s3 += carry2; // s2 -= carry2 * ((uint64_t) 1L << 21); $carry2 = $s2 >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; // carry3 = s3 >> 21; // s4 += carry3; // s3 -= carry3 * ((uint64_t) 1L << 21); $carry3 = $s3 >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; // carry4 = s4 >> 21; // s5 += carry4; // s4 -= carry4 * ((uint64_t) 1L << 21); $carry4 = $s4 >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; // carry5 = s5 >> 21; // s6 += carry5; // s5 -= carry5 * ((uint64_t) 1L << 21); $carry5 = $s5 >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; // carry6 = s6 >> 21; // s7 += carry6; // s6 -= carry6 * ((uint64_t) 1L << 21); $carry6 = $s6 >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; // carry7 = s7 >> 21; // s8 += carry7; // s7 -= carry7 * ((uint64_t) 1L << 21); $carry7 = $s7 >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; // carry8 = s8 >> 21; // s9 += carry8; // s8 -= carry8 * ((uint64_t) 1L << 21); $carry8 = $s8 >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; // carry9 = s9 >> 21; // s10 += carry9; // s9 -= carry9 * ((uint64_t) 1L << 21); $carry9 = $s9 >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; // carry10 = s10 >> 21; // s11 += carry10; // s10 -= carry10 * ((uint64_t) 1L << 21); $carry10 = $s10 >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; // carry11 = s11 >> 21; // s12 += carry11; // s11 -= carry11 * ((uint64_t) 1L << 21); $carry11 = $s11 >> 21; $s12 += $carry11; $s11 -= $carry11 << 21; // s0 += s12 * 666643; // s1 += s12 * 470296; // s2 += s12 * 654183; // s3 -= s12 * 997805; // s4 += s12 * 136657; // s5 -= s12 * 683901; $s0 += self::mul($s12, 666643, 20); $s1 += self::mul($s12, 470296, 19); $s2 += self::mul($s12, 654183, 20); $s3 -= self::mul($s12, 997805, 20); $s4 += self::mul($s12, 136657, 18); $s5 -= self::mul($s12, 683901, 20); // carry0 = s0 >> 21; // s1 += carry0; // s0 -= carry0 * ((uint64_t) 1L << 21); $carry0 = $s0 >> 21; $s1 += $carry0; $s0 -= $carry0 << 21; // carry1 = s1 >> 21; // s2 += carry1; // s1 -= carry1 * ((uint64_t) 1L << 21); $carry1 = $s1 >> 21; $s2 += $carry1; $s1 -= $carry1 << 21; // carry2 = s2 >> 21; // s3 += carry2; // s2 -= carry2 * ((uint64_t) 1L << 21); $carry2 = $s2 >> 21; $s3 += $carry2; $s2 -= $carry2 << 21; // carry3 = s3 >> 21; // s4 += carry3; // s3 -= carry3 * ((uint64_t) 1L << 21); $carry3 = $s3 >> 21; $s4 += $carry3; $s3 -= $carry3 << 21; // carry4 = s4 >> 21; // s5 += carry4; // s4 -= carry4 * ((uint64_t) 1L << 21); $carry4 = $s4 >> 21; $s5 += $carry4; $s4 -= $carry4 << 21; // carry5 = s5 >> 21; // s6 += carry5; // s5 -= carry5 * ((uint64_t) 1L << 21); $carry5 = $s5 >> 21; $s6 += $carry5; $s5 -= $carry5 << 21; // carry6 = s6 >> 21; // s7 += carry6; // s6 -= carry6 * ((uint64_t) 1L << 21); $carry6 = $s6 >> 21; $s7 += $carry6; $s6 -= $carry6 << 21; // carry7 = s7 >> 21; // s8 += carry7; // s7 -= carry7 * ((uint64_t) 1L << 21); $carry7 = $s7 >> 21; $s8 += $carry7; $s7 -= $carry7 << 21; // carry8 = s8 >> 21; // s9 += carry8; // s8 -= carry8 * ((uint64_t) 1L << 21); $carry8 = $s8 >> 21; $s9 += $carry8; $s8 -= $carry8 << 21; // carry9 = s9 >> 21; // s10 += carry9; // s9 -= carry9 * ((uint64_t) 1L << 21); $carry9 = $s9 >> 21; $s10 += $carry9; $s9 -= $carry9 << 21; // carry10 = s10 >> 21; // s11 += carry10; // s10 -= carry10 * ((uint64_t) 1L << 21); $carry10 = $s10 >> 21; $s11 += $carry10; $s10 -= $carry10 << 21; $s = array_fill(0, 32, 0); // s[0] = s0 >> 0; $s[0] = $s0 >> 0; // s[1] = s0 >> 8; $s[1] = $s0 >> 8; // s[2] = (s0 >> 16) | (s1 * ((uint64_t) 1 << 5)); $s[2] = ($s0 >> 16) | ($s1 << 5); // s[3] = s1 >> 3; $s[3] = $s1 >> 3; // s[4] = s1 >> 11; $s[4] = $s1 >> 11; // s[5] = (s1 >> 19) | (s2 * ((uint64_t) 1 << 2)); $s[5] = ($s1 >> 19) | ($s2 << 2); // s[6] = s2 >> 6; $s[6] = $s2 >> 6; // s[7] = (s2 >> 14) | (s3 * ((uint64_t) 1 << 7)); $s[7] = ($s2 >> 14) | ($s3 << 7); // s[8] = s3 >> 1; $s[8] = $s3 >> 1; // s[9] = s3 >> 9; $s[9] = $s3 >> 9; // s[10] = (s3 >> 17) | (s4 * ((uint64_t) 1 << 4)); $s[10] = ($s3 >> 17) | ($s4 << 4); // s[11] = s4 >> 4; $s[11] = $s4 >> 4; // s[12] = s4 >> 12; $s[12] = $s4 >> 12; // s[13] = (s4 >> 20) | (s5 * ((uint64_t) 1 << 1)); $s[13] = ($s4 >> 20) | ($s5 << 1); // s[14] = s5 >> 7; $s[14] = $s5 >> 7; // s[15] = (s5 >> 15) | (s6 * ((uint64_t) 1 << 6)); $s[15] = ($s5 >> 15) | ($s6 << 6); // s[16] = s6 >> 2; $s[16] = $s6 >> 2; // s[17] = s6 >> 10; $s[17] = $s6 >> 10; // s[18] = (s6 >> 18) | (s7 * ((uint64_t) 1 << 3)); $s[18] = ($s6 >> 18) | ($s7 << 3); // s[19] = s7 >> 5; $s[19] = $s7 >> 5; // s[20] = s7 >> 13; $s[20] = $s7 >> 13; // s[21] = s8 >> 0; $s[21] = $s8 >> 0; // s[22] = s8 >> 8; $s[22] = $s8 >> 8; // s[23] = (s8 >> 16) | (s9 * ((uint64_t) 1 << 5)); $s[23] = ($s8 >> 16) | ($s9 << 5); // s[24] = s9 >> 3; $s[24] = $s9 >> 3; // s[25] = s9 >> 11; $s[25] = $s9 >> 11; // s[26] = (s9 >> 19) | (s10 * ((uint64_t) 1 << 2)); $s[26] = ($s9 >> 19) | ($s10 << 2); // s[27] = s10 >> 6; $s[27] = $s10 >> 6; // s[28] = (s10 >> 14) | (s11 * ((uint64_t) 1 << 7)); $s[28] = ($s10 >> 14) | ($s11 << 7); // s[29] = s11 >> 1; $s[29] = $s11 >> 1; // s[30] = s11 >> 9; $s[30] = $s11 >> 9; // s[31] = s11 >> 17; $s[31] = $s11 >> 17; return self::intArrayToString($s); } /** * @param string $s * @return string */ public static function sc25519_sq($s) { return self::sc25519_mul($s, $s); } /** * @param string $s * @param int $n * @param string $a * @return string */ public static function sc25519_sqmul($s, $n, $a) { for ($i = 0; $i < $n; ++$i) { $s = self::sc25519_sq($s); } return self::sc25519_mul($s, $a); } /** * @param string $s * @return string */ public static function sc25519_invert($s) { $_10 = self::sc25519_sq($s); $_11 = self::sc25519_mul($s, $_10); $_100 = self::sc25519_mul($s, $_11); $_1000 = self::sc25519_sq($_100); $_1010 = self::sc25519_mul($_10, $_1000); $_1011 = self::sc25519_mul($s, $_1010); $_10000 = self::sc25519_sq($_1000); $_10110 = self::sc25519_sq($_1011); $_100000 = self::sc25519_mul($_1010, $_10110); $_100110 = self::sc25519_mul($_10000, $_10110); $_1000000 = self::sc25519_sq($_100000); $_1010000 = self::sc25519_mul($_10000, $_1000000); $_1010011 = self::sc25519_mul($_11, $_1010000); $_1100011 = self::sc25519_mul($_10000, $_1010011); $_1100111 = self::sc25519_mul($_100, $_1100011); $_1101011 = self::sc25519_mul($_100, $_1100111); $_10010011 = self::sc25519_mul($_1000000, $_1010011); $_10010111 = self::sc25519_mul($_100, $_10010011); $_10111101 = self::sc25519_mul($_100110, $_10010111); $_11010011 = self::sc25519_mul($_10110, $_10111101); $_11100111 = self::sc25519_mul($_1010000, $_10010111); $_11101011 = self::sc25519_mul($_100, $_11100111); $_11110101 = self::sc25519_mul($_1010, $_11101011); $recip = self::sc25519_mul($_1011, $_11110101); $recip = self::sc25519_sqmul($recip, 126, $_1010011); $recip = self::sc25519_sqmul($recip, 9, $_10); $recip = self::sc25519_mul($recip, $_11110101); $recip = self::sc25519_sqmul($recip, 7, $_1100111); $recip = self::sc25519_sqmul($recip, 9, $_11110101); $recip = self::sc25519_sqmul($recip, 11, $_10111101); $recip = self::sc25519_sqmul($recip, 8, $_11100111); $recip = self::sc25519_sqmul($recip, 9, $_1101011); $recip = self::sc25519_sqmul($recip, 6, $_1011); $recip = self::sc25519_sqmul($recip, 14, $_10010011); $recip = self::sc25519_sqmul($recip, 10, $_1100011); $recip = self::sc25519_sqmul($recip, 9, $_10010111); $recip = self::sc25519_sqmul($recip, 10, $_11110101); $recip = self::sc25519_sqmul($recip, 8, $_11010011); return self::sc25519_sqmul($recip, 8, $_11101011); } /** * @param string $s * @return string */ public static function clamp($s) { $s_ = self::stringToIntArray($s); $s_[0] &= 248; $s_[31] |= 64; $s_[31] &= 128; return self::intArrayToString($s_); } /** * Ensure limbs are less than 28 bits long to prevent float promotion. * * This uses a constant-time conditional swap under the hood. * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_normalize(ParagonIE_Sodium_Core_Curve25519_Fe $f) { $x = (PHP_INT_SIZE << 3) - 1; // 31 or 63 $g = self::fe_copy($f); for ($i = 0; $i < 10; ++$i) { $mask = -(($g[$i] >> $x) & 1); /* * Get two candidate normalized values for $g[$i], depending on the sign of $g[$i]: */ $a = $g[$i] & 0x7ffffff; $b = -((-$g[$i]) & 0x7ffffff); /* * Return the appropriate candidate value, based on the sign of the original input: * * The following is equivalent to this ternary: * * $g[$i] = (($g[$i] >> $x) & 1) ? $a : $b; * * Except what's written doesn't contain timing leaks. */ $g[$i] = ($a ^ (($a ^ $b) & $mask)); } return $g; } } XChaCha20.php 0000644 00000006452 14721742304 0006671 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_XChaCha20', false)) { return; } /** * Class ParagonIE_Sodium_Core_XChaCha20 */ class ParagonIE_Sodium_Core_XChaCha20 extends ParagonIE_Sodium_Core_HChaCha20 { /** * @internal You should not use this directly from another application * * @param int $len * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function stream($len = 64, $nonce = '', $key = '') { if (self::strlen($nonce) !== 24) { throw new SodiumException('Nonce must be 24 bytes long'); } return self::encryptBytes( new ParagonIE_Sodium_Core_ChaCha20_Ctx( self::hChaCha20( self::substr($nonce, 0, 16), $key ), self::substr($nonce, 16, 8) ), str_repeat("\x00", $len) ); } /** * @internal You should not use this directly from another application * * @param int $len * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function ietfStream($len = 64, $nonce = '', $key = '') { if (self::strlen($nonce) !== 24) { throw new SodiumException('Nonce must be 24 bytes long'); } return self::encryptBytes( new ParagonIE_Sodium_Core_ChaCha20_IetfCtx( self::hChaCha20( self::substr($nonce, 0, 16), $key ), "\x00\x00\x00\x00" . self::substr($nonce, 16, 8) ), str_repeat("\x00", $len) ); } /** * @internal You should not use this directly from another application * * @param string $message * @param string $nonce * @param string $key * @param string $ic * @return string * @throws SodiumException * @throws TypeError */ public static function streamXorIc($message, $nonce = '', $key = '', $ic = '') { if (self::strlen($nonce) !== 24) { throw new SodiumException('Nonce must be 24 bytes long'); } return self::encryptBytes( new ParagonIE_Sodium_Core_ChaCha20_Ctx( self::hChaCha20(self::substr($nonce, 0, 16), $key), self::substr($nonce, 16, 8), $ic ), $message ); } /** * @internal You should not use this directly from another application * * @param string $message * @param string $nonce * @param string $key * @param string $ic * @return string * @throws SodiumException * @throws TypeError */ public static function ietfStreamXorIc($message, $nonce = '', $key = '', $ic = '') { if (self::strlen($nonce) !== 24) { throw new SodiumException('Nonce must be 24 bytes long'); } return self::encryptBytes( new ParagonIE_Sodium_Core_ChaCha20_IetfCtx( self::hChaCha20(self::substr($nonce, 0, 16), $key), "\x00\x00\x00\x00" . self::substr($nonce, 16, 8), $ic ), $message ); } } AEGIS256.php 0000644 00000007016 14721742304 0006352 0 ustar 00 <?php if (!defined('SODIUM_COMPAT_AEGIS_C0')) { define('SODIUM_COMPAT_AEGIS_C0', "\x00\x01\x01\x02\x03\x05\x08\x0d\x15\x22\x37\x59\x90\xe9\x79\x62"); } if (!defined('SODIUM_COMPAT_AEGIS_C1')) { define('SODIUM_COMPAT_AEGIS_C1', "\xdb\x3d\x18\x55\x6d\xc2\x2f\xf1\x20\x11\x31\x42\x73\xb5\x28\xdd"); } class ParagonIE_Sodium_Core_AEGIS256 extends ParagonIE_Sodium_Core_AES { /** * @param string $ct * @param string $tag * @param string $ad * @param string $key * @param string $nonce * @return string * @throws SodiumException */ public static function decrypt($ct, $tag, $ad, $key, $nonce) { $state = self::init($key, $nonce); // ad_blocks = Split(ZeroPad(ad, 128), 128) $ad_blocks = (self::strlen($ad) + 15) >> 4; // for ai in ad_blocks: // Absorb(ai) for ($i = 0; $i < $ad_blocks; ++$i) { $ai = self::substr($ad, $i << 4, 16); if (self::strlen($ai) < 16) { $ai = str_pad($ai, 16, "\0", STR_PAD_RIGHT); } $state->absorb($ai); } $msg = ''; $cn = self::strlen($ct) & 15; $ct_blocks = self::strlen($ct) >> 4; // ct_blocks = Split(ZeroPad(ct, 128), 128) // cn = Tail(ct, |ct| mod 128) for ($i = 0; $i < $ct_blocks; ++$i) { $msg .= $state->dec(self::substr($ct, $i << 4, 16)); } // if cn is not empty: // msg = msg || DecPartial(cn) if ($cn) { $start = $ct_blocks << 4; $msg .= $state->decPartial(self::substr($ct, $start, $cn)); } $expected_tag = $state->finalize( self::strlen($ad) << 3, self::strlen($msg) << 3 ); if (!self::hashEquals($expected_tag, $tag)) { try { // The RFC says to erase msg, so we shall try: ParagonIE_Sodium_Compat::memzero($msg); } catch (SodiumException $ex) { // Do nothing if we cannot memzero } throw new SodiumException('verification failed'); } return $msg; } /** * @param string $msg * @param string $ad * @param string $key * @param string $nonce * @return array * @throws SodiumException */ public static function encrypt($msg, $ad, $key, $nonce) { $state = self::init($key, $nonce); $ad_len = self::strlen($ad); $msg_len = self::strlen($msg); $ad_blocks = ($ad_len + 15) >> 4; for ($i = 0; $i < $ad_blocks; ++$i) { $ai = self::substr($ad, $i << 4, 16); if (self::strlen($ai) < 16) { $ai = str_pad($ai, 16, "\0", STR_PAD_RIGHT); } $state->absorb($ai); } $ct = ''; $msg_blocks = ($msg_len + 15) >> 4; for ($i = 0; $i < $msg_blocks; ++$i) { $xi = self::substr($msg, $i << 4, 16); if (self::strlen($xi) < 16) { $xi = str_pad($xi, 16, "\0", STR_PAD_RIGHT); } $ct .= $state->enc($xi); } $tag = $state->finalize( $ad_len << 3, $msg_len << 3 ); return array( self::substr($ct, 0, $msg_len), $tag ); } /** * @param string $key * @param string $nonce * @return ParagonIE_Sodium_Core_AEGIS_State256 */ public static function init($key, $nonce) { return ParagonIE_Sodium_Core_AEGIS_State256::init($key, $nonce); } } Ed25519.php 0000644 00000042114 14721742304 0006221 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Ed25519', false)) { return; } if (!class_exists('ParagonIE_Sodium_Core_Curve25519', false)) { require_once dirname(__FILE__) . '/Curve25519.php'; } /** * Class ParagonIE_Sodium_Core_Ed25519 */ abstract class ParagonIE_Sodium_Core_Ed25519 extends ParagonIE_Sodium_Core_Curve25519 { const KEYPAIR_BYTES = 96; const SEED_BYTES = 32; const SCALAR_BYTES = 32; /** * @internal You should not use this directly from another application * * @return string (96 bytes) * @throws Exception * @throws SodiumException * @throws TypeError */ public static function keypair() { $seed = random_bytes(self::SEED_BYTES); $pk = ''; $sk = ''; self::seed_keypair($pk, $sk, $seed); return $sk . $pk; } /** * @internal You should not use this directly from another application * * @param string $pk * @param string $sk * @param string $seed * @return string * @throws SodiumException * @throws TypeError */ public static function seed_keypair(&$pk, &$sk, $seed) { if (self::strlen($seed) !== self::SEED_BYTES) { throw new RangeException('crypto_sign keypair seed must be 32 bytes long'); } /** @var string $pk */ $pk = self::publickey_from_secretkey($seed); $sk = $seed . $pk; return $sk; } /** * @internal You should not use this directly from another application * * @param string $keypair * @return string * @throws TypeError */ public static function secretkey($keypair) { if (self::strlen($keypair) !== self::KEYPAIR_BYTES) { throw new RangeException('crypto_sign keypair must be 96 bytes long'); } return self::substr($keypair, 0, 64); } /** * @internal You should not use this directly from another application * * @param string $keypair * @return string * @throws TypeError */ public static function publickey($keypair) { if (self::strlen($keypair) !== self::KEYPAIR_BYTES) { throw new RangeException('crypto_sign keypair must be 96 bytes long'); } return self::substr($keypair, 64, 32); } /** * @internal You should not use this directly from another application * * @param string $sk * @return string * @throws SodiumException * @throws TypeError */ public static function publickey_from_secretkey($sk) { /** @var string $sk */ $sk = hash('sha512', self::substr($sk, 0, 32), true); $sk[0] = self::intToChr( self::chrToInt($sk[0]) & 248 ); $sk[31] = self::intToChr( (self::chrToInt($sk[31]) & 63) | 64 ); return self::sk_to_pk($sk); } /** * @param string $pk * @return string * @throws SodiumException * @throws TypeError */ public static function pk_to_curve25519($pk) { if (self::small_order($pk)) { throw new SodiumException('Public key is on a small order'); } $A = self::ge_frombytes_negate_vartime(self::substr($pk, 0, 32)); $p1 = self::ge_mul_l($A); if (!self::fe_isnonzero($p1->X)) { throw new SodiumException('Unexpected zero result'); } # fe_1(one_minus_y); # fe_sub(one_minus_y, one_minus_y, A.Y); # fe_invert(one_minus_y, one_minus_y); $one_minux_y = self::fe_invert( self::fe_sub( self::fe_1(), $A->Y ) ); # fe_1(x); # fe_add(x, x, A.Y); # fe_mul(x, x, one_minus_y); $x = self::fe_mul( self::fe_add(self::fe_1(), $A->Y), $one_minux_y ); # fe_tobytes(curve25519_pk, x); return self::fe_tobytes($x); } /** * @internal You should not use this directly from another application * * @param string $sk * @return string * @throws SodiumException * @throws TypeError */ public static function sk_to_pk($sk) { return self::ge_p3_tobytes( self::ge_scalarmult_base( self::substr($sk, 0, 32) ) ); } /** * @internal You should not use this directly from another application * * @param string $message * @param string $sk * @return string * @throws SodiumException * @throws TypeError */ public static function sign($message, $sk) { /** @var string $signature */ $signature = self::sign_detached($message, $sk); return $signature . $message; } /** * @internal You should not use this directly from another application * * @param string $message A signed message * @param string $pk Public key * @return string Message (without signature) * @throws SodiumException * @throws TypeError */ public static function sign_open($message, $pk) { /** @var string $signature */ $signature = self::substr($message, 0, 64); /** @var string $message */ $message = self::substr($message, 64); if (self::verify_detached($signature, $message, $pk)) { return $message; } throw new SodiumException('Invalid signature'); } /** * @internal You should not use this directly from another application * * @param string $message * @param string $sk * @return string * @throws SodiumException * @throws TypeError */ public static function sign_detached($message, $sk) { # crypto_hash_sha512(az, sk, 32); $az = hash('sha512', self::substr($sk, 0, 32), true); # az[0] &= 248; # az[31] &= 63; # az[31] |= 64; $az[0] = self::intToChr(self::chrToInt($az[0]) & 248); $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64); # crypto_hash_sha512_init(&hs); # crypto_hash_sha512_update(&hs, az + 32, 32); # crypto_hash_sha512_update(&hs, m, mlen); # crypto_hash_sha512_final(&hs, nonce); $hs = hash_init('sha512'); hash_update($hs, self::substr($az, 32, 32)); hash_update($hs, $message); $nonceHash = hash_final($hs, true); # memmove(sig + 32, sk + 32, 32); $pk = self::substr($sk, 32, 32); # sc_reduce(nonce); # ge_scalarmult_base(&R, nonce); # ge_p3_tobytes(sig, &R); $nonce = self::sc_reduce($nonceHash) . self::substr($nonceHash, 32); $sig = self::ge_p3_tobytes( self::ge_scalarmult_base($nonce) ); # crypto_hash_sha512_init(&hs); # crypto_hash_sha512_update(&hs, sig, 64); # crypto_hash_sha512_update(&hs, m, mlen); # crypto_hash_sha512_final(&hs, hram); $hs = hash_init('sha512'); hash_update($hs, self::substr($sig, 0, 32)); hash_update($hs, self::substr($pk, 0, 32)); hash_update($hs, $message); $hramHash = hash_final($hs, true); # sc_reduce(hram); # sc_muladd(sig + 32, hram, az, nonce); $hram = self::sc_reduce($hramHash); $sigAfter = self::sc_muladd($hram, $az, $nonce); $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32); try { ParagonIE_Sodium_Compat::memzero($az); } catch (SodiumException $ex) { $az = null; } return $sig; } /** * @internal You should not use this directly from another application * * @param string $sig * @param string $message * @param string $pk * @return bool * @throws SodiumException * @throws TypeError */ public static function verify_detached($sig, $message, $pk) { if (self::strlen($sig) < 64) { throw new SodiumException('Signature is too short'); } if ((self::chrToInt($sig[63]) & 240) && self::check_S_lt_L(self::substr($sig, 32, 32))) { throw new SodiumException('S < L - Invalid signature'); } if (self::small_order($sig)) { throw new SodiumException('Signature is on too small of an order'); } if ((self::chrToInt($sig[63]) & 224) !== 0) { throw new SodiumException('Invalid signature'); } $d = 0; for ($i = 0; $i < 32; ++$i) { $d |= self::chrToInt($pk[$i]); } if ($d === 0) { throw new SodiumException('All zero public key'); } /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */ $orig = ParagonIE_Sodium_Compat::$fastMult; // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification. ParagonIE_Sodium_Compat::$fastMult = true; /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */ $A = self::ge_frombytes_negate_vartime($pk); /** @var string $hDigest */ $hDigest = hash( 'sha512', self::substr($sig, 0, 32) . self::substr($pk, 0, 32) . $message, true ); /** @var string $h */ $h = self::sc_reduce($hDigest) . self::substr($hDigest, 32); /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */ $R = self::ge_double_scalarmult_vartime( $h, $A, self::substr($sig, 32) ); /** @var string $rcheck */ $rcheck = self::ge_tobytes($R); // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before. ParagonIE_Sodium_Compat::$fastMult = $orig; return self::verify_32($rcheck, self::substr($sig, 0, 32)); } /** * @internal You should not use this directly from another application * * @param string $S * @return bool * @throws SodiumException * @throws TypeError */ public static function check_S_lt_L($S) { if (self::strlen($S) < 32) { throw new SodiumException('Signature must be 32 bytes'); } $L = array( 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 ); $c = 0; $n = 1; $i = 32; /** @var array<int, int> $L */ do { --$i; $x = self::chrToInt($S[$i]); $c |= ( (($x - $L[$i]) >> 8) & $n ); $n &= ( (($x ^ $L[$i]) - 1) >> 8 ); } while ($i !== 0); return $c === 0; } /** * @param string $R * @return bool * @throws SodiumException * @throws TypeError */ public static function small_order($R) { /** @var array<int, array<int, int>> $blocklist */ $blocklist = array( /* 0 (order 4) */ array( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ), /* 1 (order 1) */ array( 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ), /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */ array( 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05 ), /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */ array( 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a ), /* p-1 (order 2) */ array( 0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85 ), /* p (order 4) */ array( 0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa ), /* p+1 (order 1) */ array( 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f ), /* p+2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */ array( 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f ), /* p+55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */ array( 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f ), /* 2p-1 (order 2) */ array( 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ), /* 2p (order 4) */ array( 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ), /* 2p+1 (order 1) */ array( 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ) ); /** @var int $countBlocklist */ $countBlocklist = count($blocklist); for ($i = 0; $i < $countBlocklist; ++$i) { $c = 0; for ($j = 0; $j < 32; ++$j) { $c |= self::chrToInt($R[$j]) ^ (int) $blocklist[$i][$j]; } if ($c === 0) { return true; } } return false; } /** * @param string $s * @return string * @throws SodiumException */ public static function scalar_complement($s) { $t_ = self::L . str_repeat("\x00", 32); sodium_increment($t_); $s_ = $s . str_repeat("\x00", 32); ParagonIE_Sodium_Compat::sub($t_, $s_); return self::sc_reduce($t_); } /** * @return string * @throws SodiumException */ public static function scalar_random() { do { $r = ParagonIE_Sodium_Compat::randombytes_buf(self::SCALAR_BYTES); $r[self::SCALAR_BYTES - 1] = self::intToChr( self::chrToInt($r[self::SCALAR_BYTES - 1]) & 0x1f ); } while ( !self::check_S_lt_L($r) || ParagonIE_Sodium_Compat::is_zero($r) ); return $r; } /** * @param string $s * @return string * @throws SodiumException */ public static function scalar_negate($s) { $t_ = self::L . str_repeat("\x00", 32) ; $s_ = $s . str_repeat("\x00", 32) ; ParagonIE_Sodium_Compat::sub($t_, $s_); return self::sc_reduce($t_); } /** * @param string $a * @param string $b * @return string * @throws SodiumException */ public static function scalar_add($a, $b) { $a_ = $a . str_repeat("\x00", 32); $b_ = $b . str_repeat("\x00", 32); ParagonIE_Sodium_Compat::add($a_, $b_); return self::sc_reduce($a_); } /** * @param string $x * @param string $y * @return string * @throws SodiumException */ public static function scalar_sub($x, $y) { $yn = self::scalar_negate($y); return self::scalar_add($x, $yn); } } Poly1305/State.php 0000644 00000031160 14721742304 0007576 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_Poly1305_State', false)) { return; } /** * Class ParagonIE_Sodium_Core_Poly1305_State */ class ParagonIE_Sodium_Core_Poly1305_State extends ParagonIE_Sodium_Core_Util { /** * @var array<int, int> */ protected $buffer = array(); /** * @var bool */ protected $final = false; /** * @var array<int, int> */ public $h; /** * @var int */ protected $leftover = 0; /** * @var int[] */ public $r; /** * @var int[] */ public $pad; /** * ParagonIE_Sodium_Core_Poly1305_State constructor. * * @internal You should not use this directly from another application * * @param string $key * @throws InvalidArgumentException * @throws TypeError */ public function __construct($key = '') { if (self::strlen($key) < 32) { throw new InvalidArgumentException( 'Poly1305 requires a 32-byte key' ); } /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */ $this->r = array( (int) ((self::load_4(self::substr($key, 0, 4))) & 0x3ffffff), (int) ((self::load_4(self::substr($key, 3, 4)) >> 2) & 0x3ffff03), (int) ((self::load_4(self::substr($key, 6, 4)) >> 4) & 0x3ffc0ff), (int) ((self::load_4(self::substr($key, 9, 4)) >> 6) & 0x3f03fff), (int) ((self::load_4(self::substr($key, 12, 4)) >> 8) & 0x00fffff) ); /* h = 0 */ $this->h = array(0, 0, 0, 0, 0); /* save pad for later */ $this->pad = array( self::load_4(self::substr($key, 16, 4)), self::load_4(self::substr($key, 20, 4)), self::load_4(self::substr($key, 24, 4)), self::load_4(self::substr($key, 28, 4)), ); $this->leftover = 0; $this->final = false; } /** * Zero internal buffer upon destruction */ public function __destruct() { $this->r[0] ^= $this->r[0]; $this->r[1] ^= $this->r[1]; $this->r[2] ^= $this->r[2]; $this->r[3] ^= $this->r[3]; $this->r[4] ^= $this->r[4]; $this->h[0] ^= $this->h[0]; $this->h[1] ^= $this->h[1]; $this->h[2] ^= $this->h[2]; $this->h[3] ^= $this->h[3]; $this->h[4] ^= $this->h[4]; $this->pad[0] ^= $this->pad[0]; $this->pad[1] ^= $this->pad[1]; $this->pad[2] ^= $this->pad[2]; $this->pad[3] ^= $this->pad[3]; $this->leftover = 0; $this->final = true; } /** * @internal You should not use this directly from another application * * @param string $message * @return self * @throws SodiumException * @throws TypeError */ public function update($message = '') { $bytes = self::strlen($message); if ($bytes < 1) { return $this; } /* handle leftover */ if ($this->leftover) { $want = ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE - $this->leftover; if ($want > $bytes) { $want = $bytes; } for ($i = 0; $i < $want; ++$i) { $mi = self::chrToInt($message[$i]); $this->buffer[$this->leftover + $i] = $mi; } // We snip off the leftmost bytes. $message = self::substr($message, $want); $bytes = self::strlen($message); $this->leftover += $want; if ($this->leftover < ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) { // We still don't have enough to run $this->blocks() return $this; } $this->blocks( self::intArrayToString($this->buffer), ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE ); $this->leftover = 0; } /* process full blocks */ if ($bytes >= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) { /** @var int $want */ $want = $bytes & ~(ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE - 1); if ($want >= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) { $block = self::substr($message, 0, $want); if (self::strlen($block) >= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) { $this->blocks($block, $want); $message = self::substr($message, $want); $bytes = self::strlen($message); } } } /* store leftover */ if ($bytes) { for ($i = 0; $i < $bytes; ++$i) { $mi = self::chrToInt($message[$i]); $this->buffer[$this->leftover + $i] = $mi; } $this->leftover = (int) $this->leftover + $bytes; } return $this; } /** * @internal You should not use this directly from another application * * @param string $message * @param int $bytes * @return self * @throws TypeError */ public function blocks($message, $bytes) { if (self::strlen($message) < 16) { $message = str_pad($message, 16, "\x00", STR_PAD_RIGHT); } /** @var int $hibit */ $hibit = $this->final ? 0 : 1 << 24; /* 1 << 128 */ $r0 = (int) $this->r[0]; $r1 = (int) $this->r[1]; $r2 = (int) $this->r[2]; $r3 = (int) $this->r[3]; $r4 = (int) $this->r[4]; $s1 = self::mul($r1, 5, 3); $s2 = self::mul($r2, 5, 3); $s3 = self::mul($r3, 5, 3); $s4 = self::mul($r4, 5, 3); $h0 = $this->h[0]; $h1 = $this->h[1]; $h2 = $this->h[2]; $h3 = $this->h[3]; $h4 = $this->h[4]; while ($bytes >= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) { /* h += m[i] */ $h0 += self::load_4(self::substr($message, 0, 4)) & 0x3ffffff; $h1 += (self::load_4(self::substr($message, 3, 4)) >> 2) & 0x3ffffff; $h2 += (self::load_4(self::substr($message, 6, 4)) >> 4) & 0x3ffffff; $h3 += (self::load_4(self::substr($message, 9, 4)) >> 6) & 0x3ffffff; $h4 += (self::load_4(self::substr($message, 12, 4)) >> 8) | $hibit; /* h *= r */ $d0 = ( self::mul($h0, $r0, 27) + self::mul($s4, $h1, 27) + self::mul($s3, $h2, 27) + self::mul($s2, $h3, 27) + self::mul($s1, $h4, 27) ); $d1 = ( self::mul($h0, $r1, 27) + self::mul($h1, $r0, 27) + self::mul($s4, $h2, 27) + self::mul($s3, $h3, 27) + self::mul($s2, $h4, 27) ); $d2 = ( self::mul($h0, $r2, 27) + self::mul($h1, $r1, 27) + self::mul($h2, $r0, 27) + self::mul($s4, $h3, 27) + self::mul($s3, $h4, 27) ); $d3 = ( self::mul($h0, $r3, 27) + self::mul($h1, $r2, 27) + self::mul($h2, $r1, 27) + self::mul($h3, $r0, 27) + self::mul($s4, $h4, 27) ); $d4 = ( self::mul($h0, $r4, 27) + self::mul($h1, $r3, 27) + self::mul($h2, $r2, 27) + self::mul($h3, $r1, 27) + self::mul($h4, $r0, 27) ); /* (partial) h %= p */ /** @var int $c */ $c = $d0 >> 26; /** @var int $h0 */ $h0 = $d0 & 0x3ffffff; $d1 += $c; /** @var int $c */ $c = $d1 >> 26; /** @var int $h1 */ $h1 = $d1 & 0x3ffffff; $d2 += $c; /** @var int $c */ $c = $d2 >> 26; /** @var int $h2 */ $h2 = $d2 & 0x3ffffff; $d3 += $c; /** @var int $c */ $c = $d3 >> 26; /** @var int $h3 */ $h3 = $d3 & 0x3ffffff; $d4 += $c; /** @var int $c */ $c = $d4 >> 26; /** @var int $h4 */ $h4 = $d4 & 0x3ffffff; $h0 += (int) self::mul($c, 5, 3); /** @var int $c */ $c = $h0 >> 26; /** @var int $h0 */ $h0 &= 0x3ffffff; $h1 += $c; // Chop off the left 32 bytes. $message = self::substr( $message, ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE ); $bytes -= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE; } $this->h = array( (int) ($h0 & 0xffffffff), (int) ($h1 & 0xffffffff), (int) ($h2 & 0xffffffff), (int) ($h3 & 0xffffffff), (int) ($h4 & 0xffffffff) ); return $this; } /** * @internal You should not use this directly from another application * * @return string * @throws TypeError */ public function finish() { /* process the remaining block */ if ($this->leftover) { $i = $this->leftover; $this->buffer[$i++] = 1; for (; $i < ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE; ++$i) { $this->buffer[$i] = 0; } $this->final = true; $this->blocks( self::substr( self::intArrayToString($this->buffer), 0, ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE ), ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE ); } $h0 = (int) $this->h[0]; $h1 = (int) $this->h[1]; $h2 = (int) $this->h[2]; $h3 = (int) $this->h[3]; $h4 = (int) $this->h[4]; /** @var int $c */ $c = $h1 >> 26; /** @var int $h1 */ $h1 &= 0x3ffffff; /** @var int $h2 */ $h2 += $c; /** @var int $c */ $c = $h2 >> 26; /** @var int $h2 */ $h2 &= 0x3ffffff; $h3 += $c; /** @var int $c */ $c = $h3 >> 26; $h3 &= 0x3ffffff; $h4 += $c; /** @var int $c */ $c = $h4 >> 26; $h4 &= 0x3ffffff; /** @var int $h0 */ $h0 += self::mul($c, 5, 3); /** @var int $c */ $c = $h0 >> 26; /** @var int $h0 */ $h0 &= 0x3ffffff; /** @var int $h1 */ $h1 += $c; /* compute h + -p */ /** @var int $g0 */ $g0 = $h0 + 5; /** @var int $c */ $c = $g0 >> 26; /** @var int $g0 */ $g0 &= 0x3ffffff; /** @var int $g1 */ $g1 = $h1 + $c; /** @var int $c */ $c = $g1 >> 26; $g1 &= 0x3ffffff; /** @var int $g2 */ $g2 = $h2 + $c; /** @var int $c */ $c = $g2 >> 26; /** @var int $g2 */ $g2 &= 0x3ffffff; /** @var int $g3 */ $g3 = $h3 + $c; /** @var int $c */ $c = $g3 >> 26; /** @var int $g3 */ $g3 &= 0x3ffffff; /** @var int $g4 */ $g4 = ($h4 + $c - (1 << 26)) & 0xffffffff; /* select h if h < p, or h + -p if h >= p */ /** @var int $mask */ $mask = ($g4 >> 31) - 1; $g0 &= $mask; $g1 &= $mask; $g2 &= $mask; $g3 &= $mask; $g4 &= $mask; /** @var int $mask */ $mask = ~$mask & 0xffffffff; /** @var int $h0 */ $h0 = ($h0 & $mask) | $g0; /** @var int $h1 */ $h1 = ($h1 & $mask) | $g1; /** @var int $h2 */ $h2 = ($h2 & $mask) | $g2; /** @var int $h3 */ $h3 = ($h3 & $mask) | $g3; /** @var int $h4 */ $h4 = ($h4 & $mask) | $g4; /* h = h % (2^128) */ /** @var int $h0 */ $h0 = (($h0) | ($h1 << 26)) & 0xffffffff; /** @var int $h1 */ $h1 = (($h1 >> 6) | ($h2 << 20)) & 0xffffffff; /** @var int $h2 */ $h2 = (($h2 >> 12) | ($h3 << 14)) & 0xffffffff; /** @var int $h3 */ $h3 = (($h3 >> 18) | ($h4 << 8)) & 0xffffffff; /* mac = (h + pad) % (2^128) */ $f = (int) ($h0 + $this->pad[0]); $h0 = (int) $f; $f = (int) ($h1 + $this->pad[1] + ($f >> 32)); $h1 = (int) $f; $f = (int) ($h2 + $this->pad[2] + ($f >> 32)); $h2 = (int) $f; $f = (int) ($h3 + $this->pad[3] + ($f >> 32)); $h3 = (int) $f; return self::store32_le($h0 & 0xffffffff) . self::store32_le($h1 & 0xffffffff) . self::store32_le($h2 & 0xffffffff) . self::store32_le($h3 & 0xffffffff); } } XSalsa20.php 0000644 00000002533 14721742304 0006621 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_XSalsa20', false)) { return; } /** * Class ParagonIE_Sodium_Core_XSalsa20 */ abstract class ParagonIE_Sodium_Core_XSalsa20 extends ParagonIE_Sodium_Core_HSalsa20 { /** * Expand a key and nonce into an xsalsa20 keystream. * * @internal You should not use this directly from another application * * @param int $len * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function xsalsa20($len, $nonce, $key) { $ret = self::salsa20( $len, self::substr($nonce, 16, 8), self::hsalsa20($nonce, $key) ); return $ret; } /** * Encrypt a string with XSalsa20. Doesn't provide integrity. * * @internal You should not use this directly from another application * * @param string $message * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function xsalsa20_xor($message, $nonce, $key) { return self::xorStrings( $message, self::xsalsa20( self::strlen($message), $nonce, $key ) ); } } SipHash.php 0000644 00000020051 14721742304 0006616 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_SipHash', false)) { return; } /** * Class ParagonIE_SodiumCompat_Core_SipHash * * Only uses 32-bit arithmetic, while the original SipHash used 64-bit integers */ class ParagonIE_Sodium_Core_SipHash extends ParagonIE_Sodium_Core_Util { /** * @internal You should not use this directly from another application * * @param int[] $v * @return int[] * */ public static function sipRound(array $v) { # v0 += v1; list($v[0], $v[1]) = self::add( array($v[0], $v[1]), array($v[2], $v[3]) ); # v1=ROTL(v1,13); list($v[2], $v[3]) = self::rotl_64((int) $v[2], (int) $v[3], 13); # v1 ^= v0; $v[2] = (int) $v[2] ^ (int) $v[0]; $v[3] = (int) $v[3] ^ (int) $v[1]; # v0=ROTL(v0,32); list($v[0], $v[1]) = self::rotl_64((int) $v[0], (int) $v[1], 32); # v2 += v3; list($v[4], $v[5]) = self::add( array((int) $v[4], (int) $v[5]), array((int) $v[6], (int) $v[7]) ); # v3=ROTL(v3,16); list($v[6], $v[7]) = self::rotl_64((int) $v[6], (int) $v[7], 16); # v3 ^= v2; $v[6] = (int) $v[6] ^ (int) $v[4]; $v[7] = (int) $v[7] ^ (int) $v[5]; # v0 += v3; list($v[0], $v[1]) = self::add( array((int) $v[0], (int) $v[1]), array((int) $v[6], (int) $v[7]) ); # v3=ROTL(v3,21); list($v[6], $v[7]) = self::rotl_64((int) $v[6], (int) $v[7], 21); # v3 ^= v0; $v[6] = (int) $v[6] ^ (int) $v[0]; $v[7] = (int) $v[7] ^ (int) $v[1]; # v2 += v1; list($v[4], $v[5]) = self::add( array((int) $v[4], (int) $v[5]), array((int) $v[2], (int) $v[3]) ); # v1=ROTL(v1,17); list($v[2], $v[3]) = self::rotl_64((int) $v[2], (int) $v[3], 17); # v1 ^= v2;; $v[2] = (int) $v[2] ^ (int) $v[4]; $v[3] = (int) $v[3] ^ (int) $v[5]; # v2=ROTL(v2,32) list($v[4], $v[5]) = self::rotl_64((int) $v[4], (int) $v[5], 32); return $v; } /** * Add two 32 bit integers representing a 64-bit integer. * * @internal You should not use this directly from another application * * @param int[] $a * @param int[] $b * @return array<int, mixed> */ public static function add(array $a, array $b) { /** @var int $x1 */ $x1 = $a[1] + $b[1]; /** @var int $c */ $c = $x1 >> 32; // Carry if ($a + $b) > 0xffffffff /** @var int $x0 */ $x0 = $a[0] + $b[0] + $c; return array( $x0 & 0xffffffff, $x1 & 0xffffffff ); } /** * @internal You should not use this directly from another application * * @param int $int0 * @param int $int1 * @param int $c * @return array<int, mixed> */ public static function rotl_64($int0, $int1, $c) { $int0 &= 0xffffffff; $int1 &= 0xffffffff; $c &= 63; if ($c === 32) { return array($int1, $int0); } if ($c > 31) { $tmp = $int1; $int1 = $int0; $int0 = $tmp; $c &= 31; } if ($c === 0) { return array($int0, $int1); } return array( 0xffffffff & ( ($int0 << $c) | ($int1 >> (32 - $c)) ), 0xffffffff & ( ($int1 << $c) | ($int0 >> (32 - $c)) ), ); } /** * Implements Siphash-2-4 using only 32-bit numbers. * * When we split an int into two, the higher bits go to the lower index. * e.g. 0xDEADBEEFAB10C92D becomes [ * 0 => 0xDEADBEEF, * 1 => 0xAB10C92D * ]. * * @internal You should not use this directly from another application * * @param string $in * @param string $key * @return string * @throws SodiumException * @throws TypeError */ public static function sipHash24($in, $key) { $inlen = self::strlen($in); # /* "somepseudorandomlygeneratedbytes" */ # u64 v0 = 0x736f6d6570736575ULL; # u64 v1 = 0x646f72616e646f6dULL; # u64 v2 = 0x6c7967656e657261ULL; # u64 v3 = 0x7465646279746573ULL; $v = array( 0x736f6d65, // 0 0x70736575, // 1 0x646f7261, // 2 0x6e646f6d, // 3 0x6c796765, // 4 0x6e657261, // 5 0x74656462, // 6 0x79746573 // 7 ); // v0 => $v[0], $v[1] // v1 => $v[2], $v[3] // v2 => $v[4], $v[5] // v3 => $v[6], $v[7] # u64 k0 = LOAD64_LE( k ); # u64 k1 = LOAD64_LE( k + 8 ); $k = array( self::load_4(self::substr($key, 4, 4)), self::load_4(self::substr($key, 0, 4)), self::load_4(self::substr($key, 12, 4)), self::load_4(self::substr($key, 8, 4)) ); // k0 => $k[0], $k[1] // k1 => $k[2], $k[3] # b = ( ( u64 )inlen ) << 56; $b = array( $inlen << 24, 0 ); // See docblock for why the 0th index gets the higher bits. # v3 ^= k1; $v[6] ^= $k[2]; $v[7] ^= $k[3]; # v2 ^= k0; $v[4] ^= $k[0]; $v[5] ^= $k[1]; # v1 ^= k1; $v[2] ^= $k[2]; $v[3] ^= $k[3]; # v0 ^= k0; $v[0] ^= $k[0]; $v[1] ^= $k[1]; $left = $inlen; # for ( ; in != end; in += 8 ) while ($left >= 8) { # m = LOAD64_LE( in ); $m = array( self::load_4(self::substr($in, 4, 4)), self::load_4(self::substr($in, 0, 4)) ); # v3 ^= m; $v[6] ^= $m[0]; $v[7] ^= $m[1]; # SIPROUND; # SIPROUND; $v = self::sipRound($v); $v = self::sipRound($v); # v0 ^= m; $v[0] ^= $m[0]; $v[1] ^= $m[1]; $in = self::substr($in, 8); $left -= 8; } # switch( left ) # { # case 7: b |= ( ( u64 )in[ 6] ) << 48; # case 6: b |= ( ( u64 )in[ 5] ) << 40; # case 5: b |= ( ( u64 )in[ 4] ) << 32; # case 4: b |= ( ( u64 )in[ 3] ) << 24; # case 3: b |= ( ( u64 )in[ 2] ) << 16; # case 2: b |= ( ( u64 )in[ 1] ) << 8; # case 1: b |= ( ( u64 )in[ 0] ); break; # case 0: break; # } switch ($left) { case 7: $b[0] |= self::chrToInt($in[6]) << 16; case 6: $b[0] |= self::chrToInt($in[5]) << 8; case 5: $b[0] |= self::chrToInt($in[4]); case 4: $b[1] |= self::chrToInt($in[3]) << 24; case 3: $b[1] |= self::chrToInt($in[2]) << 16; case 2: $b[1] |= self::chrToInt($in[1]) << 8; case 1: $b[1] |= self::chrToInt($in[0]); case 0: break; } // See docblock for why the 0th index gets the higher bits. # v3 ^= b; $v[6] ^= $b[0]; $v[7] ^= $b[1]; # SIPROUND; # SIPROUND; $v = self::sipRound($v); $v = self::sipRound($v); # v0 ^= b; $v[0] ^= $b[0]; $v[1] ^= $b[1]; // Flip the lower 8 bits of v2 which is ($v[4], $v[5]) in our implementation # v2 ^= 0xff; $v[5] ^= 0xff; # SIPROUND; # SIPROUND; # SIPROUND; # SIPROUND; $v = self::sipRound($v); $v = self::sipRound($v); $v = self::sipRound($v); $v = self::sipRound($v); # b = v0 ^ v1 ^ v2 ^ v3; # STORE64_LE( out, b ); return self::store32_le($v[1] ^ $v[3] ^ $v[5] ^ $v[7]) . self::store32_le($v[0] ^ $v[2] ^ $v[4] ^ $v[6]); } } X25519.php 0000644 00000022352 14721742304 0006102 0 ustar 00 <?php if (class_exists('ParagonIE_Sodium_Core_X25519', false)) { return; } /** * Class ParagonIE_Sodium_Core_X25519 */ abstract class ParagonIE_Sodium_Core_X25519 extends ParagonIE_Sodium_Core_Curve25519 { /** * Alters the objects passed to this method in place. * * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @param ParagonIE_Sodium_Core_Curve25519_Fe $g * @param int $b * @return void * @psalm-suppress MixedAssignment */ public static function fe_cswap( ParagonIE_Sodium_Core_Curve25519_Fe $f, ParagonIE_Sodium_Core_Curve25519_Fe $g, $b = 0 ) { $f0 = (int) $f[0]; $f1 = (int) $f[1]; $f2 = (int) $f[2]; $f3 = (int) $f[3]; $f4 = (int) $f[4]; $f5 = (int) $f[5]; $f6 = (int) $f[6]; $f7 = (int) $f[7]; $f8 = (int) $f[8]; $f9 = (int) $f[9]; $g0 = (int) $g[0]; $g1 = (int) $g[1]; $g2 = (int) $g[2]; $g3 = (int) $g[3]; $g4 = (int) $g[4]; $g5 = (int) $g[5]; $g6 = (int) $g[6]; $g7 = (int) $g[7]; $g8 = (int) $g[8]; $g9 = (int) $g[9]; $b = -$b; $x0 = ($f0 ^ $g0) & $b; $x1 = ($f1 ^ $g1) & $b; $x2 = ($f2 ^ $g2) & $b; $x3 = ($f3 ^ $g3) & $b; $x4 = ($f4 ^ $g4) & $b; $x5 = ($f5 ^ $g5) & $b; $x6 = ($f6 ^ $g6) & $b; $x7 = ($f7 ^ $g7) & $b; $x8 = ($f8 ^ $g8) & $b; $x9 = ($f9 ^ $g9) & $b; $f[0] = $f0 ^ $x0; $f[1] = $f1 ^ $x1; $f[2] = $f2 ^ $x2; $f[3] = $f3 ^ $x3; $f[4] = $f4 ^ $x4; $f[5] = $f5 ^ $x5; $f[6] = $f6 ^ $x6; $f[7] = $f7 ^ $x7; $f[8] = $f8 ^ $x8; $f[9] = $f9 ^ $x9; $g[0] = $g0 ^ $x0; $g[1] = $g1 ^ $x1; $g[2] = $g2 ^ $x2; $g[3] = $g3 ^ $x3; $g[4] = $g4 ^ $x4; $g[5] = $g5 ^ $x5; $g[6] = $g6 ^ $x6; $g[7] = $g7 ^ $x7; $g[8] = $g8 ^ $x8; $g[9] = $g9 ^ $x9; } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $f * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function fe_mul121666(ParagonIE_Sodium_Core_Curve25519_Fe $f) { $h = array( self::mul((int) $f[0], 121666, 17), self::mul((int) $f[1], 121666, 17), self::mul((int) $f[2], 121666, 17), self::mul((int) $f[3], 121666, 17), self::mul((int) $f[4], 121666, 17), self::mul((int) $f[5], 121666, 17), self::mul((int) $f[6], 121666, 17), self::mul((int) $f[7], 121666, 17), self::mul((int) $f[8], 121666, 17), self::mul((int) $f[9], 121666, 17) ); /** @var int $carry9 */ $carry9 = ($h[9] + (1 << 24)) >> 25; $h[0] += self::mul($carry9, 19, 5); $h[9] -= $carry9 << 25; /** @var int $carry1 */ $carry1 = ($h[1] + (1 << 24)) >> 25; $h[2] += $carry1; $h[1] -= $carry1 << 25; /** @var int $carry3 */ $carry3 = ($h[3] + (1 << 24)) >> 25; $h[4] += $carry3; $h[3] -= $carry3 << 25; /** @var int $carry5 */ $carry5 = ($h[5] + (1 << 24)) >> 25; $h[6] += $carry5; $h[5] -= $carry5 << 25; /** @var int $carry7 */ $carry7 = ($h[7] + (1 << 24)) >> 25; $h[8] += $carry7; $h[7] -= $carry7 << 25; /** @var int $carry0 */ $carry0 = ($h[0] + (1 << 25)) >> 26; $h[1] += $carry0; $h[0] -= $carry0 << 26; /** @var int $carry2 */ $carry2 = ($h[2] + (1 << 25)) >> 26; $h[3] += $carry2; $h[2] -= $carry2 << 26; /** @var int $carry4 */ $carry4 = ($h[4] + (1 << 25)) >> 26; $h[5] += $carry4; $h[4] -= $carry4 << 26; /** @var int $carry6 */ $carry6 = ($h[6] + (1 << 25)) >> 26; $h[7] += $carry6; $h[6] -= $carry6 << 26; /** @var int $carry8 */ $carry8 = ($h[8] + (1 << 25)) >> 26; $h[9] += $carry8; $h[8] -= $carry8 << 26; foreach ($h as $i => $value) { $h[$i] = (int) $value; } return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($h); } /** * @internal You should not use this directly from another application * * Inline comments preceded by # are from libsodium's ref10 code. * * @param string $n * @param string $p * @return string * @throws SodiumException * @throws TypeError */ public static function crypto_scalarmult_curve25519_ref10($n, $p) { # for (i = 0;i < 32;++i) e[i] = n[i]; $e = '' . $n; # e[0] &= 248; $e[0] = self::intToChr( self::chrToInt($e[0]) & 248 ); # e[31] &= 127; # e[31] |= 64; $e[31] = self::intToChr( (self::chrToInt($e[31]) & 127) | 64 ); # fe_frombytes(x1,p); $x1 = self::fe_frombytes($p); # fe_1(x2); $x2 = self::fe_1(); # fe_0(z2); $z2 = self::fe_0(); # fe_copy(x3,x1); $x3 = self::fe_copy($x1); # fe_1(z3); $z3 = self::fe_1(); # swap = 0; /** @var int $swap */ $swap = 0; # for (pos = 254;pos >= 0;--pos) { for ($pos = 254; $pos >= 0; --$pos) { # b = e[pos / 8] >> (pos & 7); /** @var int $b */ $b = self::chrToInt( $e[(int) floor($pos / 8)] ) >> ($pos & 7); # b &= 1; $b &= 1; # swap ^= b; $swap ^= $b; # fe_cswap(x2,x3,swap); self::fe_cswap($x2, $x3, $swap); # fe_cswap(z2,z3,swap); self::fe_cswap($z2, $z3, $swap); # swap = b; $swap = $b; # fe_sub(tmp0,x3,z3); $tmp0 = self::fe_sub($x3, $z3); # fe_sub(tmp1,x2,z2); $tmp1 = self::fe_sub($x2, $z2); # fe_add(x2,x2,z2); $x2 = self::fe_add($x2, $z2); # fe_add(z2,x3,z3); $z2 = self::fe_add($x3, $z3); # fe_mul(z3,tmp0,x2); $z3 = self::fe_mul($tmp0, $x2); # fe_mul(z2,z2,tmp1); $z2 = self::fe_mul($z2, $tmp1); # fe_sq(tmp0,tmp1); $tmp0 = self::fe_sq($tmp1); # fe_sq(tmp1,x2); $tmp1 = self::fe_sq($x2); # fe_add(x3,z3,z2); $x3 = self::fe_add($z3, $z2); # fe_sub(z2,z3,z2); $z2 = self::fe_sub($z3, $z2); # fe_mul(x2,tmp1,tmp0); $x2 = self::fe_mul($tmp1, $tmp0); # fe_sub(tmp1,tmp1,tmp0); $tmp1 = self::fe_sub($tmp1, $tmp0); # fe_sq(z2,z2); $z2 = self::fe_sq($z2); # fe_mul121666(z3,tmp1); $z3 = self::fe_mul121666($tmp1); # fe_sq(x3,x3); $x3 = self::fe_sq($x3); # fe_add(tmp0,tmp0,z3); $tmp0 = self::fe_add($tmp0, $z3); # fe_mul(z3,x1,z2); $z3 = self::fe_mul($x1, $z2); # fe_mul(z2,tmp1,tmp0); $z2 = self::fe_mul($tmp1, $tmp0); } # fe_cswap(x2,x3,swap); self::fe_cswap($x2, $x3, $swap); # fe_cswap(z2,z3,swap); self::fe_cswap($z2, $z3, $swap); # fe_invert(z2,z2); $z2 = self::fe_invert($z2); # fe_mul(x2,x2,z2); $x2 = self::fe_mul($x2, $z2); # fe_tobytes(q,x2); return self::fe_tobytes($x2); } /** * @internal You should not use this directly from another application * * @param ParagonIE_Sodium_Core_Curve25519_Fe $edwardsY * @param ParagonIE_Sodium_Core_Curve25519_Fe $edwardsZ * @return ParagonIE_Sodium_Core_Curve25519_Fe */ public static function edwards_to_montgomery( ParagonIE_Sodium_Core_Curve25519_Fe $edwardsY, ParagonIE_Sodium_Core_Curve25519_Fe $edwardsZ ) { $tempX = self::fe_add($edwardsZ, $edwardsY); $tempZ = self::fe_sub($edwardsZ, $edwardsY); $tempZ = self::fe_invert($tempZ); return self::fe_mul($tempX, $tempZ); } /** * @internal You should not use this directly from another application * * @param string $n * @return string * @throws SodiumException * @throws TypeError */ public static function crypto_scalarmult_curve25519_ref10_base($n) { # for (i = 0;i < 32;++i) e[i] = n[i]; $e = '' . $n; # e[0] &= 248; $e[0] = self::intToChr( self::chrToInt($e[0]) & 248 ); # e[31] &= 127; # e[31] |= 64; $e[31] = self::intToChr( (self::chrToInt($e[31]) & 127) | 64 ); $A = self::ge_scalarmult_base($e); if ( !($A->Y instanceof ParagonIE_Sodium_Core_Curve25519_Fe) || !($A->Z instanceof ParagonIE_Sodium_Core_Curve25519_Fe) ) { throw new TypeError('Null points encountered'); } $pk = self::edwards_to_montgomery($A->Y, $A->Z); return self::fe_tobytes($pk); } } SecretStream/State.php 0000644 00000007050 14721742304 0010744 0 ustar 00 <?php /** * Class ParagonIE_Sodium_Core_SecretStream_State */ class ParagonIE_Sodium_Core_SecretStream_State { /** @var string $key */ protected $key; /** @var int $counter */ protected $counter; /** @var string $nonce */ protected $nonce; /** @var string $_pad */ protected $_pad; /** * ParagonIE_Sodium_Core_SecretStream_State constructor. * @param string $key * @param string|null $nonce */ public function __construct($key, $nonce = null) { $this->key = $key; $this->counter = 1; if (is_null($nonce)) { $nonce = str_repeat("\0", 12); } $this->nonce = str_pad($nonce, 12, "\0", STR_PAD_RIGHT);; $this->_pad = str_repeat("\0", 4); } /** * @return self */ public function counterReset() { $this->counter = 1; $this->_pad = str_repeat("\0", 4); return $this; } /** * @return string */ public function getKey() { return $this->key; } /** * @return string */ public function getCounter() { return ParagonIE_Sodium_Core_Util::store32_le($this->counter); } /** * @return string */ public function getNonce() { if (!is_string($this->nonce)) { $this->nonce = str_repeat("\0", 12); } if (ParagonIE_Sodium_Core_Util::strlen($this->nonce) !== 12) { $this->nonce = str_pad($this->nonce, 12, "\0", STR_PAD_RIGHT); } return $this->nonce; } /** * @return string */ public function getCombinedNonce() { return $this->getCounter() . ParagonIE_Sodium_Core_Util::substr($this->getNonce(), 0, 8); } /** * @return self */ public function incrementCounter() { ++$this->counter; return $this; } /** * @return bool */ public function needsRekey() { return ($this->counter & 0xffff) === 0; } /** * @param string $newKeyAndNonce * @return self */ public function rekey($newKeyAndNonce) { $this->key = ParagonIE_Sodium_Core_Util::substr($newKeyAndNonce, 0, 32); $this->nonce = str_pad( ParagonIE_Sodium_Core_Util::substr($newKeyAndNonce, 32), 12, "\0", STR_PAD_RIGHT ); return $this; } /** * @param string $str * @return self */ public function xorNonce($str) { $this->nonce = ParagonIE_Sodium_Core_Util::xorStrings( $this->getNonce(), str_pad( ParagonIE_Sodium_Core_Util::substr($str, 0, 8), 12, "\0", STR_PAD_RIGHT ) ); return $this; } /** * @param string $string * @return self */ public static function fromString($string) { $state = new ParagonIE_Sodium_Core_SecretStream_State( ParagonIE_Sodium_Core_Util::substr($string, 0, 32) ); $state->counter = ParagonIE_Sodium_Core_Util::load_4( ParagonIE_Sodium_Core_Util::substr($string, 32, 4) ); $state->nonce = ParagonIE_Sodium_Core_Util::substr($string, 36, 12); $state->_pad = ParagonIE_Sodium_Core_Util::substr($string, 48, 8); return $state; } /** * @return string */ public function toString() { return $this->key . $this->getCounter() . $this->nonce . $this->_pad; } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.3-4ubuntu2.24 | Генерация страницы: 0.24 |
proxy
|
phpinfo
|
Настройка