546 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			546 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * The Global functionality of the plugin.
 | 
						|
 *
 | 
						|
 * Defines the functionality loaded on admin.
 | 
						|
 *
 | 
						|
 * @since      1.0.71
 | 
						|
 * @package    RankMath
 | 
						|
 * @subpackage RankMath\Rest
 | 
						|
 * @author     Rank Math <support@rankmath.com>
 | 
						|
 */
 | 
						|
 | 
						|
namespace RankMath\ContentAI;
 | 
						|
 | 
						|
use WP_Error;
 | 
						|
use WP_REST_Server;
 | 
						|
use WP_REST_Request;
 | 
						|
use WP_REST_Controller;
 | 
						|
use RankMath\Helper;
 | 
						|
use RankMath\Admin\Admin_Helper;
 | 
						|
 | 
						|
defined( 'ABSPATH' ) || exit;
 | 
						|
 | 
						|
/**
 | 
						|
 * Rest class.
 | 
						|
 */
 | 
						|
class Rest extends WP_REST_Controller {
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Registered data.
 | 
						|
	 *
 | 
						|
	 * @var array|false
 | 
						|
	 */
 | 
						|
	private $registered;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Constructor.
 | 
						|
	 */
 | 
						|
	public function __construct() {
 | 
						|
		$this->namespace  = \RankMath\Rest\Rest_Helper::BASE . '/ca';
 | 
						|
		$this->registered = Admin_Helper::get_registration_data();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Registers the routes for the objects of the controller.
 | 
						|
	 */
 | 
						|
	public function register_routes() {
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/researchKeyword',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::CREATABLE,
 | 
						|
				'callback'            => [ $this, 'research_keyword' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/getCredits',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::CREATABLE,
 | 
						|
				'callback'            => [ $this, 'get_credits' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/createPost',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::CREATABLE,
 | 
						|
				'callback'            => [ $this, 'create_post' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/saveOutput',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::CREATABLE,
 | 
						|
				'callback'            => [ $this, 'save_output' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/deleteOutput',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::CREATABLE,
 | 
						|
				'callback'            => [ $this, 'delete_output' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/updateRecentPrompt',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::CREATABLE,
 | 
						|
				'callback'            => [ $this, 'update_recent_prompt' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/updatePrompt',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::CREATABLE,
 | 
						|
				'callback'            => [ $this, 'update_prompt' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/savePrompts',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::CREATABLE,
 | 
						|
				'callback'            => [ $this, 'save_prompts' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/pingContentAI',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::READABLE,
 | 
						|
				'callback'            => [ $this, 'ping_content_ai' ],
 | 
						|
				'permission_callback' => [ $this, 'has_ping_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		register_rest_route(
 | 
						|
			$this->namespace,
 | 
						|
			'/migrateuser',
 | 
						|
			[
 | 
						|
				'methods'             => WP_REST_Server::READABLE,
 | 
						|
				'callback'            => [ $this, 'migrate_user' ],
 | 
						|
				'permission_callback' => [ $this, 'has_permission' ],
 | 
						|
			]
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Check API key in request.
 | 
						|
	 *
 | 
						|
	 * @param  WP_REST_Request $request Full details about the request.
 | 
						|
	 * @return bool                     Whether the API key matches or not.
 | 
						|
	 */
 | 
						|
	public function has_ping_permission( WP_REST_Request $request ) {
 | 
						|
		if ( empty( $this->registered ) ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		return $request->get_param( 'apiKey' ) === $this->registered['api_key'] &&
 | 
						|
			$request->get_param( 'username' ) === $this->registered['username'];
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Determines if the current user can manage analytics.
 | 
						|
	 *
 | 
						|
	 * @return true
 | 
						|
	 */
 | 
						|
	public function has_permission() {
 | 
						|
		if ( ! Helper::has_cap( 'content_ai' ) || empty( $this->registered ) ) {
 | 
						|
			return new WP_Error(
 | 
						|
				'rest_cannot_access',
 | 
						|
				__( 'Sorry, only authenticated users can research the keyword.', 'rank-math' ),
 | 
						|
				[ 'status' => rest_authorization_required_code() ]
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get Content AI Credits.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return int Credits.
 | 
						|
	 */
 | 
						|
	public function get_credits( WP_REST_Request $request ) {
 | 
						|
		$credits = Helper::get_content_ai_credits( true, true );
 | 
						|
		if ( ! empty( $credits['error'] ) ) {
 | 
						|
			$error       = $credits['error'];
 | 
						|
			$error_texts = Helper::get_content_ai_errors();
 | 
						|
			return [
 | 
						|
				'error'   => ! empty( $error_texts[ $error ] ) ? wp_specialchars_decode( $error_texts[ $error ], ENT_QUOTES ) : $error,
 | 
						|
				'credits' => isset( $credits['credits'] ) ? $credits['credits'] : '',
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		return $credits;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Research a keyword.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function research_keyword( WP_REST_Request $request ) {
 | 
						|
		$object_id    = $request->get_param( 'objectID' );
 | 
						|
		$country      = $request->get_param( 'country' );
 | 
						|
		$keyword      = mb_strtolower( $request->get_param( 'keyword' ) );
 | 
						|
		$force_update = $request->get_param( 'force_update' );
 | 
						|
		$keyword_data = get_option( 'rank_math_ca_data' );
 | 
						|
 | 
						|
		if ( ! in_array( get_post_type( $object_id ), (array) Helper::get_settings( 'general.content_ai_post_types' ), true ) ) {
 | 
						|
			return [
 | 
						|
				'data' => esc_html__( 'Content AI is not enabled on this Post type.', 'rank-math' ),
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		if ( ! apply_filters( 'rank_math/content_ai/call_api', true ) ) {
 | 
						|
			return [
 | 
						|
				'data' => 'show_dummy_data',
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		if (
 | 
						|
			! $force_update &&
 | 
						|
			! empty( $keyword_data ) &&
 | 
						|
			! empty( $keyword_data[ $country ] ) &&
 | 
						|
			! empty( $keyword_data[ $country ][ $keyword ] )
 | 
						|
		) {
 | 
						|
			update_post_meta(
 | 
						|
				$object_id,
 | 
						|
				'rank_math_ca_keyword',
 | 
						|
				[
 | 
						|
					'keyword' => $keyword,
 | 
						|
					'country' => $country,
 | 
						|
				]
 | 
						|
			);
 | 
						|
 | 
						|
			return [
 | 
						|
				'data'    => $keyword_data[ $country ][ $keyword ],
 | 
						|
				'keyword' => $keyword,
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		$data = $this->get_researched_data( $keyword, $country, $force_update );
 | 
						|
		if ( ! empty( $data['error'] ) ) {
 | 
						|
			return $this->get_errored_data( $data['error'] );
 | 
						|
		}
 | 
						|
 | 
						|
		$credits = ! empty( $data['credits'] ) ? $data['credits'] : 0;
 | 
						|
		if ( ! empty( $credits ) ) {
 | 
						|
			$credits = $credits['available'] - $credits['taken'];
 | 
						|
		}
 | 
						|
 | 
						|
		$data = $data['data']['details'];
 | 
						|
		$this->get_recommendations( $data );
 | 
						|
 | 
						|
		update_post_meta(
 | 
						|
			$object_id,
 | 
						|
			'rank_math_ca_keyword',
 | 
						|
			[
 | 
						|
				'keyword' => $keyword,
 | 
						|
				'country' => $country,
 | 
						|
			]
 | 
						|
		);
 | 
						|
		$keyword_data[ $country ][ $keyword ] = $data;
 | 
						|
		update_option( 'rank_math_ca_data', $keyword_data, false );
 | 
						|
		Helper::update_credits( $credits );
 | 
						|
 | 
						|
		return [
 | 
						|
			'data'    => $keyword_data[ $country ][ $keyword ],
 | 
						|
			'credits' => $credits,
 | 
						|
			'keyword' => $keyword,
 | 
						|
		];
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Create a new Post from Content AI Page.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function create_post( WP_REST_Request $request ) {
 | 
						|
		$content       = $request->get_param( 'content' );
 | 
						|
		$title         = 'Content AI Post';
 | 
						|
		$blocks        = parse_blocks( $content );
 | 
						|
		$current_block = ! empty( $blocks ) ? current( $blocks ) : '';
 | 
						|
		if (
 | 
						|
			! empty( $current_block ) &&
 | 
						|
			$current_block['blockName'] === 'core/heading' &&
 | 
						|
			$current_block['attrs']['level'] === 1
 | 
						|
		) {
 | 
						|
			$title = wp_strip_all_tags( $current_block['innerHTML'] );
 | 
						|
		}
 | 
						|
 | 
						|
		$post_id = wp_insert_post(
 | 
						|
			[
 | 
						|
				'post_title'   => $title,
 | 
						|
				'post_content' => $content,
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		return wp_specialchars_decode( add_query_arg( 'tab', 'content-ai', get_edit_post_link( $post_id ) ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Save the API output.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function save_output( WP_REST_Request $request ) {
 | 
						|
		$outputs      = $request->get_param( 'outputs' );
 | 
						|
		$endpoint     = $request->get_param( 'endpoint' );
 | 
						|
		$is_chat      = $request->get_param( 'isChat' );
 | 
						|
		$attributes   = $request->get_param( 'attributes' );
 | 
						|
		$credits_data = $request->get_param( 'credits' );
 | 
						|
 | 
						|
		if ( ! empty( $credits_data ) ) {
 | 
						|
			$credits = ! empty( $credits_data['credits'] ) ? $credits_data['credits'] : [];
 | 
						|
			$data    = [
 | 
						|
				'credits'      => ! empty( $credits['available'] ) ? $credits['available'] - $credits['taken'] : 0,
 | 
						|
				'plan'         => ! empty( $credits_data['plan'] ) ? $credits_data['plan'] : '',
 | 
						|
				'refresh_date' => ! empty( $credits_data['refreshDate'] ) ? $credits_data['refreshDate'] : '',
 | 
						|
			];
 | 
						|
 | 
						|
			Helper::update_credits( $data );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $is_chat ) {
 | 
						|
			Helper::update_chats( current( $outputs ), end( $attributes['messages'] ), $attributes['session'], $attributes['isNew'], $attributes['regenerate'] );
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		return Helper::update_outputs( $endpoint, $outputs );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Delete the API output.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function delete_output( WP_REST_Request $request ) {
 | 
						|
		$is_chat = $request->get_param( 'isChat' );
 | 
						|
		if ( $is_chat ) {
 | 
						|
			return Helper::delete_chats( $request->get_param( 'index' ) );
 | 
						|
		}
 | 
						|
 | 
						|
		return Helper::delete_outputs();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Update the Prompts.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function update_prompt( WP_REST_Request $request ) {
 | 
						|
		$prompt = $request->get_param( 'prompt' );
 | 
						|
 | 
						|
		if ( is_string( $prompt ) ) {
 | 
						|
			return Helper::delete_prompt( $prompt );
 | 
						|
		}
 | 
						|
 | 
						|
		return Helper::update_prompts( $prompt );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Save the Prompts.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function save_prompts( WP_REST_Request $request ) {
 | 
						|
		$prompts = $request->get_param( 'prompts' );
 | 
						|
		if ( empty( $prompts ) ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		return Helper::save_default_prompts( $prompts );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Update the Recent Prompts.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function update_recent_prompt( WP_REST_Request $request ) {
 | 
						|
		$prompt = $request->get_param( 'prompt' );
 | 
						|
		return Helper::update_recent_prompts( $prompt );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Endpoing to update the AI plan and credits.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function ping_content_ai( WP_REST_Request $request ) {
 | 
						|
		$credits = ! empty( $data['credits'] ) ? json_decode( $data['credits'], true ) : [];
 | 
						|
		$data    = [
 | 
						|
			'credits'      => ! empty( $credits['available'] ) ? $credits['available'] - $credits['taken'] : 0,
 | 
						|
			'plan'         => $request->get_param( 'plan' ),
 | 
						|
			'refresh_date' => $request->get_param( 'refreshDate' ),
 | 
						|
		];
 | 
						|
 | 
						|
		Helper::update_credits( $data );
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Migrate user to nest js server.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function migrate_user( WP_REST_Request $request ) {
 | 
						|
		return Helper::migrate_user_to_nest_js();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get data from the API.
 | 
						|
	 *
 | 
						|
	 * @param string $keyword      Researched keyword.
 | 
						|
	 * @param string $country      Researched country.
 | 
						|
	 * @param bool   $force_update Whether to force update the researched data.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	private function get_researched_data( $keyword, $country, $force_update = false ) {
 | 
						|
		$args = [
 | 
						|
			'username' => rawurlencode( $this->registered['username'] ),
 | 
						|
			'api_key'  => rawurlencode( $this->registered['api_key'] ),
 | 
						|
			'keyword'  => rawurlencode( $keyword ),
 | 
						|
			'site_url' => rawurlencode( Helper::get_home_url() ),
 | 
						|
			'new_api'  => 1,
 | 
						|
		];
 | 
						|
 | 
						|
		if ( 'all' !== $country ) {
 | 
						|
			$args['locale'] = rawurlencode( $country );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $force_update ) {
 | 
						|
			$args['force_refresh'] = 1;
 | 
						|
		}
 | 
						|
 | 
						|
		$url = add_query_arg(
 | 
						|
			$args,
 | 
						|
			CONTENT_AI_URL . '/ai/research'
 | 
						|
		);
 | 
						|
 | 
						|
		$data = wp_remote_get(
 | 
						|
			$url,
 | 
						|
			[
 | 
						|
				'timeout' => 60,
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		$response_code = wp_remote_retrieve_response_code( $data );
 | 
						|
		if ( 200 !== $response_code ) {
 | 
						|
			return [
 | 
						|
				'error' => 410 !== $response_code ? $data['response']['message'] : wp_kses_post(
 | 
						|
					sprintf(
 | 
						|
						// Translators: link to the update page.
 | 
						|
						__( 'There is a new version of Content AI available! %s the Rank Math SEO plugin to use this feature.', 'rank-math' ),
 | 
						|
						'<a href="' . esc_url( self_admin_url( 'update-core.php' ) ) . '">' . __( 'Please update', 'rank-math' ) . '</a>'
 | 
						|
					)
 | 
						|
				),
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		$data = wp_remote_retrieve_body( $data );
 | 
						|
		$data = json_decode( $data, true );
 | 
						|
 | 
						|
		if ( empty( $data['error'] ) && empty( $data['data']['details'] ) ) {
 | 
						|
			return [
 | 
						|
				'error' => esc_html__( 'No data found for the researched keyword.', 'rank-math' ),
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get errored data.
 | 
						|
	 *
 | 
						|
	 * @param array $error Error data received from the API.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	private function get_errored_data( $error ) {
 | 
						|
		if ( empty( $error['code'] ) ) {
 | 
						|
			return [
 | 
						|
				'data' => $error,
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		if ( 'invalid_domain' === $error['code'] ) {
 | 
						|
			return [
 | 
						|
				'data' => esc_html__( 'This feature is not available on the localhost.', 'rank-math' ),
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		if ( 'domain_limit_reached' === $error['code'] ) {
 | 
						|
			return [
 | 
						|
				'data' => esc_html__( 'You have used all the free credits which are allowed to this domain.', 'rank-math' ),
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		return [
 | 
						|
			'data'    => '',
 | 
						|
			'credits' => $error['code'],
 | 
						|
		];
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the Recommendations data.
 | 
						|
	 *
 | 
						|
	 * @param array $data Researched data.
 | 
						|
	 */
 | 
						|
	private function get_recommendations( &$data ) {
 | 
						|
		foreach ( $data['recommendations'] as $key => $value ) {
 | 
						|
			if ( ! is_array( $value ) ) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			$data['recommendations'][ $key ]['total'] = array_sum( $value );
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |