ajax/module.php 0000755 00000015615 15111605150 0007473 0 ustar 00 ajax_actions[ $tag ] = compact( 'tag', 'callback' );
}
/**
* Handle ajax request.
*
* Verify ajax nonce, and run all the registered actions for this request.
*
* Fired by `wp_ajax_elementor_ajax` action.
*
* @since 2.0.0
* @access public
*/
public function handle_ajax_request() {
if ( ! $this->verify_request_nonce() ) {
$this->add_response_data( false, esc_html__( 'Token Expired.', 'elementor' ) )
->send_error( Exceptions::UNAUTHORIZED );
}
$editor_post_id = 0;
if ( ! empty( $_REQUEST['editor_post_id'] ) ) {
$editor_post_id = absint( $_REQUEST['editor_post_id'] );
Plugin::$instance->db->switch_to_post( $editor_post_id );
}
/**
* Register ajax actions.
*
* Fires when an ajax request is received and verified.
*
* Used to register new ajax action handles.
*
* @since 2.0.0
*
* @param self $this An instance of ajax manager.
*/
do_action( 'elementor/ajax/register_actions', $this );
if ( ! empty( $_REQUEST['actions'] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, each action should sanitize its own data.
$this->requests = json_decode( wp_unslash( $_REQUEST['actions'] ), true );
}
foreach ( $this->requests as $id => $action_data ) {
$this->current_action_id = $id;
if ( ! isset( $this->ajax_actions[ $action_data['action'] ] ) ) {
$this->add_response_data( false, esc_html__( 'Action not found.', 'elementor' ), Exceptions::BAD_REQUEST );
continue;
}
if ( $editor_post_id ) {
$action_data['data']['editor_post_id'] = $editor_post_id;
}
try {
$data = $action_data['data'] ?? [];
$results = call_user_func( $this->ajax_actions[ $action_data['action'] ]['callback'], $data, $this );
if ( false === $results ) {
$this->add_response_data( false );
} else {
$this->add_response_data( true, $results );
}
} catch ( \Exception $e ) {
$this->add_response_data( false, $e->getMessage(), $e->getCode() );
}
}
$this->current_action_id = null;
$this->send_success();
}
/**
* Get current action data.
*
* Retrieve the data for the current ajax request.
*
* @since 2.0.1
* @access public
*
* @return bool|mixed Ajax request data if action exist, False otherwise.
*/
public function get_current_action_data() {
if ( ! $this->current_action_id ) {
return false;
}
return $this->requests[ $this->current_action_id ];
}
/**
* Create nonce.
*
* Creates a cryptographic token to
* give the user an access to Elementor ajax actions.
*
* @since 2.3.0
* @access public
*
* @return string The nonce token.
*/
public function create_nonce() {
return wp_create_nonce( self::NONCE_KEY );
}
/**
* Verify request nonce.
*
* Whether the request nonce verified or not.
*
* @since 2.3.0
* @access public
*
* @return bool True if request nonce verified, False otherwise.
*/
public function verify_request_nonce() {
return wp_verify_nonce( Utils::get_super_global_value( $_REQUEST, '_nonce' ), self::NONCE_KEY );
}
protected function get_init_settings() {
return [
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => $this->create_nonce(),
];
}
/**
* Ajax success response.
*
* Send a JSON response data back to the ajax request, indicating success.
*
* @since 2.0.0
* @access protected
*/
private function send_success() {
$response = [
'success' => true,
'data' => [
'responses' => $this->response_data,
],
];
$json = wp_json_encode( $response );
while ( ob_get_status() ) {
ob_end_clean();
}
header( 'Content-Type: application/json; charset=UTF-8' );
echo $json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
wp_die( '', '', [ 'response' => null ] );
}
/**
* Ajax failure response.
*
* Send a JSON response data back to the ajax request, indicating failure.
*
* @since 2.0.0
* @access protected
*
* @param null $code
*/
private function send_error( $code = null ) {
wp_send_json_error( [
'responses' => $this->response_data,
], $code );
}
/**
* Add response data.
*
* Add new response data to the array of all the ajax requests.
*
* @since 2.0.0
* @access protected
*
* @param bool $success True if the requests returned successfully, False
* otherwise.
* @param mixed $data Optional. Response data. Default is null.
*
* @param int $code Optional. Response code. Default is 200.
*
* @return Module An instance of ajax manager.
*/
private function add_response_data( $success, $data = null, $code = 200 ) {
$this->response_data[ $this->current_action_id ] = [
'success' => $success,
'code' => $code,
'data' => $data,
];
return $this;
}
}
connect/apps/base-app.php 0000755 00000051057 15111605150 0011347 0 ustar 00 get_slug();
}
/**
* @since 2.3.0
* @access protected
* @abstract
*/
abstract protected function update_settings();
/**
* @since 2.3.0
* @access public
* @static
*/
public static function get_class_name() {
return get_called_class();
}
/**
* @access public
* @abstract
*/
public function render_admin_widget() {
// PHPCS - the method get_title return a plain string.
echo '
' . $this->get_title() . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( $this->is_connected() ) {
$remote_user = $this->get( 'user' );
$title = sprintf(
/* translators: %s: Remote user. */
esc_html__( 'Connected as %s', 'elementor' ),
'' . esc_html( $remote_user->email ) . ''
);
$label = esc_html__( 'Disconnect', 'elementor' );
$url = $this->get_admin_url( 'disconnect' );
$attr = '';
printf(
'%s %s',
// PHPCS - the variable $title is already escaped above.
$title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
// PHPCS - the variable $attr is a plain string.
$attr, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_attr( $url ),
esc_html( $label )
);
} else {
echo 'Not Connected';
}
echo '
';
$this->print_app_info();
if ( current_user_can( 'manage_options' ) ) {
printf( '', esc_url( $this->get_admin_url( 'reset' ) ), esc_html__( 'Reset Data', 'elementor' ) );
}
echo '
';
}
/**
* @since 2.3.0
* @access protected
*/
protected function get_option_name() {
return static::OPTION_NAME_PREFIX . $this->get_slug();
}
/**
* @since 2.3.0
* @access public
*/
public function admin_notice() {
$notices = $this->get( 'notices' );
if ( ! $notices ) {
return;
}
$this->print_notices( $notices );
$this->delete( 'notices' );
}
public function get_app_token_from_cli_token( $cli_token ) {
$response = $this->request( 'get_app_token_from_cli_token', [
'cli_token' => $cli_token,
] );
if ( is_wp_error( $response ) ) {
// PHPCS - the variable $response does not contain a user input value.
wp_die( $response, $response->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
// Use state as usual.
$_REQUEST['state'] = $this->get( 'state' );
$_REQUEST['code'] = $response->code;
}
/**
* @since 2.3.0
* @access public
*/
public function action_authorize() {
if ( $this->is_connected() ) {
$this->add_notice( esc_html__( 'Already connected.', 'elementor' ), 'info' );
$this->redirect_to_admin_page();
return;
}
$this->set_client_id();
$this->set_request_state();
$this->redirect_to_remote_authorize_url();
}
public function action_reset() {
$this->redirect_to_admin_page();
}
/**
* @since 2.3.0
* @access public
*/
public function action_get_token() {
if ( $this->is_connected() ) {
$this->redirect_to_admin_page();
}
//phpcs:ignore WordPress.Security.NonceVerification.Recommended - The user as been authorized before in 'connect'.
$state = Utils::get_super_global_value( $_REQUEST, 'state' );
if ( $state !== $this->get( 'state' ) ) {
$this->add_notice( 'Get Token: Invalid Request.', 'error' );
$this->redirect_to_admin_page();
}
$response = $this->request( 'get_token', [
'grant_type' => 'authorization_code',
'code' => Utils::get_super_global_value( $_REQUEST, 'code' ), //phpcs:ignore WordPress.Security.NonceVerification.Recommended
'redirect_uri' => rawurlencode( $this->get_admin_url( 'get_token' ) ),
'client_id' => $this->get( 'client_id' ),
] );
if ( is_wp_error( $response ) ) {
$notice = 'Cannot Get Token:' . $response->get_error_message();
$this->add_notice( $notice, 'error' );
$this->redirect_to_admin_page();
}
$this->delete( 'state' );
$this->set( (array) $response );
if ( ! empty( $response->data_share_opted_in ) && current_user_can( 'manage_options' ) ) {
Tracker::set_opt_in( true );
}
$this->after_connect();
// Add the notice *after* the method `after_connect`, so an app can redirect without the notice.
$this->add_notice( esc_html__( 'Connected successfully.', 'elementor' ) );
$this->redirect_to_admin_page();
}
/**
* @since 2.3.0
* @access public
*/
public function action_disconnect() {
if ( $this->is_connected() ) {
$this->disconnect();
$this->add_notice( esc_html__( 'Disconnected successfully.', 'elementor' ) );
}
$this->redirect_to_admin_page();
}
/**
* @since 2.8.0
* @access public
*/
public function action_reconnect() {
$this->disconnect();
$this->action_authorize();
}
/**
* @since 2.3.0
* @access public
*/
public function get_admin_url( $action, $params = [] ) {
$params = [
'app' => $this->get_slug(),
'action' => $action,
'nonce' => wp_create_nonce( $this->get_slug() . $action ),
] + $params;
$admin_url = Str::encode_idn_url( get_admin_url() );
$admin_url .= 'admin.php?page=' . Admin::PAGE_ID;
return add_query_arg( $params, $admin_url );
}
/**
* @since 2.3.0
* @access public
*/
public function is_connected() {
return (bool) $this->get( 'access_token' );
}
/**
* @since 2.3.0
* @access protected
*/
protected function init() {}
/**
* @since 2.3.0
* @access protected
*/
protected function init_data() {}
/**
* @since 2.3.0
* @access protected
*/
protected function after_connect() {}
/**
* @since 2.3.0
* @access public
*/
public function get( $key, $default_value = null ) {
$this->init_data();
return isset( $this->data[ $key ] ) ? $this->data[ $key ] : $default_value;
}
/**
* @since 2.3.0
* @access protected
*/
protected function set( $key, $value = null ) {
$this->init_data();
if ( is_array( $key ) ) {
$this->data = array_replace_recursive( $this->data, $key );
} else {
$this->data[ $key ] = $value;
}
$this->update_settings();
}
/**
* @since 2.3.0
* @access protected
*/
protected function delete( $key = null ) {
$this->init_data();
if ( $key ) {
unset( $this->data[ $key ] );
} else {
$this->data = [];
}
$this->update_settings();
}
/**
* @since 2.3.0
* @access protected
*/
protected function add( $key, $value, $default_value = '' ) {
$new_value = $this->get( $key, $default_value );
if ( is_array( $new_value ) ) {
$new_value[] = $value;
} elseif ( is_string( $new_value ) ) {
$new_value .= $value;
} elseif ( is_numeric( $new_value ) ) {
$new_value += $value;
}
$this->set( $key, $new_value );
}
/**
* @since 2.3.0
* @access protected
*/
protected function add_notice( $content, $type = 'success' ) {
$this->add( 'notices', compact( 'content', 'type' ), [] );
}
/**
* @param $action
* @param array $request_body
* @param false $as_array
*
* @return mixed|\WP_Error
*/
protected function request( $action, $request_body = [], $as_array = false ) {
$request_body = $this->get_connect_info() + $request_body;
return $this->http_request(
'POST',
$action,
[
'timeout' => 25,
'body' => $request_body,
'headers' => $this->is_connected() ?
[ 'X-Elementor-Signature' => $this->generate_signature( $request_body ) ] :
[],
],
[
'return_type' => $as_array ? static::HTTP_RETURN_TYPE_ARRAY : static::HTTP_RETURN_TYPE_OBJECT,
]
);
}
/**
* Get Base Connect Info
*
* Returns an array of connect info.
*
* @return array
*/
protected function get_base_connect_info() {
return [
'app' => $this->get_slug(),
'access_token' => $this->get( 'access_token' ),
'client_id' => $this->get( 'client_id' ),
'local_id' => get_current_user_id(),
'site_key' => $this->get_site_key(),
'home_url' => trailingslashit( home_url() ),
];
}
/**
* Get all the connect information
*
* @return array
*/
protected function get_connect_info() {
$connect_info = $this->get_base_connect_info();
$additional_info = [];
/**
* Additional connect info.
*
* Filters the connection information when connecting to Elementor servers.
* This hook can be used to add more information or add more data.
*
* @param array $additional_info Additional connecting information array.
* @param Base_App $this The base app instance.
*/
$additional_info = apply_filters( 'elementor/connect/additional-connect-info', $additional_info, $this );
return array_merge( $connect_info, $additional_info );
}
/**
* @param $endpoint
*
* @return array
*/
protected function generate_authentication_headers( $endpoint ) {
$connect_info = ( new Collection( $this->get_connect_info() ) )
->map_with_keys( function ( $value, $key ) {
// For bc `get_connect_info` returns the connect info with underscore,
// headers with underscore are not valid, so all the keys with underscore will be replaced to hyphen.
return [ str_replace( '_', '-', $key ) => $value ];
} )
->replace_recursive( [ 'endpoint' => $endpoint ] )
->sort_keys();
return $connect_info
->merge( [ 'X-Elementor-Signature' => $this->generate_signature( $connect_info->all() ) ] )
->all();
}
/**
* Send an http request
*
* @param $method
* @param $endpoint
* @param array $args
* @param array $options
*
* @return mixed|\WP_Error
*/
protected function http_request( $method, $endpoint, $args = [], $options = [] ) {
$options = wp_parse_args( $options, [
'return_type' => static::HTTP_RETURN_TYPE_OBJECT,
] );
$args = array_replace_recursive( [
'headers' => $this->is_connected() ? $this->generate_authentication_headers( $endpoint ) : [],
'method' => $method,
'timeout' => 10,
], $args );
$response = $this->http->request_with_fallback(
$this->get_generated_urls( $endpoint ),
$args
);
if ( is_wp_error( $response ) && empty( $options['with_error_data'] ) ) {
// PHPCS - the variable $response does not contain a user input value.
wp_die( $response, [ 'back_link' => true ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$body = wp_remote_retrieve_body( $response );
$response_code = (int) wp_remote_retrieve_response_code( $response );
if ( ! $response_code ) {
return new \WP_Error( 500, 'No Response' );
}
// Server sent a success message without content.
if ( 'null' === $body ) {
$body = true;
}
$body = json_decode( $body, static::HTTP_RETURN_TYPE_ARRAY === $options['return_type'] );
if ( false === $body ) {
return new \WP_Error( 422, 'Wrong Server Response' );
}
if ( 201 === $response_code ) {
return $body;
}
if ( 200 !== $response_code ) {
// In case $as_array = true.
$body = (object) $body;
$message = isset( $body->message ) ? $body->message : wp_remote_retrieve_response_message( $response );
$code = (int) ( isset( $body->code ) ? $body->code : $response_code );
if ( ! $code ) {
$code = $response_code;
}
if ( 401 === $code ) {
$this->delete();
$should_retry = ! in_array( $this->auth_mode, [ 'xhr', 'cli' ], true );
if ( $should_retry ) {
$this->action_authorize();
}
}
if ( isset( $options['with_error_data'] ) && true === $options['with_error_data'] ) {
return new \WP_Error( $code, $message, $body );
}
return new \WP_Error( $code, $message );
}
return $body;
}
/**
* Create a signature for the http request
*
* @param array $payload
*
* @return false|string
*/
private function generate_signature( $payload = [] ) {
return hash_hmac(
'sha256',
wp_json_encode( $payload, JSON_NUMERIC_CHECK ),
$this->get( 'access_token_secret' )
);
}
/**
* @since 2.3.0
* @access protected
*/
protected function get_api_url() {
return static::API_URL . '/' . $this->get_slug();
}
/**
* @since 2.3.0
* @access protected
*/
protected function get_remote_site_url() {
return static::SITE_URL . '/' . $this->get_slug();
}
/**
* @since 2.3.0
* @access protected
*/
protected function get_remote_authorize_url() {
$redirect_uri = $this->get_auth_redirect_uri();
$allowed_query_params_to_propagate = [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_term',
'utm_content',
'source',
'screen_hint',
];
$query_params = ( new Collection( $_GET ) ) // phpcs:ignore
->only( $allowed_query_params_to_propagate )
->merge( [
'action' => 'authorize',
'response_type' => 'code',
'client_id' => $this->get( 'client_id' ),
'auth_secret' => $this->get( 'auth_secret' ),
'state' => $this->get( 'state' ),
'redirect_uri' => rawurlencode( $redirect_uri ),
'may_share_data' => current_user_can( 'manage_options' ) && ! Tracker::is_allow_track(),
'reconnect_nonce' => wp_create_nonce( $this->get_slug() . 'reconnect' ),
] );
$utm_campaign = get_transient( 'elementor_core_campaign' );
if ( ! empty( $utm_campaign ) ) {
foreach ( [ 'source', 'medium', 'campaign' ] as $key ) {
if ( ! empty( $utm_campaign[ $key ] ) ) {
$query_params->offsetSet( 'utm_' . $key, $utm_campaign[ $key ] );
}
}
}
return add_query_arg( $query_params->all(), $this->get_remote_site_url() );
}
/**
* @since 2.3.0
* @access protected
*/
protected function redirect_to_admin_page( $url = '' ) {
if ( ! $url ) {
$url = Admin::$url;
}
switch ( $this->auth_mode ) {
case 'popup':
$this->print_popup_close_script( $url );
break;
case 'cli':
case 'rest':
$this->admin_notice();
die;
default:
wp_safe_redirect( $url );
die;
}
}
/**
* @since 2.3.0
* @access protected
*/
protected function set_client_id() {
$source = Utils::get_super_global_value( $_REQUEST, 'source' ) ?? ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
$response = $this->request(
'get_client_id',
[
'source' => esc_attr( $source ),
]
);
if ( is_wp_error( $response ) ) {
// PHPCS - the variable $response does not contain a user input value.
wp_die( $response, $response->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$this->set( 'client_id', $response->client_id );
$this->set( 'auth_secret', $response->auth_secret );
}
/**
* @since 2.3.0
* @access protected
*/
protected function set_request_state() {
$this->set( 'state', wp_generate_password( 12, false ) );
}
protected function get_popup_success_event_data() {
return [];
}
/**
* @since 2.3.0
* @access protected
*/
protected function print_popup_close_script( $url ) {
$data = $this->get_popup_success_event_data();
?>
is_connected() ) {
// Try update the server, but not needed to handle errors.
$this->request( 'disconnect' );
}
$this->delete();
}
/**
* @since 2.3.0
* @access protected
*/
public function get_site_key() {
$site_key = get_option( static::OPTION_CONNECT_SITE_KEY );
if ( ! $site_key ) {
$site_key = md5( uniqid( wp_generate_password() ) );
update_option( static::OPTION_CONNECT_SITE_KEY, $site_key );
}
return $site_key;
}
protected function redirect_to_remote_authorize_url() {
switch ( $this->auth_mode ) {
case 'cli':
case 'rest':
$this->get_app_token_from_cli_token( Utils::get_super_global_value( $_REQUEST, 'token' ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
return;
default:
wp_redirect( $this->get_remote_authorize_url() ); //phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Safe redirect is used here.
die;
}
}
protected function get_auth_redirect_uri() {
$redirect_uri = $this->get_admin_url( 'get_token' );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
$val = Utils::get_super_global_value( $_REQUEST, 'redirect_to' );
if ( $val ) {
$redirect_uri = add_query_arg( [ 'redirect_to' => $val ], $redirect_uri );
}
switch ( $this->auth_mode ) {
case 'popup':
$redirect_uri = add_query_arg( [
'mode' => 'popup',
'callback_id' => esc_attr( Utils::get_super_global_value( $_REQUEST, 'callback_id' ) ), //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
], $redirect_uri );
break;
}
return $redirect_uri;
}
protected function print_notices( $notices ) {
switch ( $this->auth_mode ) {
case 'cli':
foreach ( $notices as $notice ) {
printf( '[%s] %s', wp_kses_post( $notice['type'] ), wp_kses_post( $notice['content'] ) );
}
break;
case 'rest':
// After `wp_send_json` the script will die.
$this->delete( 'notices' );
wp_send_json( $notices );
break;
default:
/**
* @var Admin_Notices $admin_notices
*/
$admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' );
foreach ( $notices as $notice ) {
$options = [
'description' => wp_kses_post( wpautop( $notice['content'] ) ),
'type' => $notice['type'],
'icon' => false,
];
$admin_notices->print_admin_notice( $options );
}
}
}
protected function get_app_info() {
return [];
}
protected function print_app_info() {
$app_info = $this->get_app_info();
foreach ( $app_info as $key => $item ) {
if ( $item['value'] ) {
$status = 'Exist';
$color = 'green';
} else {
$status = 'Empty';
$color = 'red';
}
// PHPCS - the values of $item['label'], $color, $status are plain strings.
printf( '%s: %s
', $item['label'], $color, $status ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
private function get_generated_urls( $endpoint ) {
$base_urls = $this->get_api_url();
if ( ! is_array( $base_urls ) ) {
$base_urls = [ $base_urls ];
}
return array_map( function ( $base_url ) use ( $endpoint ) {
return trailingslashit( $base_url ) . $endpoint;
}, $base_urls );
}
private function init_auth_mode() {
$is_rest = defined( 'REST_REQUEST' ) && REST_REQUEST;
$is_ajax = wp_doing_ajax();
if ( $is_rest || $is_ajax ) {
// Set default to 'xhr' if rest or ajax request.
$this->set_auth_mode( 'xhr' );
}
$mode = Utils::get_super_global_value( $_REQUEST, 'mode' );
if ( $mode ) {
$allowed_auth_modes = [
'popup',
];
if ( defined( 'WP_CLI' ) && WP_CLI ) {
$allowed_auth_modes[] = 'cli';
}
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
$allowed_auth_modes[] = 'rest';
}
if ( in_array( $mode, $allowed_auth_modes, true ) ) {
$this->set_auth_mode( $mode );
}
}
}
public function set_auth_mode( $mode ) {
$this->auth_mode = $mode;
}
/**
* @since 2.3.0
* @access public
*/
public function __construct() {
add_action( 'admin_notices', [ $this, 'admin_notice' ] );
$this->init_auth_mode();
$this->http = new Http();
/**
* Allow extended apps to customize the __construct without call parent::__construct.
*/
$this->init();
}
}
connect/apps/base-user-app.php 0000755 00000001061 15111605150 0012311 0 ustar 00 get_option_name(), $this->data );
}
/**
* @since 2.3.0
* @access protected
*/
protected function init_data() {
$this->data = get_user_option( $this->get_option_name() );
if ( ! $this->data ) {
$this->data = [];
}
}
}
connect/apps/common-app.php 0000755 00000001614 15111605150 0011717 0 ustar 00 data = & self::$common_data;
}
public function action_reset() {
delete_user_option( get_current_user_id(), static::OPTION_CONNECT_COMMON_DATA_KEY );
parent::action_reset();
}
}
connect/apps/connect.php 0000755 00000000674 15111605150 0011307 0 ustar 00 is_connected() ) {
return new \WP_Error( '401', esc_html__( 'Connecting to the Library failed. Please try reloading the page and try again', 'elementor' ) );
}
$body_args = [
'id' => $id,
// Which API version is used.
'api_version' => ELEMENTOR_VERSION,
// Which language to return.
'site_lang' => get_bloginfo( 'language' ),
];
/**
* API: Template body args.
*
* Filters the body arguments send with the GET request when fetching the content.
*
* @since 1.0.0
*
* @param array $body_args Body arguments.
*/
$body_args = apply_filters( 'elementor/api/get_templates/body_args', $body_args );
$template_content = $this->request( 'get_template_content', $body_args, true );
if ( is_wp_error( $template_content ) && 401 === $template_content->get_error_code() ) {
// Normalize 401 message
return new \WP_Error( 401, esc_html__( 'Connecting to the Library failed. Please try reloading the page and try again', 'elementor' ) );
}
return $template_content;
}
public function localize_settings( $settings ) {
$is_connected = $this->is_connected();
/** @var ConnectModule $connect */
$connect = Plugin::$instance->common->get_component( 'connect' );
$user_id = $this->get_user_id();
return array_replace_recursive( $settings, [
'library_connect' => [
'is_connected' => $is_connected,
'user_id' => $user_id,
'subscription_plans' => $connect->get_subscription_plans( 'template-library' ),
// TODO: Remove `base_access_level`.
'base_access_level' => ConnectModule::ACCESS_LEVEL_CORE,
'base_access_tier' => ConnectModule::ACCESS_TIER_FREE,
'current_access_level' => ConnectModule::ACCESS_LEVEL_CORE,
'current_access_tier' => ConnectModule::ACCESS_TIER_FREE,
'plan_type' => ConnectModule::ACCESS_TIER_FREE,
],
] );
}
public function library_connect_popup_seen() {
User::set_introduction_viewed( [
'introductionKey' => 'library_connect',
] );
}
/**
* @param \Elementor\Core\Common\Modules\Ajax\Module $ajax_manager
*/
public function register_ajax_actions( $ajax_manager ) {
$ajax_manager->register_ajax_action( 'library_connect_popup_seen', [ $this, 'library_connect_popup_seen' ] );
}
private function get_user_id() {
$token = $this->get( 'access_token' );
if ( ! is_string( $token ) ) {
return null;
}
$parts = explode( '.', $token );
if ( count( $parts ) !== 3 ) {
return null;
}
try {
$payload_encoded = $parts[1];
$payload_encoded = str_pad( $payload_encoded, strlen( $payload_encoded ) + ( 4 - strlen( $payload_encoded ) % 4 ) % 4, '=' );
$payload_json = base64_decode( strtr( $payload_encoded, '-_', '+/' ), true );
$payload = json_decode( $payload_json, true );
if ( ! isset( $payload['sub'] ) ) {
return null;
}
return $payload['sub'];
} catch ( Exception $e ) {
error_log( 'JWT Decoding Error: ' . $e->getMessage() );
return null;
}
}
/**
* After Connect
*
* After Connecting to the library, re-fetch the library data to get it up to date.
*
* @since 3.7.0
*/
protected function after_connect() {
Api::get_library_data( true );
}
protected function get_app_info() {
return [
'user_common_data' => [
'label' => 'User Common Data',
'value' => get_user_option( $this->get_option_name(), get_current_user_id() ),
],
'connect_site_key' => [
'label' => 'Site Key',
'value' => get_option( self::OPTION_CONNECT_SITE_KEY ),
],
];
}
protected function get_popup_success_event_data() {
return [
'access_level' => ConnectModule::ACCESS_LEVEL_CORE,
'access_tier' => ConnectModule::ACCESS_TIER_FREE,
'plan_type' => ConnectModule::ACCESS_TIER_FREE,
'tracking_opted_in' => $this->get( 'data_share_opted_in' ) ?? false,
'user_id' => $this->get_user_id(),
];
}
protected function init() {
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
add_filter( 'elementor/common/localize_settings', [ $this, 'localize_settings' ] );
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
}
}
connect/rest/readme.md 0000755 00000004524 15111605150 0010734 0 ustar 00 # Elementor Library Connect REST API
This module provides REST API endpoints for connecting and disconnecting your WordPress site to the Elementor Library, similar in purpose to the [Elementor CLI Library Connect command](https://developers.elementor.com/docs/cli/library-connect/).
## Overview
The REST API allows programmatic connection and disconnection to the Elementor Library, which is useful for automation, integrations, and testing.
**Note:** The REST API is intended for internal and advanced use, mirroring the functionality of the CLI command.
## Endpoints
### 1. Connect to Elementor Library
- **URL:** `/index.php?rest_route=/elementor/v1/library/connect`
- **Method:** `POST`
- **Permissions:** Requires the `manage_options` capability (typically administrators).
- **Body Parameters:**
- `token` (string, required): The connect token from your Elementor account dashboard.
#### Example Request
```http
POST /index.php?rest_route=/elementor/v1/library/connect
Content-Type: application/json
Authorization: Basic {{encoded_wp_credentials}}
{
"token": "YOUR_CLI_TOKEN"
}
```
#### Example Success Response
```json
{
"success": true,
"message": "Connected successfully."
}
```
#### Example Error Response
```json
{
"code": "elementor_library_not_connected",
"message": "Failed to connect to Elementor Library.",
"data": {
"status": 500
}
}
```
---
### 2. Disconnect from Elementor Library
- **URL:** `/index.php?rest_route=/elementor/v1/library/connect`
- **Method:** `DELETE`
- **Permissions:** Requires the `manage_options` capability.
#### Example Request
```http
DELETE /index.php?rest_route=/elementor/v1/library/connect
Authorization: Basic {{encoded_wp_credentials}}
```
#### Example Success Response
```json
{
"success": true,
"message": "Disconnected successfully."
}
```
#### Example Error Response
```json
{
"code": "elementor_library_disconnect_error",
"message": "Error message here",
"data": {
"status": 500
}
}
```
---
## Permissions
All endpoints require the user to have the `manage_options` capability.
## Error Handling
Errors are returned as standard WordPress REST API error objects, with a `code`, `message`, and HTTP status.
## Reference
- For CLI usage and more context, see the [Elementor CLI Library Connect documentation](https://developers.elementor.com/docs/cli/library-connect/).
connect/rest/rest-api.php 0000755 00000010230 15111605150 0011401 0 ustar 00 \WP_REST_Server::CREATABLE,
'callback' => [ $this, 'connect' ],
'permission_callback' => [ $this, 'connect_permissions_check' ],
'args' => [
'token' => [
'required' => true,
'type' => 'string',
'description' => 'Connect CLI token',
],
],
],
]
);
register_rest_route(
self::REST_NAMESPACE,
self::REST_BASE . '/connect',
[
[
'methods' => \WP_REST_Server::DELETABLE,
'callback' => [ $this, 'disconnect' ],
'permission_callback' => [ $this, 'connect_permissions_check' ],
],
]
);
}
public function connect( \WP_REST_Request $request ) {
$app = $this->get_connect_app();
if ( ! $app ) {
return $this->elementor_library_app_not_available();
}
$app->set_auth_mode( self::AUTH_MODE );
$_REQUEST['mode'] = self::AUTH_MODE;
$_REQUEST['token'] = $request->get_param( 'token' );
try {
$app->action_authorize();
$app->action_get_token();
if ( $app->is_connected() ) {
return $this->success_response(
[ 'message' => __( 'Connected successfully.', 'elementor' ) ],
WP_Http::CREATED );
} else {
return $this->error_response(
'elementor_library_not_connected',
__( 'Failed to connect to Elementor Library.', 'elementor' ),
WP_Http::INTERNAL_SERVER_ERROR
);
}
} catch ( \Exception $e ) {
return $this->error_response(
'elementor_library_connect_error',
$e->getMessage(),
WP_Http::INTERNAL_SERVER_ERROR
);
}
}
public function disconnect( \WP_REST_Request $request ) {
$app = $this->get_connect_app();
if ( ! $app ) {
return $this->elementor_library_app_not_available();
}
$app->set_auth_mode( self::AUTH_MODE );
$_REQUEST['mode'] = self::AUTH_MODE;
try {
$app->action_disconnect();
return $this->success_response(
[ 'message' => __( 'Disconnected successfully.', 'elementor' ) ],
WP_Http::OK
);
} catch ( \Exception $e ) {
return $this->error_response(
'elementor_library_disconnect_error',
$e->getMessage(),
WP_Http::INTERNAL_SERVER_ERROR
);
}
}
public function connect_permissions_check( \WP_REST_Request $request ) {
return current_user_can( 'manage_options' );
}
private function route_wrapper( callable $cb ) {
try {
$response = $cb();
} catch ( \Exception $e ) {
return $this->error_response(
'unexpected_error',
__( 'Something went wrong', 'elementor' ),
WP_Http::INTERNAL_SERVER_ERROR
);
}
return $response;
}
private function error_response( $code, $message, $status = WP_Http::BAD_REQUEST ) {
return new \WP_Error(
$code,
$message,
[ 'status' => $status ]
);
}
private function success_response( $data = [], $status = WP_Http::OK ) {
$response = rest_ensure_response( array_merge( [ 'success' => true ], $data ) );
$response->set_status( $status );
return $response;
}
private function elementor_library_app_not_available() {
return $this->error_response(
'elementor_library_app_not_available',
__( 'Elementor Library app is not available.', 'elementor' ),
WP_Http::INTERNAL_SERVER_ERROR
);
}
/**
* Get the connect app.
*
* @return \Elementor\Core\Common\Modules\Connect\Apps\Library|null
*/
private function get_connect_app() {
$connect = Plugin::$instance->common->get_component( 'connect' );
if ( ! $connect ) {
return null;
}
$app = $connect->get_app( 'library' );
if ( ! $app ) {
$connect->init();
$app = $connect->get_app( 'library' );
}
return $app;
}
}
connect/admin.php 0000755 00000006345 15111605150 0010004 0 ustar 00 register( static::PAGE_ID, new Connect_Menu_Item() );
}
/**
* @since 2.3.0
* @access public
*/
public function on_load_page() {
if ( ! $this->user_has_enough_permissions() ) {
wp_die( 'You do not have sufficient permissions to access this page.', 'You do not have sufficient permissions to access this page.', [
'back_link' => true,
] );
}
// Allow a per-request default landing URL when provided via a safe `redirect_to` parameter.
$maybe_redirect_to = $this->get_valid_redirect_to_from_request();
if ( $maybe_redirect_to ) {
self::$url = $maybe_redirect_to;
}
if ( isset( $_GET['action'], $_GET['app'] ) ) {
$manager = Plugin::$instance->common->get_component( 'connect' );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$app_slug = Utils::get_super_global_value( $_GET, 'app' );
$app = $manager->get_app( $app_slug );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$action = Utils::get_super_global_value( $_GET, 'action' );
$nonce_action = $app_slug . $action;
if ( ! $app ) {
wp_die( 'Unknown app: ' . esc_attr( $app_slug ) );
}
if ( ! wp_verify_nonce( Utils::get_super_global_value( $_GET, 'nonce' ), $nonce_action ) ) {
wp_die( 'Invalid Nonce', 'Invalid Nonce', [
'back_link' => true,
] );
}
$method = 'action_' . $action;
if ( method_exists( $app, $method ) ) {
call_user_func( [ $app, $method ] );
}
}
}
private function user_has_enough_permissions() {
if ( current_user_can( 'manage_options' ) ) {
return true;
}
if ( 'library' === Utils::get_super_global_value( $_GET, 'app' ) ) {
return current_user_can( 'edit_posts' );
}
return false;
}
/**
* @since 2.3.0
* @access public
*/
public function __construct() {
self::$url = admin_url( 'admin.php?page=' . self::PAGE_ID );
add_action( 'elementor/admin/menu/register', [ $this, 'register_admin_menu' ] );
add_action( 'elementor/admin/menu/after_register', function ( Admin_Menu_Manager $admin_menu, array $hooks ) {
if ( ! empty( $hooks[ static::PAGE_ID ] ) ) {
add_action( 'load-' . $hooks[ static::PAGE_ID ], [ $this, 'on_load_page' ] );
}
}, 10, 2 );
}
}
connect/connect-menu-item.php 0000755 00000002237 15111605150 0012237 0 ustar 00 common->get_component( 'connect' )->get_apps();
?>
';
$app->render_admin_widget();
echo '
';
}
?>
registered_apps = [
'connect' => Connect::get_class_name(),
'library' => Library::get_class_name(),
];
// When using REST API the parent module is construct after the action 'elementor/init'
// so this part of code make sure to register the module "apps".
if ( did_action( 'elementor/init' ) ) {
$this->init();
} else {
// Note: The priority 11 is for allowing plugins to add their register callback on elementor init.
add_action( 'elementor/init', [ $this, 'init' ], 11 );
}
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
add_filter( 'elementor/tracker/send_tracking_data_params', function ( $params ) {
return $this->add_tracking_data( $params );
} );
}
/**
* Register default apps.
*
* Registers the default apps.
*
* @since 2.3.0
* @access public
*/
public function init() {
if ( is_admin() ) {
$this->admin_page = new Admin();
}
/**
* Register Elementor apps.
*
* Fires after Elementor registers the default apps.
*
* @since 2.3.0
*
* @param self $this The apps manager instance.
*/
do_action( 'elementor/connect/apps/register', $this );
foreach ( $this->registered_apps as $slug => $class ) {
$this->apps[ $slug ] = new $class();
}
}
/**
* Register app.
*
* Registers an app.
*
* @since 2.3.0
* @access public
*
* @param string $slug App slug.
* @param string $class_name App full class name.
*
* @return self The updated apps manager instance.
*/
public function register_app( $slug, $class_name ) {
$this->registered_apps[ $slug ] = $class_name;
return $this;
}
/**
* Get app instance.
*
* Retrieve the app instance.
*
* @since 2.3.0
* @access public
*
* @param $slug
*
* @return Base_App|null
*/
public function get_app( $slug ) {
if ( isset( $this->apps[ $slug ] ) ) {
return $this->apps[ $slug ];
}
return null;
}
/**
* @since 2.3.0
* @access public
* @return Base_App[]
*/
public function get_apps() {
return $this->apps;
}
/**
* @since 2.3.0
* @access public
*/
public function register_category( $slug, $args ) {
$this->categories[ $slug ] = $args;
return $this;
}
/**
* @since 2.3.0
* @access public
*/
public function get_categories() {
return $this->categories;
}
/**
* @param string $context Where this subscription plan should be shown.
*
* @return array
*/
public function get_subscription_plans( $context = '' ) {
$base_url = Utils::has_pro() ? 'https://my.elementor.com/upgrade-subscription' : 'https://elementor.com/pro';
$promotion_url = $base_url . '/?utm_source=' . $context . '&utm_medium=wp-dash&utm_campaign=gopro';
return [
static::ACCESS_TIER_FREE => [
'label' => null,
'promotion_url' => null,
'color' => null,
],
static::ACCESS_TIER_ESSENTIAL => [
'label' => 'Pro',
'promotion_url' => $promotion_url,
'color' => '#92003B',
],
static::ACCESS_TIER_ESSENTIAL_OCT_2023 => [
'label' => 'Advanced', // Should be the same label as "Advanced".
'promotion_url' => $promotion_url,
'color' => '#92003B',
],
static::ACCESS_TIER_ADVANCED => [
'label' => 'Advanced',
'promotion_url' => $promotion_url,
'color' => '#92003B',
],
static::ACCESS_TIER_EXPERT => [
'label' => 'Expert',
'promotion_url' => $promotion_url,
'color' => '#92003B',
],
static::ACCESS_TIER_AGENCY => [
'label' => 'Agency',
'promotion_url' => $promotion_url,
'color' => '#92003B',
],
];
}
private function add_tracking_data( $params ) {
$users = [];
$users_query = new WP_User_Query( [
'count_total' => false, // Disable SQL_CALC_FOUND_ROWS.
'meta_query' => [
'key' => Common_App::OPTION_CONNECT_COMMON_DATA_KEY,
'compare' => 'EXISTS',
],
] );
foreach ( $users_query->get_results() as $user ) {
$connect_common_data = get_user_option( Common_App::OPTION_CONNECT_COMMON_DATA_KEY, $user->ID );
if ( $connect_common_data ) {
$users [] = [
'id' => $user->ID,
'email' => $connect_common_data['user']->email,
'roles' => implode( ', ', $user->roles ),
];
}
}
$params['usages'][ $this->get_name() ] = [
'site_key' => get_option( Base_App::OPTION_CONNECT_SITE_KEY ),
'count' => count( $users ),
'users' => $users,
];
return $params;
}
public function register_rest_routes() {
$rest_api = new Rest_Api();
$rest_api->register_routes();
}
}
event-tracker/data/controller.php 0000755 00000003513 15111605150 0013123 0 ustar 00 index_endpoint->register_items_route( \WP_REST_Server::CREATABLE, [
'event_data' => [
'description' => 'All the recorded event data in JSON format',
'type' => 'object',
'required' => true,
],
] );
}
/**
* Get Permissions Callback
*
* This endpoint should only accept POST requests, and currently we only track site administrator actions.
*
* @since 3.6.0
*
* @param \WP_REST_Request $request
* @return bool
*/
public function get_permission_callback( $request ) {
if ( WP_REST_Server::CREATABLE !== $request->get_method() ) {
return false;
}
return current_user_can( 'manage_options' );
}
/**
* Create Items
*
* Receives a request for adding an event data entry into the database. If the request contains event data, this
* method initiates creation of a database entry with the event data in the Events DB table.
*
* @since 3.6.0
*
* @param \WP_REST_Request $request
* @return bool
*/
public function create_items( $request ) {
$request_body = $request->get_json_params();
if ( empty( $request_body['event_data'] ) ) {
return false;
}
/** @var Events_DB_Manager $event_tracker_db_manager */
$event_tracker_db_manager = Plugin::$instance->common
->get_component( 'event-tracker' )
->get_component( 'events-db' );
$event_tracker_db_manager->create_entry( $request_body['event_data'] );
return true;
}
}
event-tracker/db.php 0000755 00000011410 15111605150 0010407 0 ustar 00 wpdb->prefix . self::TABLE_NAME;
}
/**
* Prepare Database for Entry
*
* The events database should have a limit of up to 1000 event entries stored daily.
* Before adding a new entry to the database, we make sure that the limit of 1000 events is not reached.
* If there are 1000 or more entries in the DB, we delete the earliest-inserted entry before inserting a new one.
*
* @since 3.6.0
*/
public function prepare_db_for_entry() {
$events = $this->get_event_ids_from_db();
if ( 1000 <= count( $events ) ) {
$event_ids = [];
foreach ( $events as $event ) {
$event_ids[] = $event->id;
}
// Sort the array by entry ID
array_multisort( $event_ids, SORT_ASC, $events );
// Delete the smallest ID (which is the earliest DB entry)
$this->wpdb->delete( $this->get_table_name(), [ 'ID' => $events[0]->id ] );
}
}
/**
* Create Entry
*
* Adds an event entry to the database.
*
* @since 3.6.0
*/
public function create_entry( $event_data ) {
$this->prepare_db_for_entry();
$connect = Plugin::$instance->common->get_component( 'connect' );
/** @var Library $library */
$library = $connect->get_apps()['library'];
if ( ! isset( $event_data['details'] ) ) {
$event_data['details'] = [];
}
if ( $library->is_connected() ) {
$user_connect_data = get_user_option( Common_App::OPTION_CONNECT_COMMON_DATA_KEY );
// Add the user's client ID to the event.
$event_data['details']['client_id'] = $user_connect_data['client_id'];
}
$event_data['details'] = wp_json_encode( $event_data['details'] );
$entry = [
'event_data' => wp_json_encode( $event_data ),
'created_at' => $event_data['ts'],
];
$this->wpdb->insert( $this->get_table_name(), $entry );
}
/**
* Get Event IDs From DB
*
* Fetches the IDs of all events saved in the database.
*
* @since 3.6.0
*
* @return array|object|null
*/
public function get_event_ids_from_db() {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $this->wpdb->get_results( "SELECT id FROM {$this->get_table_name()}" );
}
/**
* Reset Table
*
* Empties the contents of the Events DB table.
*
* @since 3.6.0
*/
public static function reset_table() {
global $wpdb;
$table_name = $wpdb->prefix . self::TABLE_NAME;
// Delete all content of the table.
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( "TRUNCATE TABLE {$table_name}" );
}
/**
* Create Table
*
* Creates the `wp_e_events` database table.
*
* @since 3.6.0
*
* @param string $query to that looks for the Events table in the DB. Used for checking if table was created.
*/
private function create_table( $query ) {
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$table_name = $this->get_table_name();
$charset_collate = $this->wpdb->get_charset_collate();
$e_events_table = "CREATE TABLE `{$table_name}` (
id bigint(20) unsigned auto_increment primary key,
event_data text null,
created_at datetime not null
) {$charset_collate};";
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$this->wpdb->query( $e_events_table );
// Check if table was created successfully.
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
if ( $this->wpdb->get_var( $query ) === $table_name ) {
update_option( self::DB_VERSION_OPTION_KEY, self::CURRENT_DB_VERSION, false );
}
}
/**
* Add Indexes
*
* Adds an index to the events table for the creation date column.
*
* @since 3.6.0
*/
private function add_indexes() {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$this->wpdb->query( 'ALTER TABLE ' . $this->get_table_name() . '
ADD INDEX `created_at_index` (`created_at`)
' );
}
public function __construct() {
global $wpdb;
$this->wpdb = $wpdb;
// Check if table exists. If not, create it.
$query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $this->get_table_name() ) );
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
if ( $wpdb->get_var( $query ) !== $this->get_table_name() ) {
$this->create_table( $query );
$this->add_indexes();
}
}
}
event-tracker/module.php 0000755 00000001642 15111605150 0011315 0 ustar 00 Tracker::is_allow_track(),
];
}
public function __construct() {
// Initialize Events Database Table
$this->add_component( 'events-db', new DB() );
// Handle User Data Deletion/Export requests.
new Personal_Data();
Plugin::$instance->data_manager_v2->register_controller( new Controller() );
}
}
event-tracker/personal-data.php 0000755 00000003711 15111605150 0012561 0 ustar 00 common
->get_component( 'event-tracker' )
->get_component( 'events-db' );
$events = $event_tracker_db_manager->get_event_ids_from_db();
$events_count = count( $events );
DB::reset_table();
// Validate table deleted
$updated_events = $event_tracker_db_manager->get_event_ids_from_db();
$updated_events_count = count( $updated_events );
return [
'items_removed' => $events_count - $updated_events_count,
'items_retained' => 0,
'messages' => [],
'done' => 0 === $updated_events_count,
];
}
/**
* Add eraser to the list of erasers.
*
* @param $erasers
*
* @return array[]
*/
private function add_eraser( $erasers ) {
return $erasers + [
self::WP_KEY => [
'eraser_friendly_name' => $this->get_title(),
'callback' => function () {
return $this->erase_data();
},
],
];
}
/**
* Personal_Data constructor.
*/
public function __construct() {
add_filter( 'wp_privacy_personal_data_erasers', function ( $exporters ) {
return $this->add_eraser( $exporters );
} );
}
}
events-manager/module.php 0000755 00000004412 15111605150 0011455 0 ustar 00 experiments->is_feature_active( self::EXPERIMENT_NAME );
$settings = [
'can_send_events' => $can_send_events,
'elementor_version' => ELEMENTOR_VERSION,
'site_url' => hash( 'sha256', get_site_url() ),
'wp_version' => get_bloginfo( 'version' ),
'user_agent' => esc_html( Utils::get_super_global_value( $_SERVER, 'HTTP_USER_AGENT' ) ),
'site_language' => get_locale(),
'site_key' => get_option( Base_App::OPTION_CONNECT_SITE_KEY ),
'subscription_id' => self::get_subscription_id(),
'subscription' => self::get_subscription(),
'token' => ELEMENTOR_EDITOR_EVENTS_MIXPANEL_TOKEN,
];
return $settings;
}
public static function get_experimental_data(): array {
return [
'name' => static::EXPERIMENT_NAME,
'title' => esc_html__( 'Elementor Editor Events', 'elementor' ),
'description' => esc_html__( 'Editor events processing', 'elementor' ),
'hidden' => true,
'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA,
'default' => Experiments_Manager::STATE_INACTIVE,
'new_site' => [
'default_active' => true,
'minimum_installation_version' => '3.32.0',
],
];
}
private static function get_subscription_id() {
$subscription = self::get_subscription();
return $subscription['subscription_id'] ?? null;
}
private static function get_subscription() {
if ( ! Utils::has_pro() ) {
return null;
}
$license_data = get_option( '_elementor_pro_license_v2_data' );
if ( ! isset( $license_data['value'] ) ) {
return null;
}
return json_decode( $license_data['value'], true );
}
}
finder/categories/create.php 0000755 00000005635 15111605150 0012123 0 ustar 00 documents->get_document_types();
// TODO: Remove - Support 'post' backwards compatibility - See `Documents_Manager::register_default_types()`.
unset( $registered_document_types['post'] );
$elementor_supported_post_types = array_flip( get_post_types_by_support( 'elementor' ) );
foreach ( $registered_document_types as $document_name => $document_class ) {
$document_properties = $document_class::get_properties();
if ( empty( $document_properties['show_in_finder'] ) ) {
continue;
}
if ( ! empty( $document_properties['cpt'] ) ) {
foreach ( $document_properties['cpt'] as $cpt ) {
unset( $elementor_supported_post_types[ $cpt ] );
}
}
$result[ $document_name ] = $this->create_item_url_by_document_class( $document_class );
}
foreach ( $elementor_supported_post_types as $post_type => $val ) {
$result[ $post_type ] = $this->create_item_url_by_post_type( $post_type );
}
return $result;
}
private function create_item_url_by_post_type( $post_type ) {
$post_type_object = get_post_type_object( $post_type );
// If there is an old post type from inactive plugins.
if ( ! $post_type_object ) {
return false;
}
return $this->get_create_new_template(
sprintf(
/* translators: %s: Post type singular name. */
__( 'Add New %s', 'elementor' ),
$post_type_object->labels->singular_name
),
Plugin::$instance->documents->get_create_new_post_url( $post_type )
);
}
private function create_item_url_by_document_class( $document_class ) {
$result = $this->get_create_new_template(
$document_class::get_add_new_title(),
$document_class::get_create_url()
);
$lock_behavior = $document_class::get_lock_behavior_v2();
$is_locked = ! empty( $lock_behavior ) && $lock_behavior->is_locked();
if ( $is_locked ) {
$result['lock'] = $lock_behavior->get_config();
}
return $result;
}
private function get_create_new_template( $add_new_title, $url ) {
return [
'title' => $add_new_title,
'icon' => 'plus-circle-o',
'url' => $url,
'keywords' => [ $add_new_title, 'post', 'page', 'template', 'new', 'create' ],
];
}
}
finder/categories/edit.php 0000755 00000005271 15111605150 0011601 0 ustar 00 false,
] );
$post_types[] = Source_Local::CPT;
$document_types = Plugin::$instance->documents->get_document_types( [
'is_editable' => true,
'show_in_finder' => true,
] );
$recently_edited_query_args = [
'no_found_rows' => true,
'post_type' => $post_types,
'post_status' => [ 'publish', 'draft', 'private', 'pending', 'future' ],
'posts_per_page' => '10',
'meta_query' => [
[
'key' => '_elementor_edit_mode',
'value' => 'builder',
],
[
'relation' => 'or',
[
'key' => Document::TYPE_META_KEY,
'compare' => 'NOT EXISTS',
],
[
'key' => Document::TYPE_META_KEY,
'value' => array_keys( $document_types ),
],
],
],
'orderby' => 'modified',
's' => $options['filter'],
];
$recently_edited_query = new \WP_Query( $recently_edited_query_args );
$items = [];
/** @var \WP_Post $post */
foreach ( $recently_edited_query->posts as $post ) {
$document = Plugin::$instance->documents->get( $post->ID );
if ( ! $document ) {
continue;
}
$is_template = Source_Local::CPT === $post->post_type;
$description = $document->get_title();
$icon = 'document-file';
if ( $is_template ) {
$description = esc_html__( 'Template', 'elementor' ) . ' / ' . $description;
$icon = 'post-title';
}
$items[] = [
'icon' => $icon,
'title' => esc_html( $post->post_title ),
'description' => $description,
'url' => $document->get_edit_url(),
'actions' => [
[
'name' => 'view',
'url' => $document->get_permalink(),
'icon' => 'preview-medium',
],
],
];
}
return $items;
}
}
finder/categories/general.php 0000755 00000004522 15111605150 0012267 0 ustar 00 [
'title' => esc_html__( 'Saved Templates', 'elementor' ),
'icon' => 'library-save',
'url' => Source_Local::get_admin_url(),
'keywords' => [ 'template', 'section', 'page', 'library' ],
],
'system-info' => [
'title' => esc_html__( 'System Info', 'elementor' ),
'icon' => 'info-circle-o',
'url' => admin_url( 'admin.php?page=elementor-system-info' ),
'keywords' => [ 'system', 'info', 'environment', 'elementor' ],
],
'role-manager' => [
'title' => esc_html__( 'Role Manager', 'elementor' ),
'icon' => 'person',
'url' => Role_Manager::get_url(),
'keywords' => [ 'role', 'manager', 'user', 'elementor' ],
],
'knowledge-base' => [
'title' => esc_html__( 'Knowledge Base', 'elementor' ),
'url' => admin_url( 'admin.php?page=go_knowledge_base_site' ),
'keywords' => [ 'help', 'knowledge', 'docs', 'elementor' ],
],
'theme-builder' => [
'title' => esc_html__( 'Theme Builder', 'elementor' ),
'icon' => 'library-save',
'url' => Plugin::$instance->app->get_settings( 'menu_url' ),
'keywords' => [ 'template', 'header', 'footer', 'single', 'archive', 'search', '404', 'library' ],
],
'kit-library' => [
'title' => esc_html__( 'Website Templates', 'elementor' ),
'icon' => 'kit-parts',
'url' => Plugin::$instance->app->get_base_url() . '&source=finder#/kit-library',
'keywords' => [ 'Website Templates', 'kit library', 'kit', 'library', 'site parts', 'parts', 'assets', 'templates' ],
],
];
}
}
finder/categories/settings.php 0000755 00000004534 15111605150 0012515 0 ustar 00 [
'title' => esc_html__( 'General Settings', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'general' ),
'keywords' => [ 'general', 'settings', 'elementor' ],
],
'integrations' => [
'title' => esc_html__( 'Integrations', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'integrations' ),
'keywords' => [ 'integrations', 'settings', 'elementor' ],
],
'advanced' => [
'title' => esc_html__( 'Advanced', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'advanced' ),
'keywords' => [ 'advanced', 'settings', 'elementor' ],
],
'performance' => [
'title' => esc_html__( 'Performance', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'performance' ),
'keywords' => [ 'performance', 'settings', 'elementor' ],
],
'experiments' => [
'title' => esc_html__( 'Experiments', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'experiments' ),
'keywords' => [ 'settings', 'elementor', 'experiments' ],
],
'features' => [
'title' => esc_html__( 'Features', 'elementor' ),
'url' => ElementorSettings::get_settings_tab_url( 'experiments' ),
'keywords' => [ 'settings', 'elementor', 'features' ],
],
'element-manager' => [
'title' => esc_html__( 'Element Manager', 'elementor' ),
'url' => admin_url( 'admin.php?page=' . ElementManagerModule::PAGE_ID ),
'keywords' => [ 'settings', 'elements', 'widgets', 'manager' ],
],
];
}
}
finder/categories/site.php 0000755 00000004047 15111605150 0011620 0 ustar 00 [
'title' => esc_html__( 'Homepage', 'elementor' ),
'url' => home_url(),
'icon' => 'home-heart',
'keywords' => [ 'home', 'page' ],
],
'wordpress-dashboard' => [
'title' => esc_html__( 'Dashboard', 'elementor' ),
'icon' => 'dashboard',
'url' => admin_url(),
'keywords' => [ 'dashboard', 'wordpress' ],
],
'wordpress-menus' => [
'title' => esc_html__( 'Menus', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'nav-menus.php' ),
'keywords' => [ 'menu', 'wordpress' ],
],
'wordpress-themes' => [
'title' => esc_html__( 'Themes', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'themes.php' ),
'keywords' => [ 'themes', 'wordpress' ],
],
'wordpress-customizer' => [
'title' => esc_html__( 'Customizer', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'customize.php' ),
'keywords' => [ 'customizer', 'wordpress' ],
],
'wordpress-plugins' => [
'title' => esc_html__( 'Plugins', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'plugins.php' ),
'keywords' => [ 'plugins', 'wordpress' ],
],
'wordpress-users' => [
'title' => esc_html__( 'Users', 'elementor' ),
'icon' => 'wordpress',
'url' => admin_url( 'users.php' ),
'keywords' => [ 'users', 'profile', 'wordpress' ],
],
];
}
}
finder/categories/tools.php 0000755 00000004055 15111605150 0012013 0 ustar 00 [
'title' => esc_html__( 'Tools', 'elementor' ),
'icon' => 'tools',
'url' => $tools_url,
'keywords' => [ 'tools', 'regenerate css', 'safe mode', 'debug bar', 'sync library', 'elementor' ],
],
'replace-url' => [
'title' => esc_html__( 'Replace URL', 'elementor' ),
'icon' => 'tools',
'url' => $tools_url . '#tab-replace_url',
'keywords' => [ 'tools', 'replace url', 'domain', 'elementor' ],
],
'maintenance-mode' => [
'title' => esc_html__( 'Maintenance Mode', 'elementor' ),
'icon' => 'tools',
'url' => $tools_url . '#tab-maintenance_mode',
'keywords' => [ 'tools', 'maintenance', 'coming soon', 'elementor' ],
],
'import-export' => [
'title' => esc_html__( 'Import Export', 'elementor' ),
'icon' => 'import-export',
'url' => $tools_url . '#tab-import-export-kit',
'keywords' => [ 'tools', 'import export', 'import', 'export', 'kit' ],
],
];
if ( ElementorTools::can_user_rollback_versions() ) {
$items['version-control'] = [
'title' => esc_html__( 'Version Control', 'elementor' ),
'icon' => 'time-line',
'url' => $tools_url . '#tab-versions',
'keywords' => [ 'tools', 'version', 'control', 'rollback', 'beta', 'elementor' ],
];
}
return $items;
}
}
finder/base-category.php 0000755 00000003127 15111605150 0011252 0 ustar 00 modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function(
get_class( $this ) . '::' . __FUNCTION__,
'3.5.0',
'This method will be replaced with an abstract method.'
);
return '';
}
/**
* Get category items.
*
* @since 2.3.0
* @abstract
* @access public
*
* @param array $options
*
* @return array
*/
abstract public function get_category_items( array $options = [] );
/**
* Is dynamic.
*
* Determine if the category is dynamic.
*
* @since 2.3.0
* @access public
*
* @return bool
*/
public function is_dynamic() {
return false;
}
/**
* Get init settings.
*
* @since 2.3.0
* @access protected
*
* @return array
*/
protected function get_init_settings() {
$settings = [
'title' => $this->get_title(),
'dynamic' => $this->is_dynamic(),
];
if ( ! $settings['dynamic'] ) {
$settings['items'] = $this->get_category_items();
}
return $settings;
}
}
finder/categories-manager.php 0000755 00000007273 15111605150 0012270 0 ustar 00 modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function(
__METHOD__,
'3.5.0',
'register()'
);
$this->register( $category, $category_name );
}
/**
* Register finder category.
*
* @since 3.5.0
* @access public
*
* @param Base_Category $finder_category_instance An Instance of a category.
* @param string $finder_category_name A Category name. Deprecated parameter.
*
* @return void
*/
public function register( Base_Category $finder_category_instance, $finder_category_name = null ) {
// TODO: For BC. Remove in the future.
if ( $finder_category_name ) {
Plugin::instance()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_argument(
'$finder_category_name', '3.5.0'
);
} else {
$finder_category_name = $finder_category_instance->get_id();
}
$this->categories[ $finder_category_name ] = $finder_category_instance;
}
/**
* Unregister a finder category.
*
* @param string $finder_category_name - Category to unregister.
*
* @return void
* @since 3.6.0
* @access public
*/
public function unregister( $finder_category_name ) {
unset( $this->categories[ $finder_category_name ] );
}
/**
* Get categories.
*
* Retrieve the registered categories, or a specific category if the category name
* is provided as a parameter.
*
* @since 2.3.0
* @access public
*
* @param string $category Category name.
*
* @return Base_Category|Base_Category[]|null
*/
public function get_categories( $category = '' ) {
if ( ! $this->categories ) {
$this->init_categories();
}
if ( $category ) {
if ( isset( $this->categories[ $category ] ) ) {
return $this->categories[ $category ];
}
return null;
}
return $this->categories;
}
/**
* Init categories.
*
* Used to initialize the native finder categories.
*
* @since 2.3.0
* @access private
*/
private function init_categories() {
foreach ( $this->categories_list as $category_name ) {
$class_name = __NAMESPACE__ . '\Categories\\' . $category_name;
$this->register( new $class_name() );
}
/**
* Elementor Finder categories init.
*
* Fires after Elementor Finder initialize it's native categories.
*
* This hook should be used to add your own Finder categories.
*
* @since 2.3.0
* @deprecated 3.5.0 Use `elementor/finder/register` hook instead.
*
* @param Categories_Manager $this.
*/
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->do_deprecated_action(
'elementor/finder/categories/init',
[ $this ],
'3.5.0',
'elementor/finder/register'
);
/**
* Elementor Finder categories registration.
*
* Fires after Elementor Finder initialize it's native categories.
*
* This hook should be used to register your own Finder categories.
*
* @since 3.5.0
*
* @param Categories_Manager $this Finder Categories manager.
*/
do_action( 'elementor/finder/register', $this );
}
}
finder/module.php 0000755 00000005221 15111605150 0010007 0 ustar 00 categories_manager = new Categories_Manager();
$this->add_template();
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
}
/**
* Get name.
*
* @since 2.3.0
* @access public
*
* @return string
*/
public function get_name() {
return 'finder';
}
/**
* Add template.
*
* @since 2.3.0
* @access public
*/
public function add_template() {
Plugin::$instance->common->add_template( __DIR__ . '/template.php' );
}
/**
* Register ajax actions.
*
* @since 2.3.0
* @access public
*
* @param Ajax $ajax
*/
public function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'finder_get_category_items', [ $this, 'ajax_get_category_items' ] );
}
/**
* Ajax get category items.
*
* @since 2.3.0
* @access public
*
* @param array $data
*
* @return array
*
* @throws \Exception If finder category registration fails or validation errors occur.
*/
public function ajax_get_category_items( array $data ) {
if ( ! current_user_can( 'manage_options' ) ) {
throw new \Exception( 'Access denied.' );
}
$category = $this->categories_manager->get_categories( $data['category'] );
return $category->get_category_items( $data );
}
/**
* Get init settings.
*
* @since 2.3.0
* @access protected
*
* @return array
*/
protected function get_init_settings() {
$categories = $this->categories_manager->get_categories();
$categories_data = [];
foreach ( $categories as $category_name => $category ) {
$categories_data[ $category_name ] = array_merge( $category->get_settings(), [ 'name' => $category_name ] );
}
/**
* Finder categories.
*
* Filters the list of finder categories. This hook is used to manage Finder
* categories - to add new categories, remove and edit existing categories.
*
* @since 2.3.0
*
* @param array $categories_data A list of finder categories.
*/
$categories_data = apply_filters( 'elementor/finder/categories', $categories_data );
return [
'data' => $categories_data,
];
}
}
finder/template.php 0000755 00000003632 15111605150 0010341 0 ustar 00