base/endpoint.php000064400000021633147207001150010011 0ustar00>register()`. * * @param \Elementor\Data\Base\Controller $controller * * @throws \Exception */ public function __construct( $controller ) { if ( ! ( $controller instanceof Controller ) ) { throw new \Exception( 'Invalid controller.' ); } $this->controller = $controller; $this->register(); } /** * Get endpoint name. * * @return string */ abstract public function get_name(); /** * Get base route. * * Removing 'index' from endpoint. * * @return string */ public function get_base_route() { $endpoint_name = $this->get_name(); // TODO: Allow this only for internal routes. // TODO: Make difference between internal and external endpoints. if ( 'index' === $endpoint_name ) { $endpoint_name = ''; } return '/' . $this->controller->get_rest_base() . '/' . $endpoint_name; } /** * Register the endpoint. * * By default: register get items route. * * @throws \Exception */ protected function register() { $this->register_items_route(); } /** * Register sub endpoint. * * @param string $route * @param string $endpoint_class * * @return \Elementor\Data\Base\SubEndpoint * @throws \Exception */ protected function register_sub_endpoint( $route, $endpoint_class ) { $endpoint_instance = new $endpoint_class( $route, $this ); if ( ! ( $endpoint_instance instanceof SubEndpoint ) ) { throw new \Exception( 'Invalid endpoint instance.' ); } $endpoint_route = $route . '/' . $endpoint_instance->get_name(); $this->sub_endpoints[ $endpoint_route ] = $endpoint_instance; $component_name = $endpoint_instance->controller->get_rest_base(); $parent_instance = $endpoint_instance->get_parent(); $parent_name = $endpoint_instance->get_name(); $parent_format_suffix = $parent_instance::get_format(); $current_format_suffix = $endpoint_instance::get_format(); $command = $component_name . '/' . $parent_name; $format = $component_name . '/' . $parent_format_suffix . '/' . $parent_name . '/' . $current_format_suffix; Manager::instance()->register_endpoint_format( $command, $format ); return $endpoint_instance; } /** * Base callback. * * All reset requests from the client should pass this function. * * @param string $methods * @param \WP_REST_Request $request * @param bool $is_multi * * @return mixed|\WP_Error|\WP_HTTP_Response|\WP_REST_Response * @throws \Exception */ public function base_callback( $methods, $request, $is_multi = false ) { // TODO: Find better solution. $json_params = $request->get_json_params(); if ( $json_params ) { $request->set_body_params( $json_params ); } // TODO: Handle permission callback. switch ( $methods ) { case WP_REST_Server::READABLE: $result = $is_multi ? $this->get_items( $request ) : $this->get_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::CREATABLE: $result = $is_multi ? $this->create_items( $request ) : $this->create_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::EDITABLE: $result = $is_multi ? $this->update_items( $request ) : $this->update_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::DELETABLE: $result = $is_multi ? $this->delete_items( $request ) : $this->delete_item( $request->get_param( 'id' ), $request ); break; default: throw new \Exception( 'Invalid method.' ); } return rest_ensure_response( $result ); } /** * Retrieves a collection of items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { return $this->controller->get_items( $request ); } /** * Retrieves one item from the collection. * * @param string $id * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function get_item( $id, $request ) { return $this->controller->get_item( $request ); } /** * Get permission callback. * * By default get permission callback from the controller. * * @param \WP_REST_Request $request Full data about the request. * * @return boolean */ public function get_permission_callback( $request ) { return $this->controller->get_permission_callback( $request ); } /** * Creates one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function create_item( $id, $request ) { return $this->controller->create_item( $request ); } /** * Creates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function create_items( $request ) { return $this->controller->create_items( $request ); } /** * Updates one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_item( $id, $request ) { return $this->controller->update_item( $request ); } /** * Updates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_items( $request ) { return $this->controller->update_items( $request ); } /** * Delete one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function delete_item( $id, $request ) { return $this->controller->delete_item( $request ); } /** * Delete multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function delete_items( $request ) { return $this->controller->delete_items( $request ); } /** * Register item route. * * @param array $args * @param string $route * @param string $methods * * @throws \Exception */ public function register_item_route( $methods = WP_REST_Server::READABLE, $args = [], $route = '/' ) { $args = array_merge( [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', ], ], $args ); if ( isset( $args['id'] ) && $args['id'] ) { $route .= '(?P[\w]+)/'; } $this->register_route( $route, $methods, function ( $request ) use ( $methods ) { return $this->base_callback( $methods, $request ); }, $args ); } /** * Register items route. * * @param string $methods * * @throws \Exception */ public function register_items_route( $methods = WP_REST_Server::READABLE ) { $this->register_route( '', $methods, function ( $request ) use ( $methods ) { return $this->base_callback( $methods, $request, true ); } ); } /** * Register route. * * @param string $route * @param string $methods * @param null $callback * @param array $args * * @return bool * @throws \Exception */ public function register_route( $route = '', $methods = WP_REST_Server::READABLE, $callback = null, $args = [] ) { if ( ! in_array( $methods, self::AVAILABLE_METHODS, true ) ) { throw new \Exception( 'Invalid method.' ); } $route = $this->get_base_route() . $route; return register_rest_route( $this->controller->get_namespace(), $route, [ [ 'args' => $args, 'methods' => $methods, 'callback' => $callback, 'permission_callback' => function ( $request ) { return $this->get_permission_callback( $request ); }, ], ] ); } } base/processor.php000064400000000674147207001150010212 0ustar00controller = $controller; } /** * Get processor command. * * @return string */ abstract public function get_command(); } base/processor/after.php000064400000000724147207001150011307 0ustar00deprecated(); $this->namespace = Manager::ROOT_NAMESPACE . '/v' . Manager::VERSION; $this->rest_base = Manager::REST_BASE . $this->get_name(); add_action( 'rest_api_init', function () { $this->register(); // Because 'register' is protected. } ); /** * Since all actions were removed for custom internal REST server. * Re-add the actions. */ add_action( 'elementor_rest_api_before_init', function () { add_action( 'rest_api_init', function() { $this->register(); } ); } ); } /** * Get controller name. * * @return string */ abstract public function get_name(); /** * Get controller namespace. * * @return string */ public function get_namespace() { return $this->namespace; } /** * Get controller reset base. * * @return string */ public function get_rest_base() { return $this->rest_base; } /** * Get controller route. * * @return string */ public function get_controller_route() { return $this->get_namespace() . '/' . $this->get_rest_base(); } /** * Retrieves the index for a controller. * * @return \WP_REST_Response|\WP_Error */ public function get_controller_index() { $server = rest_get_server(); $routes = $server->get_routes(); $endpoints = array_intersect_key( $server->get_routes(), $routes ); $controller_route = $this->get_controller_route(); array_walk( $endpoints, function ( &$item, $endpoint ) use ( &$endpoints, $controller_route ) { if ( ! strstr( $endpoint, $controller_route ) ) { unset( $endpoints[ $endpoint ] ); } } ); $data = [ 'namespace' => $this->get_namespace(), 'controller' => $controller_route, 'routes' => $server->get_data_for_routes( $endpoints ), ]; $response = rest_ensure_response( $data ); // Link to the root index. $response->add_link( 'up', rest_url( '/' ) ); return $response; } /** * Get processors. * * @param string $command * * @return \Elementor\Data\Base\Processor[] */ public function get_processors( $command ) { $result = []; if ( isset( $this->processors[ $command ] ) ) { $result = $this->processors[ $command ]; } return $result; } public function get_items( $request ) { return $this->get_controller_index(); } /** * Creates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function create_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Updates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Delete multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function delete_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Register endpoints. */ abstract public function register_endpoints(); /** * Register processors. */ public function register_processors() { } /** * Register internal endpoints. */ protected function register_internal_endpoints() { register_rest_route( $this->get_namespace(), '/' . $this->get_rest_base(), [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'args' => [], 'permission_callback' => function ( $request ) { return $this->get_permission_callback( $request ); }, ], ] ); } /** * Register endpoint. * * @param string $endpoint_class * * @return \Elementor\Data\Base\Endpoint */ protected function register_endpoint( $endpoint_class ) { $endpoint_instance = new $endpoint_class( $this ); // TODO: Validate instance like in register_sub_endpoint(). $endpoint_route = $this->get_name() . '/' . $endpoint_instance->get_name(); $this->endpoints[ $endpoint_route ] = $endpoint_instance; $command = $endpoint_route; $format = $endpoint_instance::get_format(); if ( $command ) { $format = $command . '/' . $format; } else { $format = $format . $command; } // `$e.data.registerFormat()`. Manager::instance()->register_endpoint_format( $command, $format ); return $endpoint_instance; } /** * Register a processor. * * That will be later attached to the endpoint class. * * @param string $processor_class * * @return \Elementor\Data\Base\Processor $processor_instance */ protected function register_processor( $processor_class ) { $processor_instance = new $processor_class( $this ); // TODO: Validate processor instance. $command = $processor_instance->get_command(); if ( ! isset( $this->processors[ $command ] ) ) { $this->processors[ $command ] = []; } $this->processors[ $command ] [] = $processor_instance; return $processor_instance; } /** * Register. * * Endpoints & processors. */ protected function register() { $this->register_internal_endpoints(); $this->register_endpoints(); // Aka hooks. $this->register_processors(); } /** * Retrieves a recursive collection of all endpoint(s), items. * * Get items recursive, will run overall endpoints of the current controller. * For each endpoint it will run `$endpoint->getItems( $request ) // the $request passed in get_items_recursive`. * Will skip $skip_endpoints endpoint(s). * * Example, scenario: * Controller 'test-controller'. * Controller endpoints: 'endpoint1', 'endpoint2'. * Endpoint2 get_items method: `get_items() { return 'test' }`. * Call `Controller.get_items_recursive( ['endpoint1'] )`, result: [ 'endpoint2' => 'test' ]; * * @param array $skip_endpoints * * @return array */ public function get_items_recursive( $skip_endpoints = [] ) { $response = []; foreach ( $this->endpoints as $endpoint ) { // Skip self. if ( in_array( $endpoint, $skip_endpoints, true ) ) { continue; } $response[ $endpoint->get_name() ] = $endpoint->get_items( null ); } return $response; } /** * Get permission callback. * * Default controller permission callback. * By default endpoint will inherit the permission callback from the controller. * By default permission is `current_user_can( 'administrator' );`. * * @param \WP_REST_Request $request * * @return bool */ public function get_permission_callback( $request ) { // The function is public since endpoint need to access it. switch ( $request->get_method() ) { case 'GET': case 'POST': case 'UPDATE': case 'PUT': case 'DELETE': case 'PATCH': return current_user_can( 'administrator' ); } return false; } private static $notify_deprecated = true; private function deprecated() { add_action( 'elementor/init', function () { if ( ! self::$notify_deprecated ) { return; } Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( 'Elementor\Data\Manager', '3.5.0', 'Elementor\Data\V2\Manager' ); self::$notify_deprecated = false; } ); } } base/sub-endpoint.php000064400000001361147207001150010574 0ustar00parent_endpoint = $parent_endpoint; $this->parent_route = $parent_route; parent::__construct( $this->parent_endpoint->controller ); } /** * Get parent route. * * @return \Elementor\Data\Base\Endpoint */ public function get_parent() { return $this->parent_endpoint; } public function get_base_route() { $controller_name = $this->controller->get_name(); return $controller_name . '/' . $this->parent_route . $this->get_name(); } } manager.php000064400000022274147207001150006673 0ustar00controllers; } private function get_cache( $key ) { return self::get_items( $this->cache, $key ); } private function set_cache( $key, $value ) { $this->cache[ $key ] = $value; } /** * Register controller. * * @param string $controller_class_name * * @return \Elementor\Data\Base\Controller */ public function register_controller( $controller_class_name ) { $controller_instance = new $controller_class_name(); return $this->register_controller_instance( $controller_instance ); } /** * Register controller instance. * * @param \Elementor\Data\Base\Controller $controller_instance * * @return \Elementor\Data\Base\Controller */ public function register_controller_instance( $controller_instance ) { // TODO: Validate instance. $this->controllers[ $controller_instance->get_name() ] = $controller_instance; return $controller_instance; } /** * Register endpoint format. * * @param string $command * @param string $format * */ public function register_endpoint_format( $command, $format ) { $this->command_formats[ $command ] = rtrim( $format, '/' ); } public function register_rest_error_handler() { if ( ! $this->is_internal() ) { $logger_manager = \Elementor\Core\Logger\Manager::instance(); $logger_manager->register_error_handler(); } } /** * Find controller instance. * * By given command name. * * @param string $command * * @return false|\Elementor\Data\Base\Controller */ public function find_controller_instance( $command ) { $command_parts = explode( '/', $command ); $assumed_command_parts = []; foreach ( $command_parts as $command_part ) { $assumed_command_parts [] = $command_part; foreach ( $this->controllers as $controller_name => $controller ) { $assumed_command = implode( '/', $assumed_command_parts ); if ( $assumed_command === $controller_name ) { return $controller; } } } return false; } /** * Command extract args. * * @param string $command * @param array $args * * @return \stdClass */ public function command_extract_args( $command, $args = [] ) { $result = new \stdClass(); $result->command = $command; $result->args = $args; if ( false !== strpos( $command, '?' ) ) { $command_parts = explode( '?', $command ); $pure_command = $command_parts[0]; $query_string = $command_parts[1]; parse_str( $query_string, $temp ); $result->command = rtrim( $pure_command, '/' ); $result->args = array_merge( $args, $temp ); } return $result; } /** * Command to endpoint. * * Format is required otherwise $command will returned. * * @param string $command * @param string $format * @param array $args * * @return string endpoint */ public function command_to_endpoint( $command, $format, $args ) { $endpoint = $command; if ( $format ) { $formatted = $format; array_walk( $args, function ( $val, $key ) use ( &$formatted ) { $formatted = str_replace( '{' . $key . '}', $val, $formatted ); } ); // Remove remaining format if not requested via `$args`. if ( strstr( $formatted, '/{' ) ) { /** * Example: * $command = 'example/documents'; * $format = 'example/documents/{document_id}/elements/{element_id}'; * $formatted = 'example/documents/1618/elements/{element_id}'; * Result: * $formatted = 'example/documents/1618/elements'; */ $formatted = substr( $formatted, 0, strpos( $formatted, '/{' ) ); } $endpoint = $formatted; } return $endpoint; } /** * Run server. * * Init WordPress reset api. * * @return \WP_REST_Server */ public function run_server() { /** * If run_server() called means, that rest api is simulated from the backend. */ $this->is_internal = true; if ( ! $this->server ) { // Remove all 'rest_api_init' actions. remove_all_actions( 'rest_api_init' ); // Call custom reset api loader. do_action( 'elementor_rest_api_before_init' ); $this->server = rest_get_server(); // Init API. } return $this->server; } /** * Kill server. * * Free server and controllers. */ public function kill_server() { global $wp_rest_server; $this->controllers = []; $this->command_formats = []; $this->server = false; $this->is_internal = false; $this->cache = []; $wp_rest_server = false; } /** * Run processor. * * @param \Elementor\Data\Base\Processor $processor * @param array $data * * @return mixed */ public function run_processor( $processor, $data ) { if ( call_user_func_array( [ $processor, 'get_conditions' ], $data ) ) { return call_user_func_array( [ $processor, 'apply' ], $data ); } return null; } /** * Run processors. * * Filter them by class. * * @param \Elementor\Data\Base\Processor[] $processors * @param string $filter_by_class * @param array $data * * @return false|array */ public function run_processors( $processors, $filter_by_class, $data ) { foreach ( $processors as $processor ) { if ( $processor instanceof $filter_by_class ) { if ( Processor\Before::class === $filter_by_class ) { $this->run_processor( $processor, $data ); } elseif ( Processor\After::class === $filter_by_class ) { $result = $this->run_processor( $processor, $data ); if ( $result ) { $data[1] = $result; } } else { // TODO: error break; } } } return isset( $data[1] ) ? $data[1] : false; } /** * Run request. * * Simulate rest API from within the backend. * Use args as query. * * @param string $endpoint * @param array $args * @param string $method * * @return \WP_REST_Response */ private function run_request( $endpoint, $args, $method ) { $this->run_server(); $endpoint = '/' . self::ROOT_NAMESPACE . '/v' . self::VERSION . '/' . $endpoint; // Run reset api. $request = new \WP_REST_Request( $method, $endpoint ); if ( 'GET' === $method ) { $request->set_query_params( $args ); } else { $request->set_body_params( $args ); } return rest_do_request( $request ); } /** * Run endpoint. * * Wrapper for `$this->run_request` return `$response->getData()` instead of `$response`. * * @param string $endpoint * @param array $args * @param string $method * * @return array */ public function run_endpoint( $endpoint, $args = [], $method = 'GET' ) { $response = $this->run_request( $endpoint, $args, $method ); return $response->get_data(); } /** * Run ( simulated reset api ). * * Do: * Init reset server. * Run before processors. * Run command as reset api endpoint from internal. * Run after processors. * * @param string $command * @param array $args * @param string $method * * @return array|false processed result */ public function run( $command, $args = [], $method = 'GET' ) { $key = crc32( $command . '-' . wp_json_encode( $args ) . '-' . $method ); $cache = $this->get_cache( $key ); if ( $cache ) { return $cache; } $this->run_server(); $controller_instance = $this->find_controller_instance( $command ); if ( ! $controller_instance ) { $this->set_cache( $key, [] ); return []; } $extracted_command = $this->command_extract_args( $command, $args ); $command = $extracted_command->command; $args = $extracted_command->args; $format = isset( $this->command_formats[ $command ] ) ? $this->command_formats[ $command ] : false; $command_processors = $controller_instance->get_processors( $command ); $endpoint = $this->command_to_endpoint( $command, $format, $args ); $this->run_processors( $command_processors, Processor\Before::class, [ $args ] ); $response = $this->run_request( $endpoint, $args, $method ); $result = $response->get_data(); if ( $response->is_error() ) { $this->set_cache( $key, [] ); return []; } $result = $this->run_processors( $command_processors, Processor\After::class, [ $args, $result ] ); $this->set_cache( $key, $result ); return $result; } public function is_internal() { return $this->is_internal; } } v2/base/endpoint/index.php000064400000003172147207001150011445 0ustar00controller->get_full_name()}/{id}"; } public function get_public_name() { return ''; } public function get_items( $request ) { return $this->controller->get_items( $request ); } public function get_item( $id, $request ) { return $this->controller->get_item( $request ); } public function create_items( $request ) { return $this->controller->create_items( $request ); } public function create_item( $id, $request ) { return $this->controller->create_item( $request ); } public function update_items( $request ) { return $this->controller->update_items( $request ); } public function update_item( $id, $request ) { return $this->controller->update_item( $request ); } public function delete_items( $request ) { return $this->controller->delete_items( $request ); } public function delete_item( $id, $request ) { return $this->controller->delete_item( $request ); } public function register_items_route( $methods = WP_REST_Server::READABLE, $args = [] ) { parent::register_items_route( $methods, array_merge( $this->controller->get_items_args( $methods ), $args ) ); } public function register_item_route( $methods = WP_REST_Server::READABLE, $args = [], $route = '/' ) { parent::register_item_route( $methods, array_merge( $this->controller->get_item_args( $methods ), $args ), $route ); } } v2/base/endpoint/index/all-children.php000064400000003570147207001150014005 0ustar00controller->get_name() . '/index'; } /* * Retrieves a result(s) of all controller endpoint(s), items. * * Run overall endpoints of the current controller. * * Example, scenario: * 'settings' - controller * 'settings/products' - endpoint * 'settings/partners' - endpoint * Result: * [ * 'products' => [ * 0 => ... * 1 => ... * ], * 'partners' => [ * 0 => ... * 1 => ... * ], * ] */ public function get_items( $request ) { $response = []; foreach ( $this->controller->get_sub_controllers() as $controller ) { $controller_route = $this->get_controller()->get_base_route() . '/' . $controller->get_name(); $result = Manager::instance()->run_request( $controller_route ); if ( ! $result->is_error() ) { $response[ $controller->get_name() ] = $result->get_data(); } } foreach ( $this->controller->endpoints as $endpoint ) { // Skip self. if ( $endpoint === $this ) { continue; } $result = Manager::instance()->run_request( $endpoint->get_base_route() ); if ( ! $result->is_error() ) { $response[ $endpoint->get_name() ] = $result->get_data(); } } return $response; } } v2/base/endpoint/index/sub-index-endpoint.php000064400000002740147207001150015161 0ustar00controller->get_parent()->get_name() . '/{id}/' . $this->controller->get_name() . '/{sub_id}'; } public function get_base_route() { $parent_controller = $this->controller->get_parent(); $parent_index_endpoint = $parent_controller->index_endpoint; $parent_controller_route = ''; // In case `$parent_index_endpoint` is AllChildren, it cannot support id_arg_name. if ( ! $parent_index_endpoint instanceof AllChildren ) { $parent_controller_route = "(?P<{$parent_index_endpoint->id_arg_name}>[\w]+)"; } return untrailingslashit('/' . implode( '/', array_filter( [ trim( $parent_index_endpoint->get_base_route(), '/' ), $parent_controller_route, $this->controller->get_name(), $this->get_public_name(), ] ) ) ); } } v2/base/endpoint.php000064400000005741147207001150010342 0ustar00controller; } /** * Get current parent. * * @return \Elementor\Data\V2\Base\Controller|\Elementor\Data\V2\Base\Endpoint */ public function get_parent() { return $this->parent; } /** * Get public name. * * @return string */ public function get_public_name() { return $this->get_name(); } /** * Get full command name ( including index ). * * @return string */ public function get_full_command() { $parent = $this->get_parent(); if ( $parent instanceof Controller ) { return $this->controller->get_full_name() . '/' . $this->get_name(); } return $this->get_name_ancestry(); } /** * Get name ancestry format, example: 'alpha/beta/delta'. * * @return string */ public function get_name_ancestry() { $ancestors = $this->get_ancestors(); $ancestors_names = []; foreach ( $ancestors as $ancestor ) { $ancestors_names [] = $ancestor->get_name(); } return implode( '/', $ancestors_names ); } /** * Register sub endpoint. * * @param \Elementor\Data\V2\Base\Endpoint $endpoint * * @return \Elementor\Data\V2\Base\Endpoint */ public function register_sub_endpoint( Endpoint $endpoint ) { $command = $endpoint->get_full_command(); $format = $endpoint->get_format(); $this->sub_endpoints[ $command ] = $endpoint; Manager::instance()->register_endpoint_format( $command, $format ); return $endpoint; } /** * Get ancestors. * * @return \Elementor\Data\V2\Base\Endpoint[] */ private function get_ancestors() { $ancestors = []; $current = $this; do { if ( $current ) { $ancestors [] = $current; } $current = $current->get_parent(); } while ( $current ); return array_reverse( $ancestors ); } /** * Endpoint constructor. * * @param \Elementor\Data\V2\Base\Controller|\Elementor\Data\V2\Base\Endpoint $parent * @param string $route */ public function __construct( $parent, $route = '/' ) { $controller = $parent; $this->parent = $parent; // In case, its behave like sub-endpoint. if ( ! ( $parent instanceof Controller ) ) { $controller = $parent->get_controller(); } parent::__construct( $controller, $route ); } } v2/base/processor.php000064400000001547147207001150010541 0ustar00controller = $controller; } } v2/base/base-route.php000064400000024422147207001150010565 0ustar00 '/' * 'abc' => '/abc/' * '/abc' => '/abc/' * 'abc/' => '/abc/' * '/abc/' => '/abc/' * * @param string $route * * @return string */ private function ensure_slashes( $route ) { if ( '/' !== $route[0] ) { $route = '/' . $route; } return trailingslashit( $route ); } /** * Get base route. * This method should always return the base route starts with '/' and ends without '/'. * * @return string */ public function get_base_route() { $name = $this->get_public_name(); $parent = $this->get_parent(); $parent_base = $parent->get_base_route(); $route = '/'; if ( ! ( $parent instanceof Controller ) ) { $route = $parent->item_route ? $parent->item_route['route'] . '/' : $this->route; } return untrailingslashit( '/' . trim( $parent_base . $route . $name, '/' ) ); } /** * Get permission callback. * * By default get permission callback from the controller. * * @param \WP_REST_Request $request Full data about the request. * * @return boolean */ public function get_permission_callback( $request ) { return $this->controller->get_permission_callback( $request ); } /** * Retrieves a collection of items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function get_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Retrieves one item from the collection. * * @param string $id * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function get_item( $id, $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Creates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function create_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Creates one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function create_item( $id, $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Updates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function update_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Updates one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function update_item( $id, $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Delete multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function delete_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Delete one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function delete_item( $id, $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Register the endpoint. * * By default: register get items route. */ protected function register() { $this->register_items_route(); } protected function register_route( $route = '', $methods = WP_REST_Server::READABLE, $args = [] ) { if ( ! in_array( $methods, self::AVAILABLE_METHODS, true ) ) { trigger_error( "Invalid method: '$methods'.", E_USER_ERROR ); // phpcs:ignore } $route = $this->get_base_route() . $route; $this->routes [] = [ 'args' => $args, 'route' => $route, ]; /** * Determine behaviour of `base_callback()` and `get_permission_callback()`: * For `base_callback()` which applying the action. * Whether it's a one item request and should call `get_item_permission_callback()` or it's mutil items request and should call `get_items_permission_callback()`. */ $is_multi = ! empty( $args['is_multi'] ); if ( $is_multi ) { unset( $args['is_multi'] ); } $callback = function ( $request ) use ( $methods, $args, $is_multi ) { return $this->base_callback( $methods, $request, $is_multi ); }; return register_rest_route( $this->controller->get_namespace(), $route, [ [ 'args' => $args, 'methods' => $methods, 'callback' => $callback, 'permission_callback' => function ( $request ) { return $this->get_permission_callback( $request ); }, ], ] ); } /** * Register items route. * * @param string $methods * @param array $args */ public function register_items_route( $methods = WP_REST_Server::READABLE, $args = [] ) { $args['is_multi'] = true; $this->register_route( '', $methods, $args ); } /** * Register item route. * * @param string $route * @param array $args * @param string $methods */ public function register_item_route( $methods = WP_REST_Server::READABLE, $args = [], $route = '/' ) { if ( ! empty( $args['id_arg_name'] ) ) { $this->id_arg_name = $args['id_arg_name']; unset( $args['id_arg_name'] ); } if ( ! empty( $args['id_arg_type_regex'] ) ) { $this->id_arg_type_regex = $args['id_arg_type_regex']; unset( $args['id_arg_type_regex'] ); } $args = array_merge( [ $this->id_arg_name => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', 'required' => true, ], ], $args ); $route .= '(?P<' . $this->id_arg_name . '>' . $this->id_arg_type_regex . ')'; $this->item_route = [ 'args' => $args, 'route' => $route, ]; $this->register_route( $route, $methods, $args ); } /** * Base callback. * All reset requests from the client should pass this function. * * @param string $methods * @param \WP_REST_Request $request * @param bool $is_multi * @param array $args * * @return mixed|\WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function base_callback( $methods, $request, $is_multi = false, $args = [] ) { if ( $request ) { $json_params = $request->get_json_params(); if ( $json_params ) { $request->set_body_params( $json_params ); } } $args = wp_parse_args( $args, [ 'is_debug' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ), ] ); $result = new \WP_Error( 'invalid_methods', 'route not supported.' ); $request->set_param( 'is_multi', $is_multi ); try { switch ( $methods ) { case WP_REST_Server::READABLE: $result = $is_multi ? $this->get_items( $request ) : $this->get_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::CREATABLE: $result = $is_multi ? $this->create_items( $request ) : $this->create_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::EDITABLE: $result = $is_multi ? $this->update_items( $request ) : $this->update_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::DELETABLE: $result = $is_multi ? $this->delete_items( $request ) : $this->delete_item( $request->get_param( 'id' ), $request ); break; } } catch ( Data_Exception $e ) { $result = $e->to_wp_error(); } catch ( \Exception $e ) { if ( empty( $args['is_debug'] ) ) { $result = ( new Error_500() )->to_wp_error(); } else { // For frontend. $exception_mapping = [ 'trace' => $e->getTrace(), 'file' => $e->getFile(), 'line' => $e->getLine(), ]; $e->debug = $exception_mapping; $result = ( new Data_Exception( $e->getMessage(), $e->getCode(), $e ) )->to_wp_error(); } } return rest_ensure_response( $result ); } /** * Constructor. * * run `$this->register()`. * * @param \Elementor\Data\V2\Base\Controller $controller * @param string $route */ protected function __construct( Controller $controller, $route ) { $this->controller = $controller; $this->route = $this->ensure_slashes( $route ); $this->register(); } } v2/base/processor/after.php000064400000001041147207001150011627 0ustar00parent ) { return $this->get_name(); } return $this->parent->get_name() . '/' . $this->get_name(); } /** * Get controller namespace. * * @return string */ public function get_namespace() { return $this->namespace; } /** * Get controller reset base. * * @return string */ public function get_base_route() { if ( ! $this->parent ) { return $this->rest_base; } return $this->parent->get_base_route() . '/' . $this->get_name(); } /** * Get controller route. * * @return string */ public function get_controller_route() { return $this->get_namespace() . '/' . $this->get_base_route(); } /** * Retrieves rest route(s) index for current controller. * * @return \WP_REST_Response|\WP_Error */ public function get_controller_index() { $server = rest_get_server(); $routes = $server->get_routes(); $endpoints = array_intersect_key( $server->get_routes(), $routes ); $controller_route = $this->get_controller_route(); array_walk( $endpoints, function ( &$item, $endpoint ) use ( &$endpoints, $controller_route ) { if ( ! strstr( $endpoint, $controller_route ) ) { unset( $endpoints[ $endpoint ] ); } } ); $data = [ 'namespace' => $this->get_namespace(), 'controller' => $controller_route, 'routes' => $server->get_data_for_routes( $endpoints ), ]; $response = rest_ensure_response( $data ); // Link to the root index. $response->add_link( 'up', rest_url( '/' ) ); return $response; } /** * Get items args of index endpoint. * * Is method is used when `get_collection_params()` is not enough, and need of knowing the methods is required. * * @param string $methods * * @return array */ public function get_items_args( $methods ) { if ( \WP_REST_Server::READABLE === $methods ) { return $this->get_collection_params(); } return []; } /** * Get item args of index endpoint. * * @param string $methods * * @return array */ public function get_item_args( $methods ) { return []; } /** * Get permission callback. * * Default controller permission callback. * By default endpoint will inherit the permission callback from the controller. * * @param \WP_REST_Request $request * * @return bool */ public function get_permission_callback( $request ) { $is_multi = (bool) $request->get_param( 'is_multi' ); $result = false; // The function is public since endpoint need to access it. // Utilize 'WP_REST_Controller' get_permission_check methods. switch ( $request->get_method() ) { case 'GET': $result = $is_multi ? $this->get_items_permissions_check( $request ) : $this->get_item_permissions_check( $request ); break; case 'POST': $result = $is_multi ? $this->create_items_permissions_check( $request ) : $this->create_item_permissions_check( $request ); break; case 'UPDATE': case 'PUT': case 'PATCH': $result = $is_multi ? $this->update_items_permissions_check( $request ) : $this->update_item_permissions_check( $request ); break; case 'DELETE': $result = $is_multi ? $this->delete_items_permissions_check( $request ) : $this->delete_item_permissions_check( $request ); break; } if ( $result instanceof \WP_Error ) { throw new WP_Error_Exception( $result ); } return $result; } /** * Checks if a given request has access to create items. ** * * @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_items_permissions_check( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Checks if a given request has access to update items. * * @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_items_permissions_check( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Checks if a given request has access to delete items. * * @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 ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } public function get_items( $request ) { return $this->get_controller_index(); } /** * Creates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function create_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Updates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Delete multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function delete_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Get the parent controller. * * @return \Elementor\Data\V2\Base\Controller|null */ public function get_parent() { return $this->parent; } /** * Get sub controller(s). * * @return \Elementor\Data\V2\Base\Controller[] */ public function get_sub_controllers() { return $this->sub_controllers; } /** * Get processors. * * @param string $command * * @return \Elementor\Data\V2\Base\Processor[] */ public function get_processors( $command ) { $result = []; if ( isset( $this->processors[ $command ] ) ) { $result = $this->processors[ $command ]; } return $result; } /** * Register processors. */ public function register_processors() { } /** * Register index endpoint. */ protected function register_index_endpoint() { if ( ! $this->parent ) { $this->register_endpoint( new Endpoint\Index( $this ) ); return; } $this->register_endpoint( new Endpoint\Index\Sub_Index_Endpoint( $this ) ); } /** * Register endpoint. * * @param \Elementor\Data\V2\Base\Endpoint $endpoint * * @return \Elementor\Data\V2\Base\Endpoint */ protected function register_endpoint( Endpoint $endpoint ) { $command = $endpoint->get_full_command(); if ( $endpoint instanceof Endpoint\Index ) { $this->index_endpoint = $endpoint; } else { $this->endpoints[ $command ] = $endpoint; } $format = $endpoint->get_format(); // `$e.data.registerFormat()`. Manager::instance()->register_endpoint_format( $command, $format ); return $endpoint; } /** * Register a processor. * * That will be later attached to the endpoint class. * * @param Processor $processor * * @return \Elementor\Data\V2\Base\Processor $processor_instance */ protected function register_processor( Processor $processor ) { $command = $processor->get_command(); if ( ! isset( $this->processors[ $command ] ) ) { $this->processors[ $command ] = []; } $this->processors[ $command ] [] = $processor; return $processor; } /** * Register. * * Endpoints & processors. */ protected function register() { $this->register_index_endpoint(); $this->register_endpoints(); // Aka hooks. $this->register_processors(); } /** * Get collection params by 'additionalProperties' context. * * @param string $context * * @return array */ protected function get_collection_params_by_additional_props_context( $context ) { $result = []; $collection_params = $this->get_collection_params(); foreach ( $collection_params as $collection_param_key => $collection_param ) { if ( isset( $collection_param['additionalProperties']['context'] ) && $context === $collection_param['additionalProperties']['context'] ) { $result[ $collection_param_key ] = $collection_param; } } return $result; } /** * When `$this->get_parent_name` is extended, the controller will have a parent, and will know to behave like a sub-controller. * * @param string $parent_name */ private function act_as_sub_controller( $parent_name ) { $this->parent = Manager::instance()->get_controller( $parent_name ); if ( ! $this->parent ) { trigger_error( "Cannot find parent controller: '$parent_name'", E_USER_ERROR ); // phpcs:ignore } $this->parent->sub_controllers [] = $this; } /** * Controller constructor. * * Register endpoints on 'rest_api_init'. */ public function __construct() { $this->namespace = static::get_default_namespace() . '/v' . static::get_default_version(); $this->rest_base = $this->get_name(); add_action( 'rest_api_init', function () { $this->register(); // Because 'register' is protected. } ); /** * Since all actions were removed for custom internal REST server. * Re-add the actions. */ add_action( 'elementor_rest_api_before_init', function () { add_action( 'rest_api_init', function () { $this->register(); } ); } ); $parent_name = $this->get_parent_name(); if ( $parent_name ) { $this->act_as_sub_controller( $parent_name ); } } } v2/base/exceptions/error-404.php000064400000000533147207001150012333 0ustar00get_error_message(), $wp_error->get_error_code(), [ 'status' => $wp_error->get_error_code(), ] ); } } v2/base/exceptions/data-exception.php000064400000002155147207001150013604 0ustar00 '', 'data' => [], ]; public function get_code() { return 'reset-http-error'; } public function get_message() { return '501 Not Implemented'; } public function get_data() { return [ 'status' => $this->get_http_error_code(), // 'status' is used by WP to pass the http error code. ]; } public function to_wp_error() { return new \WP_Error( $this->custom_data['code'], $this->message, $this->custom_data['data'] ); } protected function get_http_error_code() { return 501; // 501 Not Implemented } protected function apply() {} public function __construct( $message = '', $code = '', $data = [] ) { $this->message = empty( $message ) ? $this->get_message() : $message; $this->custom_data['code'] = empty( $code ) ? $this->get_code() : $code; $this->custom_data['data'] = empty( $data ) ? $this->get_data() : $data; parent::__construct( $this->message, 0, null ); $this->apply(); } } v2/base/exceptions/error-500.php000064400000000556147207001150012335 0ustar00controllers; } /** * @param string $name * * @return \Elementor\Data\V2\Base\Controller|false */ public function get_controller( $name ) { if ( isset( $this->controllers[ $name ] ) ) { return $this->controllers[ $name ]; } return false; } private function get_cache( $key ) { return self::get_items( $this->cache, $key ); } private function set_cache( $key, $value ) { $this->cache[ $key ] = $value; } /** * Register controller. * * @param \Elementor\Data\V2\Base\Controller $controller_instance * * @return \Elementor\Data\V2\Base\Controller */ public function register_controller( Controller $controller_instance ) { $this->controllers[ $controller_instance->get_name() ] = $controller_instance; return $controller_instance; } /** * Register endpoint format. * * @param string $command * @param string $format * */ public function register_endpoint_format( $command, $format ) { $this->command_formats[ $command ] = untrailingslashit( $format ); } /** * Find controller instance. * * By given command name. * * @param string $command * * @return false|\Elementor\Data\V2\Base\Controller */ public function find_controller_instance( $command ) { $command_parts = explode( '/', $command ); $assumed_command_parts = []; foreach ( $command_parts as $command_part ) { $assumed_command_parts [] = $command_part; foreach ( $this->controllers as $controller_name => $controller ) { $assumed_command = implode( '/', $assumed_command_parts ); if ( $assumed_command === $controller_name ) { return $controller; } } } return false; } /** * Command extract args. * * @param string $command * @param array $args * * @return \stdClass */ public function command_extract_args( $command, $args = [] ) { $result = new \stdClass(); $result->command = $command; $result->args = $args; if ( false !== strpos( $command, '?' ) ) { $command_parts = explode( '?', $command ); $pure_command = $command_parts[0]; $query_string = $command_parts[1]; parse_str( $query_string, $temp ); $result->command = untrailingslashit( $pure_command ); $result->args = array_merge( $args, $temp ); } return $result; } /** * Command to endpoint. * * Format is required otherwise $command will returned. * * @param string $command * @param string $format * @param array $args * * @return string endpoint */ public function command_to_endpoint( $command, $format, $args ) { $endpoint = $command; if ( $format ) { $formatted = $format; array_walk( $args, function ( $val, $key ) use ( &$formatted ) { $formatted = str_replace( '{' . $key . '}', $val, $formatted ); } ); // Remove remaining format if not requested via `$args`. if ( strstr( $formatted, '/{' ) ) { /** * Example: * $command = 'example/documents'; * $format = 'example/documents/{document_id}/elements/{element_id}'; * $formatted = 'example/documents/1618/elements/{element_id}'; * Result: * $formatted = 'example/documents/1618/elements'; */ $formatted = substr( $formatted, 0, strpos( $formatted, '/{' ) ); } $endpoint = $formatted; } return $endpoint; } /** * Run server. * * Init WordPress reset api. * * @return \WP_REST_Server */ public function run_server() { /** * If run_server() called means, that rest api is simulated from the backend. */ $this->is_internal = true; if ( ! $this->server ) { // Remove all 'rest_api_init' actions. remove_all_actions( 'rest_api_init' ); // Call custom reset api loader. do_action( 'elementor_rest_api_before_init' ); $this->server = rest_get_server(); // Init API. } return $this->server; } /** * Kill server. * * Free server and controllers. */ public function kill_server() { global $wp_rest_server; $this->controllers = []; $this->command_formats = []; $this->server = false; $this->is_internal = false; $this->cache = []; $wp_rest_server = false; } /** * Run processor. * * @param \Elementor\Data\V2\Base\Processor $processor * @param array $data * * @return mixed */ public function run_processor( $processor, $data ) { if ( call_user_func_array( [ $processor, 'get_conditions' ], $data ) ) { return call_user_func_array( [ $processor, 'apply' ], $data ); } return null; } /** * Run processors. * * Filter them by class. * * @param \Elementor\Data\V2\Base\Processor[] $processors * @param string $filter_by_class * @param array $data * * @return false|array */ public function run_processors( $processors, $filter_by_class, $data ) { foreach ( $processors as $processor ) { if ( $processor instanceof $filter_by_class ) { if ( Processor\Before::class === $filter_by_class ) { $this->run_processor( $processor, $data ); } elseif ( Processor\After::class === $filter_by_class ) { $result = $this->run_processor( $processor, $data ); if ( $result ) { $data[1] = $result; } } else { trigger_error( "Invalid processor filter: '\${ $filter_by_class }'" ); // phpcs:ignore break; } } } return isset( $data[1] ) ? $data[1] : false; } /** * Run request. * * Simulate rest API from within the backend. * Use args as query. * * @param string $endpoint * @param array $args * @param string $method * @param string $namespace (optional) * @param string $version (optional) * * @return \WP_REST_Response */ public function run_request( $endpoint, $args = [], $method = \WP_REST_Server::READABLE, $namespace = self::ROOT_NAMESPACE, $version = self::VERSION ) { $this->run_server(); $endpoint = '/' . $namespace . '/v' . $version . '/' . trim( $endpoint, '/' ); // Run reset api. $request = new \WP_REST_Request( $method, $endpoint ); if ( 'GET' === $method ) { $request->set_query_params( $args ); } else { $request->set_body_params( $args ); } return rest_do_request( $request ); } /** * Run endpoint. * * Wrapper for `$this->run_request` return `$response->getData()` instead of `$response`. * * @param string $endpoint * @param array $args * @param string $method * * @return array */ public function run_endpoint( $endpoint, $args = [], $method = 'GET' ) { // The method become public since it used in `Elementor\Data\V2\Base\Endpoint\Index\AllChildren`. $response = $this->run_request( $endpoint, $args, $method ); return $response->get_data(); } /** * Run ( simulated reset api ). * * Do: * Init reset server. * Run before processors. * Run command as reset api endpoint from internal. * Run after processors. * * @param string $command * @param array $args * @param string $method * * @return array|false processed result */ public function run( $command, $args = [], $method = 'GET' ) { $key = crc32( $command . '-' . wp_json_encode( $args ) . '-' . $method ); $cache = $this->get_cache( $key ); if ( $cache ) { return $cache; } $this->run_server(); $controller_instance = $this->find_controller_instance( $command ); if ( ! $controller_instance ) { $this->set_cache( $key, [] ); return []; } $extracted_command = $this->command_extract_args( $command, $args ); $command = $extracted_command->command; $args = $extracted_command->args; $format = isset( $this->command_formats[ $command ] ) ? $this->command_formats[ $command ] : false; $command_processors = $controller_instance->get_processors( $command ); $endpoint = $this->command_to_endpoint( $command, $format, $args ); $this->run_processors( $command_processors, Processor\Before::class, [ $args ] ); $response = $this->run_request( $endpoint, $args, $method ); $result = $response->get_data(); if ( $response->is_error() ) { $this->set_cache( $key, [] ); return []; } $result = $this->run_processors( $command_processors, Processor\After::class, [ $args, $result ] ); $this->set_cache( $key, $result ); return $result; } public function is_internal() { return $this->is_internal; } } Transient.php000064400000003447147207162400007237 0ustar00getKey(); set_transient( $key, $this->getSerializable(), $this->ttl ); $this->clean(); return $this; } /** * Retrieve and unserialize this object from WordPress transient cache * @return bool whether object existed in cache */ public function fetch(){ $key = 'loco_'.$this->getKey(); $data = get_transient( $key ); try { $this->setUnserialized($data); return true; } catch( InvalidArgumentException $e ){ return false; } } /** * @param int * @return self */ public function setLifespan( $ttl ){ $this->ttl = (int) $ttl; return $this; } /** * Set keep-alive interval * @param int * @return self */ public function keepAlive( $timeout ){ $time = $this->getTimestamp(); // legacy objects (with ttl=0) had no timestamp, so will always be touched. // make dirty if this number of seconds has elapsed since last persisted. if( time() > ( $time + $timeout ) ){ $this->touch()->persistLazily(); } return $this; } }Settings.php000064400000020336147207162400007064 0ustar00 '', 'gen_hash' => false, 'use_fuzzy' => true, 'fuzziness' => 20, 'num_backups' => 5, 'pot_alias' => [ 'default.po', 'en_US.po', 'en.po' ], 'php_alias' => [ 'php', 'twig' ], 'jsx_alias' => [], 'fs_persist' => false, 'fs_protect' => 1, 'pot_protect' => 1, 'pot_expected' => 1, 'max_php_size' => '100K', 'po_utf8_bom' => false, 'po_width' => '79', 'jed_pretty' => false, 'jed_clean' => false, 'ajax_files' => true, 'deepl_api_key' => '', 'google_api_key' => '', 'microsoft_api_key' => '', 'microsoft_api_region' => 'global', 'lecto_api_key' => '', ]; /** * Create default settings instance * @return Loco_data_Settings */ public static function create(){ $args = self::$defaults; $args['version'] = loco_plugin_version(); return new Loco_data_Settings( $args ); } /** * Get currently configured global settings * @return Loco_data_Settings */ public static function get(){ $opts = self::$current; if( ! $opts ){ $opts = self::create(); $opts->fetch(); self::$current = $opts; // allow hooks to modify settings do_action('loco_settings', $opts ); } return $opts; } /** * Destroy current settings * @return void */ public static function clear(){ delete_option('loco_settings'); self::$current = null; } /** * Destroy current settings and return a fresh one * @return Loco_data_Settings */ public static function reset(){ self::clear(); return self::$current = self::create(); } /** * {@inheritdoc} */ public function offsetSet( $prop, $value ){ $value = parent::cast($prop,$value,self::$defaults); parent::offsetSet( $prop, $value ); } /** * Commit current settings to WordPress DB * @return bool */ public function persist(){ $this->version = loco_plugin_version(); $this->clean(); return update_option('loco_settings', $this->getSerializable() ); } /** * Pull current settings from WordPress DB and merge into this object * @return bool whether settings where previously saved */ public function fetch(){ $data = get_option('loco_settings'); if( is_array($data) ){ $copy = new Loco_data_Settings; $copy->setUnserialized($data); // preserve any defaults not in previously saved data // this will occur if we've added options since setting were saved $data = $copy->getArrayCopy() + $this->getArrayCopy(); // could ensure redundant keys are removed, but no need currently // $data = array_intersect_key( $data, self::$defaults ); $this->exchangeArray( $data ); $this->clean(); return true; } return false; } /** * Run migration in case plugin has been upgraded since settings last saved * @return bool whether upgrade has occurred */ public function migrate(){ $updated = false; // Always update version number in settings after an upgrade $old = $this->version; $new = loco_plugin_version(); if( version_compare($old,$new,'<') ){ $this->persist(); $updated = true; /*/ feature alerts: if( '2.6.' === substr($new,0,4) && '2.6.' !== substr($old,0,4) ){ Loco_error_AdminNotices::info( __('Loco Translate 2.6 adds ......','loco-translate') ) ->addLink( apply_filters('loco_external','https://localise.biz/wordpress/plugin/changelog'), __('Documentation','loco-translate') ); }*/ } return $updated; } /** * Populate ALL settings from raw postdata. * @param array $data Posted setting values * @param array|null $filter Optional filter to restrict modifiable values * @return Loco_data_Settings */ public function populate( array $data, $filter = null ){ // set all keys present in posted data foreach( $data as $prop => $value ){ try { if( is_null($filter) || in_array($prop,$filter,true) ) { $this->offsetSet( $prop, $value ); } } catch( InvalidArgumentException $e ){ // skipping invalid key } } // set missing boolean keys as false, because unchecked checkboxes won't post anything $defaults = self::$defaults; if( is_array($filter) ){ $defaults = array_intersect_key( array_flip($filter) ,$defaults); } foreach( array_diff_key($defaults,$data) as $prop => $default ){ if( is_bool($default) ){ parent::offsetSet( $prop, false ); } } // enforce missing values that must have a default, but were passed empty foreach( ['php_alias','max_php_size','po_width'] as $prop ){ if( isset($data[$prop]) && '' === $data[$prop] ){ parent::offsetSet( $prop, self::$defaults[$prop] ); } } return $this; } /** * Map a file extension to registered types, defaults to "php" * @param string $ext File extension * @param string $default Optional default * @return string php, js, json, twig or $default */ public function ext2type( $ext, $default = 'php' ){ $types = ['php'=>'php', 'js'=>'js', 'json'=>'json', 'twig'=>'twig'] // <- canonical + array_fill_keys( $this->php_alias, 'php') + array_fill_keys( $this->jsx_alias, 'js') ; $ext = strtolower($ext); return isset($types[$ext]) ? $types[$ext] : $default; } } Option.php000064400000002441147207162400006531 0ustar00getKey(); return update_option( $key, $this->getSerializable(), false ); } /** * Retrieve and unserialize this object from WordPress options table * @return bool whether object existed in cache */ public function fetch(){ $key = 'loco_'.$this->getKey(); if( $data = get_option($key) ){ try { $this->setUnserialized($data); return true; } catch( InvalidArgumentException $e ){ // suppress validation error // @codeCoverageIgnore } } return false; } /** * Delete option from WordPress */ public function remove(){ $key = 'loco_'.$this->getKey(); return delete_option( $key ); } }CompiledData.php000064400000003572147207162400007615 0ustar00data = loco_include( $path ); $this->name = $name; } public function destroy(){ unset( self::$reg[$this->name], $this->data ); } #[ReturnTypeWillChange] public function offsetGet( $k ){ return isset($this->data[$k]) ? $this->data[$k] : null; } #[ReturnTypeWillChange] public function offsetExists( $k ){ return isset($this->data[$k]); } #[ReturnTypeWillChange] public function offsetUnset( $k ){ throw new RuntimeException('Read only'); } #[ReturnTypeWillChange] public function offsetSet( $k, $v ){ throw new RuntimeException('Read only'); } #[ReturnTypeWillChange] public function count(){ return count($this->data); } /** * Implements IteratorAggregate::getIterator * @return ArrayIterator */ #[ReturnTypeWillChange] public function getIterator(){ return new ArrayIterator( $this->data ); } }Session.php000064400000011243147207162400006704 0ustar00clear(); } catch( Exception $e ){ // probably no session to destroy } self::$current = null; } } /** * Commit current session data to WordPress storage and remove from memory */ public static function close(){ if( self::$current && self::$current->dirty ){ self::$current->persist(); self::$current = null; } } /** * @internal */ final public function __construct( array $raw = [] ){ $this->token = wp_get_session_token(); if( ! $this->token ){ throw new Loco_error_Exception('Failed to get session token'); } parent::__construct( [] ); $this->manager = WP_Session_Tokens::get_instance( get_current_user_id() ); // populate object from stored session data $data = $this->getRaw(); if( isset($data['loco']) ){ $this->setUnserialized( $data['loco'] ); } // any initial arbitrary data can be merged on top foreach( $raw as $prop => $value ){ $this[$prop] = $value; } // enforce single instance self::$current = $this; // ensure against unclean shutdown if( loco_debugging() ){ register_shutdown_function( [$this,'_on_shutdown'] ); } } /** * @internal * Ensure against unclean use of session storage */ public function _on_shutdown(){ if( $this->dirty ){ trigger_error('Unclean session shutdown: call either Loco_data_Session::destroy or Loco_data_Session::close'); } } /** * Get raw session data held by WordPress * @return array */ private function getRaw(){ $data = $this->manager->get( $this->token ); // session data will exist if WordPress login is valid if( ! $data || ! is_array($data) ){ throw new Loco_error_Exception('Invalid session'); } return $data; } /** * Persist object in WordPress usermeta table * @return Loco_data_Session */ public function persist(){ $data = $this->getRaw(); $data['loco'] = $this->getSerializable(); $this->manager->update( $this->token, $data ); $this->dirty = false; return $this; } /** * Clear object data and remove our key from WordPress usermeta record * @return Loco_data_Session */ public function clear(){ $data = $this->getRaw(); if( isset($data['loco']) ){ unset( $data['loco'] ); $this->manager->update( $this->token, $data ); } $this->exchangeArray( [] ); $this->dirty = false; return $this; } /** * @param string name of messages bag, e.g. "errors" * @param string optionally put message in rather than getting data out * @return mixed */ public function flash( $bag, $data = null ){ if( isset($data) ){ $this->dirty = true; $this[$bag][] = $data; } // else get first object in bag and remove before returning else if( isset($this[$bag]) ){ if( $data = array_shift($this[$bag]) ){ $this->dirty = true; return $data; } } return null; } /** * {@inheritDoc} */ public function offsetSet( $index, $newval ){ if( ! isset($this[$index]) || $newval !== $this[$index] ){ $this->dirty = true; parent::offsetSet( $index, $newval ); } } /** * {@inheritDoc} */ public function offsetUnset( $index ){ if( isset($this[$index]) ){ $this->dirty = true; parent::offsetUnset( $index ); } } } Upload.php000064400000006226147207162400006512 0ustar00getContents(); } /** * @param array $data member of $_FILE * @throws Loco_error_UploadException */ public function __construct( array $data ){ // https://www.php.net/manual/en/features.file-upload.errors.php $code = (int) $data['error']; switch( $code ){ case UPLOAD_ERR_OK: break; case UPLOAD_ERR_INI_SIZE: throw new Loco_error_UploadException('The uploaded file exceeds the upload_max_filesize directive in php.ini',$code); case UPLOAD_ERR_FORM_SIZE: throw new Loco_error_UploadException('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',$code); case UPLOAD_ERR_PARTIAL: throw new Loco_error_UploadException('The uploaded file was only partially uploaded',$code); case UPLOAD_ERR_NO_FILE: throw new Loco_error_UploadException('No file was uploaded, or data is empty',$code); case UPLOAD_ERR_NO_TMP_DIR: throw new Loco_error_UploadException('Missing temporary folder for uploaded file',$code); case UPLOAD_ERR_CANT_WRITE: throw new Loco_error_UploadException('Failed to save uploaded file to disk',$code); case UPLOAD_ERR_EXTENSION: throw new Loco_error_UploadException('Your server blocked the file upload',$code); default: throw new Loco_error_UploadException('Unknown file upload error',$code); } // Upload is OK according to PHP, but may need moving (if upload_tmp_dir is not in open_basedir) $path = $data['tmp_name']; if( ! Loco_fs_File::is_readable($path) ){ $safe = get_temp_dir().basename($path); if( $safe !== $path ){ Loco_error_AdminNotices::debug( sprintf('Moving uploaded file: %s -> %s',$path,$safe) ); if( move_uploaded_file( $path, $safe ) ){ register_shutdown_function('unlink',$safe); $data['tmp_name'] = $safe; $path = $safe; } } } if( ! is_file($path) ){ throw new Loco_error_UploadException('Uploaded file is not readable',UPLOAD_ERR_NO_FILE); } // upload ok, but check it's not empty if( 0 === filesize($path) ){ throw new Loco_error_UploadException('Uploaded file contains no data',UPLOAD_ERR_NO_FILE); } $this->data = $data; parent::__construct($path); } /** * Get name of original file * @return string */ public function getOriginalName(){ return $this->data['name']; } } Serializable.php000064400000012036147207162400007670 0ustar00setFlags( ArrayObject::ARRAY_AS_PROPS ); parent::__construct( $data ); $this->dirty = (bool) $data; } /** * @internal */ final public function __destruct(){ if( $this->lazy ){ $this->persistIfDirty(); } } /** * Check if object's properties have change since last clean * @return bool */ public function isDirty(){ return $this->dirty; } /** * Make not dirty * @return self */ protected function clean(){ $this->dirty = false; return $this; } /** * Force dirtiness for next check * @return static */ protected function touch(){ $this->dirty = true; return $this; } /** * Enable lazy persistence on object destruction, if dirty * @return static */ public function persistLazily(){ $this->lazy = true; return $this; } /** * Call persist method only if has changed since last clean * @return static */ public function persistIfDirty(){ if( $this->isDirty() ){ $this->persist(); } return $this; } /** * {@inheritdoc} * override so we can set dirty flag */ #[ReturnTypeWillChange] public function offsetSet( $key, $value ){ if( ! isset($this[$key]) || $value !== $this[$key] ){ parent::offsetSet( $key, $value ); $this->dirty = true; } } /** * {@inheritdoc} * override so we can set dirty flag */ #[ReturnTypeWillChange] public function offsetUnset( $key ){ if( isset($this[$key]) ){ parent::offsetUnset($key); $this->dirty = true; } } /** * @param string|int|float $version * @return self */ public function setVersion( $version ){ if( $version !== $this->v ){ $this->v = $version; $this->dirty = true; } return $this; } /** * @return string|int|float */ public function getVersion(){ return $this->v; } /** * @return int */ public function getTimestamp(){ return $this->t; } /** * Get serializable data for storage * @return array */ protected function getSerializable(){ return [ 'c' => get_class($this), 'v' => $this->getVersion(), 'd' => $this->getArrayCopy(), 't' => time(), ]; } /** * Restore object state from array as returned from getSerializable * @param array $data * @return self */ protected function setUnserialized( $data ){ if( ! is_array($data) || ! isset($data['d']) ) { throw new InvalidArgumentException('Unexpected data'); } if( get_class($this) !== $data['c'] ){ throw new InvalidArgumentException('Unexpected class name'); } // ok to populate ArrayObject $this->exchangeArray( $data['d'] ); // setting version as it was in database $this->setVersion( $data['v'] ); // timestamp may not be present in old objects $this->t = isset($data['t']) ? $data['t'] : 0; // object is being restored, probably from disk so start with clean state $this->dirty = false; return $this; } /** * @param string $prop * @param mixed $value * @param array $defaults * @return mixed */ protected static function cast( $prop, $value, array $defaults ){ if( ! array_key_exists($prop,$defaults) ){ throw new InvalidArgumentException('Invalid option, '.$prop ); } $default = $defaults[$prop]; // cast to same type as default if( is_bool($default) ){ $value = (bool) $value; } else if( is_int($default) ){ $value = (int) $value; } else if( is_array($default) ){ if( ! is_array($value) ){ $value = preg_split( '/[\\s,]+/', trim($value), -1, PREG_SPLIT_NO_EMPTY ); } } else { $value = (string) $value; } return $value; } }Cookie.php000064400000003521147207162400006472 0ustar00setName( $name ); } } } /** * @internal */ public function __toString(){ $data = $this->getArrayCopy(); return http_build_query( $data, null, '&' ); } /** * @return Loco_data_Cookie */ public function setName( $name ){ $this->name = $name; return $this; } /** * @return string */ public function getName(){ return $this->name; } /** * Send cookie to the browser, unless filtered out. * @return bool|null */ public function send(){ if( false !== apply_filters( 'loco_setcookie', $this ) ){ $value = (string) $this; // @codeCoverageIgnoreStart return setcookie( $this->name, $value, $this->expires, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true ); } } /** * Empty values such that sending cookie would remove it from browser * @return Loco_data_Cookie */ public function kill(){ $this->exchangeArray( [] ); $this->expires = time() - 86400; return $this; } }Preferences.php000064400000011223147207162400007520 0ustar00 '', 'locales' => [], ]; /** * Get current user's preferences * @return Loco_data_Preferences */ public static function get(){ $id = get_current_user_id(); if( ! $id ){ // allow null return only on command line. All web users must be logged in if( 'cli' === PHP_SAPI || defined('LOCO_TEST') ){ return null; } throw new Exception( 'No current user' ); // @codeCoverageIgnore } if( isset(self::$current[$id]) ){ return self::$current[$id]; } $prefs = self::create($id); self::$current[$id] = $prefs; $prefs->fetch(); return $prefs; } /** * Create default settings instance * @param int User ID * @return Loco_data_Preferences */ public static function create( $id ){ $prefs = new Loco_data_Preferences( self::$defaults ); $prefs->user_id = $id; return $prefs; } /** * Destroy current user's preferences * @return void */ public static function clear(){ get_current_user_id() && self::get()->remove(); } /** * Persist object in WordPress usermeta table * @return bool */ public function persist(){ return update_user_meta( $this->user_id, 'loco_prefs', $this->getSerializable() ) ? true : false; } /** * Retrieve and unserialize this object from WordPress usermeta table * @return bool whether object existed in cache */ public function fetch(){ $data = get_user_meta( $this->user_id, 'loco_prefs', true ); // See comments in Loco_data_Settings if( is_array($data) ){ $copy = new Loco_data_Preferences; $copy->setUnserialized($data); $data = $copy->getArrayCopy() + $this->getArrayCopy(); $this->exchangeArray($data); $this->clean(); return true; } return false; } /** * Delete usermeta entry from WordPress * return bool */ public function remove(){ $id = $this->user_id; self::$current[$id] = null; return delete_user_meta( $id, 'loco_prefs' ); } /** * Populate all settings from raw postdata. * @param array * @return Loco_data_Preferences */ public function populate( array $data ){ // set all keys present in array foreach( $data as $prop => $value ){ try { $this->offsetSet( $prop, $value ); } catch( InvalidArgumentException $e ){ // skipping invalid key } } return $this; } /** * {@inheritdoc} */ public function offsetSet( $prop, $value ){ $value = parent::cast($prop,$value,self::$defaults); parent::offsetSet( $prop, $value ); } /** * Get default Last-Translator credit * @return string */ public function default_credit(){ $user = wp_get_current_user(); $name = (string) $user->get('display_name'); if( $user->get('user_login') === $name ){ $name = ''; } return $name; } /** * Check if user wants to know about this locale * @param Loco_Locale locale to match in whitelist * @return bool */ public function has_locale( Loco_Locale $locale ){ $haystack = $this->locales; if( $haystack ){ foreach( $haystack as $tag ){ $tag = strtolower($tag); // allow language wildcard. en_GB allowed by "en" if( $locale->lang === $tag ){ return true; } // else normalize whitelist before comparison if( Loco_Locale::parse($tag)->__toString() === $locale->__toString() ){ return true; } } return false; } return true; } }RecentItems.php000064400000005705147207162400007511 0ustar00fetch(); } return self::$current; } /** * Trash data and remove from memory */ public static function destroy(){ $tmp = new Loco_data_RecentItems; $tmp->remove(); self::$current = null; } /** * @internal * @return Loco_data_RecentItems */ private function push( $object, array $indexes ){ foreach( $indexes as $key => $id ){ $stack = isset($this[$key]) ? $this[$key] : []; // remove before add ensures latest item appended to hashmap unset($stack[$id]); $stack[$id] = time(); $this[$key] = $stack; // TODO prune stack to maximum length } return $this; } /** * @return array */ private function getItems( $key, $offset, $count ){ $stack = isset($this[$key]) ? $this[$key] : []; // hash map should automatically be in "push" order, meaning most recent last // sorting gives wrong order for same-second updates (only relevant in tests, but still..) // asort( $stack, SORT_NUMERIC ); $stack = array_reverse( array_keys( $stack ) ); if( is_null($count) && 0 === $offset ){ return $stack; } return array_slice( $stack, $offset, $count, false ); } /** * @return int */ private function hasItem( $key, $id ){ if( isset($this[$key]) && ( $items = $this[$key] ) && isset($items[$id]) ){ return $items[$id]; } return 0; } /** * Push bundle to the front of recent bundles stack * @return Loco_data_RecentItems */ public function pushBundle( Loco_package_Bundle $bundle ){ return $this->push( $bundle, [ 'bundle' => $bundle->getId() ] ); } /** * Get bundle IDs * @return array */ public function getBundles( $offset = 0, $count = null ){ return $this->getItems('bundle', $offset, $count ); } /** * Check if a bundle has been recently used * @return int timestamp item was added, 0 if absent */ public function hasBundle( $id ){ return $this->hasItem( 'bundle', $id ); } /** * TODO other types of item * Push project to the front of recent bundles stack * @return Loco_data_RecentItems * public function pushProject( Loco_package_Project $project ){ return $this; }*/ } Permissions.php000064400000013474147207162400007604 0ustar00= 4.3 * @return WP_Roles */ private static function wp_roles(){ global $wp_roles; if( ! isset($wp_roles) ){ get_role('ping'); } return $wp_roles; } /** * Set up default roles and capabilities * @return WP_Roles */ public static function init(){ $roles = self::wp_roles(); $apply = []; // ensure translator role exists and is not locked out $role = $roles->get_role('translator'); if( $role instanceof WP_Role ){ $role->has_cap('read') || $role->add_cap('read'); } // else absence of translator role indicates first run // by default we'll initially allow full access to anyone that can manage_options else { $apply['translator'] = $roles->add_role( 'translator', 'Translator', ['read'=>true] ); foreach( $roles->role_objects as $id => $role ){ if( $role->has_cap('manage_options') ){ $apply[$id] = $role; } } } // fix broken permissions whereby super admin cannot access Loco at all. // this could happen if another plugin added the translator role beforehand. if( ! isset($apply['administrator']) && ! is_multisite() ){ $apply['administrator'] = $roles->get_role('administrator'); } foreach( $apply as $role ){ if( $role instanceof WP_Role ){ foreach( self::$caps as $cap ){ $role->has_cap($cap) || $role->add_cap($cap); } } } return $roles; } /** * Construct instance, ensuring default roles and capabilities exist */ public function __construct(){ self::init(); } /** * @return WP_Role[] */ public function getRoles(){ $roles = self::wp_roles(); return $roles->role_objects; } /** * Check if role is protected such that user cannot lock themselves out when modifying settings * @param WP_Role WordPress role object to check * @return bool */ public function isProtectedRole( WP_Role $role ){ // if current user has this role and is not the super user, prevent lock-out $user = wp_get_current_user(); if( $user instanceof WP_User && ! is_super_admin($user->ID) && $user->has_cap('manage_options') ){ return in_array( $role->name, $user->roles, true ); } // admin users of single site install must never be denied access // note that there is no such thing as a network admin role, but network admins have all permissions return is_multisite() ? false : $role->has_cap('delete_users'); } /** * Completely remove all Loco permissions, as if uninstalling * @return Loco_data_Permissions */ public function remove(){ /* @var $role WP_Role */ foreach( $this->getRoles() as $role ){ foreach( self::$caps as $cap ){ $role->has_cap($cap) && $role->remove_cap($cap); } } // we'll only remove our custom role if it has no capabilities other than admin access // this avoids breaking other plugins that use it, or added it before Loco was installed. if( $role = get_role('translator') ){ if( ! $role->capabilities || ['read'] === array_keys($role->capabilities) ){ remove_role('translator'); } } return $this; } /** * Reset to default: roles include no Loco capabilities unless they have super admin privileges * @return WP_Role[] */ public function reset(){ $roles = $this->getRoles(); /* @var $role WP_Role */ foreach( $roles as $role ){ // always provide access to site admins on first run $grant = $this->isProtectedRole($role); foreach( self::$caps as $cap ){ if( $grant ){ $role->has_cap($cap) || $role->add_cap($cap); } else { $role->has_cap($cap) && $role->remove_cap($cap); } } } return $roles; } /** * Get translated WordPress role name * @param string * @return string */ public function getRoleName( $id ){ if( 'translator' === $id ){ $label = _x( 'Translator', 'User role', 'loco-translate' ); } else { $names = self::wp_roles()->role_names; $label = isset($names[$id]) ? translate_user_role( $names[$id] ) : $id; } return $label; } /** * Populate permission settings from posted checkboxes * @param string[] * @return self */ public function populate( array $caps ){ // drop all permissions before adding (cos checkboxes) $roles = $this->reset(); foreach( $caps as $id => $checked ){ if( isset($roles[$id]) ){ $role = $roles[$id]; /* @var $role WP_Role */ foreach( self::$caps as $cap ){ if( ! empty($checked[$cap]) ){ $role->has_cap($cap) || $role->add_cap($cap); } } } } return $this; } } responsive-widgets.json000064400000000267147207170370011315 0ustar00{"accordion":true,"alert":true,"icon-box":true,"icon-list":true,"image-box":true,"image-gallery":true,"progress":true,"star-rating":true,"tabs":true,"toggle":true,"nested-tabs":true} gp.php000064400000001603147207170660005675 0ustar00'4.0.0','aliases'=>['arg'=>'an','bg-bg'=>'bg','bn-bd'=>'bn','bre'=>'br','bs-ba'=>'bs','ca-valencia'=>'ca-val','cs-cz'=>'cs','da-dk'=>'da','de-de'=>'de','ewe'=>'ee','en-us'=>'en','es-es'=>'es','fa-ir'=>'fa','fr-fr'=>'fr','gl-es'=>'gl','haw-us'=>'haw','he-il'=>'he','hi-in'=>'hi','hu-hu'=>'hu','id-id'=>'id','is-is'=>'is','it-it'=>'it','jv-id'=>'jv','ka-ge'=>'ka','ko-kr'=>'ko','lb-lu'=>'lb','lt-lt'=>'lt','me-me'=>'me','mg-mg'=>'mg','mk-mk'=>'mk','ml-in'=>'ml','ms-my'=>'ms','my-mm'=>'mya','ne-np'=>'ne','nb-no'=>'nb','nl-nl'=>'nl','nn-no'=>'nn','pa-in'=>'pa','art-xpirate'=>'pirate','pl-pl'=>'pl','pt-pt'=>'pt','pt-pt-ao90'=>'pt-ao90','ro-ro'=>'ro','ru-ru'=>'ru','si-lk'=>'si','sk-sk'=>'sk','sl-si'=>'sl','so-so'=>'so','sr-rs'=>'sr','su-id'=>'su','sv-se'=>'sv','ta-in'=>'ta','tr-tr'=>'tr','tt-ru'=>'tt','ug-cn'=>'ug','uz-uz'=>'uz']]; languages.php000064400000007612147207170660007243 0ustar00'Afar','ab'=>'Abkhazian','ae'=>'Avestan','af'=>'Afrikaans','ak'=>'Akan','am'=>'Amharic','an'=>'Aragonese','ar'=>'Arabic','arq'=>'Algerian Arabic','ary'=>'Moroccan Arabic','as'=>'Assamese','ast'=>'Asturian','av'=>'Avaric','ay'=>'Aymara','az'=>'Azerbaijani','azb'=>'South Azerbaijani','ba'=>'Bashkir','bal'=>'Baluchi','bcc'=>'Southern Balochi','be'=>'Belarusian','bg'=>'Bulgarian','bgn'=>'Western Balochi','bho'=>'Bhojpuri','bi'=>'Bislama','bm'=>'Bambara','bn'=>'Bengali','bo'=>'Tibetan','br'=>'Breton','brx'=>'Bodo (India)','bs'=>'Bosnian','ca'=>'Catalan','ce'=>'Chechen','ceb'=>'Cebuano','ch'=>'Chamorro','ckb'=>'Central Kurdish','co'=>'Corsican','cr'=>'Cree','cs'=>'Czech','cu'=>'Church Slavic','cv'=>'Chuvash','cy'=>'Welsh','da'=>'Danish','de'=>'German','dsb'=>'Lower Sorbian','dv'=>'Dhivehi','dz'=>'Dzongkha','ee'=>'Ewe','el'=>'Greek','en'=>'English','eo'=>'Esperanto','es'=>'Spanish','et'=>'Estonian','eu'=>'Basque','fa'=>'Persian','ff'=>'Fulah','fi'=>'Finnish','fj'=>'Fijian','fo'=>'Faroese','fon'=>'Fon','fr'=>'French','frp'=>'Arpitan','fuc'=>'Pulaar','fur'=>'Friulian','fy'=>'Western Frisian','ga'=>'Irish','gax'=>'Borana-Arsi-Guji Oromo','gd'=>'Scottish Gaelic','gl'=>'Galician','gn'=>'Guarani','gu'=>'Gujarati','gv'=>'Manx','ha'=>'Hausa','haw'=>'Hawaiian','haz'=>'Hazaragi','he'=>'Hebrew','hi'=>'Hindi','ho'=>'Hiri Motu','hr'=>'Croatian','hsb'=>'Upper Sorbian','ht'=>'Haitian','hu'=>'Hungarian','hy'=>'Armenian','hz'=>'Herero','ia'=>'Interlingua','id'=>'Indonesian','ie'=>'Interlingue','ig'=>'Igbo','ii'=>'Sichuan Yi','ik'=>'Inupiaq','io'=>'Ido','is'=>'Icelandic','it'=>'Italian','iu'=>'Inuktitut','ja'=>'Japanese','jv'=>'Javanese','ka'=>'Georgian','kaa'=>'Kara-Kalpak','kab'=>'Kabyle','kg'=>'Kongo','ki'=>'Kikuyu','kj'=>'Kuanyama','kk'=>'Kazakh','kl'=>'Kalaallisut','km'=>'Central Khmer','kmr'=>'Northern Kurdish','kn'=>'Kannada','ko'=>'Korean','kr'=>'Kanuri','ks'=>'Kashmiri','ku'=>'Kurdish','kv'=>'Komi','kw'=>'Cornish','ky'=>'Kirghiz','la'=>'Latin','lb'=>'Luxembourgish','lg'=>'Ganda','li'=>'Limburgan','lij'=>'Ligurian','lmo'=>'Lombard','ln'=>'Lingala','lo'=>'Lao','lt'=>'Lithuanian','lu'=>'Luba-Katanga','lv'=>'Latvian','mai'=>'Maithili','mfe'=>'Morisyen','mg'=>'Malagasy','mh'=>'Marshallese','mi'=>'Maori','mk'=>'Macedonian','ml'=>'Malayalam','mn'=>'Mongolian','mr'=>'Marathi','ms'=>'Malay','mt'=>'Maltese','my'=>'Burmese','na'=>'Nauru','nb'=>'Norwegian Bokmål','nd'=>'North Ndebele','ne'=>'Nepali','ng'=>'Ndonga','nl'=>'Dutch','nn'=>'Norwegian Nynorsk','no'=>'Norwegian','nqo'=>'N\'Ko','nr'=>'South Ndebele','nv'=>'Navajo','ny'=>'Nyanja','oc'=>'Occitan (post 1500)','oj'=>'Ojibwa','om'=>'Oromo','or'=>'Oriya','ory'=>'Oriya (individual language)','os'=>'Ossetian','pa'=>'Panjabi','pap'=>'Papiamento','pcd'=>'Picard','pcm'=>'Nigerian Pidgin','pi'=>'Pali','pl'=>'Polish','ps'=>'Pushto','pt'=>'Portuguese','qu'=>'Quechua','rhg'=>'Rohingya','rm'=>'Romansh','rn'=>'Rundi','ro'=>'Romanian','ru'=>'Russian','rw'=>'Kinyarwanda','sa'=>'Sanskrit','sah'=>'Yakut','sc'=>'Sardinian','scn'=>'Sicilian','sd'=>'Sindhi','se'=>'Northern Sami','sg'=>'Sango','sh'=>'Serbo-Croatian','si'=>'Sinhala','sk'=>'Slovak','skr'=>'Saraiki','sl'=>'Slovenian','sm'=>'Samoan','sn'=>'Shona','so'=>'Somali','sq'=>'Albanian','sr'=>'Serbian','ss'=>'Swati','st'=>'Southern Sotho','su'=>'Sundanese','sv'=>'Swedish','sw'=>'Swahili','syr'=>'Syriac','szl'=>'Silesian','ta'=>'Tamil','te'=>'Telugu','tg'=>'Tajik','th'=>'Thai','ti'=>'Tigrinya','tk'=>'Turkmen','tl'=>'Tagalog','tn'=>'Tswana','to'=>'Tonga (Tonga Islands)','tr'=>'Turkish','ts'=>'Tsonga','tt'=>'Tatar','tw'=>'Twi','twd'=>'Twents','ty'=>'Tahitian','tzm'=>'Central Atlas Tamazight','ug'=>'Uighur','uk'=>'Ukrainian','ur'=>'Urdu','uz'=>'Uzbek','ve'=>'Venda','vec'=>'Venetian','vi'=>'Vietnamese','vo'=>'Volapük','wa'=>'Walloon','wo'=>'Wolof','xh'=>'Xhosa','yi'=>'Yiddish','yo'=>'Yoruba','za'=>'Zhuang','zgh'=>'Standard Moroccan Tamazight','zh'=>'Chinese','zu'=>'Zulu','tlh'=>'Klingon'];regions.php000064400000011721147207170660006737 0ustar00'Andorra','AE'=>'United Arab Emirates','AF'=>'Afghanistan','AG'=>'Antigua and Barbuda','AI'=>'Anguilla','AL'=>'Albania','AM'=>'Armenia','AO'=>'Angola','AQ'=>'Antarctica','AR'=>'Argentina','AS'=>'American Samoa','AT'=>'Austria','AU'=>'Australia','AW'=>'Aruba','AX'=>'Åland Islands','AZ'=>'Azerbaijan','BA'=>'Bosnia and Herzegovina','BB'=>'Barbados','BD'=>'Bangladesh','BE'=>'Belgium','BF'=>'Burkina Faso','BG'=>'Bulgaria','BH'=>'Bahrain','BI'=>'Burundi','BJ'=>'Benin','BL'=>'Saint Barthélemy','BM'=>'Bermuda','BN'=>'Brunei Darussalam','BO'=>'Bolivia','BQ'=>'Bonaire, Sint Eustatius and Saba','BR'=>'Brazil','BS'=>'Bahamas','BT'=>'Bhutan','BV'=>'Bouvet Island','BW'=>'Botswana','BY'=>'Belarus','BZ'=>'Belize','CA'=>'Canada','CC'=>'Cocos (Keeling) Islands','CD'=>'The Democratic Republic of the Congo','CF'=>'Central African Republic','CG'=>'Congo','CH'=>'Switzerland','CI'=>'Côte d\'Ivoire','CK'=>'Cook Islands','CL'=>'Chile','CM'=>'Cameroon','CN'=>'China','CO'=>'Colombia','CR'=>'Costa Rica','CU'=>'Cuba','CV'=>'Cabo Verde; Cape Verde','CW'=>'Curaçao','CX'=>'Christmas Island','CY'=>'Cyprus','CZ'=>'Czech Republic','DE'=>'Germany','DJ'=>'Djibouti','DK'=>'Denmark','DM'=>'Dominica','DO'=>'Dominican Republic','DZ'=>'Algeria','EC'=>'Ecuador','EE'=>'Estonia','EG'=>'Egypt','EH'=>'Western Sahara','ER'=>'Eritrea','ES'=>'Spain','ET'=>'Ethiopia','FI'=>'Finland','FJ'=>'Fiji','FK'=>'Falkland Islands (Malvinas)','FM'=>'Federated States of Micronesia','FO'=>'Faroe Islands','FR'=>'France','GA'=>'Gabon','GB'=>'United Kingdom','GD'=>'Grenada','GE'=>'Georgia','GF'=>'French Guiana','GG'=>'Guernsey','GH'=>'Ghana','GI'=>'Gibraltar','GL'=>'Greenland','GM'=>'Gambia','GN'=>'Guinea','GP'=>'Guadeloupe','GQ'=>'Equatorial Guinea','GR'=>'Greece','GS'=>'South Georgia and the South Sandwich Islands','GT'=>'Guatemala','GU'=>'Guam','GW'=>'Guinea-Bissau','GY'=>'Guyana','HK'=>'Hong Kong','HM'=>'Heard Island and McDonald Islands','HN'=>'Honduras','HR'=>'Croatia','HT'=>'Haiti','HU'=>'Hungary','ID'=>'Indonesia','IE'=>'Ireland','IL'=>'Israel','IM'=>'Isle of Man','IN'=>'India','IO'=>'British Indian Ocean Territory','IQ'=>'Iraq','IR'=>'Islamic Republic of Iran','IS'=>'Iceland','IT'=>'Italy','JE'=>'Jersey','JM'=>'Jamaica','JO'=>'Jordan','JP'=>'Japan','KE'=>'Kenya','KG'=>'Kyrgyzstan','KH'=>'Cambodia','KI'=>'Kiribati','KM'=>'Comoros','KN'=>'Saint Kitts and Nevis','KP'=>'Democratic People\'s Republic of Korea','KR'=>'Republic of Korea','KW'=>'Kuwait','KY'=>'Cayman Islands','KZ'=>'Kazakhstan','LA'=>'Lao People\'s Democratic Republic','LB'=>'Lebanon','LC'=>'Saint Lucia','LI'=>'Liechtenstein','LK'=>'Sri Lanka','LR'=>'Liberia','LS'=>'Lesotho','LT'=>'Lithuania','LU'=>'Luxembourg','LV'=>'Latvia','LY'=>'Libya','MA'=>'Morocco','MC'=>'Monaco','MD'=>'Moldova','ME'=>'Montenegro','MF'=>'Saint Martin (French part)','MG'=>'Madagascar','MH'=>'Marshall Islands','MK'=>'The Former Yugoslav Republic of Macedonia','ML'=>'Mali','MM'=>'Myanmar','MN'=>'Mongolia','MO'=>'Macao','MP'=>'Northern Mariana Islands','MQ'=>'Martinique','MR'=>'Mauritania','MS'=>'Montserrat','MT'=>'Malta','MU'=>'Mauritius','MV'=>'Maldives','MW'=>'Malawi','MX'=>'Mexico','MY'=>'Malaysia','MZ'=>'Mozambique','NA'=>'Namibia','NC'=>'New Caledonia','NE'=>'Niger','NF'=>'Norfolk Island','NG'=>'Nigeria','NI'=>'Nicaragua','NL'=>'Netherlands','NO'=>'Norway','NP'=>'Nepal','NR'=>'Nauru','NU'=>'Niue','NZ'=>'New Zealand','OM'=>'Oman','PA'=>'Panama','PE'=>'Peru','PF'=>'French Polynesia','PG'=>'Papua New Guinea','PH'=>'Philippines','PK'=>'Pakistan','PL'=>'Poland','PM'=>'Saint Pierre and Miquelon','PN'=>'Pitcairn','PR'=>'Puerto Rico','PS'=>'State of Palestine','PT'=>'Portugal','PW'=>'Palau','PY'=>'Paraguay','QA'=>'Qatar','RE'=>'Réunion','RO'=>'Romania','RS'=>'Serbia','RU'=>'Russian Federation','RW'=>'Rwanda','SA'=>'Saudi Arabia','SB'=>'Solomon Islands','SC'=>'Seychelles','SD'=>'Sudan','SE'=>'Sweden','SG'=>'Singapore','SH'=>'Saint Helena, Ascension and Tristan da Cunha','SI'=>'Slovenia','SJ'=>'Svalbard and Jan Mayen','SK'=>'Slovakia','SL'=>'Sierra Leone','SM'=>'San Marino','SN'=>'Senegal','SO'=>'Somalia','SR'=>'Suriname','SS'=>'South Sudan','ST'=>'Sao Tome and Principe','SV'=>'El Salvador','SX'=>'Sint Maarten (Dutch part)','SY'=>'Syrian Arab Republic','SZ'=>'Swaziland','TC'=>'Turks and Caicos Islands','TD'=>'Chad','TF'=>'French Southern Territories','TG'=>'Togo','TH'=>'Thailand','TJ'=>'Tajikistan','TK'=>'Tokelau','TL'=>'Timor-Leste','TM'=>'Turkmenistan','TN'=>'Tunisia','TO'=>'Tonga','TR'=>'Turkey','TT'=>'Trinidad and Tobago','TV'=>'Tuvalu','TW'=>'Taiwan','TZ'=>'United Republic of Tanzania','UA'=>'Ukraine','UG'=>'Uganda','UM'=>'United States Minor Outlying Islands','US'=>'United States','UY'=>'Uruguay','UZ'=>'Uzbekistan','VA'=>'Holy See (Vatican City State)','VC'=>'Saint Vincent and the Grenadines','VE'=>'Venezuela','VG'=>'British Virgin Islands','VI'=>'U.S. Virgin Islands','VN'=>'Viet Nam','VU'=>'Vanuatu','WF'=>'Wallis and Futuna','WS'=>'Samoa','YE'=>'Yemen','YT'=>'Mayotte','ZA'=>'South Africa','ZM'=>'Zambia','ZW'=>'Zimbabwe','ZZ'=>'Private use'];plurals.php000064400000005633147207170660006760 0ustar001,'am'=>1,'ar'=>2,'ary'=>2,'be'=>3,'bm'=>4,'bo'=>4,'br'=>1,'bs'=>3,'cs'=>5,'cy'=>6,'dz'=>4,'ff'=>1,'fr'=>1,'ga'=>7,'gd'=>8,'gv'=>9,'hr'=>10,'id'=>4,'ii'=>4,'iu'=>11,'ja'=>4,'ka'=>4,'kk'=>4,'km'=>4,'kn'=>4,'ko'=>4,'kw'=>11,'ky'=>4,'ln'=>1,'lo'=>4,'lt'=>12,'lv'=>13,'mg'=>1,'mi'=>1,'mk'=>14,'ms'=>4,'mt'=>15,'my'=>4,'nr'=>4,'oc'=>1,'pl'=>16,'ro'=>17,'ru'=>3,'sa'=>11,'sg'=>4,'sk'=>5,'sl'=>18,'sm'=>4,'sr'=>3,'su'=>4,'th'=>4,'ti'=>1,'tl'=>1,'to'=>4,'tt'=>4,'ug'=>4,'uk'=>3,'vi'=>4,'wa'=>1,'wo'=>4,'yo'=>4,'zh'=>4,''=>[0=>[0=>'n != 1',1=>[1=>'one','0,2,3…'=>'other']],1=>[0=>'n > 1',1=>['0,1'=>'one','2,3,4…'=>'other']],2=>[0=>'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100 >= 3 && n%100<=10 ? 3 : n%100 >= 11 && n%100<=99 ? 4 : 5',1=>[0=>'zero',1=>'one',2=>'two','3,4,5…'=>'few','11,12,13…'=>'many','100,101,102…'=>'other']],3=>[0=>'(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>['1,21,31…'=>'one','2,3,4…'=>'few','0,5,6…'=>'other']],4=>[0=>'0',1=>['0,1,2…'=>'other']],5=>[0=>'( n == 1 ) ? 0 : ( n >= 2 && n <= 4 ) ? 1 : 2',1=>[1=>'one','2,3,4'=>'few','0,5,6…'=>'other']],6=>[0=>'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 5',1=>[0=>'zero',1=>'one',2=>'two',3=>'few',6=>'many','4,5,7…'=>'other']],7=>[0=>'n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4',1=>[1=>'one',2=>'two','0,3,4…'=>'few','7,8,9…'=>'many','11,12,13…'=>'other']],8=>[0=>'n==1||n==11 ? 0 : n==2||n==12 ? 1 :(n >= 3 && n<=10)||(n >= 13 && n<=19)? 2 : 3',1=>['1,11'=>'one','2,12'=>'two','3,4,5…'=>'few','0,20,21…'=>'other']],9=>[0=>'n%10==1 ? 0 : n%10==2 ? 1 : n%20==0 ? 2 : 3',1=>['1,11,21…'=>'one','2,12,22…'=>'two','0,20,100…'=>'few','3,4,5…'=>'other']],10=>[0=>'n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2',1=>['1,21,31…'=>'one','2,3,4…'=>'few','0,5,6…'=>'other']],11=>[0=>'n == 1 ? 0 : n == 2 ? 1 : 2',1=>[1=>'one',2=>'two','0,3,4…'=>'other']],12=>[0=>'(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>['1,21,31…'=>'one','2,3,4…'=>'few','0,10,11…'=>'other']],13=>[0=>'n%10==0||( n%100 >= 11 && n%100<=19)? 0 :(n%10==1 && n%100!=11 ? 1 : 2)',1=>['0,10,11…'=>'zero','1,21,31…'=>'one','2,3,4…'=>'other']],14=>[0=>'( n % 10 == 1 && n % 100 != 11 ) ? 0 : 1',1=>['1,21,31…'=>'one','0,2,3…'=>'other']],15=>[0=>'(n==1 ? 0 : n==0||( n%100>1 && n%100<11)? 1 :(n%100>10 && n%100<20)? 2 : 3)',1=>[1=>'one','0,2,3…'=>'few','11,12,13…'=>'many','20,21,22…'=>'other']],16=>[0=>'(n==1 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>[1=>'one','2,3,4…'=>'few','0,5,6…'=>'other']],17=>[0=>'(n==1 ? 0 :(((n%100>19)||(( n%100==0)&&(n!=0)))? 2 : 1))',1=>[1=>'one','0,2,3…'=>'few','20,21,22…'=>'other']],18=>[0=>'n%100==1 ? 0 : n%100==2 ? 1 : n%100==3||n%100==4 ? 2 : 3',1=>['1,101,201…'=>'one','2,102,202…'=>'two','3,4,103…'=>'few','0,5,6…'=>'other']]]]; locales.php000064400000014232147207170660006713 0ustar00[0=>'Afrikaans',1=>'Afrikaans'],'am'=>[0=>'Amharic',1=>'አማርኛ'],'arg'=>[0=>'Aragonese',1=>'Aragonés'],'ar'=>[0=>'Arabic',1=>'العربية'],'ary'=>[0=>'Moroccan Arabic',1=>'العربية المغربية'],'as'=>[0=>'Assamese',1=>'অসমীয়া'],'azb'=>[0=>'South Azerbaijani',1=>'گؤنئی آذربایجان'],'az'=>[0=>'Azerbaijani',1=>'Azərbaycan dili'],'bel'=>[0=>'Belarusian',1=>'Беларуская мова'],'bg_BG'=>[0=>'Bulgarian',1=>'Български'],'bn_BD'=>[0=>'Bengali (Bangladesh)',1=>'বাংলা'],'bo'=>[0=>'Tibetan',1=>'བོད་ཡིག'],'bs_BA'=>[0=>'Bosnian',1=>'Bosanski'],'ca'=>[0=>'Catalan',1=>'Català'],'ceb'=>[0=>'Cebuano',1=>'Cebuano'],'cs_CZ'=>[0=>'Czech',1=>'Čeština'],'cy'=>[0=>'Welsh',1=>'Cymraeg'],'da_DK'=>[0=>'Danish',1=>'Dansk'],'de_AT'=>[0=>'German (Austria)',1=>'Deutsch (Österreich)'],'de_DE'=>[0=>'German',1=>'Deutsch'],'de_DE_formal'=>[0=>'German (Formal)',1=>'Deutsch (Sie)'],'de_CH'=>[0=>'German (Switzerland)',1=>'Deutsch (Schweiz)'],'de_CH_informal'=>[0=>'German (Switzerland, Informal)',1=>'Deutsch (Schweiz, Du)'],'dsb'=>[0=>'Lower Sorbian',1=>'Dolnoserbšćina'],'dzo'=>[0=>'Dzongkha',1=>'རྫོང་ཁ'],'el'=>[0=>'Greek',1=>'Ελληνικά'],'en_ZA'=>[0=>'English (South Africa)',1=>'English (South Africa)'],'en_GB'=>[0=>'English (UK)',1=>'English (UK)'],'en_CA'=>[0=>'English (Canada)',1=>'English (Canada)'],'en_NZ'=>[0=>'English (New Zealand)',1=>'English (New Zealand)'],'en_AU'=>[0=>'English (Australia)',1=>'English (Australia)'],'eo'=>[0=>'Esperanto',1=>'Esperanto'],'es_AR'=>[0=>'Spanish (Argentina)',1=>'Español de Argentina'],'es_CL'=>[0=>'Spanish (Chile)',1=>'Español de Chile'],'es_ES'=>[0=>'Spanish (Spain)',1=>'Español'],'es_CO'=>[0=>'Spanish (Colombia)',1=>'Español de Colombia'],'es_VE'=>[0=>'Spanish (Venezuela)',1=>'Español de Venezuela'],'es_CR'=>[0=>'Spanish (Costa Rica)',1=>'Español de Costa Rica'],'es_EC'=>[0=>'Spanish (Ecuador)',1=>'Español de Ecuador'],'es_PE'=>[0=>'Spanish (Peru)',1=>'Español de Perú'],'es_DO'=>[0=>'Spanish (Dominican Republic)',1=>'Español de República Dominicana'],'es_UY'=>[0=>'Spanish (Uruguay)',1=>'Español de Uruguay'],'es_PR'=>[0=>'Spanish (Puerto Rico)',1=>'Español de Puerto Rico'],'es_MX'=>[0=>'Spanish (Mexico)',1=>'Español de México'],'es_GT'=>[0=>'Spanish (Guatemala)',1=>'Español de Guatemala'],'et'=>[0=>'Estonian',1=>'Eesti'],'eu'=>[0=>'Basque',1=>'Euskara'],'fa_IR'=>[0=>'Persian',1=>'فارسی'],'fa_AF'=>[0=>'Persian (Afghanistan)',1=>'(فارسی (افغانستان'],'fi'=>[0=>'Finnish',1=>'Suomi'],'fr_FR'=>[0=>'French (France)',1=>'Français'],'fr_BE'=>[0=>'French (Belgium)',1=>'Français de Belgique'],'fr_CA'=>[0=>'French (Canada)',1=>'Français du Canada'],'fur'=>[0=>'Friulian',1=>'Friulian'],'fy'=>[0=>'Frisian',1=>'Frysk'],'gd'=>[0=>'Scottish Gaelic',1=>'Gàidhlig'],'gl_ES'=>[0=>'Galician',1=>'Galego'],'gu'=>[0=>'Gujarati',1=>'ગુજરાતી'],'haz'=>[0=>'Hazaragi',1=>'هزاره گی'],'he_IL'=>[0=>'Hebrew',1=>'עִבְרִית'],'hi_IN'=>[0=>'Hindi',1=>'हिन्दी'],'hr'=>[0=>'Croatian',1=>'Hrvatski'],'hsb'=>[0=>'Upper Sorbian',1=>'Hornjoserbšćina'],'hu_HU'=>[0=>'Hungarian',1=>'Magyar'],'hy'=>[0=>'Armenian',1=>'Հայերեն'],'id_ID'=>[0=>'Indonesian',1=>'Bahasa Indonesia'],'is_IS'=>[0=>'Icelandic',1=>'Íslenska'],'it_IT'=>[0=>'Italian',1=>'Italiano'],'ja'=>[0=>'Japanese',1=>'日本語'],'jv_ID'=>[0=>'Javanese',1=>'Basa Jawa'],'ka_GE'=>[0=>'Georgian',1=>'ქართული'],'kab'=>[0=>'Kabyle',1=>'Taqbaylit'],'kk'=>[0=>'Kazakh',1=>'Қазақ тілі'],'km'=>[0=>'Khmer',1=>'ភាសាខ្មែរ'],'kn'=>[0=>'Kannada',1=>'ಕನ್ನಡ'],'ko_KR'=>[0=>'Korean',1=>'한국어'],'ckb'=>[0=>'Kurdish (Sorani)',1=>'كوردی‎'],'kir'=>[0=>'Kyrgyz',1=>'Кыргызча'],'lo'=>[0=>'Lao',1=>'ພາສາລາວ'],'lt_LT'=>[0=>'Lithuanian',1=>'Lietuvių kalba'],'lv'=>[0=>'Latvian',1=>'Latviešu valoda'],'mk_MK'=>[0=>'Macedonian',1=>'Македонски јазик'],'ml_IN'=>[0=>'Malayalam',1=>'മലയാളം'],'mn'=>[0=>'Mongolian',1=>'Монгол'],'mr'=>[0=>'Marathi',1=>'मराठी'],'ms_MY'=>[0=>'Malay',1=>'Bahasa Melayu'],'my_MM'=>[0=>'Myanmar (Burmese)',1=>'ဗမာစာ'],'nb_NO'=>[0=>'Norwegian (Bokmål)',1=>'Norsk bokmål'],'ne_NP'=>[0=>'Nepali',1=>'नेपाली'],'nl_NL_formal'=>[0=>'Dutch (Formal)',1=>'Nederlands (Formeel)'],'nl_NL'=>[0=>'Dutch',1=>'Nederlands'],'nl_BE'=>[0=>'Dutch (Belgium)',1=>'Nederlands (België)'],'nn_NO'=>[0=>'Norwegian (Nynorsk)',1=>'Norsk nynorsk'],'oci'=>[0=>'Occitan',1=>'Occitan'],'pa_IN'=>[0=>'Panjabi (India)',1=>'ਪੰਜਾਬੀ'],'pl_PL'=>[0=>'Polish',1=>'Polski'],'ps'=>[0=>'Pashto',1=>'پښتو'],'pt_PT'=>[0=>'Portuguese (Portugal)',1=>'Português'],'pt_PT_ao90'=>[0=>'Portuguese (Portugal, AO90)',1=>'Português (AO90)'],'pt_AO'=>[0=>'Portuguese (Angola)',1=>'Português de Angola'],'pt_BR'=>[0=>'Portuguese (Brazil)',1=>'Português do Brasil'],'rhg'=>[0=>'Rohingya',1=>'Ruáinga'],'ro_RO'=>[0=>'Romanian',1=>'Română'],'ru_RU'=>[0=>'Russian',1=>'Русский'],'sah'=>[0=>'Sakha',1=>'Сахалыы'],'snd'=>[0=>'Sindhi',1=>'سنڌي'],'si_LK'=>[0=>'Sinhala',1=>'සිංහල'],'sk_SK'=>[0=>'Slovak',1=>'Slovenčina'],'skr'=>[0=>'Saraiki',1=>'سرائیکی'],'sl_SI'=>[0=>'Slovenian',1=>'Slovenščina'],'sq'=>[0=>'Albanian',1=>'Shqip'],'sr_RS'=>[0=>'Serbian',1=>'Српски језик'],'sv_SE'=>[0=>'Swedish',1=>'Svenska'],'sw'=>[0=>'Swahili',1=>'Kiswahili'],'szl'=>[0=>'Silesian',1=>'Ślōnskŏ gŏdka'],'ta_IN'=>[0=>'Tamil',1=>'தமிழ்'],'ta_LK'=>[0=>'Tamil (Sri Lanka)',1=>'தமிழ்'],'te'=>[0=>'Telugu',1=>'తెలుగు'],'th'=>[0=>'Thai',1=>'ไทย'],'tl'=>[0=>'Tagalog',1=>'Tagalog'],'tr_TR'=>[0=>'Turkish',1=>'Türkçe'],'tt_RU'=>[0=>'Tatar',1=>'Татар теле'],'tah'=>[0=>'Tahitian',1=>'Reo Tahiti'],'ug_CN'=>[0=>'Uighur',1=>'ئۇيغۇرچە'],'uk'=>[0=>'Ukrainian',1=>'Українська'],'ur'=>[0=>'Urdu',1=>'اردو'],'uz_UZ'=>[0=>'Uzbek',1=>'O‘zbekcha'],'vi'=>[0=>'Vietnamese',1=>'Tiếng Việt'],'zh_CN'=>[0=>'Chinese (China)',1=>'简体中文'],'zh_HK'=>[0=>'Chinese (Hong Kong)',1=>'香港中文'],'zh_TW'=>[0=>'Chinese (Taiwan)',1=>'繁體中文']];