pp_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; } }