/home/techb158/public_html/wp-content/plugins/optinmonster/OMAPI
NameSizeModeActions
EasyDigitalDownloads/-0755rm
Elementor/-0755rm
Integrations/-0755rm
MemberPress/-0755rm
Plugins/-0755rm
Promos/-0755rm
Rules/-0755rm
Shortcodes/-0755rm
WooCommerce/-0755rm
WPForms/-0755rm
Actions.php79700666editdlrm
Ajax.php14940666editdlrm
Api.php145070666editdlrm
ApiAuth.php24630666editdlrm
ApiKey.php52030666editdlrm
AssetLoader.php54890666editdlrm
BaseRestApi.php68890666editdlrm
Blocks.php131050666editdlrm
ClassicEditor.php70840666editdlrm
ConstantContact.php75910666editdlrm
Debug.php45920666editdlrm
EasyDigitalDownloads.php95530666editdlrm
Elementor.php54930666editdlrm
Inserter.php115710666editdlrm
InstallSkin.php13860666editdlrm
InstallSkinCompat.php13950666editdlrm
MailPoet.php139740666editdlrm
MemberPress.php42160666editdlrm
Menu.php173010666editdlrm
Notifications.php189100666editdlrm
OmuApi.php41220666editdlrm
Output.php258480666editdlrm
Pages.php180190666editdlrm
Partners.php55570666editdlrm
Plugins.php249230666editdlrm
Promos.php11310666editdlrm
Refresh.php65530666editdlrm
RestApi.php441900666editdlrm
RevenueAttribution.php30370666editdlrm
Review.php14820666editdlrm
Rules.php240000666editdlrm
Save.php119990666editdlrm
Shortcode.php36670666editdlrm
Sites.php85530666editdlrm
Support.php91920666editdlrm
Type.php37220666editdlrm
Urls.php89510666editdlrm
Utils.php75890666editdlrm
Validate.php92660666editdlrm
Welcome.php49290666editdlrm
Widget.php66620666editdlrm
WooCommerce.php200450666editdlrm
WpErrorException.php7140666editdlrm
WPForms.php26650666editdlrm
Edit: /home/techb158/public_html/wp-content/plugins/optinmonster/OMAPI/RestApi.php (44190B)
namespace, 'info', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_or_has_api_key' ), 'callback' => array( $this, 'output_info' ), ) ); // Fetch in-depth support info about this WP installation. register_rest_route( $this->namespace, 'support', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_or_has_api_key' ), 'callback' => array( $this, 'support_info' ), ) ); // Toggles rule debug. register_rest_route( $this->namespace, 'support/debug/enable', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_or_has_api_key' ), 'callback' => array( $this, 'rule_debug_enable' ), ) ); register_rest_route( $this->namespace, 'support/debug/disable', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_or_has_api_key' ), 'callback' => array( $this, 'rule_debug_disable' ), ) ); // Proxy route for getting the /me data from the app. register_rest_route( $this->namespace, 'me', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'get_me' ), ) ); // Route for triggering refreshing/syncing of all campaigns. register_rest_route( $this->namespace, 'campaigns/refresh', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'refresh_campaigns' ), ) ); // Route for fetching the campaign data for specific campaign. register_rest_route( $this->namespace, 'campaigns/(?P\w+)', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'get_campaign_data' ), ) ); // Route for updating the campaign data. register_rest_route( $this->namespace, 'campaigns/(?P\w+)', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'update_campaign_data' ), ) ); // Route for triggering refreshing/syncing of a single campaign. register_rest_route( $this->namespace, 'campaigns/(?P[\w-]+)/sync', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'logged_in_or_has_api_key' ), 'callback' => array( $this, 'sync_campaign' ), ) ); // Route for fetching data/resources needed for the campaigns. register_rest_route( $this->namespace, 'resources', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'get_wp_resources' ), ) ); register_rest_route( $this->namespace, 'notifications', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'get_notifications' ), ) ); register_rest_route( $this->namespace, 'notifications/dismiss', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'dismiss_notification' ), ) ); register_rest_route( $this->namespace, 'notifications/create', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'create_event_notification' ), ) ); register_rest_route( $this->namespace, 'plugins', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'get_am_plugins_list' ), ) ); register_rest_route( $this->namespace, 'plugins', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'handle_plugin_action' ), ) ); register_rest_route( $this->namespace, 'api', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'can_store_api_key' ), 'callback' => array( $this, 'init_api_key_connection' ), ) ); // Only register the regenerate route when we have a token in the DB. if ( OMAPI_ApiAuth::has_token() ) { register_rest_route( $this->namespace, 'api/regenerate', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'can_store_regenerated_api_key' ), 'callback' => array( $this, 'store_regenerated_api_key' ), ) ); } register_rest_route( $this->namespace, 'api', array( 'methods' => WP_REST_Server::DELETABLE, 'permission_callback' => array( $this, 'can_delete_api_key' ), 'callback' => array( $this, 'disconnect' ), ) ); register_rest_route( $this->namespace, 'settings', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_and_can_access_route' ), 'callback' => array( $this, 'get_settings' ), ) ); register_rest_route( $this->namespace, 'settings', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'can_update_settings' ), 'callback' => array( $this, 'update_settings' ), ) ); register_rest_route( $this->namespace, 'review/dismiss', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'can_dismiss_review' ), 'callback' => array( $this, 'dismiss_review' ), ) ); register_rest_route( $this->namespace, 'omu/courses', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_or_has_api_key' ), 'callback' => array( $this, 'get_courses' ), ) ); register_rest_route( $this->namespace, 'omu/guides', array( 'methods' => WP_REST_Server::READABLE, 'permission_callback' => array( $this, 'logged_in_or_has_api_key' ), 'callback' => array( $this, 'get_guides' ), ) ); register_rest_route( $this->namespace, 'account/sync', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'logged_in_or_has_api_key' ), 'callback' => array( $this, 'sync_account' ), ) ); register_rest_route( $this->namespace, 'account/connect', array( 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => array( $this, 'has_connection_token' ), 'callback' => array( $this, 'connect_account' ), ) ); do_action( 'optin_monster_api_rest_register_routes', $this ); } /** * Filters the list of request headers that are allowed for CORS requests, * and ensures our API key is allowed. * * @since 1.9.12 * * @param string[] $allow_headers The list of headers to allow. * * @return string[] */ public function set_allow_headers( $allow_headers ) { $allow_headers[] = 'X-OptinMonster-ApiKey'; $this->allow_header_set = true; // remove fall-through. remove_filter( 'rest_send_nocache_headers', array( $this, 'fallback_set_allow_headers' ), 999 ); return $allow_headers; } /** * Fallback to make sure we set the allow headers. * * @since 1.9.12 * * @param bool $rest_send_nocache_headers Whether to send no-cache headers. * We ignore this, because we're simply using this * as an action hook. * * @return bool Unchanged result. */ public function fallback_set_allow_headers( $rest_send_nocache_headers ) { if ( ! $this->allow_header_set && ! headers_sent() ) { foreach ( headers_list() as $header ) { if ( 0 === strpos( $header, 'Access-Control-Allow-Headers: ' ) ) { list( $key, $value ) = explode( 'Access-Control-Allow-Headers: ', $header ); if ( false === strpos( $value, 'X-OptinMonster-ApiKey' ) ) { header( 'Access-Control-Allow-Headers: ' . $value . ', X-OptinMonster-ApiKey' ); } $this->allow_header_set = true; break; } } } return $rest_send_nocache_headers; } /** * Gets the /me data from the app. * * Route: GET omapp/v1/me * * @since 2.6.6 * * @return WP_REST_Response The API Response */ public function get_me() { $data = OMAPI_Api::fetch_me_cached( true ); return is_wp_error( $data ) ? $this->wp_error_to_response( $data, OMAPI_Api::instance()->response_body ) : new WP_REST_Response( $data, 200 ); } /** * Triggers refreshing our campaigns. * * Route: POST omapp/v1/campaigns/refresh * * @since 1.9.10 * * @return WP_REST_Response The API Response */ public function refresh_campaigns() { $result = $this->base->refresh->refresh(); return is_wp_error( $result ) ? $this->wp_error_to_response( $result, OMAPI_Api::instance()->response_body ) : new WP_REST_Response( array( 'message' => esc_html__( 'OK', 'optin-monster-api' ) ), 200 ); } /** * Fetch some quick info about this WP installation * (WP version, plugin version, rest url, home url, WooCommerce version) * * Route: GET omapp/v1/info * * @since 1.9.10 * * @return WP_REST_Response */ public function output_info() { return new WP_REST_Response( $this->base->refresh->get_info_args(), 200 ); } /** * Fetch in-depth support info about this WP installation. * Used for the debug PDF, but can also be requested by support staff with the right api key. * * Route: GET omapp/v1/support * * @since 1.9.10 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response */ public function support_info( $request ) { $support = new OMAPI_Support(); $format = $request->get_param( 'format' ); if ( empty( $format ) ) { $format = 'raw'; } return new WP_REST_Response( $support->get_support_data( $format ), 200 ); } /** * Enables the rules debug output for this site. * (Still requires the omwpdebug query var on the frontend) * * Route: GET omapp/v1/support/debug/enable * * @since 2.4.0 * * @return WP_REST_Response */ public function rule_debug_enable() { return $this->toggle_rule_debug( true ); } /** * Disables the rules debug output for this site. * * Route: GET omapp/v1/support/debug/disable * * @since 2.4.0 * * @return WP_REST_Response */ public function rule_debug_disable() { return $this->toggle_rule_debug( false ); } /** * Toggles the rules debug setting. * * @since 2.4.0 * * @param boolean $enable Whether to enable/disable the rules debug setting. * * @return WP_REST_Response */ protected function toggle_rule_debug( $enable ) { $options = $this->base->get_option(); if ( $enable ) { $options['api']['omwpdebug'] = true; } else { unset( $options['api']['omwpdebug'] ); } $updated = $this->base->save->update_option( $options ); return new WP_REST_Response( array( 'message' => $updated ? esc_html__( 'OK', 'optin-monster-api' ) : esc_html__( 'Not Modified', 'optin-monster-api' ), ), $updated ? 200 : 202 ); } /** * Triggering refreshing/syncing of a single campaign. * * Route: POST omapp/v1/campaigns/(?P[\w-]+)/sync * * @since 1.9.10 * * @param WP_REST_Request $request The REST Request. * @return WP_REST_Response The API Response */ public function sync_campaign( $request ) { $campaign_id = $request->get_param( 'id' ); if ( empty( $campaign_id ) ) { return new WP_REST_Response( array( 'message' => esc_html__( 'No campaign ID given.', 'optin-monster-api' ) ), 400 ); } $this->base->refresh->sync( $campaign_id ); return new WP_REST_Response( array( 'message' => esc_html__( 'OK', 'optin-monster-api' ) ), 200 ); } /** * Gets all the data needed for the campaign dashboard for a given campaign. * * Route: GET omapp/v1/campaigns/(?P\w+) * * @since 1.9.10 * * @param WP_REST_Request $request The REST Request. * @return WP_REST_Response The API Response * * @throws OMAPI_WpErrorException If there is a WordPress error during the process. */ public function get_campaign_data( $request ) { try { $campaign_id = $request->get_param( 'id' ); if ( empty( $campaign_id ) ) { return new WP_REST_Response( array( 'message' => esc_html__( 'No campaign ID given.', 'optin-monster-api' ) ), 400 ); } $campaign = $this->base->get_optin_by_slug( $campaign_id ); if ( empty( $campaign->ID ) ) { $this->base->refresh->sync( $campaign_id ); if ( is_wp_error( $this->base->refresh->error ) ) { $e = new OMAPI_WpErrorException(); throw $e->setWpError( $this->base->refresh->error ); } $campaign = $this->base->get_optin_by_slug( $campaign_id ); } if ( empty( $campaign->ID ) ) { return new WP_REST_Response( array( /* translators: %s: the campaign post id. */ 'message' => sprintf( esc_html__( 'Could not find campaign by given ID: %s. Are you sure campaign is associated with this site?', 'optin-monster-api' ), $campaign_id ), ), 404 ); } // Get Campaigns Data. $data = $this->base->collect_campaign_data( $campaign ); $data = apply_filters( 'optin_monster_api_setting_ui_data_for_campaign', $data, $campaign ); return new WP_REST_Response( $data, 200 ); } catch ( Exception $e ) { return $this->exception_to_response( $e ); } } /** * Updates data for given campaign. * * Route: POST omapp/v1/campaigns/(?P\w+) * * @since 1.9.10 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response */ public function update_campaign_data( $request ) { $campaign_id = $request->get_param( 'id' ); $campaign = $this->base->get_optin_by_slug( $campaign_id ); if ( empty( $campaign ) || empty( $campaign->ID ) ) { return new WP_REST_Response( array( /* translators: %s: the campaign post id. */ 'message' => sprintf( esc_html__( 'Could not find campaign by given ID: %s. Are you sure campaign is associated with this site?', 'optin-monster-api' ), $campaign_id ), ), 404 ); } // Get the Request Params. $fields = json_decode( $request->get_body(), true ); // A malformed or non-object body decodes to something other than an // array; treat it as no fields rather than indexing/iterating a scalar. if ( ! is_array( $fields ) ) { $fields = array(); } if ( ! empty( $fields['taxonomies'] ) ) { if ( isset( $fields['taxonomies']['categories'] ) ) { $fields['categories'] = $fields['taxonomies']['categories']; } // Save the data from the regular taxonomies fields into the WC specific tax field. // For back-compatibility. $fields['is_wc_product_category'] = isset( $fields['taxonomies']['product_cat'] ) ? $fields['taxonomies']['product_cat'] : array(); $fields['is_wc_product_tag'] = isset( $fields['taxonomies']['product_tag'] ) ? $fields['taxonomies']['product_tag'] : array(); } // Only write recognized campaign settings; ignore any other keys so a // caller cannot set arbitrary _omapi_* post meta on the campaign. $allowed = self::get_allowed_campaign_meta_keys(); foreach ( $fields as $key => $value ) { if ( ! in_array( $key, $allowed, true ) ) { continue; } $value = $this->sanitize( $value ); update_post_meta( $campaign->ID, '_omapi_' . $key, $value ); } return new WP_REST_Response( array( 'message' => esc_html__( 'OK', 'optin-monster-api' ) ), 200 ); } /** * Campaign meta keys (without the `_omapi_` prefix) that may be written * via the update_campaign_data REST endpoint. * * The campaign builder round-trips every existing `_omapi_*` meta key back * through this endpoint (its getSettings() posts the whole post_meta object * minus `ids`/`type`), so this must cover every key the plugin or an active * integration persists via that screen, or a save silently drops the * setting. To avoid that, integration display-rule keys (WooCommerce, EDD, * MemberPress) are read from the public `optin_monster_api_output_fields` * filter, so a newly-added integration field is allowed automatically with * no duplicated list to drift out of sync. * * `ids`, `type`, `output`, `shortcode`, and `shortcode_output` are always excluded: * OMAPI_Save owns them on the SaaS-sync path and the builder only * round-trips them incidentally. Excluding `ids`/`type` removes a * campaign-identity corruption vector; excluding `shortcode_output` (and its * `shortcode` flag) removes the stored-XSS write vector in issue #833, since * that meta feeds an html_entity_decode() render sink in OMAPI_Output. * * @since 2.17.0 * * @return array Allowed meta keys (without the `_omapi_` prefix). */ public static function get_allowed_campaign_meta_keys() { $keys = array( // Core display/output rules ( OMAPI_Rules::$fields, minus type ). 'enabled', 'automatic', 'users', 'never', 'only', 'categories', 'taxonomies', 'show', 'test', // Inline auto-placement ( OMAPI_Output ). 'auto_location', 'auto_location_paragraphs', 'auto_location_words', // MailPoet integration ( OMAPI_MailPoet ). 'mailpoet', 'mailpoet_list', 'mailpoet_fields_auto_create', 'mailpoet_mapped_fields', 'mailpoet_optin_fields_config', 'mailpoet_phone_field', ); // Ensure integrations have registered before reading their filter fields. OMAPI_Rules::init_extensions(); $keys = apply_filters( 'optin_monster_api_output_fields', $keys ); $keys = is_array( $keys ) ? array_unique( $keys ) : array(); // These keys are never writable here regardless of what a filter adds. $never_writable = array( 'ids', 'type', 'output', 'shortcode', 'shortcode_output' ); return array_values( array_diff( $keys, $never_writable ) ); } /** * Gets all the data/resources needed for the campaigns. * * Route: GET omapp/v1/resources * * @since 1.9.10 * * @param WP_REST_Request $request The REST Request. * @return WP_REST_Response The API Response */ public function get_wp_resources( $request ) { global $wpdb; $excluded = $request->get_param( 'excluded' ); $excluded = ! empty( $excluded ) ? explode( ',', $excluded ) : array(); if ( $request->get_param( 'refresh' ) ) { $result = $this->refresh_campaigns(); if ( is_wp_error( $result ) ) { $error_data = $result->get_error_data(); if ( empty( $error_data['type'] ) || 'no-campaigns-error' !== $error_data['type'] ) { return $result; } } } $campaign_data = array(); if ( ! in_array( 'campaigns', $excluded, true ) ) { // Get Campaigns Data. $campaigns = $this->base->get_optins( array( 'post_status' => 'any' ) ); $campaigns = ! empty( $campaigns ) ? $campaigns : array(); foreach ( $campaigns as $campaign ) { $campaign_data[] = $this->base->collect_campaign_data( $campaign ); } } $mailpoet = $this->base->is_mailpoet_active(); $taxonomy_map = array(); if ( ! in_array( 'taxonomies', $excluded, true ) ) { // Get Taxonomies Data. $taxonomies = get_taxonomies( array( 'public' => true ), 'objects' ); $taxonomies = apply_filters( 'optin_monster_api_setting_ui_taxonomies', $taxonomies ); foreach ( $taxonomies as $taxonomy ) { if ( 'category' === $taxonomy->name ) { $cats = get_categories(); $taxonomy_map['category'] = array( 'name' => 'category', 'label' => ucwords( $taxonomy->label ), 'terms' => is_array( $cats ) ? array_values( $cats ) : array(), 'for' => $taxonomy->object_type, ); continue; } $terms = get_terms( array( 'taxonomy' => $taxonomy->name, 'get' => 'all', ) ); $taxonomy_map[ $taxonomy->name ] = array( 'name' => $taxonomy->name, 'label' => ucwords( $taxonomy->label ), 'terms' => is_array( $terms ) ? array_values( $terms ) : array(), 'for' => $taxonomy->object_type, ); } } $posts = array(); if ( ! in_array( 'posts', $excluded, true ) ) { // Posts query for public post types in publish/future status. $post_types = get_post_types( array( 'public' => true ) ); // Guard against an empty set so we never emit `IN ()` (a syntax error). if ( ! empty( $post_types ) ) { $placeholders = implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ); $posts = (array) $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders is only %s tokens (one per public post type); the post-type values are bound via prepare(). "SELECT ID AS `value`, post_title AS `name` FROM {$wpdb->posts} WHERE post_type IN ($placeholders) AND post_status IN ('publish', 'future') ORDER BY post_title ASC", array_values( $post_types ) ), ARRAY_A ); } } $post_types = ! in_array( 'post_types', $excluded, true ) ? array_values( get_post_types( array( 'public' => true ), 'object' ) ) : array(); // Get "Config" data. $config = array( 'hasMailPoet' => $mailpoet, 'isWooActive' => OMAPI_WooCommerce::is_active(), 'isWooConnected' => OMAPI_WooCommerce::is_connected(), 'isWPFormsActive' => OMAPI_WPForms::is_active(), 'isEddActive' => OMAPI_EasyDigitalDownloads::is_active(), 'isEddConnected' => OMAPI_EasyDigitalDownloads::is_connected(), 'mailPoetLists' => $mailpoet && ! in_array( 'mailPoetLists', $excluded, true ) ? $this->base->mailpoet->get_lists() : array(), 'mailPoetFields' => $mailpoet && ! in_array( 'mailPoetFields', $excluded, true ) ? $this->base->mailpoet->get_custom_fields_dropdown_values() : array(), ); $omapi_plugins = new OMAPI_Plugins(); $response_data = apply_filters( 'optin_monster_api_setting_ui_data', array( 'config' => $config, 'campaigns' => $campaign_data, 'taxonomies' => $taxonomy_map, 'posts' => $posts, 'post_types' => $post_types, 'siteId' => $this->base->get_site_id(), 'siteIds' => $this->base->get_site_ids(), 'pluginsInfo' => $omapi_plugins->get_active_plugins_header_value(), ) ); return new WP_REST_Response( $response_data, 200 ); } /** * Gets the list of AM notifications. * * Route: GET omapp/v1/notifications * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response */ public function get_notifications( $request ) { add_filter( 'optin_monster_api_admin_notifications_has_access', array( $this, 'maybe_allow' ) ); if ( ! $this->base->notifications->has_access() ) { return new WP_REST_Response( array(), 206 ); } // Make sure we have all the required user parameters. $this->base->actions->maybe_fetch_missing_data(); return new WP_REST_Response( $this->base->notifications->get( true ), 200 ); } /** * Dismiss a given notifications. * * Route: POST omapp/v1/notifications/dismiss * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response */ public function dismiss_notification( $request ) { add_filter( 'optin_monster_api_admin_notifications_has_access', array( $this, 'maybe_allow' ) ); $ids = $request->get_json_params(); if ( $this->base->notifications->dismiss( $ids ) ) { return new WP_REST_Response( $this->base->notifications->get( true ), 200 ); } return new WP_REST_Response( array( 'message' => sprintf( /* translators: %s: the notification id(s). */ esc_html__( 'Could not dismiss: %s', 'optin-monster-api' ), implode( ', ', $ids ) ), ), 400 ); } /** * Dismiss a given notifications. * * Route: POST omapp/v1/notifications/create * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response */ public function create_event_notification( $request ) { add_filter( 'optin_monster_api_admin_notifications_has_access', array( $this, 'maybe_allow' ) ); $payload = $request->get_json_params(); $errors = array(); foreach ( $payload as $notification ) { $added = $this->base->notifications->add_event( $notification ); if ( is_wp_error( $added ) ) { $errors[] = $added; } } $updated = $this->base->notifications->get( true ); if ( ! empty( $errors ) ) { $message = count( $payload ) > 1 ? sprintf( /* translators: %s: "Some" or "one". */ esc_html__( 'Could not create %s of the event notifications!', 'optin-monster-api' ), count( $errors ) > 1 ? esc_html__( 'some', 'optin-monster-api' ) : esc_html__( 'one', 'optin-monster-api' ) ) : esc_html__( 'Could not create event notification!', 'optin-monster-api' ); foreach ( $errors as $error ) { $message .= '
- ' . $error->get_error_message(); } return new WP_REST_Response( array( 'message' => $message, 'notifications' => $updated, ), 400 ); } return new WP_REST_Response( $updated, 200 ); } /** * Maybe allow api-key authenticated user to see notifications. * * @since 2.0.0 * * @param bool $access If current user has access to notifications. * * @return bool Maybe modified access. */ public function maybe_allow( $access ) { if ( ! $access && $this->has_valid_api_key ) { $access = ! $this->base->get_option( 'hide_announcements' ); } return $access; } /** * Gets the list of AM plugins. * * Route: GET omapp/v1/plugins * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response */ public function get_am_plugins_list( $request ) { $plugins = new OMAPI_Plugins(); $data = $plugins->get_list_with_status(); $action_nonce = wp_create_nonce( 'om_plugin_action_nonce' ); foreach ( $data as $plugin_id => $plugin ) { $data[ $plugin_id ]['actionNonce'] = $action_nonce; } return new WP_REST_Response( array_values( $data ), 200 ); } /** * Handles installing or activating an AM plugin. * * Route: POST omapp/v1/plugins * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response * @throws Exception If plugin action fails. */ public function handle_plugin_action( $request ) { try { // Check the nonce. if ( ! $this->has_valid_plugin_action_nonce( $request ) ) { throw new Exception( esc_html__( 'Security token invalid!', 'optin-monster-api' ), rest_authorization_required_code() ); } $id = $request->get_param( 'id' ); if ( empty( $id ) ) { throw new Exception( esc_html__( 'Plugin Id required.', 'optin-monster-api' ), 400 ); } $plugins = new OMAPI_Plugins(); $plugin = $plugins->get( $id ); if ( empty( $plugin['installed'] ) ) { if ( empty( $plugin['url'] ) ) { throw new Exception( esc_html__( 'Plugin install URL required.', 'optin-monster-api' ), 400 ); } return new WP_REST_Response( $plugins->install_plugin( $plugin ), 200 ); } $which = 'default' === $plugin['which'] ? $id : $plugin['which']; return new WP_REST_Response( $plugins->activate_plugin( $which ), 200 ); } catch ( Exception $e ) { return $this->exception_to_response( $e ); } } /** * Whether the request is authorized to perform a plugin install/activate action. * * Accepts the per-plugin `om_plugin_action_nonce` minted by `get_am_plugins_list()`, and * falls back to the `wp_rest` nonce when that one is absent or invalid. * * The fallback is not a weakening: this route's permission callback already requires the * OptinMonster access capability, and WP core rejects any cookie-authenticated write * without a valid `wp_rest` nonce before this callback ever runs, so CSRF is covered * either way. Requiring `om_plugin_action_nonce` on its own meant a client replaying a * cached plugins list sent an expired token and legitimate installs 403'd (#944). * * @since 2.17.0 * * @param WP_REST_Request $request The REST Request. * * @return bool Whether the request carries a valid token. */ protected function has_valid_plugin_action_nonce( $request ) { $action_nonce = $request->get_param( 'actionNonce' ); if ( ! empty( $action_nonce ) && wp_verify_nonce( $action_nonce, 'om_plugin_action_nonce' ) ) { return true; } // Check the same value core would have used, header first, then the query/body param. $rest_nonce = $request->get_header( 'X-WP-Nonce' ); if ( empty( $rest_nonce ) ) { $rest_nonce = $request->get_param( '_wpnonce' ); } return ! empty( $rest_nonce ) && (bool) wp_verify_nonce( $rest_nonce, 'wp_rest' ); } /** * Handles storing the API key and initiating the API connection. * * Route: POST omapp/v1/api * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response * @throws Exception|OMAPI_WpErrorException If plugin action fails or API Key is missing. */ public function init_api_key_connection( $request ) { try { $apikey = $request->get_param( 'key' ); if ( empty( $apikey ) ) { throw new Exception( esc_html__( 'API Key Missing!', 'optin-monster-api' ), 400 ); } $result = OMAPI_ApiKey::init_connection( $apikey ); if ( is_wp_error( $result ) ) { $e = new OMAPI_WpErrorException(); throw $e->setWpError( $result ); } return new WP_REST_Response( $result, 200 ); } catch ( Exception $e ) { return $this->exception_to_response( $e ); } } /** * Determine if we can store the given api key. * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return bool * @throws Exception|OMAPI_WpErrorException If plugin action fails. */ public function can_store_api_key( $request ) { try { $this->verify_request_nonce( $request ); $apikey = $request->get_param( 'key' ); if ( empty( $apikey ) ) { throw new Exception( esc_html__( 'API Key Missing!', 'optin-monster-api' ), 400 ); } $result = OMAPI_ApiKey::verify( $apikey ); if ( is_wp_error( $result ) ) { $e = new OMAPI_WpErrorException(); throw $e->setWpError( $result ); } } catch ( Exception $e ) { return $this->exception_to_response( $e ); } return OMAPI::get_instance()->can_access( 'store_api_key' ); } /** * Handles storing the regenerated API key. * * Route: POST omapp/v1/api/regenerate * * @since 2.6.5 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response * @throws Exception If plugin action fails. */ public function store_regenerated_api_key( $request ) { try { $apikey = $request->get_param( 'key' ); if ( empty( $apikey ) ) { throw new Exception( esc_html__( 'API Key Missing!', 'optin-monster-api' ), 400 ); } $options = $this->base->get_option(); $options['api']['apikey'] = $apikey; $this->base->save->update_option( $options ); OMAPI_ApiAuth::delete_token(); return $this->output_info(); } catch ( Exception $e ) { OMAPI_ApiAuth::delete_token(); return $this->exception_to_response( $e ); } } /** * Determine if we can store given regenerated api key. * * @since 2.6.5 * * @param WP_REST_Request $request The REST Request. * * @return bool * @throws Exception If plugin action fails. */ public function can_store_regenerated_api_key( $request ) { try { $tt = $request->get_param( 'tt' ); $apikey = $request->get_param( 'key' ); if ( empty( $tt ) || empty( $apikey ) ) { throw new Exception( esc_html__( 'Required Credentials Missing!', 'optin-monster-api' ), rest_authorization_required_code() ); } $validated = OMAPI_ApiAuth::validate_token( $tt ); if ( empty( $validated ) ) { throw new Exception( esc_html__( 'Invalid token!', 'optin-monster-api' ), 403 ); } return true; } catch ( Exception $e ) { OMAPI_ApiAuth::delete_token(); return $this->exception_to_response( $e ); } } /** * Handles disconnecting the API key. * * Route: DELETE omapp/v1/api * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response * @throws Exception If plugin action fails. */ public function disconnect( $request ) { try { OMAPI_ApiKey::disconnect(); return new WP_REST_Response( array( 'message' => esc_html__( 'OK', 'optin-monster-api' ) ), 204 ); } catch ( Exception $e ) { return $this->exception_to_response( $e ); } } /** * Determine if we can disconnect the api key. * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return bool * @throws Exception If plugin action fails. */ public function can_delete_api_key( $request ) { try { $this->verify_request_nonce( $request ); if ( ! OMAPI_ApiKey::has_credentials() ) { throw new Exception( esc_html__( 'API Key Missing!', 'optin-monster-api' ), 400 ); } } catch ( Exception $e ) { return $this->exception_to_response( $e ); } return OMAPI::get_instance()->can_access( 'delete_api_key' ); } /** * Handles getting the misc. settings. * * Route: GET omapp/v1/settings * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response * @throws Exception If plugin action fails. */ public function get_settings( $request ) { $defaults = $this->base->default_options(); $options = $this->base->get_option(); $misc_settings = array(); foreach ( array( 'auto_updates', 'usage_tracking', 'hide_announcements' ) as $key ) { $misc_settings[ $key ] = isset( $options[ $key ] ) ? $options[ $key ] : $defaults[ $key ]; } return new WP_REST_Response( $misc_settings, 200 ); } /** * Handles updating settings. * * Route: POST omapp/v1/settings * * @since 2.0.0 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response * @throws Exception If plugin action fails. */ public function update_settings( $request ) { try { $settings = $request->get_param( 'settings' ); if ( empty( $settings ) ) { throw new Exception( esc_html__( 'Settings Missing!', 'optin-monster-api' ), 400 ); } $allowed_settings = array( 'auto_updates' => array( 'validate' => 'is_string', ), 'usage_tracking' => array( 'validate' => 'is_bool', ), 'hide_announcements' => array( 'validate' => 'is_bool', ), 'accountId' => array( 'validate' => 'is_string', ), 'currentLevel' => array( 'validate' => 'is_string', ), 'plan' => array( 'validate' => 'is_string', ), 'resetOnboardingPlugins' => array( 'validate' => 'is_bool', ), ); $options = $this->base->get_option(); $has_settings = false; foreach ( $settings as $setting => $value ) { if ( empty( $allowed_settings[ $setting ] ) ) { continue; } $has_settings = true; if ( isset( $options[ $setting ] ) && $value === $options[ $setting ] ) { continue; } $validator = $allowed_settings[ $setting ]['validate']; if ( call_user_func( $validator, $value ) ) { switch ( $validator ) { case 'is_bool': $options[ $setting ] = (bool) $value; break; case 'is_string': $options[ $setting ] = sanitize_text_field( $value ); break; } switch ( $setting ) { case 'resetOnboardingPlugins': unset( $options['onboardingPlugins'] ); unset( $options['resetOnboardingPlugins'] ); break; } } } // Looks like we want to toggle the omwpdebug setting. if ( isset( $settings['omwpdebug'] ) ) { $enabled = wp_validate_boolean( $settings['omwpdebug'] ); if ( empty( $enabled ) ) { unset( $options['api']['omwpdebug'] ); } else { $options['api']['omwpdebug'] = true; } $has_settings = true; } // Looks like we want to toggle the beta setting. if ( isset( $settings['omwpbeta'] ) ) { $enabled = wp_validate_boolean( $settings['omwpbeta'] ); $options['beta'] = ! empty( $enabled ); $has_settings = true; } if ( ! $has_settings ) { throw new Exception( esc_html__( 'Invalid Settings!', 'optin-monster-api' ), 400 ); } // Save the updated option. $this->base->save->update_option( $options ); return $this->get_settings( $request ); } catch ( Exception $e ) { return $this->exception_to_response( $e ); } } /** * Sanitize value recursively. * * @since 1.9.10 * * @param mixed $value The value to sanitize. * * @return mixed The sanitized value. */ public function sanitize( $value ) { if ( empty( $value ) ) { return $value; } if ( is_scalar( $value ) ) { return sanitize_text_field( $value ); } if ( is_array( $value ) ) { return array_map( array( $this, 'sanitize' ), $value ); } } /** * Determine if user can dismiss review. * * @since 2.6.1 * * @param WP_REST_Request $request The REST Request. * * @return bool */ public function can_dismiss_review( $request ) { try { $this->verify_request_nonce( $request ); } catch ( Exception $e ) { return $this->exception_to_response( $e ); } return is_user_logged_in() && OMAPI::get_instance()->can_access( 'review' ); } /** * Dismisses review. * * Route: POST omapp/v1/review/dismiss * * @since 2.6.1 * * @param WP_REST_Request $request The REST Request. * * @return bool */ public function dismiss_review( $request ) { $this->base->review->dismiss_review( $request->get_param( 'later' ) ); return new WP_REST_Response( array( 'message' => esc_html__( 'OK', 'optin-monster-api' ) ), 200 ); } /** * Fetch courses from OMU. * * Route: GET omapp/v1/omu/courses * * @since 2.6.6 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response|WP_Error The API Response or WP_Error object. */ public function get_courses( $request ) { return $this->handle_omu_request( 'courses' ); } /** * Fetch guides from OMU. * * Route: GET omapp/v1/omu/guides * * @since 2.6.6 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response|WP_Error The API Response or WP_Error object. */ public function get_guides( $request ) { return $this->handle_omu_request( 'guides' ); } /** * Fetch object from OMU. * * @since 2.6.6 * * @param string $api_object The API object to fetch. * * @return WP_REST_Response|WP_Error The API Response or WP_Error object. */ protected function handle_omu_request( $api_object ) { try { $result = OMAPI_OmuApi::cached_request( $api_object ); $api = OMAPI_OmuApi::instance(); if ( is_wp_error( $result ) ) { return $this->wp_error_to_response( $result, $api->response_body ); } $result = wp_parse_args( $result, array( 'data' => array(), 'total' => 0, 'totalpages' => 0, ) ); $response = new WP_REST_Response( $result['data'], 200 ); $response->header( 'X-WP-Total', $result['total'] ); $response->header( 'X-WP-TotalPages', $result['totalpages'] ); return $response; } catch ( Exception $e ) { return $this->exception_to_response( $e ); } } /** * Triggering refreshing/syncing of account settings. * * Route: POST omapp/v1/account/sync * * @since 2.6.13 * * @param WP_REST_Request $request The REST Request. * @return WP_REST_Response The API Response */ public function sync_account( $request ) { $data = OMAPI_Api::fetch_me_cached( true ); if ( is_wp_error( $data ) ) { return new WP_REST_Response( array( 'message' => esc_html__( 'Sync failed!', 'optin-monster-api' ) ), 400 ); } return new WP_REST_Response( array( 'message' => esc_html__( 'Sync succeeded!', 'optin-monster-api' ) ), 200 ); } /** * Triggering connection of plugin to OptinMonster app. * * Route: POST omapp/v1/account/connect * * @since 2.16.6 * * @param WP_REST_Request $request The REST Request. * * @return WP_REST_Response The API Response */ public function connect_account( $request ) { // Get the connection token from the request body. $connection_token = sanitize_text_field( $request->get_param( 'connectionToken' ) ); // Only keep slugs we know how to install; a connection token doesn't // authorize storing arbitrary ones. array_values() re-indexes so the // survivors stay a JSON array for the Vue consumer. $installable = wp_list_pluck( ( new OMAPI_Plugins() )->get_list(), 'slug' ); $plugins = array_values( array_intersect( wp_parse_slug_list( $request->get_param( 'plugins' ) ), $installable ) ); // Store the array of plugins the user requested to be installed. These // will be installed the next time the user loads the plugin dashboard. if ( ! empty( $plugins ) ) { $options = $this->base->get_option(); $options['onboardingPlugins'] = $plugins; update_option( 'optin_monster_api', $options ); } // Set up the credentials array. $creds = array( 'onboardingApiKey' => $connection_token ); // Fetch the /me data. $data = OMAPI_Api::fetch_me_onboarding( $creds ); if ( is_wp_error( $data ) ) { return new WP_REST_Response( array( 'message' => esc_html__( 'Account connection failed!', 'optin-monster-api' ) ), 400 ); } // Remove the connection token from the options. We no longer need it. $options = $this->base->get_option(); unset( $options['connectionToken'] ); update_option( 'optin_monster_api', $options ); return new WP_REST_Response( array( 'message' => esc_html__( 'Account connection succeeded!', 'optin-monster-api' ) ), 200 ); } }