_REDIRECT, array( $this, 'handle_fbe_redirect' ) ); add_action( 'fbe_webhook', array( $this, 'fbe_install_webhook' ) ); add_action( 'rest_api_init', array( $this, 'init_extras_endpoint' ) ); } /** * Refreshes the local business configuration data with the latest from Facebook. * * @internal * * @since 2.0.0 */ public function refresh_business_configuration() { // bail if not connected if ( ! $this->is_connected() ) { return; } try { $response = $this->get_plugin()->get_api()->get_business_configuration( $this->get_external_business_id() ); facebook_for_woocommerce()->get_tracker()->track_facebook_business_config( $response->is_ig_shopping_enabled(), $response->is_ig_cta_enabled() ); } catch ( ApiException $exception ) { $this->get_plugin()->log( 'Could not refresh business configuration. ' . $exception->getMessage() ); } } /** * Refreshes the connected installation data. * * @since 2.0.0 */ public function refresh_installation_data() { // bail if not connected if ( ! $this->is_connected() ) { return; } try { $this->update_installation_data(); } catch ( ApiException $exception ) { $this->get_plugin()->log( 'Could not refresh installation data. ' . $exception->getMessage() ); } } /** * Retrieves and stores the connected installation data. * * @since 2.0.0 * * @throws ApiException */ private function update_installation_data() { $response = $this->get_plugin()->get_api()->get_installation_ids( $this->get_external_business_id() ); $page_id = sanitize_text_field( $response->get_page_id() ); if ( $page_id ) { update_option( \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PAGE_ID, $page_id ); // get and store a current access token for the configured page $page_access_token = $this->retrieve_page_access_token( $page_id ); $this->update_page_access_token( $page_access_token ); } if ( $response->get_pixel_id() ) { update_option( \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PIXEL_ID, sanitize_text_field( $response->get_pixel_id() ) ); } if ( $response->get_catalog_id() ) { update_option( \WC_Facebookcommerce_Integration::OPTION_PRODUCT_CATALOG_ID, sanitize_text_field( $response->get_catalog_id() ) ); } if ( $response->get_business_manager_id() ) { $this->update_business_manager_id( sanitize_text_field( $response->get_business_manager_id() ) ); } if ( $response->get_ad_account_id() ) { $this->update_ad_account_id( sanitize_text_field( $response->get_ad_account_id() ) ); } if ( $response->get_instagram_business_id() ) { $this->update_instagram_business_id( sanitize_text_field( $response->get_instagram_business_id() ) ); } if ( $response->get_commerce_merchant_settings_id() ) { $this->update_commerce_merchant_settings_id( sanitize_text_field( $response->get_commerce_merchant_settings_id() ) ); } } /** * Processes the returned connection. * * @internal * * @since 2.0.0 */ public function handle_connect() { // don't handle anything unless the user can manage WooCommerce settings if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } try { if ( empty( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['nonce'] ) ), self::ACTION_CONNECT ) ) { throw new ApiException( 'Invalid nonce' ); } $is_error = ! empty( $_GET['err'] ) ? true : false; $error_code = ! empty( $_GET['err_code'] ) ? stripslashes( wc_clean( wp_unslash( $_GET['err_code'] ) ) ) : ''; $merchant_access_token = ! empty( $_GET['merchant_access_token'] ) ? wc_clean( wp_unslash( $_GET['merchant_access_token'] ) ) : ''; $system_user_access_token = ! empty( $_GET['system_user_access_token'] ) ? wc_clean( wp_unslash( $_GET['system_user_access_token'] ) ) : ''; $system_user_id = ! empty( $_GET['system_user_id'] ) ? wc_clean( wp_unslash( $_GET['system_user_id'] ) ) : ''; if ( $is_error && $error_code ) { throw new ConnectApiException( $error_code ); } if ( ! $merchant_access_token ) { throw new ApiException( 'Access token is missing' ); } if ( ! $system_user_access_token ) { throw new ApiException( 'System User access token is missing' ); } if ( ! $system_user_id ) { throw new ApiException( 'System User ID is missing' ); } $this->update_access_token( $system_user_access_token ); $this->update_merchant_access_token( $merchant_access_token ); $this->update_system_user_id( $system_user_id ); $this->update_installation_data(); // Allow opt-out of full batch-API sync, for example if store has a large number of products. if ( facebook_for_woocommerce()->get_integration()->allow_full_batch_api_sync() ) { facebook_for_woocommerce()->get_products_sync_handler()->create_or_update_all_products(); } else { facebook_for_woocommerce()->log( 'Initial full product sync disabled by filter hook `facebook_for_woocommerce_allow_full_batch_api_sync`', 'facebook_for_woocommerce_connect' ); } update_option( 'wc_facebook_has_connected_fbe_2', 'yes' ); update_option( 'wc_facebook_has_authorized_pages_read_engagement', 'yes' ); // redirect to the Commerce onboarding if directed to do so if ( ! empty( Helper::get_requested_value( 'connect_commerce' ) ) ) { wp_safe_redirect( $this->get_commerce_connect_url() ); exit; } facebook_for_woocommerce()->get_message_handler()->add_message( __( 'Connection successful!', 'facebook-for-woocommerce' ) ); wp_safe_redirect( facebook_for_woocommerce()->get_advertise_tab_url() ); exit; } catch ( ApiException $exception ) { facebook_for_woocommerce()->log( sprintf( 'Connection failed: %s', $exception->getMessage() ) ); set_transient( 'wc_facebook_connection_failed', time(), 30 ); } catch ( ConnectApiException $exception ) { $message = $this->prepare_connect_server_message_for_user_display( $exception->getMessage() ); facebook_for_woocommerce()->log( sprintf( 'Failed to connect to Facebook. Reason: %s', $message ), 'facebook_for_woocommerce_connect' ); set_transient( 'wc_facebook_connection_failed', time(), 30 ); } wp_safe_redirect( facebook_for_woocommerce()->get_settings_url() ); exit; } /** * Prepares the error message from the connect server for the logs. * * @since 2.6.8 * @param string $message Message string that needs formatting. * @return string Formatted message string ready for logging. */ public function prepare_connect_server_message_for_user_display( $message ) { /* * In some scenarios the connect server message is a JSON encoded object. * This happens when we have detailed information from Facebook API endpoint about the error. * We want to print it pretty for the customers. */ $decoded_message = json_decode( $message ); if ( json_last_error() === JSON_ERROR_NONE ) { // If error is the first key we want to use just the body to simplify the message. $decoded_message = isset( $decoded_message->error ) ? $decoded_message->error : $decoded_message; $message = json_encode( $decoded_message, JSON_PRETTY_PRINT ); } return $message; } /** * Disconnects the integration using the Graph API. * * @internal * * @since 2.0.0 */ public function handle_disconnect() { check_admin_referer( self::ACTION_DISCONNECT ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You do not have permission to uninstall Facebook Business Extension.', 'facebook-for-woocommerce' ) ); } try { $response = facebook_for_woocommerce()->get_api()->get_user(); $id = $response->get_id(); if ( null !== $id ) { $response = facebook_for_woocommerce()->get_api()->delete_user_permission( (string) $id , 'manage_business_extension' ); facebook_for_woocommerce()->get_message_handler()->add_message( __( 'Disconnection successful.', 'facebook-for-woocommerce' ) ); } else { facebook_for_woocommerce()->log( 'User id not found for the disconnection procedure, connection will be reset.' ); } } catch ( ApiException $exception ) { facebook_for_woocommerce()->log( sprintf( 'An error occurred during disconnection: %s.', $exception->getMessage() ) ); } catch ( \Exception $exception ) { facebook_for_woocommerce()->log( sprintf( 'Internal error occurred during disconnection: %s.', $exception->getMessage() ) ); } finally { $this->disconnect(); facebook_for_woocommerce()->log( sprintf( 'Your Facebook connection settings have been reset.' ) ); wp_safe_redirect( facebook_for_woocommerce()->get_settings_url() ); exit; } } /** * Disconnects the plugin. * * Deletes local asset data. * * @since 2.0.0 */ public function disconnect() { $this->update_access_token( '' ); $this->update_page_access_token( '' ); $this->update_merchant_access_token( '' ); $this->update_system_user_id( '' ); $this->update_business_manager_id( '' ); $this->update_ad_account_id( '' ); $this->update_instagram_business_id( '' ); $this->update_commerce_merchant_settings_id( '' ); $this->update_external_business_id( '' ); update_option( \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PAGE_ID, '' ); update_option( \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PIXEL_ID, '' ); facebook_for_woocommerce()->get_integration()->update_product_catalog_id( '' ); } /** * Retrieves the configured page access token remotely. * * @since 2.1.0 * * @param string $page_id desired Facebook page ID * @return string * @throws ApiException */ private function retrieve_page_access_token( $page_id ) { facebook_for_woocommerce()->log( 'Retrieving page access token' ); $api_url = Api::GRAPH_API_URL . Api::API_VERSION; $response = wp_remote_get( $api_url . '/me/accounts?access_token=' . $this->get_access_token() ); $body = wp_remote_retrieve_body( $response ); $body = json_decode( $body, true ); if ( ! is_array( $body ) || empty( $body['data'] ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { facebook_for_woocommerce()->log( print_r( $body, true ) ); throw new ApiException( sprintf( /* translators: Placeholders: %s - API error message */ __( 'Could not retrieve page access data. %s', 'facebook for woocommerce' ), wp_remote_retrieve_response_message( $response ) ) ); } $page_access_tokens = wp_list_pluck( $body['data'], 'access_token', 'id' ); // bail if the user isn't authorized to manage the page if ( empty( $page_access_tokens[ $page_id ] ) ) { throw new ApiException( sprintf( /* translators: Placeholders: %s - Facebook page ID */ __( 'Page %s not authorized.', 'facebook-for-woocommerce' ), $page_id ) ); } return $page_access_tokens[ $page_id ]; } /** * Gets the API access token. * * @since 2.0.0 * * @return string */ public function get_access_token() { $access_token = get_option( self::OPTION_ACCESS_TOKEN, '' ); /** * Filters the API access token. * * @since 2.0.0 * * @param string $access_token access token * @param Connection $connection connection handler instance */ return apply_filters( 'wc_facebook_connection_access_token', $access_token, $this ); } /** * Gets the merchant access token. * * @since 2.0.0 * * @return string */ public function get_merchant_access_token() { $access_token = get_option( self::OPTION_MERCHANT_ACCESS_TOKEN, '' ); /** * Filters the merchant access token. * * @since 2.0.0 * * @param string $access_token access token * @param Connection $connection connection handler instance */ return apply_filters( 'wc_facebook_connection_merchant_access_token', $access_token, $this ); } /** * Gets the page access token. * * @since 2.1.0 * * @return string */ public function get_page_access_token() { $access_token = get_option( self::OPTION_PAGE_ACCESS_TOKEN, '' ); /** * Filters the page access token. * * @since 2.1.0 * * @param string $access_token page access token * @param Connection $connection connection handler instance */ return (string) apply_filters( 'wc_facebook_connection_page_access_token', $access_token, $this ); } /** * Gets the URL to start the connection flow. * * @since 2.0.0 * * @param bool $connect_commerce whether to connect to Commerce after successful FBE connection * @return string */ public function get_connect_url( $connect_commerce = false ) { // nosemgrep: audit.php.wp.security.xss.query-arg return add_query_arg( rawurlencode_deep( $this->get_connect_parameters( $connect_commerce ) ), self::OAUTH_URL ); } /** * Builds the Commerce connect URL. * * The base URL is https://www.facebook.com/commerce_manager/onboarding with two query variables: * - app_id - the developer app ID * - redirect_url - the URL where the user will land after onboarding is complete * * The redirect URL must be an approved domain, so it must be the connect.woocommerce.com proxy app. In that URL, we * include the final site URL, which is where the merchant will redirect to with the data that needs to be stored. * So the final URL looks like this without encoding: * * https://www.facebook.com/commerce_manager/onboarding/?app_id={id}&redirect_url=https://connect.woocommerce.com/auth/facebook/?site_url=https://example.com/?wc-api=wc_facebook_connect_commerce&nonce=1234 * * If testing only, &is_test_mode=true can be appended to the URL using the wc_facebook_commerce_connect_url filter * to trigger the test account flow, where fake US-based business details can be used. * * @since 2.1.0 * * @return string */ public function get_commerce_connect_url() { // build the site URL to which the user will ultimately return $site_url = add_query_arg( array( 'wc-api' => self::ACTION_CONNECT_COMMERCE, 'nonce' => wp_create_nonce( self::ACTION_CONNECT_COMMERCE ), ), home_url( '/' ) ); // build the proxy app URL where the user will land after onboarding, to be redirected to the site URL $redirect_url = add_query_arg( 'site_url', urlencode( $site_url ), $this->get_connection_authentication_url() ); // build the final connect URL, direct to Facebook $connect_url = add_query_arg( array( 'app_id' => $this->get_client_id(), // this endpoint calls the client ID "app ID" 'redirect_url' => urlencode( $redirect_url ), ), 'https://www.facebook.com/commerce_manager/onboarding/' ); /** * Filters the URL used to connect to Facebook Commerce. * * @since 2.1.0 * * @param string $connect_url connect URL */ return apply_filters( 'wc_facebook_commerce_connect_url', $connect_url ); } /** * Gets the URL to manage the connection. * * @since 2.0.0 * * @return string */ public function get_manage_url() { $app_id = $this->get_client_id(); $business_id = $this->get_external_business_id(); return "https://www.facebook.com/facebook_business_extension?app_id={$app_id}&external_business_id={$business_id}"; } /** * Gets the URL for disconnecting. * * @since 2.0.0 * * @return string */ public function get_disconnect_url() { return wp_nonce_url( add_query_arg( 'action', self::ACTION_DISCONNECT, admin_url( 'admin.php' ) ), self::ACTION_DISCONNECT ); } /** * Gets the scopes that will be requested during the connection flow. * * @since 2.0.0 * * @link https://developers.facebook.com/docs/marketing-api/access/#access_token * * @return string[] */ public function get_scopes() { $scopes = array( 'manage_business_extension', 'catalog_management', 'ads_management', 'ads_read', 'pages_read_engagement', // this scope is needed to enable order management if using the Commerce feature 'instagram_basic', ); /** * Filters the scopes that will be requested during the connection flow. * * @since 2.0.0 * * @param string[] $scopes connection scopes * @param Connection $connection connection handler instance */ return (array) apply_filters( 'wc_facebook_connection_scopes', $scopes, $this ); } /** * Gets the stored external business ID. * * @since 2.0.0 * * @return string */ public function get_external_business_id() { if ( ! is_string( $this->external_business_id ) ) { $external_id = get_option( self::OPTION_EXTERNAL_BUSINESS_ID ); if ( ! is_string( $external_id ) || empty( $external_id ) ) { /** * Filters the shop's business external ID. * * This is passed to Facebook when connecting. * Should be non-empty and without special characters, otherwise the ID will be obtained from the site URL as fallback. * * @since 2.0.0 * * @param string $external_id the shop's business external ID */ $external_id = sanitize_key( (string) apply_filters( 'wc_facebook_connection_business_id', get_bloginfo( 'name' ) ) ); if ( empty( $external_id ) ) { $external_id = sanitize_key( str_replace( array( 'http', 'https', 'www' ), '', get_bloginfo( 'url' ) ) ); } $external_id = uniqid( sprintf( '%s-', $external_id ), false ); $this->update_external_business_id( $external_id ); } $this->external_business_id = $external_id; } /** * Filters the external business ID. * * @since 2.0.0 * * @param string $external_business_id stored external business ID * @param Connection $connection connection handler instance */ return (string) apply_filters( 'wc_facebook_external_business_id', $this->external_business_id, $this ); } /** * Gets the site's business name. * * @since 2.0.0 * * @return string */ public function get_business_name() { $business_name = get_bloginfo( 'name' ); /** * Filters the shop's business name. * * This is passed to Facebook when connecting. * Defaults to the site name. Should be non-empty, otherwise the site URL will be used as fallback. * * @since 2.0.0 * * @param string $business_name the shop's business name */ $business_name = trim( (string) apply_filters( 'wc_facebook_connection_business_name', is_string( $business_name ) ? $business_name : '' ) ); if ( empty( $business_name ) ) { $business_name = get_bloginfo( 'url' ); } return html_entity_decode( $business_name, ENT_QUOTES, 'UTF-8' ); } /** * Gets the business manager ID value. * * @since 2.0.0 * * @return string */ public function get_business_manager_id() { $business_manager_id = get_option( self::OPTION_BUSINESS_MANAGER_ID, '' ); /** * Filters the Business Manager ID. * * @since 3.0.31 * * @param string $business_manager_id Business Manager ID. * @param Connection $connection The Connection handler instance. */ return (string) apply_filters( 'wc_facebook_business_manager_id', $business_manager_id, $this ); } /** * Gets the ad account ID value. * * @since 2.0.0 * * @return string */ public function get_ad_account_id() { return get_option( self::OPTION_AD_ACCOUNT_ID, '' ); } /** * Gets the System User ID value. * * @since 2.0.0 * * @return string */ public function get_system_user_id() { return get_option( self::OPTION_SYSTEM_USER_ID, '' ); } /** * Gets the Commerce manager ID value. * * @since 2.1.0 * * @return string */ public function get_commerce_manager_id() { return get_option( self::OPTION_COMMERCE_MANAGER_ID, '' ); } /** * Gets Instagram Business ID value. * * @since 2.3.0 * * @return string */ public function get_instagram_business_id() { return get_option( self::OPTION_INSTAGRAM_BUSINESS_ID, '' ); } /** * Gets Commerce merchant settings ID value. * * @since 2.3.0 * * @return string */ public function get_commerce_merchant_settings_id() { return get_option( self::OPTION_COMMERCE_MERCHANT_SETTINGS_ID, '' ); } /** * Gets the proxy URL. * * @since 2.0.0 * * @return string URL */ public function get_proxy_url() { /** * Filters the proxy URL. * * @since 2.0.0 * * @param string $proxy_url the connection proxy URL */ return (string) apply_filters( 'wc_facebook_connection_proxy_url', self::PROXY_URL ); } /** * Gets APP Store Login URL. * * @since 2.3.0 * * @return string URL */ public function get_app_store_login_url() { /** * Filters App Store login URL. * * @since 2.3.0 * * @param string $app_store_login_url the connection App Store login URL */ return (string) apply_filters( 'wc_facebook_connection_app_store_login_url', self::APP_STORE_LOGIN_URL ); } /** * Gets connect server authentication url. * * @since 2.6.8 * * @return string URL */ public function get_connection_authentication_url() { /** * Filters App Store login URL. * * @since 2.6.8 * * @param string $connection_authentication_url the connection App Store login URL */ return (string) apply_filters( 'wc_facebook_connection_authentication_url', self::CONNECTION_AUTHENTICATION_URL ); } /** * Gets the full redirect URL where the user will return to after OAuth. * * @since 2.0.0 * * @return string */ public function get_redirect_url() { $redirect_url = add_query_arg( array( 'wc-api' => self::ACTION_CONNECT, 'external_business_id' => $this->get_external_business_id(), 'nonce' => wp_create_nonce( self::ACTION_CONNECT ), 'type' => self::AUTH_TYPE_STANDARD, ), home_url( '/' ) ); /** * Filters the redirect URL where the user will return to after OAuth. * * @since 2.0.0 * * @param string $redirect_url redirect URL * @param Connection $connection connection handler instance */ return (string) apply_filters( 'wc_facebook_connection_redirect_url', $redirect_url, $this ); } /** * Gets the full set of connection parameters for starting OAuth. * * @since 2.0.0 * * @param bool $connect_commerce whether to connect to Commerce after successful FBE connection * @return array */ public function get_connect_parameters( $connect_commerce = false ) { $state = $this->get_redirect_url(); if ( $connect_commerce ) { $state = add_query_arg( 'connect_commerce', true, $state ); } /** * Filters the connection parameters. * * @since 2.0.0 * * @param array $parameters connection parameters */ // nosemgrep: audit.php.wp.security.xss.query-arg return apply_filters( 'wc_facebook_connection_parameters', array( 'client_id' => $this->get_client_id(), 'redirect_uri' => $this->get_proxy_url(), 'state' => $state, 'display' => 'page', 'response_type' => 'code', 'scope' => implode( ',', $this->get_scopes() ), 'extras' => json_encode( $this->get_connect_parameters_extras() ), ) ); } /** * Gets connection parameters extras. * * @see Connection::get_connect_parameters() * * @since 2.0.0 * * @return array associative array (to be converted to JSON encoded for connection purposes) */ private function get_connect_parameters_extras() { $parameters = array( 'setup' => array( 'external_business_id' => $this->get_external_business_id(), 'timezone' => $this->get_timezone_string(), 'currency' => get_woocommerce_currency(), 'business_vertical' => 'ECOMMERCE', 'domain' => home_url(), 'channel' => 'DEFAULT', ), 'business_config' => array( 'business' => array( 'name' => $this->get_business_name(), ), ), 'repeat' => false, ); if ( $external_merchant_settings_id = facebook_for_woocommerce()->get_integration()->get_external_merchant_settings_id() ) { $parameters['setup']['merchant_settings_id'] = $external_merchant_settings_id; } // if messenger was previously enabled if ( facebook_for_woocommerce()->get_integration()->is_messenger_enabled() ) { $parameters['business_config']['messenger_chat'] = array( 'enabled' => true, 'domains' => array( home_url( '/' ), ), ); } return $parameters; } /** * Gets the configured timezone string using values accepted by Facebook * * @since 2.0.0 * * @return string */ private function get_timezone_string() { $timezone = wc_timezone_string(); // convert +05:30 and +05:00 into Etc/GMT+5 - we ignore the minutes because Facebook does not allow minute offsets if ( preg_match( '/([+-])(\d{2}):\d{2}/', $timezone, $matches ) ) { $hours = (int) $matches[2]; $timezone = "Etc/GMT{$matches[1]}{$hours}"; } return $timezone; } /** * Stores the given ID value. * * @since 2.0.0 * * @param string $value the business manager ID */ public function update_business_manager_id( $value ) { update_option( self::OPTION_BUSINESS_MANAGER_ID, $value ); } /** * Stores the given ID value. * * @since 2.0.0 * * @param string $value the ad account ID */ public function update_ad_account_id( $value ) { update_option( self::OPTION_AD_ACCOUNT_ID, $value ); } /** * Stores the given system user ID. * * @since 2.0.0 * * @param string $value the ID */ public function update_system_user_id( $value ) { update_option( self::OPTION_SYSTEM_USER_ID, $value ); } /** * Stores the given Commerce manager ID. * * @since 2.1.0 * * @param string $id the ID */ public function update_commerce_manager_id( $id ) { update_option( self::OPTION_COMMERCE_MANAGER_ID, $id ); } /** * Stores the given Instagram Business ID. * * @since 2.3.0 * * @param string $id the ID */ public function update_instagram_business_id( $id ) { update_option( self::OPTION_INSTAGRAM_BUSINESS_ID, $id ); } /** * Stores the given Commerce merchant settings ID. * * @since 2.3.0 * * @param string $id the ID */ public function update_commerce_merchant_settings_id( $id ) { update_option( self::OPTION_COMMERCE_MERCHANT_SETTINGS_ID, $id ); } /** * Stores the given token value. * * @since 2.0.0 * * @param string $value the access token */ public function update_access_token( $value ) { update_option( self::OPTION_ACCESS_TOKEN, $value ); } /** * Stores the given merchant access token. * * @since 2.0.0 * * @param string $value the access token */ public function update_merchant_access_token( $value ) { update_option( self::OPTION_MERCHANT_ACCESS_TOKEN, $value ); } /** * Stores the given page access token. * * @since 2.1.0 * * @param string $value the access token */ public function update_page_access_token( $value ) { update_option( self::OPTION_PAGE_ACCESS_TOKEN, is_string( $value ) ? $value : '' ); } /** * Stores the given external business id. * * @since 2.6.13 * * @param string $value external business id */ public function update_external_business_id( $value ) { update_option( self::OPTION_EXTERNAL_BUSINESS_ID, is_string( $value ) ? $value : '' ); } /** * Determines whether the site is connected. * * A site is connected if there is an access token stored. * * @since 2.0.0 * * @return bool */ public function is_connected() { return (bool) $this->get_access_token(); } /** * Determines whether the site has previously connected to FBE 2. * * @since 2.0.0 * * @return bool */ public function has_previously_connected_fbe_2() { return 'yes' === get_option( 'wc_facebook_has_connected_fbe_2' ); } /** * Determines whether the site has previously connected to FBE 1.x. * * @since 2.0.0 * * @return bool */ public function has_previously_connected_fbe_1() { $integration = $this->get_plugin()->get_integration(); return $integration && $integration->get_external_merchant_settings_id(); } /** * Gets the client ID for connection. * * @since 2.0.0 * * @return string */ public function get_client_id() { /** * Filters the client ID. * * @since 2.0.0 * * @param string $client_id the client ID */ return apply_filters( 'wc_facebook_connection_client_id', self::CLIENT_ID ); } /** * Gets the plugin instance. * * @since 2.0.0 * * @return \WC_Facebookcommerce */ public function get_plugin() { return $this->plugin; } /** * Process WebHook User object, install field * * @since 2.3.0 * @link https://developers.facebook.com/docs/marketing-api/fbe/fbe2/guides/get-features#webhook * * @param object $data WebHook event data. */ public function fbe_install_webhook( $data ) { // Reject other objects other than subscribed object if ( empty( $data ) || ! isset( $data->object ) || self::WEBHOOK_SUBSCRIBED_OBJECT !== $data->object ) { $this->get_plugin()->log( 'Wrong (or empty) WebHook Event received' ); $this->get_plugin()->log( print_r( $data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r return; } $log_data = array(); $this->get_plugin()->log( 'WebHook User Event received' ); $this->get_plugin()->log( print_r( $data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r $entry = (array) $data->entry[0]; if ( empty( $entry ) ) { return; } // Filter event by subscribed field $event = array_filter( $entry['changes'], function( $change ) { return self::WEBHOOK_SUBSCRIBED_FIELD === $change->field; } ); $values = ! empty( $event[0] ) ? $event[0]->value : ''; if ( empty( $values ) ) { return; } /** * If profiles, pages and instagram_profiles fields are not included in the Webhook payload, this means the business has uninstalled FBE. * In this case also the field access_token will not be included. * * @link https://developers.facebook.com/docs/marketing-api/fbe/fbe2/guides/get-features#what-s-included-with-webhooks- */ if ( empty( $values->access_token ) ) { delete_option( 'wc_facebook_has_connected_fbe_2' ); delete_option( 'wc_facebook_has_authorized_pages_read_engagement' ); $this->disconnect(); return; } update_option( 'wc_facebook_has_connected_fbe_2', 'yes' ); update_option( 'wc_facebook_has_authorized_pages_read_engagement', 'yes' ); $system_user_access_token = ! empty( $values->access_token ) ? sanitize_text_field( $values->access_token ) : ''; $this->update_access_token( $system_user_access_token ); $log_data[ self::OPTION_ACCESS_TOKEN ] = 'Token was saved'; if ( ! empty( $entry['uid'] ) ) { $this->update_system_user_id( sanitize_text_field( $entry['uid'] ) ); $log_data[ self::OPTION_SYSTEM_USER_ID ] = sanitize_text_field( $entry['uid'] ); } $merchant_access_token = ! empty( $values->merchant_access_token ) ? sanitize_text_field( $values->merchant_access_token ) : ''; $this->update_merchant_access_token( $merchant_access_token ); $log_data[ self::OPTION_MERCHANT_ACCESS_TOKEN ] = 'Token was saved'; if ( ! empty( $values->install_time ) ) { update_option( \WC_Facebookcommerce_Integration::OPTION_PIXEL_INSTALL_TIME, sanitize_text_field( $values->install_time ) ); $log_data[ \WC_Facebookcommerce_Integration::OPTION_PIXEL_INSTALL_TIME ] = sanitize_text_field( $values->install_time ); } if ( ! empty( $values->business_id ) ) { $this->update_external_business_id( sanitize_text_field( $values->business_id ) ); $log_data[ self::OPTION_EXTERNAL_BUSINESS_ID ] = sanitize_text_field( $values->business_id ); } if ( ! empty( $values->pixel_id ) ) { update_option( \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PIXEL_ID, sanitize_text_field( $values->pixel_id ) ); $log_data[ \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PIXEL_ID ] = sanitize_text_field( $values->pixel_id ); } if ( ! empty( $values->catalog_id ) ) { update_option( \WC_Facebookcommerce_Integration::OPTION_PRODUCT_CATALOG_ID, sanitize_text_field( $values->catalog_id ) ); $log_data[ \WC_Facebookcommerce_Integration::OPTION_PRODUCT_CATALOG_ID ] = sanitize_text_field( $values->catalog_id ); } if ( ! empty( $values->business_manager_id ) ) { $this->update_business_manager_id( sanitize_text_field( $values->business_manager_id ) ); $log_data[ self::OPTION_BUSINESS_MANAGER_ID ] = sanitize_text_field( $values->business_manager_id ); } if ( ! empty( $values->ad_account_id ) ) { $this->update_ad_account_id( sanitize_text_field( $values->ad_account_id ) ); $log_data[ self::OPTION_AD_ACCOUNT_ID ] = sanitize_text_field( $values->ad_account_id ); } if ( ! empty( $values->instagram_profiles ) ) { $instagram_business_id = current( $values->instagram_profiles ); $this->update_instagram_business_id( sanitize_text_field( $instagram_business_id ) ); $log_data[ self::OPTION_INSTAGRAM_BUSINESS_ID ] = sanitize_text_field( $instagram_business_id ); } if ( ! empty( $values->commerce_merchant_settings_id ) ) { $this->update_commerce_merchant_settings_id( sanitize_text_field( $values->commerce_merchant_settings_id ) ); $log_data[ self::OPTION_COMMERCE_MERCHANT_SETTINGS_ID ] = sanitize_text_field( $values->commerce_merchant_settings_id ); } if ( ! empty( $values->pages ) ) { $page_id = current( $values->pages ); try { update_option( \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PAGE_ID, sanitize_text_field( $page_id ) ); $log_data[ \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PAGE_ID ] = sanitize_text_field( $page_id ); // get and store a current access token for the configured page $page_access_token = $this->retrieve_page_access_token( $page_id ); $this->update_page_access_token( $page_access_token ); $log_data[ self::OPTION_PAGE_ACCESS_TOKEN ] = sanitize_text_field( $page_access_token ); } catch ( \Exception $e ) { $this->get_plugin()->log( 'Could not request Page Token: ' . $e->getMessage() ); } }//end if $this->get_plugin()->log( 'WebHook User event saved data' ); $this->get_plugin()->log( print_r( $log_data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r } /** * Register Extras REST API endpoint * * @since 2.3.0 */ public function init_extras_endpoint() { register_rest_route( 'wc-facebook/v1', 'extras', array( array( 'methods' => array( 'GET', 'POST' ), 'callback' => array( $this, 'extras_callback' ), 'permission_callback' => array( $this, 'extras_permission_callback' ), ), ) ); } /** * FBE Extras endpoint permissions * * @since 2.3.0 * * @return boolean */ public function extras_permission_callback() { return current_user_can( 'manage_woocommerce' ); } /** * Return FBE extras * * @since 2.3.0 * * @return \WP_REST_Response */ public function extras_callback() { $extras = $this->get_connect_parameters_extras(); if ( empty( $extras ) ) { return new \WP_REST_Response( null, 204 ); } return new \WP_REST_Response( $extras, 200 ); } /** * Process FBE App Store login flow redirection * * @since 2.3.0 */ public function handle_fbe_redirect() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You do not have permission to finish App Store login.', 'facebook-for-woocommerce' ) ); } $redirect_uri = isset( $_REQUEST['redirect_uri'] ) ? base64_decode( wc_clean( wp_unslash( $_REQUEST['redirect_uri'] ) ) ) : ''; //phpcs:ignore // To ensure that we are not sharing any user data with other parties, only redirect to the redirect_uri if it matches the regular expression if ( empty( $redirect_uri ) || ! preg_match( '/https?:\/\/(www\.|m\.|l\.)?(\d{5}\.od\.)?(facebook|instagram|whatsapp)\.com(\/.*)?/', explode( '?', $redirect_uri )[0] ) ) { wp_safe_redirect( site_url() ); exit; } if ( empty( $_REQUEST['success'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended $url_params = array( 'store_url' => '', 'redirect_uri' => rawurlencode( $redirect_uri ), 'errors' => array( 'You need to grant access to WooCommerce.' ), ); $redirect_url = add_query_arg( $url_params, $this->get_app_store_login_url() ); } else { $redirect_url = $redirect_uri . '&extras=' . rawurlencode_deep( wp_json_encode( $this->get_connect_parameters_extras() ) ); } wp_redirect( $redirect_url ); //phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect exit; } }