diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..08d8481b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 8f7b5050..aec7a090 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -6,7 +6,7 @@ if [ -n "$php_files" ]; then echo "Running PHP CodeSniffer on the following files:" echo "$php_files" - echo "$php_files" | xargs vendor/bin/phpcs -d memory_limit=512M --standard=phpcs.xml.dist + echo "$php_files" | xargs vendor/bin/phpcs -d memory_limit=512M --standard=phpcs.xml if [ $? -ne 0 ]; then echo "PHP CodeSniffer failed. Please fix the errors." exit 1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b92700e..8980a7a2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,13 +1,11 @@ -name: Test +name: Unit tests & PHPCS on: push: - branches: - - main jobs: test-and-code-style: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: php: [8.1] @@ -22,4 +20,4 @@ jobs: run: vendor/bin/phpunit --bootstrap __tests__/bootstrap.php __tests__ - name: Run PHPCS - run: vendor/bin/phpcs --standard=WordPress --extensions=php --ignore=vendor/ . + run: vendor/bin/phpcs --standard=phpcs.xml --extensions=php --ignore=vendor/ . diff --git a/.gitignore b/.gitignore index 67e563bf..41e41e3c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ /build /js/lib/node_modules /node_modules +.DS_Store +.vscode +composer.phar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 430f93d1..b16ba18c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ Run the command to run tests and create the zip under build directory. 2. Extend the class from `FacebookWordpressIntegrationBase` 3. Define class variable `PLUGIN_FILE` to be your plugin PHP file 4. Define class variable `TRACKING_NAME` for tracking purpose, put this value under 'fb_wp_tracking' as a parameter in the pixel event -5. Define a public static function `injectPixelCode()` to inject pixel at your page +5. Define a public static function `inject_pixel_code()` to inject pixel at your page 6. Add your unit test class under `tests/` folder 7. Extend the test class from `FacebookWordpressTestBase` 8. After the classes development, run tests by `$ vendor/bin/phing` diff --git a/README.md b/README.md index 46d5551f..45fbfa15 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ https://www.facebook.com/business/help/881403525362441 2. Extend the class from `FacebookWordpressIntegrationBase` 3. Define class variable `PLUGIN_FILE` to be your plugin PHP file 4. Define class variable `TRACKING_NAME` for tracking purpose, put this value under 'fb_wp_tracking' as a parameter in the pixel event -5. Define a public static function `injectPixelCode()` to inject pixel at your page +5. Define a public static function `inject_pixel_code()` to inject pixel at your page 6. Add your unit test class under `tests/` folder 7. Extend the test class from `FacebookWordpressTestBase` 8. After the classes development, run tests by `$ vendor/bin/phing` diff --git a/__tests__/FacebookWordpressTestBase.php b/__tests__/FacebookWordpressTestBase.php index 6246ea58..9397bec8 100644 --- a/__tests__/FacebookWordpressTestBase.php +++ b/__tests__/FacebookWordpressTestBase.php @@ -1,96 +1,174 @@ setConstantsMap([ - 'FacebookPixelPlugin\Core\FacebookPixel' => [ - 'FB_INTEGRATION_TRACKING_KEY' => 'fb_integration_tracking', - ], - ]); + /** + * Sets up the environment for each test. + * + * Initializes WP_Mock, sets the global WordPress version, + * configures Mockery constants, and sets server variables + * to simulate an HTTPS request to 'www.pikachu.com'. + * + * @return void + */ + public function setUp(): void { + \WP_Mock::setUp(); + $GLOBALS['wp_version'] = '1.0'; + \Mockery::getConfiguration()->setConstantsMap( + array( + 'FacebookPixelPlugin\Core\FacebookPixel' => array( + 'FB_INTEGRATION_TRACKING_KEY' => 'fb_integration_tracking', + ), + ) + ); - $_SERVER['HTTPS'] = 'on'; - $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; - $_SERVER['REQUEST_URI'] = '/index.php'; - } + $_SERVER['HTTPS'] = 'on'; + $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; + $_SERVER['REQUEST_URI'] = '/index.php'; + } - public function tearDown(): void { + /** + * Cleans up the environment after each test. + * + * Asserts that all Mockery expectations have been met, unsets the global + * WordPress version, and calls `tearDown` on WP_Mock. + * + * @return void + */ + public function tearDown(): void { $this->addToAssertionCount( - \Mockery::getContainer()->mockery_getExpectationCount()); - unset($GLOBALS['wp_version']); - \WP_Mock::tearDown(); - } + \Mockery::getContainer()->mockery_getExpectationCount() + ); + unset( $GLOBALS['wp_version'] ); + \WP_Mock::tearDown(); + } - protected function mockIsInternalUser($is_internal_user) { - $this->mocked_fbpixel = \Mockery::mock - ('alias:FacebookPixelPlugin\Core\FacebookPluginUtils'); - $this->mocked_fbpixel->shouldReceive('isInternalUser') - ->andReturn($is_internal_user); - } + /** + * Mocks the return value of FacebookPluginUtils::isInternalUser(). + * + * @param bool $is_internal_user Whether the user is an internal user. + * + * @return void + */ + protected function mockIsInternalUser( $is_internal_user ) { + $this->mocked_fbpixel = + \Mockery::mock( 'alias:FacebookPixelPlugin\Core\FacebookPluginUtils' ); + $this->mocked_fbpixel->shouldReceive( 'is_internal_user' ) + ->andReturn( $is_internal_user ); + } - protected function mockFacebookWordpressOptions($options = array(), - $aam_settings = null){ + /** + * Mocks the return value of FacebookWordpressOptions methods. + * + * This function mocks the return values of the FacebookWordpressOptions + * class methods. The $options parameter is an associative array + * where the keys are method names and the values are the return values + * for those methods. If a key is not provided, the method will return + * a default value. + * + * @param array $options An associative + * array of method names and their + * return values. + * @param AdsPixelSettings $aam_settings The return value for the + * get_aam_settings() method. + */ + protected function mockFacebookWordpressOptions( + $options = array(), + $aam_settings = null + ) { $this->mocked_options = \Mockery::mock( - 'alias:FacebookPixelPlugin\Core\FacebookWordpressOptions'); - if(array_key_exists('agent_string', $options)){ - $this->mocked_options->shouldReceive('getAgentString')->andReturn($options['agent_string']); - } - else{ - $this->mocked_options ->shouldReceive('getAgentString') - ->andReturn('wordpress'); - } - if(array_key_exists('pixel_id', $options)){ - $this->mocked_options->shouldReceive('getPixelId')->andReturn($options['pixel_id']); + 'alias:FacebookPixelPlugin\Core\FacebookWordpressOptions' + ); + if ( array_key_exists( 'agent_string', $options ) ) { + $this->mocked_options->shouldReceive( 'get_agent_string' ) + ->andReturn( $options['agent_string'] ); + } else { + $this->mocked_options->shouldReceive( 'get_agent_string' ) + ->andReturn( 'WordPress' ); } - else{ - $this->mocked_options->shouldReceive('getPixelId')->andReturn('1234'); + if ( array_key_exists( 'pixel_id', $options ) ) { + $this->mocked_options->shouldReceive( 'get_pixel_id' ) + ->andReturn( $options['pixel_id'] ); + } else { + $this->mocked_options->shouldReceive( 'get_pixel_id' ) + ->andReturn( '1234' ); } - if(array_key_exists('access_token', $options)){ - $this->mocked_options->shouldReceive('getAccessToken')->andReturn($options['access_token']); + if ( array_key_exists( 'access_token', $options ) ) { + $this->mocked_options->shouldReceive( 'get_access_token' ) + ->andReturn( $options['access_token'] ); + } else { + $this->mocked_options->shouldReceive( 'get_access_token' ) + ->andReturn( 'abcd' ); } - else{ - $this->mocked_options->shouldReceive('getAccessToken')->andReturn('abcd'); + if ( array_key_exists( 'is_fbe_installed', $options ) ) { + $this->mocked_options->shouldReceive( 'get_is_fbe_installed' ) + ->andReturn( $options['is_fbe_installed'] ); + } else { + $this->mocked_options->shouldReceive( 'get_is_fbe_installed' ) + ->andReturn( '0' ); } - if(array_key_exists('is_fbe_installed', $options)){ - $this->mocked_options->shouldReceive('getIsFbeInstalled')->andReturn($options['is_fbe_installed']); + if ( is_null( $aam_settings ) ) { + $this->mocked_options->shouldReceive( 'get_aam_settings' ) + ->andReturn( $this->getDefaultAAMSettings() ); + } else { + $this->mocked_options->shouldReceive( 'get_aam_settings' ) + ->andReturn( $aam_settings ); } - else{ - $this->mocked_options->shouldReceive('getIsFbeInstalled')->andReturn('0'); + $this->mocked_options->shouldReceive( 'get_capi_pii_caching_status' ) + ->andReturn( 0 ); } - if($aam_settings == null){ - $this->mocked_options->shouldReceive('getAAMSettings')->andReturn($this->getDefaultAAMSettings()); - } - else{ - $this->mocked_options->shouldReceive('getAAMSettings')->andReturn($aam_settings); - } - $this->mocked_options ->shouldReceive('getCapiPiiCachingStatus') - ->andReturn(0); - } - protected function getDefaultAAMSettings(){ - $aam_settings = new AdsPixelSettings(); - $aam_settings->setPixelId('123'); - $aam_settings->setEnableAutomaticMatching(true); - $aam_settings->setEnabledAutomaticMatchingFields(AAMSettingsFields::getAllFields()); - return $aam_settings; - } + + /** + * Returns the default AdsPixelSettings object. + * + * The default AdsPixelSettings object returned by + * this method has the pixel ID + * set to '123', automatic matching enabled, and all + * automatic matching fields + * enabled. + * + * @return AdsPixelSettings The default AdsPixelSettings object. + */ + protected function getDefaultAAMSettings() { + $aam_settings = new AdsPixelSettings(); + $aam_settings->setPixelId( '123' ); + $aam_settings->setEnableAutomaticMatching( true ); + $aam_settings->setEnabledAutomaticMatchingFields( AAMSettingsFields::get_all_fields() ); + return $aam_settings; + } } diff --git a/__tests__/FileNameTest.php b/__tests__/FileNameTest.php index cedcbbb8..f2df63f4 100644 --- a/__tests__/FileNameTest.php +++ b/__tests__/FileNameTest.php @@ -1,4 +1,18 @@ assertTrue($exist); - } + /** + * Check that the name of entry point file + * 'facebook-for-wordpress.php' still + * exist. This is to make sure that the name of + * entry point file is not changed, + * since changing the file name will break how + * WordPress find the plugin file + * and will end up deactivate the plugin. + */ + public function testEntryPointFileNamePersists() { + $exist = \file_exists( __DIR__ . '/../class-facebookforwordpress.php' ); + $this->assertTrue( $exist ); + } } diff --git a/__tests__/bootstrap.php b/__tests__/bootstrap.php index 8793f64a..e2bad578 100644 --- a/__tests__/bootstrap.php +++ b/__tests__/bootstrap.php @@ -1,17 +1,24 @@ mockUseAAM('1234', true, AAMSettingsFields::getAllFields()); - $user_data_array = $this->getSampleUserData(); + $this->mockUseAAM( '1234', true, AAMSettingsFields::get_all_fields() ); + $user_data_array = + $this->getSampleUserData(); $user_data_normalized = - AAMFieldsExtractor::getNormalizedUserData($user_data_array); - $this->assertEquals('abc@mail.com', - $user_data_normalized[AAMSettingsFields::EMAIL]); - $this->assertEquals('perez', - $user_data_normalized[AAMSettingsFields::LAST_NAME]); - $this->assertEquals('pedro', - $user_data_normalized[AAMSettingsFields::FIRST_NAME]); - $this->assertEquals('567891234', - $user_data_normalized[AAMSettingsFields::PHONE]); - $this->assertEquals('m', - $user_data_normalized[AAMSettingsFields::GENDER]); - $this->assertEquals('1', - $user_data_normalized[AAMSettingsFields::EXTERNAL_ID]); - $this->assertEquals('us', - $user_data_normalized[AAMSettingsFields::COUNTRY]); - $this->assertEquals('seattle', - $user_data_normalized[AAMSettingsFields::CITY]); - $this->assertEquals('wa', - $user_data_normalized[AAMSettingsFields::STATE]); - $this->assertEquals('12345', - $user_data_normalized[AAMSettingsFields::ZIP_CODE]); - $this->assertEquals('19900611', - $user_data_normalized[AAMSettingsFields::DATE_OF_BIRTH]); + AAMFieldsExtractor::get_normalized_user_data( $user_data_array ); + + $this->assertEquals( + 'abc@mail.com', + $user_data_normalized[ AAMSettingsFields::EMAIL ] + ); + $this->assertEquals( + 'perez', + $user_data_normalized[ AAMSettingsFields::LAST_NAME ] + ); + $this->assertEquals( + 'pedro', + $user_data_normalized[ AAMSettingsFields::FIRST_NAME ] + ); + $this->assertEquals( + '567891234', + $user_data_normalized[ AAMSettingsFields::PHONE ] + ); + $this->assertEquals( + 'm', + $user_data_normalized[ AAMSettingsFields::GENDER ] + ); + $this->assertEquals( + '1', + $user_data_normalized[ AAMSettingsFields::EXTERNAL_ID ] + ); + $this->assertEquals( + 'us', + $user_data_normalized[ AAMSettingsFields::COUNTRY ] + ); + $this->assertEquals( + 'seattle', + $user_data_normalized[ AAMSettingsFields::CITY ] + ); + $this->assertEquals( + 'wa', + $user_data_normalized[ AAMSettingsFields::STATE ] + ); + $this->assertEquals( + '12345', + $user_data_normalized[ AAMSettingsFields::ZIP_CODE ] + ); + $this->assertEquals( + '19900611', + $user_data_normalized[ AAMSettingsFields::DATE_OF_BIRTH ] + ); } - public function testReturnsArrayWithRequestedUserDataWhenAamEnabled(){ - $possible_fields = AAMSettingsFields::getAllFields(); - $aam_settings = $this->mockUseAAM('1234', true); + /** + * Verifies that get_normalized_user_data() returns + * an array with only the fields enabled in the AAM settings. + * + * This test runs 25 times, with each iteration + * testing a different subset of fields. + * The subset of fields is randomly generated and + * used to set the enabled fields in the AAM settings. + * The test then verifies that the returned array + * only contains the fields that were enabled in the AAM settings. + */ + public function testReturnsArrayWithRequestedUserDataWhenAamEnabled() { + $possible_fields = AAMSettingsFields::get_all_fields(); + $aam_settings = $this->mockUseAAM( '1234', true ); $user_data_array = $this->getSampleUserData(); - for( $i = 0; $i<25; $i += 1 ){ - $fields_subset = $this->createSubset($possible_fields); - $aam_settings->setEnabledAutomaticMatchingFields($fields_subset); + for ( $i = 0; $i < 25; ++$i ) { + $fields_subset = $this->createSubset( $possible_fields ); + $aam_settings->setEnabledAutomaticMatchingFields( $fields_subset ); $user_data_array_normalized = - AAMFieldsExtractor::getNormalizedUserData($user_data_array); - $this->assertOnlyRequestedFieldsPresentInUserDataArray($fields_subset, - $user_data_array_normalized); + AAMFieldsExtractor::get_normalized_user_data( $user_data_array ); + $this->assertOnlyRequestedFieldsPresentInUserDataArray( + $fields_subset, + $user_data_array_normalized + ); } } - public function testReturnsEmptyArrayWhenAamDisabled(){ + /** + * Tests that get_normalized_user_data + * returns an empty array when AAM is disabled. + * + * This test verifies that when AAM is + * disabled, the normalized user data array is empty. + * + * @return void + */ + public function testReturnsEmptyArrayWhenAamDisabled() { $user_data_array = $this->getSampleUserData(); - $this->mockUseAAM('1234', false); + $this->mockUseAAM( '1234', false ); $user_data_array_normalized = - AAMFieldsExtractor::getNormalizedUserData($user_data_array); - $this->assertEmpty($user_data_array_normalized); + AAMFieldsExtractor::get_normalized_user_data( $user_data_array ); + $this->assertEmpty( $user_data_array_normalized ); } - public function testReturnsEmptyArrayWhenAamNotPresent(){ - $user_data_array = $this->getSampleUserData(); + /** + * Test that get_normalized_user_data returns + * an empty array when AAM is not present. + * + * This test verifies that when AAM is not + * present in the AAM settings, the normalized user data array is empty. + */ + public function testReturnsEmptyArrayWhenAamNotPresent() { + $user_data_array = $this->getSampleUserData(); $user_data_array_normalized = - AAMFieldsExtractor::getNormalizedUserData($user_data_array); - $this->assertEmpty($user_data_array_normalized); + AAMFieldsExtractor::get_normalized_user_data( $user_data_array ); + $this->assertEmpty( $user_data_array_normalized ); } - private function assertOnlyRequestedFieldsPresentInUserDataArray($fieldsSubset, - $userDataArray){ - $this->assertEquals(count($fieldsSubset), count($userDataArray)); - foreach($fieldsSubset as $field){ - $this->assertArrayHasKey($field, $userDataArray); + /** + * Asserts that the user data array only contains + * the fields specified in the fields subset. + * + * @param array $fields_subset The subset of fields to check. + * @param array $user_data_array The array of user data to check. + * + * @return void + */ + private function assertOnlyRequestedFieldsPresentInUserDataArray( + $fields_subset, + $user_data_array + ) { + $this->assertEquals( + count( $fields_subset ), + count( $user_data_array ) + ); + foreach ( $fields_subset as $field ) { + $this->assertArrayHasKey( $field, $user_data_array ); } } - private function createSubset($fields){ - shuffle($fields); - $randNum = rand()%count($fields); - $subset = array(); - for( $i = 0; $i < $randNum; $i+=1 ){ - $subset[] = $fields[$i]; + /** + * Creates a random subset of the given fields. + * + * This is used by the + * testReturnsArrayWithRequestedUserDataWhenAamEnabled test to + * generate a random subset of the user data fields to check. + * + * @param array $fields The array of fields to create a subset of. + * + * @return array The subset of fields. + */ + private function createSubset( $fields ) { + shuffle( $fields ); + $rand_num = rand() % count( $fields ); + $subset = array(); + for ( $i = 0; $i < $rand_num; ++$i ) { + $subset[] = $fields[ $i ]; } return $subset; } - private function mockUseAAM($pixel_id = '1234', $enable_aam = false, - $enable_aam_fields = []){ + /** + * Mocks the return value of FacebookWordpressOptions::get_aam_settings(). + * + * @param string $pixel_id The pixel ID + * to set in the AAM settings. + * @param bool $enable_aam Whether to enable automatic matching. + * @param array $enable_aam_fields The + * fields to enable for automatic matching. + * + * @return AdsPixelSettings The mocked AdsPixelSettings object. + */ + private function mockUseAAM( + $pixel_id = '1234', + $enable_aam = false, + $enable_aam_fields = array() + ) { $aam_settings = new AdsPixelSettings(); - $aam_settings->setPixelId($pixel_id); - $aam_settings->setEnableAutomaticMatching($enable_aam); - $aam_settings->setEnabledAutomaticMatchingFields($enable_aam_fields); + $aam_settings->setPixelId( $pixel_id ); + $aam_settings->setEnableAutomaticMatching( $enable_aam ); + $aam_settings->setEnabledAutomaticMatchingFields( $enable_aam_fields ); $this->mocked_options = \Mockery::mock( - 'alias:FacebookPixelPlugin\Core\FacebookWordpressOptions'); - $this->mocked_options->shouldReceive('getAAMSettings')->andReturn($aam_settings); + 'alias:FacebookPixelPlugin\Core\FacebookWordpressOptions' + ); + $this->mocked_options->shouldReceive( 'get_aam_settings' ) + ->andReturn( $aam_settings ); return $aam_settings; } - private function getSampleUserData(){ - return array( - AAMSettingsFields::EMAIL => 'abc@mail.com', - AAMSettingsFields::LAST_NAME => 'Perez', - AAMSettingsFields::FIRST_NAME => 'Pedro', - AAMSettingsFields::PHONE => '567-891-234', - AAMSettingsFields::GENDER => 'Male', - AAMSettingsFields::EXTERNAL_ID => '1', - AAMSettingsFields::COUNTRY => 'US', - AAMSettingsFields::CITY => 'Seattle', - AAMSettingsFields::STATE => 'WA', - AAMSettingsFields::ZIP_CODE => '12345', - AAMSettingsFields::DATE_OF_BIRTH => '1990-06-11', + /** + * Returns a sample user data array. + * + * This function returns a sample user data + * array with all the fields that can be used in automatic matching. + * + * @return array The sample user data array. + */ + private function getSampleUserData() { + return array( + AAMSettingsFields::EMAIL => 'abc@mail.com', + AAMSettingsFields::LAST_NAME => 'Perez', + AAMSettingsFields::FIRST_NAME => 'Pedro', + AAMSettingsFields::PHONE => '567-891-234', + AAMSettingsFields::GENDER => 'Male', + AAMSettingsFields::EXTERNAL_ID => '1', + AAMSettingsFields::COUNTRY => 'US', + AAMSettingsFields::CITY => 'Seattle', + AAMSettingsFields::STATE => 'WA', + AAMSettingsFields::ZIP_CODE => '12345', + AAMSettingsFields::DATE_OF_BIRTH => '1990-06-11', ); } } diff --git a/__tests__/core/EventIdGeneratorTest.php b/__tests__/core/EventIdGeneratorTest.php index 54c51f18..5fce3f45 100644 --- a/__tests__/core/EventIdGeneratorTest.php +++ b/__tests__/core/EventIdGeneratorTest.php @@ -1,5 +1,19 @@ assertEquals(100, count($event_ids)); + $event_ids = array_unique( $event_ids ); + $this->assertEquals( 100, count( $event_ids ) ); } } diff --git a/__tests__/core/FacebookPixelTest.php b/__tests__/core/FacebookPixelTest.php index cf9dd15a..6cf97feb 100644 --- a/__tests__/core/FacebookPixelTest.php +++ b/__tests__/core/FacebookPixelTest.php @@ -1,5 +1,19 @@ assertEquals('123', FacebookPixel::getPIxelId()); - FacebookPixel::setPixelId('1'); - $this->assertEquals('1', FacebookPixel::getPIxelId()); + FacebookPixel::initialize( '123' ); + $this->assertEquals( '123', FacebookPixel::get_pixel_id() ); + FacebookPixel::set_pixel_id( '1' ); + $this->assertEquals( '1', FacebookPixel::get_pixel_id() ); } - private function assertCodeStartAndEndWithScript($code) { - $this->assertStringStartsWith('assertStringEndsWith('', $code); + /** + * Asserts that the given code starts with ', $code ); } - private function assertCodeStartAndEndWithNoScript($code) { - $this->assertStringStartsNotWith('assertStringEndsNotWith('', $code); + /** + * Asserts that the given code does not start + * with ', $code ); } - private function assertCodePatternMatch($code, $keywords) { - foreach ($keywords as $keyword) { - $this->assertTrue(\strpos($code, $keyword) !== false); + + /** + * Asserts that the given code contains all the given keywords. + * + * @param string $code The code to test. + * @param array $keywords The keywords to search for. + * + * @return void + */ + private function assertCodePatternMatch( $code, $keywords ) { + foreach ( $keywords as $keyword ) { + $this->assertTrue( + strpos( $code, $keyword ) !== + false, + "Failed asserting that '$code' contains '$keyword'" + ); } } - private function assertCodePattern($function, $keyword) { - FacebookPixel::setPixelId(''); - $code = FacebookPixel::$function(); - $this->assertEmpty($code); + /** + * Asserts that the given function behaves + * correctly in terms of code pattern. + * + * It tests that when no pixel ID is set, + * the function returns an empty string. + * It tests that when a pixel ID is set, the + * function returns a string that starts + * with '; - $_POST['accessToken'] = ''; - $_POST['externalBusinessId'] = ''; - \WP_Mock::userFunction( 'sanitize_text_field', array( - 'return_in_order' => array( - '', - '', - '', - ) - ) - ); - $expectedJson = array( - 'success' => false, - 'msg' => 'Invalid values' - ); - $result = $settingsRecorder->saveFbeSettings(); - $this->assertEquals($expectedJson, $result); - } + /** + * Tests that invalid settings are not saved. + * + * This test verifies that the FacebookWordpressSettingsRecorder + * class does not save + * settings when the pixel ID, access token, or external business + * ID contain invalid + * values. The test case sets up invalid values for the + * $_POST superglobal and + * verifies that the save_fbe_settings method returns an error. + * + * @covers FacebookWordpressSettingsRecorder::save_fbe_settings + */ + public function testDoesNotSaveInvalidSettings() { + $settings_recorder = new FacebookWordpressSettingsRecorder(); + self::mockWordPressFunctions(); + global $_POST; + $_POST['pixelId'] = ''; + $_POST['accessToken'] = ''; + $_POST['externalBusinessId'] = ''; + + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - public function testSaveSettingsWithAdmin(){ - $settingsRecorder = new FacebookWordpressSettingsRecorder(); - self::mockWordPressFunctions(); - global $_POST; - $_POST['pixelId'] = '123'; - $_POST['accessToken'] = 'ABC123XYZ'; - $_POST['externalBusinessId'] = 'fbe_wordpress_123_abc'; - \WP_Mock::userFunction( 'sanitize_text_field', array( + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'return_in_order' => array( + '', + '', + '', + ), + ) + ); + + $expected_json = array( + 'success' => false, + 'msg' => 'Invalid values', + ); + $result = $settings_recorder->save_fbe_settings(); + $this->assertEquals( $expected_json, $result ); + } + + /** + * Tests that settings are saved when the current user is an administrator. + * + * This test verifies that the FacebookWordpressSettingsRecorder class saves + * settings when the current user is an administrator. + * It sets up valid values + * for the $_POST superglobal and calls the save_fbe_settings method. + * The test + * verifies that the settings are saved correctly by comparing the result + * with the expected output. + * + * @covers FacebookWordpressSettingsRecorder::save_fbe_settings + */ + public function testSaveSettingsWithAdmin() { + $settings_recorder = new FacebookWordpressSettingsRecorder(); + self::mockWordPressFunctions(); + global $_POST; + $_POST['pixelId'] = '123'; + $_POST['accessToken'] = 'ABC123XYZ'; + $_POST['externalBusinessId'] = 'fbe_wordpress_123_abc'; + if ( isset( $_POST['pixelId'] ) && isset( + $_POST['accessToken'] + ) && isset( $_POST['externalBusinessId'] ) ) { + \WP_Mock::userFunction( + 'sanitize_text_field', + array( 'return_in_order' => array( - $_POST['pixelId'], - $_POST['accessToken'], - $_POST['externalBusinessId'] - ) - ) - ); - $expectedJson = array( - 'success' => true, - 'msg' => array( - FacebookPluginConfig::PIXEL_ID_KEY => $_POST['pixelId'], - FacebookPluginConfig::ACCESS_TOKEN_KEY => $_POST['accessToken'], - FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY => - $_POST['externalBusinessId'], - FacebookPluginConfig::IS_FBE_INSTALLED_KEY => '1' - ) - ); - $result = $settingsRecorder->saveFbeSettings(); - $this->assertEquals($expectedJson, $result); + $_POST['pixelId'], + $_POST['accessToken'], + $_POST['externalBusinessId'], + ), + ) + ); + $expected_json = array( + 'success' => true, + 'msg' => array( + FacebookPluginConfig::PIXEL_ID_KEY => $_POST['pixelId'], + FacebookPluginConfig::ACCESS_TOKEN_KEY => $_POST['accessToken'], + FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY => $_POST['externalBusinessId'], + FacebookPluginConfig::IS_FBE_INSTALLED_KEY => '1', + ), + ); + + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $result = $settings_recorder->save_fbe_settings(); + $this->assertEquals( $expected_json, $result ); } + } } diff --git a/__tests__/core/PixelRendererTest.php b/__tests__/core/PixelRendererTest.php index 96817739..b849ed0e 100644 --- a/__tests__/core/PixelRendererTest.php +++ b/__tests__/core/PixelRendererTest.php @@ -1,16 +1,30 @@ setEventName('Lead') - ->setEventId('TestEventId'); - $code = PixelRenderer::render(array($event), 'Test'); - - $expected = sprintf("", $agent_string); - - $this->assertEquals($expected, $code); + FacebookWordpressOptions::set_version_info(); + $agent_string = FacebookWordpressOptions::get_agent_string(); + + $event = ( new Event() ) + ->setEventName( 'Lead' ) + ->setEventId( 'TestEventId' ); + + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + $code = PixelRenderer::render( array( $event ), 'Test' ); + + $expected = sprintf( + "", + $agent_string + ); + + $this->assertEquals( $expected, $code ); } + /** + * Test that the PixelRenderer renders the expected code for a custom event. + * + * This test ensures that the render method + * correctly generates the Pixel code + * for a custom event. It verifies that the + * output includes the 'trackCustom' + * keyword and that the event data and custom data are correctly formatted + * and included in the output. + * + * @covers \FacebookPixelPlugin\Core\PixelRenderer::render + */ public function testPixelRenderForCustomEvent() { - FacebookWordpressOptions::setVersionInfo(); - $agent_string = FacebookWordpressOptions::getAgentString(); - - $event = (new Event()) - ->setEventName('Custom') - ->setEventId('TestEventId'); - - $code = PixelRenderer::render(array($event), 'Test'); - - $expected = sprintf("", $agent_string); - - $this->assertEquals($expected, $code); + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + FacebookWordpressOptions::set_version_info(); + $agent_string = FacebookWordpressOptions::get_agent_string(); + + $event = ( new Event() ) + ->setEventName( 'Custom' ) + ->setEventId( 'TestEventId' ); + + $code = PixelRenderer::render( array( $event ), 'Test' ); + + $expected = sprintf( + "", + $agent_string + ); + + $this->assertEquals( $expected, $code ); } + /** + * Test that the PixelRenderer renders the expected code for a custom event + * with custom data. + * + * This test ensures that the render method correctly generates the Pixel + * code for a custom event with custom data. It verifies that the output + * includes the 'track' keyword and that the custom data is correctly + * formatted and included in the output. + * + * @covers \FacebookPixelPlugin\Core\PixelRenderer::render + */ public function testPixelRenderForCustomData() { - FacebookWordpressOptions::setVersionInfo(); - $agent_string = FacebookWordpressOptions::getAgentString(); - - $custom_data = (new CustomData()) - ->setCurrency('USD') - ->setValue('30.00'); - - $event = (new Event()) - ->setEventName('Purchase') - ->setEventId('TestEventId') - ->setCustomData($custom_data); - - $code = PixelRenderer::render(array($event), 'Test'); - - $expected = sprintf("", $agent_string); - - $this->assertEquals($expected, $code); + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + FacebookWordpressOptions::set_version_info(); + $agent_string = FacebookWordpressOptions::get_agent_string(); + + $custom_data = ( new CustomData() ) + ->setCurrency( 'USD' ) + ->setValue( '30.00' ); + + $event = ( new Event() ) + ->setEventName( 'Purchase' ) + ->setEventId( 'TestEventId' ) + ->setCustomData( $custom_data ); + + $code = PixelRenderer::render( array( $event ), 'Test' ); + + $expected = sprintf( + "", + $agent_string + ); + + $this->assertEquals( $expected, $code ); } + /** + * Test that the PixelRenderer renders the + * expected code for multiple events. + * + * This test verifies that the render method correctly generates the Pixel + * code when provided with multiple events. It ensures that each event is + * tracked separately and that the output includes the correct event data + * and event IDs for each event. + * + * @covers \FacebookPixelPlugin\Core\PixelRenderer::render + */ public function testPixelRenderForMultipleEvents() { - FacebookWordpressOptions::setVersionInfo(); - $agent_string = FacebookWordpressOptions::getAgentString(); - - $event1 = (new Event()) - ->setEventName('Lead') - ->setEventId('TestEventId1'); - $event2 = (new Event()) - ->setEventName('Lead') - ->setEventId('TestEventId2'); - - $code = PixelRenderer::render(array($event1, $event2), 'Test'); - - $expected = sprintf("", $agent_string); - - $this->assertEquals($expected, $code); + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + FacebookWordpressOptions::set_version_info(); + $agent_string = FacebookWordpressOptions::get_agent_string(); + + $event1 = ( new Event() ) + ->setEventName( 'Lead' ) + ->setEventId( 'TestEventId1' ); + $event2 = ( new Event() ) + ->setEventName( 'Lead' ) + ->setEventId( 'TestEventId2' ); + + $code = PixelRenderer::render( array( $event1, $event2 ), 'Test' ); + + $expected = sprintf( + "", + $agent_string + ); + + $this->assertEquals( $expected, $code ); } } diff --git a/__tests__/core/ServerEventFactoryTest.php b/__tests__/core/ServerEventFactoryTest.php index c55c09f2..f2ee3fae 100644 --- a/__tests__/core/ServerEventFactoryTest.php +++ b/__tests__/core/ServerEventFactoryTest.php @@ -1,5 +1,19 @@ array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); - $this->assertNotNull($event->getEventId()); - $this->assertEquals(36, strlen($event->getEventId())); + $this->assertNotNull( $event->getEventId() ); + $this->assertEquals( 36, strlen( $event->getEventId() ) ); } + /** + * Tests that a new event has a valid event time. + * + * This test verifies that the event time generated for a new event + * is not null and is a valid Unix timestamp, i.e. it is less than + * the current time. + * + * @return void + */ public function testNewEventHasEventTime() { - $event = ServerEventFactory::newEvent('Lead'); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); - $this->assertNotNull($event->getEventTime()); - $this->assertLessThan(1, time() - $event->getEventTime()); + $this->assertNotNull( $event->getEventTime() ); + $this->assertLessThan( 1, time() - $event->getEventTime() ); } + /** + * Tests that a new event has the correct event name. + * + * This test verifies that the event name generated for a new event + * matches the event name passed to the new_event method. + * + * @return void + */ public function testNewEventHasEventName() { - $event = ServerEventFactory::newEvent('Lead'); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); - $this->assertEquals('Lead', $event->getEventName()); + $this->assertEquals( 'Lead', $event->getEventName() ); } + /** + * Tests that a new event has an action source. + * + * This test verifies that the action source generated for a new event + * is 'website'. + * + * @return void + */ public function testNewEventHasActionSource() { - $event = ServerEventFactory::newEvent('ViewContent'); - $this->assertEquals('website', $event->getActionSource()); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'ViewContent' ); + $this->assertEquals( 'website', $event->getActionSource() ); } + /** + * Tests that a new event can be created with an IPv4 address. + * + * This test verifies that when an IPv4 address is passed in the + * X-Forwarded-For header, a new event can be created with the correct + * client IP address. + * + * @return void + */ public function testNewEventWorksWithIpV4() { $_SERVER['HTTP_X_FORWARDED_FOR'] = '24.17.77.101'; - $event = ServerEventFactory::newEvent('Lead'); - $this->assertEquals('24.17.77.101', - $event->getUserData()->getClientIpAddress()); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); + $this->assertEquals( + '24.17.77.101', + $event->getUserData()->getClientIpAddress() + ); } + /** + * Tests that a new event can be created with an IPv6 address. + * + * This test verifies that when an IPv6 address is passed in the + * X-Forwarded-For header, a new event can be created with the correct + * client IP address. + * + * @return void + */ public function testNewEventWorksWithIpV6() { $_SERVER['HTTP_X_FORWARDED_FOR'] = '2120:10a:c191:401::5:7170'; - $event = ServerEventFactory::newEvent('Lead'); - $this->assertEquals('2120:10a:c191:401::5:7170', - $event->getUserData()->getClientIpAddress()); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); + $this->assertEquals( + '2120:10a:c191:401::5:7170', + $event->getUserData()->getClientIpAddress() + ); } + /** + * Tests that a new event takes the first IP address from a list. + * + * This test verifies that when multiple IP addresses are provided + * in the X-Forwarded-For header, the first IP address is correctly + * used as the client IP address for the new event. + * + * @return void + */ public function testNewEventTakesFirstWithIpAddressList() { $_SERVER['HTTP_X_FORWARDED_FOR'] = '2120:10a:c191:401::5:7170, 24.17.77.101'; - $event = ServerEventFactory::newEvent('Lead'); - $this->assertEquals('2120:10a:c191:401::5:7170', - $event->getUserData()->getClientIpAddress()); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); + $this->assertEquals( + '2120:10a:c191:401::5:7170', + $event->getUserData()->getClientIpAddress() + ); } + /** + * Tests that a new event honors the precedence + * order for determining the client's IP address. + * + * This test verifies that when both the + * HTTP_X_FORWARDED_FOR and REMOTE_ADDR + * headers are present, the HTTP_X_FORWARDED_FOR header is used to determine + * the client's IP address for the new event. + * + * @return void + */ public function testNewEventHonorsPrecedenceForIpAddress() { $_SERVER['HTTP_X_FORWARDED_FOR'] = '24.17.77.101'; - $_SERVER['REMOTE_ADDR'] = '24.17.77.100'; + $_SERVER['REMOTE_ADDR'] = '24.17.77.100'; + + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - $event = ServerEventFactory::newEvent('Lead'); - $this->assertEquals('24.17.77.101', - $event->getUserData()->getClientIpAddress()); + $event = ServerEventFactory::new_event( 'Lead' ); + $this->assertEquals( + '24.17.77.101', + $event->getUserData()->getClientIpAddress() + ); } + /** + * Tests that a new event handles an invalid IP address correctly. + * + * This test verifies that when an invalid IP address is provided + * in the HTTP_X_FORWARDED_FOR header, the client's IP address for + * the new event is null, indicating that the invalid IP address was + * not used. + * + * @return void + */ public function testNewEventWithInvalidIpAddress() { $_SERVER['HTTP_X_FORWARDED_FOR'] = 'INVALID'; - $event = ServerEventFactory::newEvent('Lead'); - $this->assertNull($event->getUserData()->getClientIpAddress()); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); + $this->assertNull( $event->getUserData()->getClientIpAddress() ); } + /** + * Tests that a new event has the correct user agent. + * + * This test verifies that the user agent generated for a new event + * matches the user agent value set in the HTTP_USER_AGENT server variable. + * + * @return void + */ public function testNewEventHasUserAgent() { $_SERVER['HTTP_USER_AGENT'] = 'HTTP_USER_AGENT_VALUE'; - $event = ServerEventFactory::newEvent('Lead'); - $this->assertEquals('HTTP_USER_AGENT_VALUE', - $event->getUserData()->getClientUserAgent()); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); + + $this->assertEquals( + 'HTTP_USER_AGENT_VALUE', + $event->getUserData()->getClientUserAgent() + ); } + /** + * Tests that a new event has the correct event source URL with HTTPS. + * + * This test verifies that when the HTTPS server variable is set, + * the event source URL generated for a + * new event includes the 'https' scheme. + * + * @return void + */ public function testNewEventHasEventSourceUrlWithHttps() { - $_SERVER['HTTPS'] = 'anyvalue'; - $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; + $_SERVER['HTTPS'] = 'anyvalue'; + $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; $_SERVER['REQUEST_URI'] = '/index.php'; - $event = ServerEventFactory::newEvent('Lead'); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); - $this->assertEquals('https://www.pikachu.com/index.php', $event->getEventSourceUrl()); + $this->assertEquals( + 'https://www.pikachu.com/index.php', + $event->getEventSourceUrl() + ); } + /** + * Tests that a new event has the correct event source URL with HTTP. + * + * This test verifies that when the HTTPS server variable is not set, + * the event source URL generated for a + * new event includes the 'http' scheme. + * + * @return void + */ public function testNewEventHasEventSourceUrlWithHttp() { - $_SERVER['HTTPS'] = ''; - $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; + $_SERVER['HTTPS'] = ''; + $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; $_SERVER['REQUEST_URI'] = '/index.php'; - $event = ServerEventFactory::newEvent('Lead'); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); - $this->assertEquals('http://www.pikachu.com/index.php', $event->getEventSourceUrl()); + $this->assertEquals( + 'http://www.pikachu.com/index.php', + $event->getEventSourceUrl() + ); } + /** + * Tests that a new event has the correct event + * source URL with HTTP when HTTPS is set to 'off'. + * + * This test verifies that when the HTTPS server + * variable is explicitly set to 'off', + * the event source URL generated for a new event + * includes the 'http' scheme. + * + * @return void + */ public function testNewEventHasEventSourceUrlWithHttpsOff() { - $_SERVER['HTTPS'] = 'off'; - $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; + $_SERVER['HTTPS'] = 'off'; + $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; $_SERVER['REQUEST_URI'] = '/index.php'; - $event = ServerEventFactory::newEvent('Lead'); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); - $this->assertEquals('http://www.pikachu.com/index.php', $event->getEventSourceUrl()); + $this->assertEquals( + 'http://www.pikachu.com/index.php', + $event->getEventSourceUrl() + ); } + /** + * Tests that a new event has the correct event + * source URL when the prefer_referer_for_event_src flag is set to true. + * + * This test verifies that when the HTTP_REFERER server variable is set + * and the prefer_referer_for_event_src flag is set to true, + * the event source URL generated for a new event is the value in the + * HTTP_REFERER server variable. + * + * @return void + */ public function testNewEventEventSourceUrlPreferReferer() { - $_SERVER['HTTPS'] = 'off'; - $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; - $_SERVER['REQUEST_URI'] = '/index.php'; + $_SERVER['HTTPS'] = 'off'; + $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; + $_SERVER['REQUEST_URI'] = '/index.php'; $_SERVER['HTTP_REFERER'] = 'http://referrer/'; - $event = ServerEventFactory::newEvent('Lead', true); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead', true ); - $this->assertEquals('http://referrer/', $event->getEventSourceUrl()); + $this->assertEquals( 'http://referrer/', $event->getEventSourceUrl() ); } + /** + * Tests that a new event has the correct event + * source URL when the prefer_referer_for_event_src flag is set to true, + * but the HTTP_REFERER server variable is not set. + * + * This test verifies that when the HTTP_REFERER server variable is not set + * and the prefer_referer_for_event_src flag is set to true, + * the event source URL generated for a new event is the value in the + * HTTP_HOST and REQUEST_URI server variables. + * + * @return void + */ public function testNewEventEventSourceUrlWithoutReferer() { - $_SERVER['HTTPS'] = 'off'; - $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; + $_SERVER['HTTPS'] = 'off'; + $_SERVER['HTTP_HOST'] = 'www.pikachu.com'; $_SERVER['REQUEST_URI'] = '/index.php'; - $event = ServerEventFactory::newEvent('Lead', true); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead', true ); - $this->assertEquals('http://www.pikachu.com/index.php', $event->getEventSourceUrl()); + $this->assertEquals( + 'http://www.pikachu.com/index.php', + $event->getEventSourceUrl() + ); } + /** + * Tests that the fbclid is extracted from the + * URL if the FBC cookie is not found. + * + * This test verifies that when the FBC cookie is + * not set, the fbclid parameter + * from the URL is used to set the FBC value in the event. + * + * @return void + */ public function testFBClidExtractedFromUrlIfFbcNotFound() { $_GET['fbclid'] = 'fbclid_str'; - $event = ServerEventFactory::newEvent('Lead'); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - $event_fbc = $event->getUserData()->getFbc(); + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - $this->assertEquals(TRUE, str_starts_with($event_fbc, 'fb.1.')); - $this->assertEquals(TRUE, str_ends_with($event_fbc, '.fbclid_str')); + $event = ServerEventFactory::new_event( 'Lead' ); + + $event_fbc = $event->getUserData()->getFbc(); + + $this->assertEquals( true, str_starts_with( $event_fbc, 'fb.1.' ) ); + $this->assertEquals( true, str_ends_with( $event_fbc, '.fbclid_str' ) ); } + /** + * Tests that a new event correctly uses the FBC value from the cookie. + * + * This test verifies that when the FBC cookie is set, the FBC value + * is correctly retrieved and assigned to the event's user data. + * + * @return void + */ public function testNewEventHasFbc() { $_COOKIE['_fbc'] = '_fbc_value'; - $event = ServerEventFactory::newEvent('Lead'); - $this->assertEquals('_fbc_value', $event->getUserData()->getFbc()); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); + + $this->assertEquals( '_fbc_value', $event->getUserData()->getFbc() ); } + /** + * Tests that a new event correctly uses the FBP value from the cookie. + * + * This test verifies that when the FBP cookie is set, the FBP value + * is correctly retrieved and assigned to the event's user data. + * + * @return void + */ public function testNewEventHasFbp() { $_COOKIE['_fbp'] = '_fbp_value'; - $event = ServerEventFactory::newEvent('Lead'); - $this->assertEquals('_fbp_value', $event->getUserData()->getFbp()); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); + + $this->assertEquals( '_fbp_value', $event->getUserData()->getFbp() ); } + /** + * Tests the safe_create_event method with + * Personally Identifiable Information (PII). + * + * This test verifies that when PII extraction is enabled and all AAM fields + * are available, the safe_create_event + * method correctly creates a server event + * with the expected user data attributes, + * including email, phone, first name, + * last name, state, city, country code, zip code, and gender. + * + * @return void + */ public function testSafeCreateEventWithPII() { - $this->mockUseAAM('1234', true, AAMSettingsFields::getAllFields()); + $this->mockUseAAM( '1234', true, AAMSettingsFields::get_all_fields() ); + + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - $server_event = ServerEventFactory::safeCreateEvent( + $server_event = ServerEventFactory::safe_create_event( 'Lead', - array($this, 'getEventData'), + array( $this, 'getEventData' ), array(), 'test_integration' ); - $this->assertEquals( 'pika.chu@s2s.com', - $server_event->getUserData()->getEmail()); - $this->assertEquals('12345', $server_event->getUserData()->getPhone()); - $this->assertEquals('pika', $server_event->getUserData()->getFirstName()); - $this->assertEquals('chu', $server_event->getUserData()->getLastName()); - $this->assertEquals('oh', $server_event->getUserData()->getState()); - $this->assertEquals('springfield', $server_event->getUserData()->getCity()); - $this->assertEquals('us', $server_event->getUserData()->getCountryCode()); - $this->assertEquals('4321', $server_event->getUserData()->getZipCode()); - $this->assertEquals('m', $server_event->getUserData()->getGender()); + $this->assertEquals( + 'pika.chu@s2s.com', + $server_event->getUserData()->getEmail() + ); + $this->assertEquals( + '12345', + $server_event->getUserData()->getPhone() + ); + $this->assertEquals( + 'pika', + $server_event->getUserData()->getFirstName() + ); + $this->assertEquals( + 'chu', + $server_event->getUserData()->getLastName() + ); + $this->assertEquals( + 'oh', + $server_event->getUserData()->getState() + ); + $this->assertEquals( + 'springfield', + $server_event->getUserData()->getCity() + ); + $this->assertEquals( + 'us', + $server_event->getUserData()->getCountryCode() + ); + $this->assertEquals( + '4321', + $server_event->getUserData()->getZipCode() + ); + $this->assertEquals( + 'm', + $server_event->getUserData()->getGender() + ); } + /** + * Tests the safe_create_event method with + * Personally Identifiable Information (PII) extraction disabled. + * + * This test verifies that when PII extraction is + * disabled, the safe_create_event method correctly creates a server event + * with all user data attributes set to null. + * + * @return void + */ public function testSafeCreateEventWithPIIDisabled() { + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - $server_event = ServerEventFactory::safeCreateEvent( + $server_event = ServerEventFactory::safe_create_event( 'Lead', - array($this, 'getEventData'), + array( $this, 'getEventData' ), array(), 'test_integration' ); - $this->assertNull($server_event->getUserData()->getEmail()); - $this->assertNull($server_event->getUserData()->getFirstName()); - $this->assertNull($server_event->getUserData()->getLastName()); - $this->assertNull($server_event->getUserData()->getPhone()); - $this->assertNull($server_event->getUserData()->getState()); - $this->assertNull($server_event->getUserData()->getCity()); - $this->assertNull($server_event->getUserData()->getCountryCode()); - $this->assertNull($server_event->getUserData()->getZipCode()); - $this->assertNull($server_event->getUserData()->getGender()); + $this->assertNull( $server_event->getUserData()->getEmail() ); + $this->assertNull( $server_event->getUserData()->getFirstName() ); + $this->assertNull( $server_event->getUserData()->getLastName() ); + $this->assertNull( $server_event->getUserData()->getPhone() ); + $this->assertNull( $server_event->getUserData()->getState() ); + $this->assertNull( $server_event->getUserData()->getCity() ); + $this->assertNull( $server_event->getUserData()->getCountryCode() ); + $this->assertNull( $server_event->getUserData()->getZipCode() ); + $this->assertNull( $server_event->getUserData()->getGender() ); } + /** + * Returns a sample event data array. + * + * This method returns an associative array + * containing sample user data fields + * such as email, first name, last name, phone, + * state, city, country, zip code, + * and gender. These fields are used for testing purposes. + * + * @return array The sample event data array with user information. + */ public function getEventData() { return array( - 'email' => 'pika.chu@s2s.com', - 'first_name' => 'Pika', - 'last_name' => 'Chu', - 'phone' => '12345', - 'state' => 'OH', - 'city' => 'Springfield', - 'country' => 'US', - 'zip' => '4321', - 'gender' => 'M' + 'email' => 'pika.chu@s2s.com', + 'first_name' => 'Pika', + 'last_name' => 'Chu', + 'phone' => '12345', + 'state' => 'OH', + 'city' => 'Springfield', + 'country' => 'US', + 'zip' => '4321', + 'gender' => 'M', ); } - private function mockUseAAM($pixel_id = '1234', $enable_aam = false, - $enable_aam_fields = []){ + /** + * Mocks the use of AAM settings for tests. + * + * This method sets up a mock of the AdsPixelSettings + * class and the FacebookWordpressOptions class. + * The AdsPixelSettings class is configured with the + * given pixel ID, whether AAM is enabled, + * and which fields are enabled for AAM. + * + * The method then sets up the FacebookWordpressOptions + * class to return the configured AdsPixelSettings + * instance when the get_aam_settings method is called. + * + * @param string $pixel_id The ID of the pixel. + * @param bool $enable_aam Whether AAM is enabled. + * @param array $enable_aam_fields The fields to enable for AAM. + * + * @return void + */ + private function mockUseAAM( + $pixel_id = '1234', + $enable_aam = false, + $enable_aam_fields = array() + ) { $aam_settings = new AdsPixelSettings(); - $aam_settings->setPixelId($pixel_id); - $aam_settings->setEnableAutomaticMatching($enable_aam); - $aam_settings->setEnabledAutomaticMatchingFields($enable_aam_fields); + $aam_settings->setPixelId( $pixel_id ); + $aam_settings->setEnableAutomaticMatching( $enable_aam ); + $aam_settings->setEnabledAutomaticMatchingFields( $enable_aam_fields ); $this->mocked_options = \Mockery::mock( - 'alias:FacebookPixelPlugin\Core\FacebookWordpressOptions'); - $this->mocked_options->shouldReceive('getAAMSettings')->andReturn($aam_settings); + 'alias:FacebookPixelPlugin\Core\FacebookWordpressOptions' + ); + $this->mocked_options->shouldReceive( 'get_aam_settings' ) + ->andReturn( $aam_settings ); } - } diff --git a/__tests__/integration/FacebookWordpressCalderaFormTest.php b/__tests__/integration/FacebookWordpressCalderaFormTest.php index c4ca594e..27b3821e 100644 --- a/__tests__/integration/FacebookWordpressCalderaFormTest.php +++ b/__tests__/integration/FacebookWordpressCalderaFormTest.php @@ -1,16 +1,30 @@ assertHooksAdded(); - $this->assertCount(0, - FacebookServerSideEvent::getInstance()->getTrackedEvents()); + $this->assertCount( + 0, + FacebookServerSideEvent::get_instance()->get_tracked_events() + ); } + /** + * Tests the injectLeadEvent method for a non-internal user + * when the form submission is complete. + * + * This test checks that the Pixel code is correctly + * appended to the HTML output + * when the form submission status is 'complete' and the + * user is not an internal user. + * It verifies that the output HTML contains the + * expected Pixel code pattern. + * + * @return void + */ public function testInjectLeadEventWithoutInternalUserAndSubmitted() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); - $mock_out = array('status' => 'complete', 'html' => 'successful submitted'); - - $out = FacebookWordpressCalderaForm::injectLeadEvent($mock_out, null); + $mock_out = array( + 'status' => 'complete', + 'html' => 'successful submitted', + ); - $this->assertArrayHasKey('html', $out); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + $out = FacebookWordpressCalderaForm::injectLeadEvent( $mock_out, null ); + + $this->assertArrayHasKey( 'html', $out ); $code = $out['html']; $this->assertMatchesRegularExpression( - '/caldera-forms[\s\S]+End Meta Pixel Event Code/', $code); + '/caldera-forms[\s\S]+End Meta Pixel Event Code/', + $code + ); } + /** + * Tests the injectLeadEvent method for a non-internal user + * when the form submission is not complete. + * + * This test checks that the Pixel code is not appended to the HTML output + * when the form submission status is not 'complete' + * and the user is not an internal user. + * It verifies that the output HTML is not modified + * and the server-side event tracking list is empty. + * + * @return void + */ public function testInjectLeadEventWithoutInternalUserAndNotSubmitted() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); - $mock_out = array( + $mock_out = array( 'status' => 'preprocess', - 'html' => 'fail to submit form'); + 'html' => 'fail to submit form', + ); $mock_form = array(); - $out = FacebookWordpressCalderaForm::injectLeadEvent($mock_out, $mock_form); + $out = FacebookWordpressCalderaForm::injectLeadEvent( + $mock_out, + $mock_form + ); - $this->assertArrayHasKey('html', $out); + $this->assertArrayHasKey( 'html', $out ); $code = $out['html']; - $this->assertEquals('fail to submit form', $code); + $this->assertEquals( 'fail to submit form', $code ); - $this->assertCount(0, - FacebookServerSideEvent::getInstance()->getTrackedEvents()); + $this->assertCount( + 0, + FacebookServerSideEvent::get_instance()->get_tracked_events() + ); } + /** + * Tests the injectLeadEvent method for an internal + * user when the form submission is complete. + * + * This test verifies that no Pixel code is + * appended to the HTML output + * when the user is an internal user, even if the form + * submission status is 'complete'. + * It asserts that the output HTML + * remains unchanged and that no events are tracked. + * + * @return void + */ public function testInjectLeadEventWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); self::mockFacebookWordpressOptions(); - $mock_out = array('status' => 'complete', 'html' => 'successful submitted'); + $mock_out = array( + 'status' => 'complete', + 'html' => 'successful submitted', + ); $mock_form = array(); - $out = FacebookWordpressCalderaForm::injectLeadEvent($mock_out, $mock_form); + $out = FacebookWordpressCalderaForm::injectLeadEvent( + $mock_out, + $mock_form + ); - $this->assertArrayHasKey('html', $out); + $this->assertArrayHasKey( 'html', $out ); $code = $out['html']; - $this->assertEquals('successful submitted', $code); + $this->assertEquals( 'successful submitted', $code ); - $this->assertCount(0, - FacebookServerSideEvent::getInstance()->getTrackedEvents()); + $this->assertCount( + 0, + FacebookServerSideEvent::get_instance()->get_tracked_events() + ); } + /** + * Tests the injectLeadEvent method when the form submission + * is complete and the user is not an internal user. + * + * This test verifies that the Pixel code is appended to the HTML output + * and that the server-side event is tracked with the correct parameters. + */ public function testSendLeadEventViaServerAPISuccessWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); - $mock_out = array('status' => 'complete', 'html' => 'successful submitted'); - $mock_form = self::createMockForm(); + $mock_out = array( + 'status' => 'complete', + 'html' => 'successful submitted', + ); + $mock_form = self::createMockForm(); $_SERVER['HTTP_REFERER'] = 'TEST_REFERER'; - $out = FacebookWordpressCalderaForm::injectLeadEvent($mock_out, $mock_form); - - $this->assertArrayHasKey('html', $out); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + $out = FacebookWordpressCalderaForm::injectLeadEvent( + $mock_out, + $mock_form + ); + + $this->assertArrayHasKey( 'html', $out ); $code = $out['html']; $this->assertMatchesRegularExpression( - '/caldera-forms[\s\S]+End Meta Pixel Event Code/', $code); + '/caldera-forms[\s\S]+End Meta Pixel Event Code/', + $code + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('Lead', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('2061234567', $event->getUserData()->getPhone()); - $this->assertEquals('wa', $event->getUserData()->getState()); - $this->assertEquals('caldera-forms', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); - $this->assertEquals('TEST_REFERER', $event->getEventSourceUrl()); + $this->assertEquals( 'Lead', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '2061234567', $event->getUserData()->getPhone() ); + $this->assertEquals( 'wa', $event->getUserData()->getState() ); + $this->assertEquals( + 'caldera-forms', + $event->getCustomData()->getCustomProperty( + 'fb_integration_tracking' + ) + ); + $this->assertEquals( 'TEST_REFERER', $event->getEventSourceUrl() ); } + /** + * Tests the injectLeadEvent method when the form submission + * is not complete and the user is not an internal user. + * + * This test verifies that no Pixel code is appended to the HTML output and + * that no server-side event is tracked when the + * form submission status is not 'complete' + * and the user is not an internal user. + * + * @return void + */ public function testSendLeadEventViaServerAPIFailureWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); - $mock_out = array( + $mock_out = array( 'status' => 'preprocess', - 'html' => 'fail to submit form'); + 'html' => 'fail to submit form', + ); $mock_form = array(); - $out = FacebookWordpressCalderaForm::injectLeadEvent($mock_out, $mock_form); + $out = FacebookWordpressCalderaForm::injectLeadEvent( + $mock_out, + $mock_form + ); - $this->assertArrayHasKey('html', $out); + $this->assertArrayHasKey( 'html', $out ); $code = $out['html']; - $this->assertEquals('fail to submit form', $code); + $this->assertEquals( 'fail to submit form', $code ); - $this->assertCount(0, - FacebookServerSideEvent::getInstance()->getTrackedEvents()); + $this->assertCount( + 0, + FacebookServerSideEvent::get_instance()->get_tracked_events() + ); } + /** + * Tests the injectLeadEvent method when the form + * submission is complete and the user is an internal user. + * + * This test verifies that no Pixel code is appended to the HTML output and + * that no server-side event is tracked when the + * form submission status is 'complete' + * and the user is an internal user. + * + * @return void + */ public function testSendLeadEventViaServerAPIFailureWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); self::mockFacebookWordpressOptions(); - $mock_out = array('status' => 'complete', 'html' => 'successful submitted'); + $mock_out = array( + 'status' => 'complete', + 'html' => 'successful submitted', + ); $mock_form = array(); - $s2s_spy = \Mockery::spy(FacebookServerSideEvent::class); + $s2s_spy = \Mockery::spy( FacebookServerSideEvent::class ); - $out = FacebookWordpressCalderaForm::injectLeadEvent($mock_out, $mock_form); + $out = FacebookWordpressCalderaForm::injectLeadEvent( + $mock_out, + $mock_form + ); - $this->assertArrayHasKey('html', $out); + $this->assertArrayHasKey( 'html', $out ); $code = $out['html']; - $this->assertEquals('successful submitted', $code); + $this->assertEquals( 'successful submitted', $code ); - $this->assertCount(0, - FacebookServerSideEvent::getInstance()->getTrackedEvents()); + $this->assertCount( + 0, + FacebookServerSideEvent::get_instance()->get_tracked_events() + ); } + /** + * Creates a mock form data array with email, first name, + * last name, phone, and state fields populated. + * + * @return array a mock form data array + */ private static function createMockForm() { $email_field = array( - 'ID' => 'fld_1', - 'type' => 'email' + 'ID' => 'fld_1', + 'type' => 'email', ); $first_name_field = array( - 'ID' => 'fld_2', - 'slug' => 'first_name' + 'ID' => 'fld_2', + 'slug' => 'first_name', ); $last_name_field = array( - 'ID' => 'fld_3', - 'slug' => 'last_name' + 'ID' => 'fld_3', + 'slug' => 'last_name', ); $phone = array( - 'ID' => 'fld_4', - 'type' => 'phone' + 'ID' => 'fld_4', + 'type' => 'phone', ); $state_field = array( - 'ID' => 'fld_5', - 'type' => 'states' + 'ID' => 'fld_5', + 'type' => 'states', ); $_POST['fld_1'] = 'pika.chu@s2s.com'; @@ -189,7 +394,13 @@ private static function createMockForm() { $_POST['fld_5'] = 'WA'; return array( - 'fields' => array($email_field, $first_name_field, $last_name_field, - $phone, $state_field)); + 'fields' => array( + $email_field, + $first_name_field, + $last_name_field, + $phone, + $state_field, + ), + ); } } diff --git a/__tests__/integration/FacebookWordpressContactForm7Test.php b/__tests__/integration/FacebookWordpressContactForm7Test.php index f2be84ed..d39b3046 100644 --- a/__tests__/integration/FacebookWordpressContactForm7Test.php +++ b/__tests__/integration/FacebookWordpressContactForm7Test.php @@ -1,16 +1,30 @@ 'mail_sent', - 'message' => 'Thank you for your message' + 'status' => 'mail_sent', + 'message' => 'Thank you for your message', ); - $event = ServerEventFactory::newEvent('Lead'); - FacebookServerSideEvent::getInstance()->track($event); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $event = ServerEventFactory::new_event( 'Lead' ); + FacebookServerSideEvent::get_instance()->track( $event ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); $response = - FacebookWordpressContactForm7::injectLeadEvent($mock_response, null); + FacebookWordpressContactForm7::injectLeadEvent( $mock_response, null ); $this->assertMatchesRegularExpression( '/Lead[\s\S]+contact-form-7/', $response['fb_pxl_code'] ); } - public function testTrackServerEventWithoutInternalUser() - { - self::mockIsInternalUser(false); + /** + * Tests the trackServerEvent method when the user is not an internal user. + * + * This test verifies that the Pixel code is appended to the HTML output + * and that the server-side event is tracked with the correct parameters. + * + * @return void + */ + public function testTrackServerEventWithoutInternalUser() { + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $mock_result = array( - 'status' => 'mail_sent', - 'message' => 'Thank you for your message' + 'status' => 'mail_sent', + 'message' => 'Thank you for your message', ); - $mock_form = $this->createMockForm(); + $mock_form = $this->createMockForm(); $_SERVER['HTTP_REFERER'] = 'TEST_REFERER'; + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + \WP_Mock::expectActionAdded( 'wpcf7_feedback_response', array( 'FacebookPixelPlugin\\Integration\\FacebookWordpressContactForm7', - 'injectLeadEvent' + 'injectLeadEvent', ), 20, 2 ); + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + $result = - FacebookWordpressContactForm7::trackServerEvent($mock_form, $mock_result); + FacebookWordpressContactForm7::trackServerEvent( + $mock_form, + $mock_result + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('Lead', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('12223334444', $event->getUserData()->getPhone()); + $this->assertEquals( 'Lead', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '12223334444', $event->getUserData()->getPhone() ); $this->assertEquals( 'contact-form-7', - $event->getCustomData()->getCustomProperty('fb_integration_tracking') + $event->getCustomData()->getCustomProperty( + 'fb_integration_tracking' + ) ); - $this->assertEquals('TEST_REFERER', $event->getEventSourceUrl()); + $this->assertEquals( 'TEST_REFERER', $event->getEventSourceUrl() ); } - public function testTrackServerEventWithoutFormData() - { - self::mockIsInternalUser(false); + /** + * Tests the trackServerEvent method when + * the user is not an internal user and + * the form data is not available. This test + * verifies that the server-side event + * is tracked with the correct parameters. + * + * @return void + */ + public function testTrackServerEventWithoutFormData() { + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $mock_result = array( - 'status' => 'mail_sent', - 'message' => 'Thank you for your message' + 'status' => 'mail_sent', + 'message' => 'Thank you for your message', ); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + $mock_form = $this->createMockForm(); \WP_Mock::expectActionAdded( 'wpcf7_feedback_response', array( 'FacebookPixelPlugin\\Integration\\FacebookWordpressContactForm7', - 'injectLeadEvent' + 'injectLeadEvent', ), 20, 2 ); + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + $result = FacebookWordpressContactForm7::trackServerEvent( $mock_form, $mock_result ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('Lead', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); + $this->assertEquals( 'Lead', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); } - public function testTrackServerEventErrorReadingData() - { + /** + * Tests the trackServerEvent method when an error occurs while reading + * the form data. + * + * This test verifies that the Pixel code is appended to the HTML output + * and that the server-side event is tracked with the correct parameters. + * + * @return void + */ + public function testTrackServerEventErrorReadingData() { $this->markTestSkipped('Skipping test temporarily while we update error handling.'); - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $mock_result = array( - 'status' => 'mail_sent', - 'message' => 'Thank you for your message' + 'status' => 'mail_sent', + 'message' => 'Thank you for your message', ); $mock_form = $this->createMockForm(); - $mock_form->set_throw(true); + $mock_form->set_throw( false ); \WP_Mock::expectActionAdded( 'wpcf7_feedback_response', array( 'FacebookPixelPlugin\\Integration\\FacebookWordpressContactForm7', - 'injectLeadEvent' + 'injectLeadEvent', ), 20, 2 ); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + $result = - FacebookWordpressContactForm7::trackServerEvent($mock_form, $mock_result); + FacebookWordpressContactForm7::trackServerEvent( + $mock_form, + $mock_result + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('Lead', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); + $this->assertEquals( 'Lead', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); } - public function testInjectLeadEventWithInternalUser() - { - self::mockIsInternalUser(true); + /** + * Tests the injectLeadEvent method when the user is an internal user. + * + * This test verifies that the output HTML does not contain the Pixel code + * when the user is an internal user. + * + * @return void + */ + public function testInjectLeadEventWithInternalUser() { + self::mockIsInternalUser( true ); $mock_response = array( - 'status' => 'mail_sent', - 'message' => 'Thank you for your message' + 'status' => 'mail_sent', + 'message' => 'Thank you for your message', ); $response = - FacebookWordpressContactForm7::injectLeadEvent($mock_response, null); - $this->assertArrayNotHasKey('fb_pxl_code', $response); + FacebookWordpressContactForm7::injectLeadEvent( $mock_response, null ); + $this->assertArrayNotHasKey( 'fb_pxl_code', $response ); } - public function testInjectLeadEventWhenMailFails() - { - self::mockIsInternalUser(false); + /** + * Tests the injectLeadEvent method when the mail fails. + * + * This test verifies that no server-side events are tracked when the mail + * status indicates failure (e.g., validation + * failed, spam, mail failed, etc.). + * It asserts that the tracked events list is + * empty when such statuses occur. + * + * @return void + */ + public function testInjectLeadEventWhenMailFails() { + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); - $bad_statuses = [ + $bad_statuses = array( 'validation_failed', 'acceptance_missing', 'spam', 'aborted', 'mail_failed', - ]; + ); $mock_form = new MockContactForm7(); - $mock_form->set_throw(true); + $mock_form->set_throw( false ); - foreach ($bad_statuses as $status) { + foreach ( $bad_statuses as $status ) { $mock_result = array( - 'status' => $status, - 'message' => 'Error bad status' + 'status' => $status, + 'message' => 'Error bad status', ); - FacebookWordpressContactForm7::trackServerEvent($mock_form, $mock_result); + FacebookWordpressContactForm7::trackServerEvent( + $mock_form, + $mock_result + ); } $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(0, $tracked_events); + $this->assertCount( 0, $tracked_events ); } - private function createMockForm() - { + /** + * Creates a mock form object with some sample form tags. + * + * The sample form tags include email, text, and tel fields, with some + * fictional sample data. This mock form object is used in the tests to + * simulate a form submission. + * + * @return MockContactForm7 A mock form object with sample form tags. + */ + private function createMockForm() { $mock_form = new MockContactForm7(); - $mock_form->add_tag('email', 'your-email', 'pika.chu@s2s.com'); - $mock_form->add_tag('text', 'your-name', 'Pika Chu'); - $mock_form->add_tag('tel', 'your-phone-number', '12223334444'); + $mock_form->add_tag( 'email', 'your-email', 'pika.chu@s2s.com' ); + $mock_form->add_tag( 'text', 'your-name', 'Pika Chu' ); + $mock_form->add_tag( 'tel', 'your-phone-number', '12223334444' ); return $mock_form; } diff --git a/__tests__/integration/FacebookWordpressEasyDigitalDownloadsTest.php b/__tests__/integration/FacebookWordpressEasyDigitalDownloadsTest.php index c5b25516..dd3c7156 100644 --- a/__tests__/integration/FacebookWordpressEasyDigitalDownloadsTest.php +++ b/__tests__/integration/FacebookWordpressEasyDigitalDownloadsTest.php @@ -1,16 +1,31 @@ 'edd_after_checkout_cart' + $event_hook_map = array( + 'injectInitiateCheckoutEvent' => 'edd_after_checkout_cart', ); $mocked_base = \Mockery::mock( - 'alias:FacebookPixelPlugin\Integration\FacebookWordpressIntegrationBase'); - foreach ($eventHookMap as $event => $hook) { - $mocked_base->shouldReceive('addPixelFireForHook') - ->with(array( - 'hook_name' => $hook, - 'classname' => FacebookWordpressEasyDigitalDownloads::class, - 'inject_function' => $event)) - ->once(); + 'alias:FacebookPixelPlugin\Integration\FacebookWordpressIntegrationBase' + ); + foreach ( $event_hook_map as $event => $hook ) { + $mocked_base->shouldReceive( 'add_pixel_fire_for_hook' ) + ->with( + array( + 'hook_name' => $hook, + 'classname' => + FacebookWordpressEasyDigitalDownloads::class, + 'inject_function' => $event, + ) + ) + ->once(); } - FacebookWordpressEasyDigitalDownloads::injectPixelCode(); - } - - public function testInjectAddToCartEventListenerWithoutInternalUser() { - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); - - $download_id = '1234'; - FacebookWordpressEasyDigitalDownloads::injectAddToCartListener( - $download_id - ); - $this->expectOutputRegex( - '/edd-add-to-cart[\s\S]+End Meta Pixel Event Code/'); + FacebookWordpressEasyDigitalDownloads::inject_pixel_code(); } + /** + * Tests that the injectAddToCartEventId method injects the correct + * hidden input field when the user is not an internal user. + * + * This test verifies that the hidden input field is correctly injected + * into the HTML output when the user is not an internal user + * and the injectAddToCartEventId method is called. + */ public function testInjectAddToCartEventIdWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); FacebookWordpressEasyDigitalDownloads::injectAddToCartEventId(); $this->expectOutputRegex( - '/input type="hidden" name="facebook_event_id"/'); + '/input type="hidden" name="facebook_event_id"/' + ); } + /** + * Tests that the injectInitiateCheckoutEvent method correctly injects + * the Pixel code for the 'InitiateCheckout' event when the user is + * not an internal user. + * + * This test verifies that the output contains the expected Meta Pixel + * Event Code for Easy Digital Downloads and that the server-side event + * tracking records the 'InitiateCheckout' event with the correct user + * and custom data attributes. + */ public function testInitiateCheckoutEventWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $this->setupEDDMocks(); + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + FacebookWordpressEasyDigitalDownloads::injectInitiateCheckoutEvent(); $this->expectOutputRegex( - '/easy-digital-downloads[\s\S]+End Meta Pixel Event Code/'); + '/easy-digital-downloads[\s\S]+End Meta Pixel Event Code/' + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('InitiateCheckout', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals('300', $event->getCustomData()->getValue()); - $this->assertEquals('easy-digital-downloads', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); + $this->assertEquals( 'InitiateCheckout', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( '300', $event->getCustomData()->getValue() ); + $this->assertEquals( + 'easy-digital-downloads', + $event->getCustomData()->getCustomProperty( + 'fb_integration_tracking' + ) + ); } + /** + * Tests that the trackPurchaseEvent method correctly injects the Pixel code + * for the 'Purchase' event when the user is not an internal user. + * + * This test verifies that the server-side event tracking records the + * 'Purchase' event with the correct user and custom data attributes. + */ public function testPurchaseEventWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $this->setupEDDMocks(); - $payment = new class { + $payment = new class() { + /** + * Unique ID + * + * @var integer + */ public $ID = 1; }; @@ -110,210 +184,346 @@ public function testPurchaseEventWithoutInternalUser() { 'wp_footer', array( 'FacebookPixelPlugin\\Integration\\FacebookWordpressEasyDigitalDownloads', - 'injectPurchaseEvent' + 'injectPurchaseEvent', ), - 20); + 20 + ); - FacebookWordpressEasyDigitalDownloads::trackPurchaseEvent($payment, null); + FacebookWordpressEasyDigitalDownloads::trackPurchaseEvent( + $payment, + null + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('Purchase', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals(700, $event->getCustomData()->getValue()); - $this->assertEquals('product', $event->getCustomData()->getContentType()); - $this->assertEquals([99, 999], $event->getCustomData()->getContentIds()); - $this->assertEquals('easy-digital-downloads', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); + $this->assertEquals( 'Purchase', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( 700, $event->getCustomData()->getValue() ); + $this->assertEquals( + 'product', + $event->getCustomData()->getContentType() + ); + $this->assertEquals( + array( 99, 999 ), + $event->getCustomData()->getContentIds() + ); + $this->assertEquals( + 'easy-digital-downloads', + $event->getCustomData()->getCustomProperty( + 'fb_integration_tracking' + ) + ); } + /** + * Tests that the injectAddToCartListener method does not inject + * any Pixel code when the user is an internal user. + * + * This test verifies that the output does not contain any Meta Pixel + * Event Code when the injectAddToCartListener method is called by an + * internal user. + */ public function testInjectAddToCartEventListenerWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); $download_id = '1234'; FacebookWordpressEasyDigitalDownloads::injectAddToCartListener( $download_id ); - $this->expectOutputString(""); + $this->expectOutputString( '' ); } + /** + * Tests that the injectAddToCartEventId method does not inject + * any Pixel code when the user is an internal user. + * + * This test verifies that the output does not contain any Meta Pixel + * Event Code when the injectAddToCartEventId method is called by an + * internal user. + */ public function testInjectAddToCartEventIdWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); FacebookWordpressEasyDigitalDownloads::injectAddToCartEventId(); - $this->expectOutputString(""); + $this->expectOutputString( '' ); } + /** + * Tests that the injectInitiateCheckoutEvent method does not inject + * any Pixel code when the user is an internal user. + * + * This test verifies that the output does not contain any Meta Pixel + * Event Code when the injectInitiateCheckoutEvent method is called by an + * internal user. + */ public function testInjectInitiateCheckoutEventWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); FacebookWordpressEasyDigitalDownloads::injectInitiateCheckoutEvent(); - $this->expectOutputString(""); + $this->expectOutputString( '' ); } + /** + * Tests that the trackPurchaseEvent method does not inject any Pixel code + * when the user is an internal user. + * + * This test verifies that the output does not contain any Meta Pixel + * Event Code when the trackPurchaseEvent method is called by an + * internal user. + */ public function testInjectPurchaseEventWithInternalUser() { - self::mockIsInternalUser(true); - $payment = array('ID' => '1234'); - FacebookWordpressEasyDigitalDownloads::trackPurchaseEvent($payment, null); - $this->expectOutputString(""); + self::mockIsInternalUser( true ); + $payment = array( 'ID' => '1234' ); + FacebookWordpressEasyDigitalDownloads::trackPurchaseEvent( + $payment, + null + ); + $this->expectOutputString( '' ); } + /** + * Tests that the injectViewContentEvent method does not inject + * any Pixel code when the user is an internal user. + * + * This test verifies that the output does not contain any Meta Pixel + * Event Code when the injectViewContentEvent method is called by an + * internal user. + */ public function testInjectViewContentEventWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); $download_id = 1234; - FacebookWordpressEasyDigitalDownloads::injectViewContentEvent($download_id); - $this->expectOutputString(""); + FacebookWordpressEasyDigitalDownloads::injectViewContentEvent( + $download_id + ); + $this->expectOutputString( '' ); } + /** + * Tests that the injectViewContentEvent method + * correctly injects the Pixel code + * for the 'ViewContent' event when the user is not an internal user. + * + * This test verifies that the output contains the expected Meta Pixel + * Event Code for Easy Digital Downloads and that the server-side event + * tracking records the 'ViewContent' event with the correct user and + * custom data attributes, such as content IDs, content type, currency, + * content name, and value. + */ public function testInjectViewContentEventWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $this->setupEDDMocks(); - FacebookWordpressEasyDigitalDownloads::injectViewContentEvent(1234); + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + FacebookWordpressEasyDigitalDownloads::injectViewContentEvent( 1234 ); $this->expectOutputRegex( - '/easy-digital-downloads[\s\S]+End Meta Pixel Event Code/'); + '/easy-digital-downloads[\s\S]+End Meta Pixel Event Code/' + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); - $event = $tracked_events[0]; + $event = $tracked_events[0]; $custom_data = $event->getCustomData(); - $user_data = $event->getUserData(); - - $this->assertEquals('pika.chu@s2s.com', $user_data->getEmail()); - $this->assertEquals('pika', $user_data->getFirstName()); - $this->assertEquals('chu', $user_data->getLastName()); - $this->assertEquals('ViewContent', $event->getEventName()); - $this->assertEquals(['1234'], $custom_data->getContentIds() ); - $this->assertEquals('product', $custom_data->getContentType()); - $this->assertEquals('USD', $custom_data->getCurrency()); - $this->assertEquals('Encarta', $custom_data->getContentName()); - $this->assertEquals( 50, $custom_data->getValue()); - $this->assertNotNull($event->getEventTime()); + $user_data = $event->getUserData(); + + $this->assertEquals( 'pika.chu@s2s.com', $user_data->getEmail() ); + $this->assertEquals( 'pika', $user_data->getFirstName() ); + $this->assertEquals( 'chu', $user_data->getLastName() ); + $this->assertEquals( 'ViewContent', $event->getEventName() ); + $this->assertEquals( array( '1234' ), $custom_data->getContentIds() ); + $this->assertEquals( 'product', $custom_data->getContentType() ); + $this->assertEquals( 'USD', $custom_data->getCurrency() ); + $this->assertEquals( 'Encarta', $custom_data->getContentName() ); + $this->assertEquals( 50, $custom_data->getValue() ); + $this->assertNotNull( $event->getEventTime() ); } + /** + * Tests that the injectAddToCartEventAjax method correctly injects + * the Pixel code for the 'AddToCart' event when the user is not an + * internal user. + * + * This test verifies that the output contains the expected Meta Pixel + * Event Code for Easy Digital Downloads and that the server-side event + * tracking records the 'AddToCart' event with the correct user and + * custom data attributes, such as content IDs, content type, currency, + * content name, and value. + */ public function testInjectAddToCartEventAjax() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $this->setupEDDMocks(); + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + FacebookWordpressEasyDigitalDownloads::injectAddToCartEventAjax(); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); - $event = $tracked_events[0]; + $event = $tracked_events[0]; $custom_data = $event->getCustomData(); - $user_data = $event->getUserData(); - - $this->assertEquals('abc-123', $event->getEventId()); - $this->assertEquals('pika.chu@s2s.com', $user_data->getEmail()); - $this->assertEquals('pika', $user_data->getFirstName()); - $this->assertEquals('chu', $user_data->getLastName()); - $this->assertEquals('AddToCart', $event->getEventName()); - $this->assertEquals(['1234'], $custom_data->getContentIds() ); - $this->assertEquals('product', $custom_data->getContentType()); - $this->assertEquals('USD', $custom_data->getCurrency()); - $this->assertEquals('Encarta', $custom_data->getContentName()); - $this->assertEquals( 50, $custom_data->getValue()); - $this->assertNotNull($event->getEventTime()); + $user_data = $event->getUserData(); + + $this->assertEquals( 'abc-123', $event->getEventId() ); + $this->assertEquals( 'pika.chu@s2s.com', $user_data->getEmail() ); + $this->assertEquals( 'pika', $user_data->getFirstName() ); + $this->assertEquals( 'chu', $user_data->getLastName() ); + $this->assertEquals( 'AddToCart', $event->getEventName() ); + $this->assertEquals( array( '1234' ), $custom_data->getContentIds() ); + $this->assertEquals( 'product', $custom_data->getContentType() ); + $this->assertEquals( 'USD', $custom_data->getCurrency() ); + $this->assertEquals( 'Encarta', $custom_data->getContentName() ); + $this->assertEquals( 50, $custom_data->getValue() ); + $this->assertNotNull( $event->getEventTime() ); } + /** + * Sets up the necessary mocks for Easy Digital Downloads integration tests. + * + * This method mocks various functions and methods + * related to Easy Digital Downloads + * to simulate the environment for testing purposes. + * It configures the expected + * return values for functions like currency retrieval, + * cart total calculation, + * payment metadata, and download object information. It also sets up mock + * POST data and ensures nonce verification functions behave as expected. + */ private function setupEDDMocks() { - \WP_Mock::userFunction('EDD'); + \WP_Mock::userFunction( 'EDD' ); $mock_edd_utils = \Mockery::mock( - 'alias:FacebookPixelPlugin\Integration\EDDUtils'); - $mock_edd_utils->shouldReceive('getCurrency')->andReturn('USD'); - $mock_edd_utils->shouldReceive('getCartTotal')->andReturn(300); + 'alias:FacebookPixelPlugin\Integration\EDDUtils' + ); + $mock_edd_utils->shouldReceive( 'get_currency' )->andReturn( 'USD' ); + $mock_edd_utils->shouldReceive( 'get_cart_total' )->andReturn( 300 ); - $this->mocked_fbpixel->shouldReceive('getLoggedInUserInfo') - ->andReturn(array( - 'email' => 'pika.chu@s2s.com', + $this->mocked_fbpixel->shouldReceive( 'get_logged_in_user_info' ) + ->andReturn( + array( + 'email' => 'pika.chu@s2s.com', 'first_name' => 'Pika', - 'last_name' => 'Chu' + 'last_name' => 'Chu', ) ); - \WP_Mock::userFunction('edd_get_payment_meta', array( - 'args' => 1, - 'return' => array( - 'email' => 'pika.chu@s2s.com', - 'user_info' => array( - 'first_name' => 'Pika', - 'last_name' => 'Chu' - ), - 'cart_details' => array( - array( - 'id' => 99, - 'price' => 300 + \WP_Mock::userFunction( + 'edd_get_payment_meta', + array( + 'args' => 1, + 'return' => array( + 'email' => 'pika.chu@s2s.com', + 'user_info' => array( + 'first_name' => 'Pika', + 'last_name' => 'Chu', ), - array( - 'id' => 999, - 'price' => 400 - ) + 'cart_details' => array( + array( + 'id' => 99, + 'price' => 300, + ), + array( + 'id' => 999, + 'price' => 400, + ), + ), + 'currency' => 'USD', ), - 'currency' => 'USD' ) - )); + ); - \WP_Mock::userFunction('get_post_meta', array( - 'args' => array( + \WP_Mock::userFunction( + 'get_post_meta', + array( + 'args' => array( 1234, \WP_Mock\Functions::type( 'string' ), - true + true, ), 'return' => array( array( - 'amount' => 50 - ) - ) + 'amount' => 50, + ), + ), ) ); - $download_object = new \stdClass; + $download_object = new \stdClass(); $download_object->post_title = 'Encarta'; - \WP_Mock::userFunction('edd_get_download', array( - 'args' => array(\WP_Mock\Functions::type( 'int' )), - 'return' => $download_object + \WP_Mock::userFunction( + 'edd_get_download', + array( + 'args' => array( \WP_Mock\Functions::type( 'int' ) ), + 'return' => $download_object, ) ); - $_POST['nonce'] = '54321'; + $_POST['nonce'] = '54321'; $_POST['download_id'] = '1234'; - $_POST['post_data'] = 'facebook_event_id=abc-123'; + $_POST['post_data'] = 'facebook_event_id=abc-123'; - \WP_Mock::userFunction('absint', array( - 'args' => array(\WP_Mock\Functions::type( 'string' )), - 'return' => 1234 + \WP_Mock::userFunction( + 'absint', + array( + 'args' => array( \WP_Mock\Functions::type( 'string' ) ), + 'return' => 1234, ) ); - \WP_Mock::userFunction('sanitize_text_field', array( - 'args' => array(\WP_Mock\Functions::type( 'string' )), - 'return' => '54321' + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \WP_Mock\Functions::type( 'string' ) ), + 'return' => '54321', ) ); - \WP_Mock::userFunction('wp_verify_nonce', array( - 'args' => array(\WP_Mock\Functions::type( 'string' ), - \WP_Mock\Functions::type( 'string' )), - 'return' => true + \WP_Mock::userFunction( + 'wp_verify_nonce', + array( + 'args' => array( + \WP_Mock\Functions::type( 'string' ), + \WP_Mock\Functions::type( 'string' ), + ), + 'return' => true, ) ); - } } diff --git a/__tests__/integration/FacebookWordpressFormidableFormTest.php b/__tests__/integration/FacebookWordpressFormidableFormTest.php index 7e43e119..c74a8d36 100644 --- a/__tests__/integration/FacebookWordpressFormidableFormTest.php +++ b/__tests__/integration/FacebookWordpressFormidableFormTest.php @@ -1,5 +1,5 @@ array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - FacebookWordpressFormidableForm::injectPixelCode(); - } + $event = ServerEventFactory::new_event('Lead'); - public function testInjectLeadEventWithoutInternalUser() { - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); - $event = ServerEventFactory::newEvent('Lead'); - FacebookServerSideEvent::getInstance()->track($event); + FacebookServerSideEvent::get_instance()->track($event); FacebookWordpressFormidableForm::injectLeadEvent(); @@ -73,6 +81,16 @@ public function testTrackEventWithoutInternalUser() { self::mockIsInternalUser(false); self::mockFacebookWordpressOptions(); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + $mock_entry_id = 1; $mock_form_id = 1; @@ -92,7 +110,7 @@ public function testTrackEventWithoutInternalUser() { $mock_entry_id, $mock_form_id); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); $this->assertCount(1, $tracked_events); @@ -144,7 +162,7 @@ private static function setupErrorForm($entry_id) { $mock_utils = \Mockery::mock( 'alias:FacebookPixelPlugin\Integration\IntegrationUtils'); - $mock_utils->shouldReceive('getFormidableFormsEntryValues')->with($entry_id)->andReturn($entry_values); + $mock_utils->shouldReceive('get_formidable_forms_entry_values')->with($entry_id)->andReturn($entry_values); } private static function setupMockFormidableForm($entry_id) { @@ -184,6 +202,6 @@ private static function setupMockFormidableForm($entry_id) { $mock_utils = \Mockery::mock( 'alias:FacebookPixelPlugin\Integration\IntegrationUtils'); - $mock_utils->shouldReceive('getFormidableFormsEntryValues')->with($entry_id)->andReturn($entry_values); + $mock_utils->shouldReceive('get_formidable_forms_entry_values')->with($entry_id)->andReturn($entry_values); } } diff --git a/__tests__/integration/FacebookWordpressMailchimpForWpTest.php b/__tests__/integration/FacebookWordpressMailchimpForWpTest.php index 4da19123..fa6814b7 100644 --- a/__tests__/integration/FacebookWordpressMailchimpForWpTest.php +++ b/__tests__/integration/FacebookWordpressMailchimpForWpTest.php @@ -1,16 +1,30 @@ shouldReceive('addPixelFireForHook') - ->with(array( - 'hook_name' => 'mc4wp_form_subscribed', - 'classname' => FacebookWordpressMailchimpForWp::class, - 'inject_function' => 'injectLeadEvent')) - ->once(); - FacebookWordpressMailchimpForWp::injectPixelCode(); + 'alias:FacebookPixelPlugin\Integration\FacebookWordpressIntegrationBase' + ); + $mocked_base->shouldReceive( 'add_pixel_fire_for_hook' ) + ->with( + array( + 'hook_name' => 'mc4wp_form_subscribed', + 'classname' => FacebookWordpressMailchimpForWp::class, + 'inject_function' => 'injectLeadEvent', + ) + ) + ->once(); + FacebookWordpressMailchimpForWp::inject_pixel_code(); } + /** + * Tests the injectLeadEvent method when the user is not an internal user. + * + * This test verifies that the Pixel code is correctly + * appended to the HTML output + * and that the server-side event is tracked with the correct parameters. + * It ensures that the 'Lead' event is recorded with the expected user data + * and custom properties when the MailChimp for WP integration is triggered. + * + * @return void + */ public function testInjectLeadEventWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); - $_POST['EMAIL'] = 'pika.chu@s2s.com'; - $_POST['FNAME'] = 'Pika'; - $_POST['LNAME'] = 'Chu'; - $_POST['PHONE'] = '123456'; - $_POST['ADDRESS'] = array( - 'city' => 'Springfield', - 'state' => 'Ohio', - 'zip' => '54321', - 'country' => 'US' + $_POST['EMAIL'] = 'pika.chu@s2s.com'; + $_POST['FNAME'] = 'Pika'; + $_POST['LNAME'] = 'Chu'; + $_POST['PHONE'] = '123456'; + $_POST['ADDRESS'] = array( + 'city' => 'Springfield', + 'state' => 'Ohio', + 'zip' => '54321', + 'country' => 'US', ); $_SERVER['HTTP_REFERER'] = 'TEST_REFERER'; + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'sanitize_email', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_unslash', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + FacebookWordpressMailchimpForWp::injectLeadEvent(); $this->expectOutputRegex( - '/mailchimp-for-wp[\s\S]+End Meta Pixel Event Code/'); + '/mailchimp-for-wp[\s\S]+End Meta Pixel Event Code/' + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('Lead', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('123456', $event->getUserData()->getPhone()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('ohio', $event->getUserData()->getState()); - $this->assertEquals('54321', $event->getUserData()->getZipCode()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('mailchimp-for-wp', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); - $this->assertEquals('TEST_REFERER', $event->getEventSourceUrl()); + $this->assertEquals( 'Lead', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '123456', $event->getUserData()->getPhone() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'ohio', $event->getUserData()->getState() ); + $this->assertEquals( '54321', $event->getUserData()->getZipCode() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( + 'mailchimp-for-wp', + $event->getCustomData() + ->getCustomProperty( 'fb_integration_tracking' ) + ); + $this->assertEquals( 'TEST_REFERER', $event->getEventSourceUrl() ); } + /** + * Tests the injectLeadEvent method when the user is an internal user. + * + * This test verifies that no Pixel code is appended to the HTML output + * when the user is an internal user. It + * asserts that the output HTML remains + * unchanged and that no events are tracked. + * + * @return void + */ public function testInjectLeadEventWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); FacebookWordpressMailchimpForWp::injectLeadEvent(); - $this->expectOutputString(""); + $this->expectOutputString( '' ); } } diff --git a/__tests__/integration/FacebookWordpressNinjaFormsTest.php b/__tests__/integration/FacebookWordpressNinjaFormsTest.php index b62d04f9..aeba22fa 100644 --- a/__tests__/integration/FacebookWordpressNinjaFormsTest.php +++ b/__tests__/integration/FacebookWordpressNinjaFormsTest.php @@ -1,16 +1,30 @@ assertHooksAdded(); } + /** + * Tests the injectLeadEvent method when the user is not an internal user. + * + * This test verifies that the Pixel code is appended to the HTML output + * and that the server-side event is tracked with the correct parameters. + * It ensures that the 'Lead' event is recorded with the expected user data, + * including email, first name, last name, phone number, + * city, state, country, + * zip code, and gender when the Ninja Forms integration is triggered. + * It also checks that the event source URL is correctly set. + * + * @return void + */ public function testInjectLeadEventWithoutInternalUser() { - parent::mockIsInternalUser(false); + parent::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $mock_actions = array( array( - 'id' => 1, + 'id' => 1, 'settings' => array( - 'type' => 'successmessage', + 'type' => 'successmessage', 'success_msg' => 'successful', ), ), ); - $mock_form_data = $this->getMockFormData(); + $mock_form_data = $this->getMockFormData(); $_SERVER['HTTP_REFERER'] = 'TEST_REFERER'; + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + $result = FacebookWordpressNinjaForms::injectLeadEvent( $mock_actions, null, $mock_form_data ); - $this->assertNotEmpty($result); - $this->assertArrayHasKey('settings', $result[0]); - $this->assertArrayHasKey('success_msg', $result[0]['settings']); + $this->assertNotEmpty( $result ); + $this->assertArrayHasKey( 'settings', $result[0] ); + $this->assertArrayHasKey( 'success_msg', $result[0]['settings'] ); $msg = $result[0]['settings']['success_msg']; $this->assertMatchesRegularExpression( - '/ninja-forms[\s\S]+End Meta Pixel Event Code/', $msg); + '/ninja-forms[\s\S]+End Meta Pixel Event Code/', + $msg + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('Lead', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('12345', $event->getUserData()->getPhone()); - $this->assertEquals('oh', $event->getUserData()->getState()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('4321', $event->getUserData()->getZipCode()); - $this->assertEquals('m', $event->getUserData()->getGender()); - $this->assertEquals('ninja-forms', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); - $this->assertEquals('TEST_REFERER', $event->getEventSourceUrl()); + $this->assertEquals( 'Lead', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '12345', $event->getUserData()->getPhone() ); + $this->assertEquals( 'oh', $event->getUserData()->getState() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( '4321', $event->getUserData()->getZipCode() ); + $this->assertEquals( 'm', $event->getUserData()->getGender() ); + $this->assertEquals( + 'ninja-forms', + $event->getCustomData()->getCustomProperty( + 'fb_integration_tracking' + ) + ); + $this->assertEquals( 'TEST_REFERER', $event->getEventSourceUrl() ); } + /** + * Tests the injectLeadEvent method when the user is an internal user. + * + * This test verifies that the output HTML remains + * unchanged and that no events are tracked. + * + * @return void + */ public function testInjectLeadEventWithInternalUser() { - parent::mockIsInternalUser(true); + parent::mockIsInternalUser( true ); $result = FacebookWordpressNinjaForms::injectLeadEvent( 'mock_actions', @@ -99,20 +174,57 @@ public function testInjectLeadEventWithInternalUser() { 'mock_form_data' ); - $this->assertEquals('mock_actions', $result); + $this->assertEquals( 'mock_actions', $result ); } + /** + * Creates a mock form data object with some sample form tags. + * + * @return array + */ private function getMockFormData() { - $email = array('key' => 'email', 'value' => 'pika.chu@s2s.com'); - $name = array('key' => 'name', 'value' => 'Pika Chu'); - $phone = array('key' => 'phone', 'value' => '12345'); - $city = array('key' => 'city', 'value' => 'Springfield'); - $state = array('key' => 'liststate', 'value' => 'OH'); - $country = array('key' => 'listcountry', 'value' => 'US'); - $zip = array('key' => 'zip', 'value' => '4321'); - $gender = array('key' => 'gender', 'value' => 'M'); - $fields = array($email, $name, $phone, $city, - $state, $country, $zip, $gender); - return array('fields' => $fields); + $email = array( + 'key' => 'email', + 'value' => 'pika.chu@s2s.com', + ); + $name = array( + 'key' => 'name', + 'value' => 'Pika Chu', + ); + $phone = array( + 'key' => 'phone', + 'value' => '12345', + ); + $city = array( + 'key' => 'city', + 'value' => 'Springfield', + ); + $state = array( + 'key' => 'liststate', + 'value' => 'OH', + ); + $country = array( + 'key' => 'listcountry', + 'value' => 'US', + ); + $zip = array( + 'key' => 'zip', + 'value' => '4321', + ); + $gender = array( + 'key' => 'gender', + 'value' => 'M', + ); + $fields = array( + $email, + $name, + $phone, + $city, + $state, + $country, + $zip, + $gender, + ); + return array( 'fields' => $fields ); } } diff --git a/__tests__/integration/FacebookWordpressWPECommerceTest.php b/__tests__/integration/FacebookWordpressWPECommerceTest.php index 7b830d59..fe92181b 100644 --- a/__tests__/integration/FacebookWordpressWPECommerceTest.php +++ b/__tests__/integration/FacebookWordpressWPECommerceTest.php @@ -1,16 +1,30 @@ shouldReceive('addPixelFireForHook') - ->with(array( - 'hook_name' => 'wpsc_before_shopping_cart_page', - 'classname' => FacebookWordpressWPECommerce::class, - 'inject_function' => 'injectInitiateCheckoutEvent')) - ->once(); - // Purchase - \WP_Mock::expectActionAdded('wpsc_transaction_results_shutdown', - array(FacebookWordpressWPECommerce::class, 'injectPurchaseEvent'), 11, 3); - - FacebookWordpressWPECommerce::injectPixelCode(); + \WP_Mock::expectActionAdded( + 'wpsc_add_to_cart_json_response', + array( + FacebookWordpressWPECommerce::class, + 'injectAddToCartEvent', + ), + 11 + ); + + $mocked_base = + \Mockery::mock( + 'alias:FacebookPixelPlugin\Integration\FacebookWordpressIntegrationBase' + ); + $mocked_base->shouldReceive( 'add_pixel_fire_for_hook' ) + ->with( + array( + 'hook_name' => 'wpsc_before_shopping_cart_page', + 'classname' => FacebookWordpressWPECommerce::class, + 'inject_function' => 'injectInitiateCheckoutEvent', + ) + ) + ->once(); + \WP_Mock::expectActionAdded( + 'wpsc_transaction_results_shutdown', + array( FacebookWordpressWPECommerce::class, 'injectPurchaseEvent' ), + 11, + 3 + ); + + FacebookWordpressWPECommerce::inject_pixel_code(); $this->assertHooksAdded(); } + /** + * Tests that the injectAddToCartEvent method injects the correct + * Pixel code when the user is not an internal user. + * + * This test verifies that the output contains the expected Meta Pixel + * Event Code for WP eCommerce and that the server-side event tracking + * records the 'AddToCart' event with the correct user and custom data + * attributes. + */ public function testInjectAddToCartEventWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); - $parameter = array('product_id' => 1, 'widget_output' => ''); + $parameter = array( + 'product_id' => 1, + 'widget_output' => '', + ); $this->setupMocks(); - $response = FacebookWordpressWPECommerce::injectAddToCartEvent($parameter); - - $this->assertArrayHasKey('widget_output', $response); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + $response = + FacebookWordpressWPECommerce::injectAddToCartEvent( $parameter ); + + $this->assertArrayHasKey( 'widget_output', $response ); $code = $response['widget_output']; $this->assertMatchesRegularExpression( - '/wp-e-commerce[\s\S]+End Meta Pixel Event Code/', $code); + '/wp-e-commerce[\s\S]+End Meta Pixel Event Code/', + $code + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('AddToCart', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals(999, $event->getCustomData()->getValue()); - $this->assertEquals('product', $event->getCustomData()->getContentType()); - $this->assertEquals([1], $event->getCustomData()->getContentIds()); - $this->assertEquals('wp-e-commerce', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); + $this->assertEquals( 'AddToCart', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( 999, $event->getCustomData()->getValue() ); + $this->assertEquals( + 'product', + $event->getCustomData()->getContentType() + ); + $this->assertEquals( + array( 1 ), + $event->getCustomData()->getContentIds() + ); + $this->assertEquals( + 'wp-e-commerce', + $event->getCustomData() + ->getCustomProperty( 'fb_integration_tracking' ) + ); } + /** + * Tests that the injectAddToCartEvent method does not inject any Pixel code + * when the user is an internal user. + * + * This test verifies that the output does not contain any Meta Pixel + * Event Code when the injectAddToCartEvent method is called by an + * internal user. + */ public function testInjectAddToCartEventWithInternalUser() { - self::mockIsInternalUser(true); - $parameter = array('product_id' => 1, 'widget_output' => ''); + self::mockIsInternalUser( true ); + $parameter = array( + 'product_id' => 1, + 'widget_output' => '', + ); - $response = FacebookWordpressWPECommerce::injectAddToCartEvent($parameter); + $response = + FacebookWordpressWPECommerce::injectAddToCartEvent( $parameter ); - $this->assertArrayHasKey('widget_output', $response); + $this->assertArrayHasKey( 'widget_output', $response ); $code = $response['widget_output']; - $this->assertEquals("", $code); + $this->assertEquals( '', $code ); } + /** + * Tests that the injectInitiateCheckoutEvent + * method correctly injects the Pixel code + * for the 'InitiateCheckout' event when the user is not an internal user. + * + * This test verifies that the output contains + * the expected Meta Pixel Event Code for + * WP e-Commerce and that the server-side event + * tracking records the 'InitiateCheckout' + * event with the correct user and custom data attributes. + */ public function testInitiateCheckoutEventWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $this->setupMocks(); + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + FacebookWordpressWPECommerce::injectInitiateCheckoutEvent(); $this->expectOutputRegex( - '/wp-e-commerce[\s\S]+End Meta Pixel Event Code/'); + '/wp-e-commerce[\s\S]+End Meta Pixel Event Code/' + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('InitiateCheckout', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals(999, $event->getCustomData()->getValue()); - $this->assertEquals('wp-e-commerce', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); + $this->assertEquals( 'InitiateCheckout', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( 999, $event->getCustomData()->getValue() ); + $this->assertEquals( + 'wp-e-commerce', + $event->getCustomData() + ->getCustomProperty( 'fb_integration_tracking' ) + ); } + /** + * Tests that the injectInitiateCheckoutEvent method does not inject + * any Pixel code when the user is an internal user. + * + * This test verifies that the output does not contain any Meta Pixel + * Event Code when the injectInitiateCheckoutEvent method is called by an + * internal user. + */ public function testInitiateCheckoutEventWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); FacebookWordpressWPECommerce::injectInitiateCheckoutEvent(); - $this->expectOutputString(""); + $this->expectOutputString( '' ); } + /** + * Tests that the injectPurchaseEvent method + * correctly injects the Pixel code + * for the 'Purchase' event when the user is not an internal user. + * + * This test verifies that the output contains + * the expected Meta Pixel Event Code + * for WP e-Commerce and that the server-side + * event tracking records the 'Purchase' + * event with the correct user and custom data attributes. + */ public function testInjectPurchaseEventWithoutInternalUser() { - self::mockIsInternalUser(false); + self::mockIsInternalUser( false ); self::mockFacebookWordpressOptions(); $this->setupMocks(); $mock_purchase_log_object = \Mockery::mock(); - $purchase_log_object = $mock_purchase_log_object; - $session_id = null; - $display_to_screen = true; - - $mock_purchase_log_object->shouldReceive('get_items') - ->andReturn(array(0 => (object) array('prodid' => "1"))); - $mock_purchase_log_object->shouldReceive('get_total') - ->andReturn(999); + $purchase_log_object = $mock_purchase_log_object; + $session_id = null; + $display_to_screen = true; + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + $mock_purchase_log_object->shouldReceive( 'get_items' ) + ->andReturn( array( 0 => (object) array( 'prodid' => '1' ) ) ); + $mock_purchase_log_object->shouldReceive( 'get_total' ) + ->andReturn( 999 ); FacebookWordpressWPECommerce::injectPurchaseEvent( $purchase_log_object, $session_id, - $display_to_screen); + $display_to_screen + ); $this->expectOutputRegex( - '/wp-e-commerce[\s\S]+End Meta Pixel Event Code/'); + '/wp-e-commerce[\s\S]+End Meta Pixel Event Code/' + ); $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); $event = $tracked_events[0]; - $this->assertEquals('Purchase', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals(999, $event->getCustomData()->getValue()); - $this->assertEquals('wp-e-commerce', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); + $this->assertEquals( 'Purchase', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( 999, $event->getCustomData()->getValue() ); + $this->assertEquals( + 'wp-e-commerce', + $event->getCustomData() + ->getCustomProperty( 'fb_integration_tracking' ) + ); } + /** + * Tests that the injectPurchaseEvent method does not inject any Pixel code + * when the user is an internal user. + * + * This test verifies that the output does not contain any Meta Pixel + * Event Code when the injectPurchaseEvent method is called by an + * internal user, even if the display_to_screen flag is true. + */ public function testInjectPurchaseEventWithInternalUser() { - self::mockIsInternalUser(true); + self::mockIsInternalUser( true ); $mock_purchase_log_object = \Mockery::mock(); - $purchase_log_object = $mock_purchase_log_object; - $session_id = null; - $display_to_screen = true; + $purchase_log_object = $mock_purchase_log_object; + $session_id = null; + $display_to_screen = true; - $mock_purchase_log_object->shouldReceive('get_items') - ->andReturn(array(0 => (object) array('prodid' => "1"))); - $mock_purchase_log_object->shouldReceive('get_total') - ->andReturn(999); + $mock_purchase_log_object->shouldReceive( 'get_items' ) + ->andReturn( array( 0 => (object) array( 'prodid' => '1' ) ) ); + $mock_purchase_log_object->shouldReceive( 'get_total' ) + ->andReturn( 999 ); FacebookWordpressWPECommerce::injectPurchaseEvent( - $purchase_log_object, $session_id, $display_to_screen); - $this->expectOutputString(""); + $purchase_log_object, + $session_id, + $display_to_screen + ); + $this->expectOutputString( '' ); } + /** + * Sets up various mock objects and functions for + * the WP e-Commerce integration tests. + * + * This method uses Mockery to create a mock cart and + * define the behavior of the + * get_items method to return a predefined set of cart + * items. It also sets up a global + * variable for the cart and mocks the getLoggedInUserInfo + * method to return specific + * user details. Additionally, it mocks the + * wpsc_get_currency_code function to return + * a fixed currency code. These mocks are used to + * simulate the environment and behavior + * required for testing the Facebook Pixel integration with WP e-Commerce. + * + * @return void + */ private function setupMocks() { $mock_cart = \Mockery::mock(); - $mock_cart->shouldReceive('get_items') - ->andReturn( - array('1' => (object) array('product_id' => 1, 'unit_price' => 999))); + $mock_cart->shouldReceive( 'get_items' ) + ->andReturn( + array( + '1' => (object) array( + 'product_id' => 1, + 'unit_price' => 999, + ), + ) + ); $GLOBALS['wpsc_cart'] = $mock_cart; - $this->mocked_fbpixel->shouldReceive('getLoggedInUserInfo') - ->andReturn(array( - 'email' => 'pika.chu@s2s.com', + $this->mocked_fbpixel->shouldReceive( 'get_logged_in_user_info' ) + ->andReturn( + array( + 'email' => 'pika.chu@s2s.com', 'first_name' => 'Pika', - 'last_name' => 'Chu' + 'last_name' => 'Chu', ) ); - \WP_Mock::userFunction('wpsc_get_currency_code', array( - 'return' => 'USD') + \WP_Mock::userFunction( + 'wpsc_get_currency_code', + array( + 'return' => 'USD', + ) ); } } diff --git a/__tests__/integration/FacebookWordpressWPFormsTest.php b/__tests__/integration/FacebookWordpressWPFormsTest.php index 3a66f2a5..dfa45265 100644 --- a/__tests__/integration/FacebookWordpressWPFormsTest.php +++ b/__tests__/integration/FacebookWordpressWPFormsTest.php @@ -1,16 +1,30 @@ assertHooksAdded(); - } + FacebookWordpressWPForms::inject_pixel_code(); + $this->assertHooksAdded(); + } + + /** + * Tests the injectLeadEvent method when the user is not an internal user. + * + * This test verifies that the Pixel code is correctly + * appended to the HTML output + * when the user is not an internal user. It ensures that the output matches + * the expected pattern for the "wpforms-lite" event. + * + * @return void + */ + public function testInjectLeadEventWithoutInternalUser() { + parent::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); - public function testInjectLeadEventWithoutInternalUser() { - parent::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - $event = ServerEventFactory::newEvent('Lead'); - FacebookServerSideEvent::getInstance()->track($event); + $event = ServerEventFactory::new_event( 'Lead' ); + FacebookServerSideEvent::get_instance()->track( $event ); - FacebookWordpressWPForms::injectLeadEvent(); + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + FacebookWordpressWPForms::injectLeadEvent(); $this->expectOutputRegex( - '/wpforms-lite[\s\S]+End Meta Pixel Event Code/'); - } + '/wpforms-lite[\s\S]+End Meta Pixel Event Code/' + ); + } - public function testInjectLeadEventWithInternalUser() { - parent::mockIsInternalUser(true); - self::mockFacebookWordpressOptions(); + /** + * Tests the injectLeadEvent method when the user is an internal user. + * + * This test verifies that no Pixel code is appended to the HTML output + * when the user is an internal user. It + * asserts that the output HTML remains + * unchanged and that no events are tracked. + * + * @return void + */ + public function testInjectLeadEventWithInternalUser() { + parent::mockIsInternalUser( true ); + self::mockFacebookWordpressOptions(); - FacebookWordpressWPForms::injectLeadEvent('mock_form_data'); - $this->expectOutputString(""); - } + FacebookWordpressWPForms::injectLeadEvent( 'mock_form_data' ); + $this->expectOutputString( '' ); + } - public function testTrackEventWithoutInternalUser() { - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); + /** + * Tests the trackEvent method for a non-internal user. + * + * This test verifies that the Pixel code is correctly + * injected into the HTML + * output and that the server-side event is tracked + * with the correct parameters + * when the user is not an internal user. It asserts + * that the output HTML matches + * the expected pattern for the "wpforms-lite" event. + * + * @return void + */ + public function testTrackEventWithoutInternalUser() { + self::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); - $mock_entry = $this->createMockEntry(); - $mock_form_data = $this->createMockFormData(); + $mock_entry = $this->createMockEntry(); + $mock_form_data = $this->createMockFormData(); + + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); \WP_Mock::expectActionAdded( - 'wp_footer', - array(FacebookWordpressWPForms::class, - 'injectLeadEvent' - ), - 20 + 'wp_footer', + array( + FacebookWordpressWPForms::class, + 'injectLeadEvent', + ), + 20 ); FacebookWordpressWPForms::trackEvent( - $mock_entry, $mock_form_data); - - $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); - - $this->assertCount(1, $tracked_events); - - $event = $tracked_events[0]; - $this->assertEquals('Lead', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('1234567', $event->getUserData()->getPhone()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('ohio', $event->getUserData()->getState()); - $this->assertEquals('45401', $event->getUserData()->getZipCode()); - $this->assertEquals('wpforms-lite', - $event->getCustomData()->getCustomProperty('fb_integration_tracking')); - } - - public function testTrackEventWithoutInternalUserSimpleFormat() { - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); - - $mock_entry = $this->createMockEntry(true); - $mock_form_data = $this->createMockFormData(true); - $_SERVER['HTTP_REFERER'] = 'TEST_REFERER'; + $mock_entry, + $mock_form_data + ); + + $tracked_events = + FacebookServerSideEvent::get_instance()->get_tracked_events(); + + $this->assertCount( 1, $tracked_events ); + + $event = $tracked_events[0]; + $this->assertEquals( 'Lead', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '1234567', $event->getUserData()->getPhone() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'ohio', $event->getUserData()->getState() ); + $this->assertEquals( '45401', $event->getUserData()->getZipCode() ); + $this->assertEquals( + 'wpforms-lite', + $event->getCustomData()->getCustomProperty( 'fb_integration_tracking' ) + ); + } + + /** + * Tests the trackEvent method when the user is not an internal user, + * and the form data is provided in the simple format. + * + * This test verifies that the server-side event is tracked with the correct + * parameters when the user is not an internal user and the form data is + * provided in the simple format. It + * ensures that the output HTML matches the + * expected pattern for the "wpforms-lite" event. + * + * @return void + */ + public function testTrackEventWithoutInternalUserSimpleFormat() { + self::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); + + $mock_entry = $this->createMockEntry( true ); + $mock_form_data = $this->createMockFormData( true ); + $_SERVER['HTTP_REFERER'] = 'TEST_REFERER'; \WP_Mock::expectActionAdded( - 'wp_footer', - array(FacebookWordpressWPForms::class, - 'injectLeadEvent' - ), - 20 + 'wp_footer', + array( + FacebookWordpressWPForms::class, + 'injectLeadEvent', + ), + 20 ); - FacebookWordpressWPForms::trackEvent( - $mock_entry, $mock_form_data); - - $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); - - $this->assertCount(1, $tracked_events); - - $event = $tracked_events[0]; - $this->assertEquals('Lead', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('1234567', $event->getUserData()->getPhone()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('ohio', $event->getUserData()->getState()); - $this->assertEquals('45401', $event->getUserData()->getZipCode()); - $this->assertEquals('TEST_REFERER', $event->getEventSourceUrl()); - } - - private function createMockEntry($simple_format = false) { - return array( - 'fields' => array( - '0' => $simple_format - ? 'Pika Chu' - : array('first' => 'Pika', 'last' => 'Chu'), - '1' => 'pika.chu@s2s.com', - '2' => '1234567', - '3' => array( - 'country' => 'US', - 'postal' => '45401', - 'state' => 'Ohio', - 'city' => 'Springfield' + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, ) - ) ); - } - private function createMockFormData($simple_format = false) { - return array( - 'fields' => array( - array( - 'type' => 'name', - 'id' => '0', - 'format' => $simple_format ? 'simple' : 'first-last'), - array('type' => 'email', 'id' => '1'), - array('type' => 'phone', 'id' => '2'), - array('type' => 'address', 'id' => '3') - ) + FacebookWordpressWPForms::trackEvent( + $mock_entry, + $mock_form_data ); - } + + $tracked_events = + FacebookServerSideEvent::get_instance()->get_tracked_events(); + + $this->assertCount( 1, $tracked_events ); + + $event = $tracked_events[0]; + $this->assertEquals( 'Lead', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '1234567', $event->getUserData()->getPhone() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'ohio', $event->getUserData()->getState() ); + $this->assertEquals( '45401', $event->getUserData()->getZipCode() ); + $this->assertEquals( 'TEST_REFERER', $event->getEventSourceUrl() ); + } + + /** + * Creates a mock entry with predefined field values. + * + * This method creates a mock entry with sample data including email, + * first name, last name, phone, and address fields. It utilizes the + * simple format or the first-last format for the name field, depending on + * the value of the $simple_format parameter. + * + * @param bool $simple_format Whether to use the + * simple format for the name field. + * + * @return array The mock entry with predefined field values. + */ + private function createMockEntry( $simple_format = false ) { + return array( + 'fields' => array( + '0' => $simple_format ? 'Pika Chu' : array( + 'first' => 'Pika', + 'last' => 'Chu', + ), + '1' => 'pika.chu@s2s.com', + '2' => '1234567', + '3' => array( + 'country' => 'US', + 'postal' => '45401', + 'state' => 'Ohio', + 'city' => 'Springfield', + ), + ), + ); + } + + /** + * Creates a mock form data object with predefined field values. + * + * This method creates a mock form data object + * with sample data including email, + * first name, last name, phone, and address fields. It utilizes the + * simple format or the first-last format for the name field, depending on + * the value of the $simple_format parameter. + * + * @param bool $simple_format Whether to use + * the simple format for the name field. + * + * @return array The mock form data object with predefined field values. + */ + private function createMockFormData( $simple_format = false ) { + return array( + 'fields' => array( + array( + 'type' => 'name', + 'id' => '0', + 'format' => $simple_format ? 'simple' : 'first-last', + ), + array( + 'type' => 'email', + 'id' => '1', + ), + array( + 'type' => 'phone', + 'id' => '2', + ), + array( + 'type' => 'address', + 'id' => '3', + ), + ), + ); + } } diff --git a/__tests__/integration/FacebookWordpressWooCommerceTest.php b/__tests__/integration/FacebookWordpressWooCommerceTest.php index 845fc782..69379e64 100644 --- a/__tests__/integration/FacebookWordpressWooCommerceTest.php +++ b/__tests__/integration/FacebookWordpressWooCommerceTest.php @@ -1,16 +1,30 @@ mockFacebookForWooCommerce(false); +final class FacebookWordpressWooCommerceTest extends FacebookWordpressTestBase { + + /** + * Tests the inject_pixel_code method when the + * Facebook for WooCommerce plugin is not active. + * + * This test verifies that the appropriate WordPress action hooks are added, + * specifically checking that the 'woocommerce_after_checkout_form' hook is + * registered with the 'trackInitiateCheckout' method from the + * FacebookWordpressWooCommerce class. + * + * @return void + */ + public function testInjectPixelCodeWithWooNotActive() { + $this->mockFacebookForWooCommerce( false ); \WP_Mock::expectActionAdded( - 'woocommerce_after_checkout_form', - array( - FacebookWordpressWooCommerce::class, - 'trackInitiateCheckout' - ), - 40 + 'woocommerce_after_checkout_form', + array( + FacebookWordpressWooCommerce::class, + 'trackInitiateCheckout', + ), + 40 + ); + + FacebookWordpressWooCommerce::inject_pixel_code(); + } + + /** + * Tests the inject_pixel_code method when the Facebook + * for WooCommerce plugin is active. + * + * This test verifies that the 'woocommerce_after_checkout_form' action hook + * is not added when the plugin is active, ensuring + * that the 'trackInitiateCheckout' + * method from the FacebookWordpressWooCommerce class is not registered. + * + * @return void + */ + public function testInjectPixelCodeWithWooActive() { + $this->mockFacebookForWooCommerce( true ); + + \WP_Mock::expectActionNotAdded( + 'woocommerce_after_checkout_form', + array( + FacebookWordpressWooCommerce::class, + 'trackInitiateCheckout', + ), + 40 ); - FacebookWordpressWooCommerce::injectPixelCode(); - } + FacebookWordpressWooCommerce::inject_pixel_code(); + } - public function testInjectPixelCodeWithWooActive() - { - $this->mockFacebookForWooCommerce(true); + /** + * Tests that the trackPurchaseEvent method correctly + * records a 'Purchase' event + * when the user is not an internal user. + * + * This test verifies that the server-side event tracking records the + * 'Purchase' event with the correct user and custom data attributes. + * + * @return void + */ + public function testPurchaseEventWithoutInternalUser() { + self::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); - \WP_Mock::expectActionNotAdded( - 'woocommerce_after_checkout_form', - array( - FacebookWordpressWooCommerce::class, - 'trackInitiateCheckout' + $this->setupMocks(); + + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), ), - 40 - ); - - FacebookWordpressWooCommerce::injectPixelCode(); - } - - public function testPurchaseEventWithoutInternalUser() - { - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); - - $this->setupMocks(); - - FacebookWordpressWooCommerce::trackPurchaseEvent(1); - $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); - - $this->assertCount(1, $tracked_events); - - $event = $tracked_events[0]; - $this->assertEquals('Purchase', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('2062062006', $event->getUserData()->getPhone()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('ohio', $event->getUserData()->getState()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('12345', $event->getUserData()->getZipCode()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals(900, $event->getCustomData()->getValue()); - $this->assertEquals('wc_post_id_1',$event->getCustomData()->getContentIds()[0]); - - $contents = $event->getCustomData()->getContents(); - $this->assertCount(1, $contents); - $this->assertEquals('wc_post_id_1', $contents[0]->getProductId()); - $this->assertEquals(3, $contents[0]->getQuantity()); - $this->assertEquals(300, $contents[0]->getItemPrice()); + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + FacebookWordpressWooCommerce::trackPurchaseEvent( 1 ); + $tracked_events = + FacebookServerSideEvent::get_instance()->get_tracked_events(); + + $this->assertCount( 1, $tracked_events ); + + $event = $tracked_events[0]; + $this->assertEquals( 'Purchase', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '2062062006', $event->getUserData()->getPhone() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'ohio', $event->getUserData()->getState() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( '12345', $event->getUserData()->getZipCode() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( 900, $event->getCustomData()->getValue() ); + $this->assertEquals( + 'wc_post_id_1', + $event->getCustomData()->getContentIds()[0] + ); + + $contents = $event->getCustomData()->getContents(); + $this->assertCount( 1, $contents ); + $this->assertEquals( 'wc_post_id_1', $contents[0]->getProductId() ); + $this->assertEquals( 3, $contents[0]->getQuantity() ); + $this->assertEquals( 300, $contents[0]->getItemPrice() ); $this->assertEquals( - 'woocommerce', - $event->getCustomData()->getCustomProperty('fb_integration_tracking') - ); - } - - public function testInitiateCheckoutEventWithoutInternalUser() - { - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); - - $this->setupMocks(); - $this->setupCustomerBillingAddress(); - - FacebookWordpressWooCommerce::trackInitiateCheckout(); - $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); - - $this->assertCount(1, $tracked_events); - - $event = $tracked_events[0]; - - $this->assertEquals('InitiateCheckout', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('2062062006', $event->getUserData()->getPhone()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('ohio', $event->getUserData()->getState()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('12345', $event->getUserData()->getZipCode()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals(900, $event->getCustomData()->getValue()); - $this->assertEquals(3, $event->getCustomData()->getNumItems()); + 'woocommerce', + $event->getCustomData()->getCustomProperty( 'fb_integration_tracking' ) + ); + } + + /** + * Tests that the trackInitiateCheckout method correctly records an + * 'InitiateCheckout' event when the user is not an internal user. + * + * This test verifies that the server-side event tracking records the + * 'InitiateCheckout' event with the correct user and custom data + * attributes. + * + * @return void + */ + public function testInitiateCheckoutEventWithoutInternalUser() { + self::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); + + $this->setupMocks(); + $this->setupCustomerBillingAddress(); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); + + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + FacebookWordpressWooCommerce::trackInitiateCheckout(); + $tracked_events = + FacebookServerSideEvent::get_instance()->get_tracked_events(); + + $this->assertCount( 1, $tracked_events ); + + $event = $tracked_events[0]; + + $this->assertEquals( 'InitiateCheckout', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '2062062006', $event->getUserData()->getPhone() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'ohio', $event->getUserData()->getState() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( '12345', $event->getUserData()->getZipCode() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( 900, $event->getCustomData()->getValue() ); + $this->assertEquals( 3, $event->getCustomData()->getNumItems() ); $this->assertEquals( - 'wc_post_id_1', - $event->getCustomData()->getContentIds()[0] + 'wc_post_id_1', + $event->getCustomData()->getContentIds()[0] ); - $contents = $event->getCustomData()->getContents(); - $this->assertCount(1, $contents); - $this->assertEquals('wc_post_id_1', $contents[0]->getProductId()); - $this->assertEquals(3, $contents[0]->getQuantity()); - $this->assertEquals(300, $contents[0]->getItemPrice()); + $contents = $event->getCustomData()->getContents(); + $this->assertCount( 1, $contents ); + $this->assertEquals( 'wc_post_id_1', $contents[0]->getProductId() ); + $this->assertEquals( 3, $contents[0]->getQuantity() ); + $this->assertEquals( 300, $contents[0]->getItemPrice() ); $this->assertEquals( - 'woocommerce', - $event->getCustomData()->getCustomProperty('fb_integration_tracking') + 'woocommerce', + $event->getCustomData()->getCustomProperty( 'fb_integration_tracking' ) + ); + } + + /** + * Tests that the trackAddToCartEvent method correctly records an + * 'AddToCart' event when the user is not an internal user. + * + * This test verifies that the server-side event tracking records the + * 'AddToCart' event with the correct user and custom data attributes. + * + * @return void + */ + public function testAddToCartEventWithoutInternalUser() { + \WP_Mock::userFunction( + 'wp_doing_ajax', + array( 'return' => false ) + ); + + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) ); - } - public function testAddToCartEventWithoutInternalUser() - { \WP_Mock::userFunction( - 'wp_doing_ajax', - array('return' => false) - ); - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); - - $this->setupMocks(); - $this->setupCustomerBillingAddress(); - - FacebookWordpressWooCommerce::trackAddToCartEvent(1, 1, 3, null); - $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); - - $this->assertCount(1, $tracked_events); - - $event = $tracked_events[0]; - - $this->assertEquals('AddToCart', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('2062062006', $event->getUserData()->getPhone()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('ohio', $event->getUserData()->getState()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('12345', $event->getUserData()->getZipCode()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals(900, $event->getCustomData()->getValue()); + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); + + self::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); + + $this->setupMocks(); + $this->setupCustomerBillingAddress(); + + FacebookWordpressWooCommerce::trackAddToCartEvent( 1, 1, 3, null ); + $tracked_events = + FacebookServerSideEvent::get_instance()->get_tracked_events(); + + $this->assertCount( 1, $tracked_events ); + + $event = $tracked_events[0]; + + $this->assertEquals( 'AddToCart', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '2062062006', $event->getUserData()->getPhone() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'ohio', $event->getUserData()->getState() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( '12345', $event->getUserData()->getZipCode() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( 900, $event->getCustomData()->getValue() ); $this->assertEquals( - 'wc_post_id_1', - $event->getCustomData()->getContentIds()[0] + 'wc_post_id_1', + $event->getCustomData()->getContentIds()[0] ); $this->assertEquals( - 'woocommerce', - $event->getCustomData()->getCustomProperty('fb_integration_tracking') + 'woocommerce', + $event->getCustomData()->getCustomProperty( 'fb_integration_tracking' ) + ); + } + + /** + * Tests the trackAddToCartEvent method + * when the user is not an internal user + * and the request is an AJAX request. + * + * This test verifies that the "woocommerce_add_to_cart_fragments" filter is + * added and that the server-side event + * is tracked with the correct parameters + * when the user is not an internal user and the request is an AJAX request. + */ + public function testAddToCartEventAjaxWithoutInternalUser() { + \WP_Mock::userFunction( + 'wp_doing_ajax', + array( 'return' => true ) ); - } - public function testAddToCartEventAjaxWithoutInternalUser() - { \WP_Mock::userFunction( - 'wp_doing_ajax', - array('return' => true) + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) ); - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); - $this->setupMocks(); - $this->setupCustomerBillingAddress(); + self::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); + + $this->setupMocks(); + $this->setupCustomerBillingAddress(); \WP_Mock::expectFilterAdded( - 'woocommerce_add_to_cart_fragments', - array( - FacebookWordpressWooCommerce::class, - 'addPixelCodeToAddToCartFragment' - ) - ); - - FacebookWordpressWooCommerce::trackAddToCartEvent(1, 1, 3, null); - - $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); - - $this->assertCount(1, $tracked_events); - - $event = $tracked_events[0]; - - $this->assertEquals('AddToCart', $event->getEventName()); - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('2062062006', $event->getUserData()->getPhone()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('ohio', $event->getUserData()->getState()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('12345', $event->getUserData()->getZipCode()); - $this->assertEquals('USD', $event->getCustomData()->getCurrency()); - $this->assertEquals(900, $event->getCustomData()->getValue()); + 'woocommerce_add_to_cart_fragments', + array( + FacebookWordpressWooCommerce::class, + 'addPixelCodeToAddToCartFragment', + ) + ); + + FacebookWordpressWooCommerce::trackAddToCartEvent( 1, 1, 3, null ); + + $tracked_events = + FacebookServerSideEvent::get_instance()->get_tracked_events(); + + $this->assertCount( 1, $tracked_events ); + + $event = $tracked_events[0]; + + $this->assertEquals( 'AddToCart', $event->getEventName() ); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '2062062006', $event->getUserData()->getPhone() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'ohio', $event->getUserData()->getState() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( '12345', $event->getUserData()->getZipCode() ); + $this->assertEquals( 'USD', $event->getCustomData()->getCurrency() ); + $this->assertEquals( 900, $event->getCustomData()->getValue() ); $this->assertEquals( - 'wc_post_id_1', - $event->getCustomData()->getContentIds()[0] + 'wc_post_id_1', + $event->getCustomData()->getContentIds()[0] ); $this->assertEquals( - 'woocommerce', - $event->getCustomData()->getCustomProperty('fb_integration_tracking') + 'woocommerce', + $event->getCustomData()->getCustomProperty( 'fb_integration_tracking' ) + ); + } + + /** + * Tests the trackViewContentEvent method when + * the user is not an internal user. + * + * This test verifies that the Pixel code is + * correctly injected into the HTML + * output and that the server-side event is + * tracked with the correct parameters + * when the user is not an internal user. It + * asserts that the output HTML matches + * the expected pattern for the "ViewContent" event. + * + * @return void + */ + public function testViewContentWithoutAdmin() { + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) ); - } - public function testViewContentWithoutAdmin() - { - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); + self::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); + + $this->setupMocks(); + $this->setupCustomerBillingAddress(); - $this->setupMocks(); - $this->setupCustomerBillingAddress(); + $raw_post = new \stdClass(); + $raw_post->ID = 1; + global $post; + $post = $raw_post; + + \WP_Mock::userFunction( + 'sanitize_text_field', + array( + 'args' => array( \Mockery::any() ), + 'return' => function ( $input ) { + return $input; + }, + ) + ); - $raw_post = new \stdClass(); - $raw_post->ID = 1; - global $post; - $post = $raw_post; + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) + ); - FacebookWordpressWooCommerce::trackViewContentEvent(); + FacebookWordpressWooCommerce::trackViewContentEvent(); - $tracked_events = - FacebookServerSideEvent::getInstance()->getTrackedEvents(); + $tracked_events = + FacebookServerSideEvent::get_instance()->get_tracked_events(); - $this->assertCount(1, $tracked_events); + $this->assertCount( 1, $tracked_events ); - $event = $tracked_events[0]; + $event = $tracked_events[0]; - $this->assertNotNull($event->getEventTime()); - $this->assertEquals('pika.chu@s2s.com', $event->getUserData()->getEmail()); - $this->assertEquals('pika', $event->getUserData()->getFirstName()); - $this->assertEquals('chu', $event->getUserData()->getLastName()); - $this->assertEquals('2062062006', $event->getUserData()->getPhone()); - $this->assertEquals('springfield', $event->getUserData()->getCity()); - $this->assertEquals('ohio', $event->getUserData()->getState()); - $this->assertEquals('us', $event->getUserData()->getCountryCode()); - $this->assertEquals('12345', $event->getUserData()->getZipCode()); + $this->assertNotNull( $event->getEventTime() ); + $this->assertEquals( + 'pika.chu@s2s.com', + $event->getUserData()->getEmail() + ); + $this->assertEquals( 'pika', $event->getUserData()->getFirstName() ); + $this->assertEquals( 'chu', $event->getUserData()->getLastName() ); + $this->assertEquals( '2062062006', $event->getUserData()->getPhone() ); + $this->assertEquals( 'springfield', $event->getUserData()->getCity() ); + $this->assertEquals( 'ohio', $event->getUserData()->getState() ); + $this->assertEquals( 'us', $event->getUserData()->getCountryCode() ); + $this->assertEquals( '12345', $event->getUserData()->getZipCode() ); - $this->assertEquals(10, $event->getCustomData()->getValue()); + $this->assertEquals( 10, $event->getCustomData()->getValue() ); $this->assertEquals( - 'wc_post_id_1', - $event->getCustomData()->getContentIds()[0] + 'wc_post_id_1', + $event->getCustomData()->getContentIds()[0] ); $this->assertEquals( - 'Stegosaurus', - $event->getCustomData()->getContentName() + 'Stegosaurus', + $event->getCustomData()->getContentName() ); $this->assertEquals( - 'product', - $event->getCustomData()->getContentType() + 'product', + $event->getCustomData()->getContentType() ); $this->assertEquals( - 'USD', - $event->getCustomData()->getCurrency() + 'USD', + $event->getCustomData()->getCurrency() ); $this->assertEquals( - 'Dinosaurs', - $event->getCustomData()->getContentCategory() + 'Dinosaurs', + $event->getCustomData()->getContentCategory() ); $this->assertEquals( - 'woocommerce', - $event->getCustomData()->getCustomProperty('fb_integration_tracking') + 'woocommerce', + $event->getCustomData()->getCustomProperty( 'fb_integration_tracking' ) + ); + } + + /** + * Test that the enqueuePixelCode method correctly enqueues the + * appropriate Pixel code for WooCommerce events when the user + * is not an internal user. + * + * This test verifies that the output contains the expected Meta Pixel + * Event Code for WooCommerce and that the server-side event tracking + * records the event with the correct user and custom data attributes. + */ + public function testEnqueuePixelEvent() { + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) ); - } - public function testEnqueuePixelEvent() - { - self::mockIsInternalUser(false); - self::mockFacebookWordpressOptions(); + self::mockIsInternalUser( false ); + self::mockFacebookWordpressOptions(); - $this->setupMocks(); - $server_event = new Event(); - $pixel_code = FacebookWordpressWooCommerce::enqueuePixelCode($server_event); + $this->setupMocks(); + $server_event = new Event(); + $pixel_code = + FacebookWordpressWooCommerce::enqueuePixelCode( $server_event ); $this->assertMatchesRegularExpression( - '/woocommerce[\s\S]+End Meta Pixel Event Code/', - $pixel_code + '/woocommerce[\s\S]+End Meta Pixel Event Code/', + $pixel_code + ); + } + + /** + * Tests that the addPixelCodeToAddToCartFragment method correctly adds + * the Pixel code to the AJAX response fragments. + * + * This test verifies that the Pixel code is included in the AJAX fragments + * returned by the addPixelCodeToAddToCartFragment method when the + * Facebook for WooCommerce integration is triggered. It ensures that the + * appropriate HTML element ID is present in the fragments array and that + * the Pixel code matches the expected pattern for WooCommerce. + * + * @return void + */ + public function testAddPixelCodeToAddToCartFragment() { + self::mockFacebookWordpressOptions(); + + $server_event = new Event(); + FacebookServerSideEvent::get_instance()->set_pending_pixel_event( + 'addPixelCodeToAddToCartFragment', + $server_event ); - } - public function testAddPixelCodeToAddToCartFragment() - { - self::mockFacebookWordpressOptions(); - - $server_event = new Event(); - FacebookServerSideEvent::getInstance()->setPendingPixelEvent( - 'addPixelCodeToAddToCartFragment', - $server_event + \WP_Mock::userFunction( + 'wp_json_encode', + array( + 'args' => array( + \Mockery::type( 'array' ), + \Mockery::type( 'int' ), + ), + 'return' => function ( $data, $options ) { + return json_encode( $data ); + }, + ) ); - $fragments = - FacebookWordpressWooCommerce::addPixelCodeToAddToCartFragment(array()); + $fragments = + FacebookWordpressWooCommerce::addPixelCodeToAddToCartFragment( + array() + ); $this->assertArrayHasKey( - '#' . FacebookWordpressWooCommerce::DIV_ID_FOR_AJAX_PIXEL_EVENTS, - $fragments + '#' . FacebookWordpressWooCommerce::DIV_ID_FOR_AJAX_PIXEL_EVENTS, + $fragments ); - $pxl_div_code = - $fragments[ - '#' . FacebookWordpressWooCommerce::DIV_ID_FOR_AJAX_PIXEL_EVENTS - ]; + $pxl_div_code = $fragments[ '#' . + FacebookWordpressWooCommerce::DIV_ID_FOR_AJAX_PIXEL_EVENTS ]; $this->assertMatchesRegularExpression( - '/id=\'fb-pxl-ajax-code\'[\s\S]+woocommerce/', - $pxl_div_code - ); - } - - private function mockFacebookForWooCommerce($active) - { + '/id=\'fb-pxl-ajax-code\'[\s\S]+woocommerce/', + $pxl_div_code + ); + } + + /** + * Mocks the presence of the Facebook for WooCommerce plugin. + * + * This function simulates the activation status + * of the Facebook for WooCommerce + * plugin by mocking the 'get_option' function. It returns a specific plugin + * identifier if the plugin is active, otherwise returns an empty array. + * + * @param bool $active Determines if the plugin is considered active. + * If true, the plugin is mocked as active. + * If false, the plugin is mocked as inactive. + * + * @return void + */ + private function mockFacebookForWooCommerce( $active ) { \WP_Mock::userFunction( - 'get_option', - array( - 'return' => $active ? - array('facebook-for-woocommerce/facebook-for-woocommerce.php') - : array() - ) + 'get_option', + array( + 'return' => $active ? array( + 'facebook-for-woocommerce/facebook-for-woocommerce.php', + ) : array(), + ) ); - } - - private function setupCustomerBillingAddress() - { + } + + /** + * Sets up customer billing address mocks. + * + * This method is used to simulate the presence of customer billing address + * data. It uses WP_Mock to define the results of the get_user_meta function + * when requesting the billing city, state, postcode, country, and phone. + * + * @return void + */ + private function setupCustomerBillingAddress() { \WP_Mock::userFunction( - 'get_user_meta', - array( - 'times' => 1, - 'args' => array(\WP_Mock\Functions::type('int'), 'billing_city', true), - 'return' => 'Springfield' - ) + 'get_user_meta', + array( + 'times' => 1, + 'args' => array( + \WP_Mock\Functions::type( 'int' ), + 'billing_city', + true, + ), + 'return' => 'Springfield', + ) ); \WP_Mock::userFunction( - 'get_user_meta', - array( - 'times' => 1, - 'args' => array(\WP_Mock\Functions::type('int'), 'billing_state', true), - 'return' => 'Ohio' - ) + 'get_user_meta', + array( + 'times' => 1, + 'args' => array( + \WP_Mock\Functions::type( 'int' ), + 'billing_state', + true, + ), + 'return' => 'Ohio', + ) ); \WP_Mock::userFunction( - 'get_user_meta', - array( - 'times' => 1, - 'args' => array( - \WP_Mock\Functions::type('int'), - 'billing_postcode', - true - ), - 'return' => '12345' - ) + 'get_user_meta', + array( + 'times' => 1, + 'args' => array( + \WP_Mock\Functions::type( 'int' ), + 'billing_postcode', + true, + ), + 'return' => '12345', + ) ); \WP_Mock::userFunction( - 'get_user_meta', - array( - 'times' => 1, - 'args' => array( - \WP_Mock\Functions::type('int'), - 'billing_country', - true - ), - 'return' => 'US' - ) + 'get_user_meta', + array( + 'times' => 1, + 'args' => array( + \WP_Mock\Functions::type( 'int' ), + 'billing_country', + true, + ), + 'return' => 'US', + ) ); \WP_Mock::userFunction( - 'get_user_meta', - array( - 'times' => 1, - 'args' => array(\WP_Mock\Functions::type('int'), 'billing_phone', true), - 'return' => '2062062006' - ) - ); - } - - private function setupMocks() - { - $this->mocked_fbpixel->shouldReceive('getLoggedInUserInfo') - ->andReturn( + 'get_user_meta', array( - 'email' => 'pika.chu@s2s.com', - 'first_name' => 'Pika', - 'last_name' => 'Chu' + 'times' => 1, + 'args' => array( + \WP_Mock\Functions::type( 'int' ), + 'billing_phone', + true, + ), + 'return' => '2062062006', ) - ); + ); + } + + /** + * Sets up various mocks for the WooCommerce integration tests. + * + * This method uses WP_Mock to define + * the results of various functions that are + * used in the WooCommerce integration code. It sets up the following mocks: + * + * - WC(): Returns a MockWC object. + * - wc_get_order(): Returns a MockWCOrder object. + * - wc_get_product(): Returns a MockWCProduct object. + * - get_current_user_id(): Returns 1. + * - get_the_terms(): Returns an array containing a + * stdClass object with a name + * property set to 'Dinosaurs'. + * - wc_enqueue_js(): Does nothing. + * + * Additionally, it sets up the MockWC object + * to return a MockWCCart object when + * calling WC()->cart. + * + * @return void + */ + private function setupMocks() { + $this->mocked_fbpixel->shouldReceive( 'get_logged_in_user_info' ) + ->andReturn( + array( + 'email' => 'pika.chu@s2s.com', + 'first_name' => 'Pika', + 'last_name' => 'Chu', + ) + ); \WP_Mock::userFunction( - 'get_woocommerce_currency', - array( - 'return' => 'USD' - ) + 'get_woocommerce_currency', + array( + 'return' => 'USD', + ) ); - $cart = new MockWCCart(); - $cart->add_item(1, 1, 3, 300); + $cart = new MockWCCart(); + $cart->add_item( 1, 1, 3, 300 ); \WP_Mock::userFunction( - 'WC', - array( - 'return' => new MockWC($cart) - ) + 'WC', + array( + 'return' => new MockWC( $cart ), + ) ); $order = new MockWCOrder( - 'Pika', - 'Chu', - 'pika.chu@s2s.com', - '2062062006', - 'Springfield', - '12345', - 'Ohio', - 'US' + 'Pika', + 'Chu', + 'pika.chu@s2s.com', + '2062062006', + 'Springfield', + '12345', + 'Ohio', + 'US' ); - $order->add_item(1, 3, 900); + $order->add_item( 1, 3, 900 ); \WP_Mock::userFunction( - 'wc_get_order', - array( - 'return' => $order - ) + 'wc_get_order', + array( + 'return' => $order, + ) ); \WP_Mock::userFunction( - 'wc_get_product', - array( - 'return' => new MockWCProduct(1, 'single_product', 'Stegosaurus', 10) - ) + 'wc_get_product', + array( + 'return' => new MockWCProduct( + 1, + 'single_product', + 'Stegosaurus', + 10 + ), + ) ); \WP_Mock::userFunction( - 'get_current_user_id', - array( - 'return' => 1 - ) + 'get_current_user_id', + array( + 'return' => 1, + ) ); - $term = new \stdClass(); - $term->name = 'Dinosaurs'; + $term = new \stdClass(); + $term->name = 'Dinosaurs'; \WP_Mock::userFunction( - 'get_the_terms', - array( - 'return' => array($term) - ) + 'get_the_terms', + array( + 'return' => array( $term ), + ) ); - \WP_Mock::userFunction('wc_enqueue_js', array()); - } + \WP_Mock::userFunction( 'wc_enqueue_js', array() ); + } } diff --git a/__tests__/mocks/MockContactForm7.php b/__tests__/mocks/MockContactForm7.php index cf99ba94..2fa29881 100644 --- a/__tests__/mocks/MockContactForm7.php +++ b/__tests__/mocks/MockContactForm7.php @@ -1,38 +1,86 @@ throw = $throw; - } + /** + * Sets whether to throw an exception when get_current() is called. + * + * @param bool $status Whether to throw an exception. + */ + public function set_throw( $status ) { + $this->throw = $status; + } - public function add_tag($basetype, $name, $value) { - $tag = new MockContactForm7Tag($basetype, $name); - $_POST[$name] = $value; + /** + * Adds a form tag to the mock plugin. + * + * @param string $basetype The base type of the tag. + * @param string $name The name of the tag. + * @param mixed $value The value of the tag. + */ + public function add_tag( $basetype, $name, $value ) { + $tag = new MockContactForm7Tag( $basetype, $name ); + $_POST[ $name ] = $value; - $this->form_tags[] = $tag; - } + $this->form_tags[] = $tag; + } - public function scan_form_tags() { - if ($this->throw) { - throw new \Exception("Error scanning form tags!"); + /** + * Scans and retrieves the form tags. + * + * If the 'throw' property is set to true, an exception is thrown. + * + * @throws \Exception If an error occurs during form tag scanning. + * + * @return array The array of form tags. + */ + public function scan_form_tags() { + if ( $this->throw ) { + throw new \Exception( 'Error scanning form tags!' ); } - return $this->form_tags; - } + return $this->form_tags; + } } diff --git a/__tests__/mocks/MockContactForm7Tag.php b/__tests__/mocks/MockContactForm7Tag.php index 92f8930d..f0a5d1c1 100644 --- a/__tests__/mocks/MockContactForm7Tag.php +++ b/__tests__/mocks/MockContactForm7Tag.php @@ -1,24 +1,57 @@ basetype = $basetype; - $this->name = $name; - } + /** + * Initializes the MockContactForm7Tag object. + * + * @param string $basetype The base type of the contact form 7 tag. + * @param string $name The name of the contact form 7 tag. + */ + public function __construct( $basetype, $name ) { + $this->basetype = $basetype; + $this->name = $name; + } } diff --git a/__tests__/mocks/MockFormidableFormEntryValues.php b/__tests__/mocks/MockFormidableFormEntryValues.php index 7b228abb..d9bfcfa0 100644 --- a/__tests__/mocks/MockFormidableFormEntryValues.php +++ b/__tests__/mocks/MockFormidableFormEntryValues.php @@ -1,35 +1,81 @@ field_values = $field_values; - } + /** + * Creates a new instance of the MockFormidableFormEntryValues class. + * + * @param array $field_values An array of field values. + */ + public function __construct( $field_values ) { + $this->field_values = $field_values; + } - public function set_throw($throw) { - $this->throw = $throw; - } + /** + * Sets whether to throw an exception when get_field_values() is called. + * + * @param bool $status Whether to throw an exception. + */ + public function set_throw( $status ) { + $this->throw = $status; + } - public function get_field_values() { - if ($this->throw) { - throw new \Exception('Unable to read field values!'); + /** + * Retrieves the field values. + * + * If the 'throw' property is set to true, an exception is thrown. + * + * @throws \Exception If an error occurs during field value retrieval. + * + * @return array The array of field values. + */ + public function get_field_values() { + if ( $this->throw ) { + throw new \Exception( 'Unable to read field values!' ); } - return $this->field_values; - } + return $this->field_values; + } } diff --git a/__tests__/mocks/MockFormidableFormField.php b/__tests__/mocks/MockFormidableFormField.php index a65cadce..2bfb84b9 100644 --- a/__tests__/mocks/MockFormidableFormField.php +++ b/__tests__/mocks/MockFormidableFormField.php @@ -1,26 +1,67 @@ type = $type; - $this->name = $name; - $this->description = $description; - } + /** + * Constructs a new instance of the MockFormidableFormField class. + * + * @param string $type The type of the form field. + * @param string $name The name of the form field. + * @param string $description The description of the form field. + */ + public function __construct( $type, $name, $description ) { + $this->type = $type; + $this->name = $name; + $this->description = $description; + } } diff --git a/__tests__/mocks/MockFormidableFormFieldValue.php b/__tests__/mocks/MockFormidableFormFieldValue.php index a5c07fa9..be721d65 100644 --- a/__tests__/mocks/MockFormidableFormFieldValue.php +++ b/__tests__/mocks/MockFormidableFormFieldValue.php @@ -1,32 +1,77 @@ field = $field; - $this->saved_value = $saved_value; - } + /** + * Creates a new instance of the MockFormidableFormFieldValue class. + * + * @param MockFormidableFormField $field The form field instance. + * @param mixed $saved_value The saved + * value of the form field. + */ + public function __construct( $field, $saved_value ) { + $this->field = $field; + $this->saved_value = $saved_value; + } - public function get_field() { - return $this->field; - } + /** + * Retrieves the form field instance. + * + * @return MockFormidableFormField The form field instance. + */ + public function get_field() { + return $this->field; + } - public function get_saved_value() { - return $this->saved_value; - } + /** + * Retrieves the saved value of the form field. + * + * @return mixed The saved value of the form field. + */ + public function get_saved_value() { + return $this->saved_value; + } } diff --git a/__tests__/mocks/MockGravityFormField.php b/__tests__/mocks/MockGravityFormField.php index 5ec3a74d..a46724e3 100644 --- a/__tests__/mocks/MockGravityFormField.php +++ b/__tests__/mocks/MockGravityFormField.php @@ -1,30 +1,79 @@ type = $type; - $this->id = $id; - } - - public function addLabel($label, $id) { - $input = array('label' => $label, 'id' => $id); - $this->inputs[] = $input; - } + /** + * The type of the form field. + * + * @var string + */ + public $type; + + /** + * The identifier of the form field. + * + * @var string + */ + public $id; + + /** + * An array of input fields for the form field. + * + * @var array + */ + public $inputs = array(); + + /** + * Constructs a new instance of the MockGravityFormField class. + * + * @param string $type The type of the form field. + * @param string $id The identifier of the form field. + */ + public function __construct( $type, $id ) { + $this->type = $type; + $this->id = $id; + } + + /** + * Adds a label to the form field. + * + * @param string $label The text of the label. + * @param string $id The identifier of the label. + */ + public function add_label( $label, $id ) { + $input = array( + 'label' => $label, + 'id' => $id, + ); + $this->inputs[] = $input; + } } diff --git a/__tests__/mocks/MockWC.php b/__tests__/mocks/MockWC.php index 010327a4..24aa8caa 100644 --- a/__tests__/mocks/MockWC.php +++ b/__tests__/mocks/MockWC.php @@ -1,22 +1,49 @@ cart = $cart; - } + /** + * Initializes the MockWC object. + * + * @param object $cart The cart object to use. + */ + public function __construct( $cart ) { + $this->cart = $cart; + } } diff --git a/__tests__/mocks/MockWCCart.php b/__tests__/mocks/MockWCCart.php index 39ce8813..8785bd62 100644 --- a/__tests__/mocks/MockWCCart.php +++ b/__tests__/mocks/MockWCCart.php @@ -1,41 +1,105 @@ $key, - 'data' => new MockWCProduct($id), - 'quantity' => $quantity, - 'line_total' => $quantity * $price, - ); - - $this->cart[$key] = $item; - $this->total += ($quantity * $price); - $this->num_items += $quantity; - } - - public function get_cart() { - return $this->cart; - } - - public function get_cart_contents_count() { - return $this->num_items; - } + + /** + * The total value of the cart. + * + * @var int + */ + public $total = 0; + + /** + * Array of cart items. + * + * @var array + */ + private $cart = array(); + + /** + * The number of items in the cart. + * + * @var int + */ + private $num_items = 0; + + /** + * Adds an item to the cart. + * + * This method creates a new cart item with the specified key, product ID, + * quantity, and price. It updates the cart array, total cart value, and + * the number of items in the cart. + * + * @param string $key The unique key for the cart item. + * @param int $id The product ID. + * @param int $quantity The quantity of the product. + * @param float $price The price of the product. + * + * @return void + */ + public function add_item( $key, $id, $quantity, $price ) { + $item = array( + 'key' => $key, + 'data' => new MockWCProduct( $id ), + 'quantity' => $quantity, + 'line_total' => $quantity * $price, + ); + + $this->cart[ $key ] = $item; + $this->total += ( $quantity * $price ); + $this->num_items += $quantity; + } + + /** + * Returns the cart items. + * + * @return array The cart items. Each item is an associative array with the + * following keys: + * - key: The unique key for the item. + * - data: The product object. + * - quantity: The quantity of the product. + * - line_total: The total value of the product line. + */ + public function get_cart() { + return $this->cart; + } + + /** + * Retrieves the number of items in the cart. + * + * @return int The number of items in the cart. + */ + public function get_cart_contents_count() { + return $this->num_items; + } } diff --git a/__tests__/mocks/MockWCOrder.php b/__tests__/mocks/MockWCOrder.php index 93b34a92..b673d9e4 100644 --- a/__tests__/mocks/MockWCOrder.php +++ b/__tests__/mocks/MockWCOrder.php @@ -1,86 +1,246 @@ first_name = $first_name; - $this->last_name = $last_name; - $this->email = $email; - $this->phone = $phone; - $this->city = $city; - $this->postcode = $postcode; - $this->state = $state; - $this->country = $country; - } - - public function get_billing_first_name(){ - return $this->first_name; - } - - public function get_billing_last_name(){ - return $this->last_name; - } - - public function get_billing_email(){ - return $this->email; - } - - public function get_billing_postcode(){ - return $this->postcode; - } - - public function get_billing_state(){ - return $this->state; - } - - public function get_billing_country(){ - return $this->country; - } - - public function get_billing_city(){ - return $this->city; - } - - public function get_billing_phone(){ - return $this->phone; - } - - public function add_item($id, $quantity, $total) { - $item = new MockWCOrderItem($id, $quantity, $total); - $this->items[] = $item; - $this->total += $total; - } - - public function get_items() { - return $this->items; - } - - public function get_total() { - return $this->total; - } + /** + * The items in the order. + * + * @var array + */ + private $items = array(); + + /** + * The total value of the order. + * + * @var float + */ + private $total = 0; + + /** + * The first name of the order recipient. + * + * @var string + */ + private $first_name; + + /** + * The last name of the order recipient. + * + * @var string + */ + private $last_name; + + /** + * The email address of the order recipient. + * + * @var string + */ + private $email; + + /** + * The phone number of the order recipient. + * + * @var string + */ + private $phone; + + /** + * The city of the order recipient. + * + * @var string + */ + private $city; + + /** + * The postcode of the order recipient. + * + * @var string + */ + private $postcode; + + /** + * The state of the order recipient. + * + * @var string + */ + private $state; + + /** + * The country of the order recipient. + * + * @var string + */ + private $country; + + /** + * Initializes a new instance of the MockWCOrder class. + * + * @param string $first_name The first name of the order recipient. + * @param string $last_name The last name of the order recipient. + * @param string $email The email address of the order recipient. + * @param string $phone The phone number of the order recipient. + * @param string $city The city of the order recipient. + * @param string $postcode The postcode of the order recipient. + * @param string $state The state of the order recipient. + * @param string $country The country of the order recipient. + */ + public function __construct( + $first_name, + $last_name, + $email, + $phone, + $city, + $postcode, + $state, + $country + ) { + $this->first_name = $first_name; + $this->last_name = $last_name; + $this->email = $email; + $this->phone = $phone; + $this->city = $city; + $this->postcode = $postcode; + $this->state = $state; + $this->country = $country; + } + + /** + * Retrieves the billing first name. + * + * @return string The billing first name. + */ + public function get_billing_first_name() { + return $this->first_name; + } + + /** + * Retrieves the billing last name. + * + * @return string The billing last name. + */ + public function get_billing_last_name() { + return $this->last_name; + } + + /** + * Retrieves the billing email address. + * + * @return string The billing email address. + */ + public function get_billing_email() { + return $this->email; + } + + /** + * Retrieves the billing postcode. + * + * @return string The billing postcode. + */ + public function get_billing_postcode() { + return $this->postcode; + } + + /** + * Retrieves the billing state. + * + * @return string The billing state. + */ + public function get_billing_state() { + return $this->state; + } + + /** + * Retrieves the billing country. + * + * @return string The billing country. + */ + public function get_billing_country() { + return $this->country; + } + + /** + * Retrieves the billing city. + * + * @return string The billing city. + */ + public function get_billing_city() { + return $this->city; + } + + /** + * Retrieves the billing phone number. + * + * @return string The billing phone number. + */ + public function get_billing_phone() { + return $this->phone; + } + + /** + * Adds an item to the order. + * + * This method creates a new order item with the specified product ID, + * quantity, and total price, and adds it to the order's item list. It also + * updates the total value of the order by adding the item's total price. + * + * @param int $id The product ID. + * @param int $quantity The quantity of the product. + * @param float $total The total price of the product. + * + * @return void + */ + public function add_item( $id, $quantity, $total ) { + $item = new MockWCOrderItem( $id, $quantity, $total ); + $this->items[] = $item; + $this->total += $total; + } + + /** + * Returns the items in the order. + * + * @return array The items in the order. Each item is an instance of + * MockWCOrderItem. + */ + public function get_items() { + return $this->items; + } + + /** + * Returns the total value of the cart. + * + * @return float The total value of the cart. + */ + public function get_total() { + return $this->total; + } } diff --git a/__tests__/mocks/MockWCOrderItem.php b/__tests__/mocks/MockWCOrderItem.php index 7df804c4..e213c1d4 100644 --- a/__tests__/mocks/MockWCOrderItem.php +++ b/__tests__/mocks/MockWCOrderItem.php @@ -1,38 +1,95 @@ id = $id; - $this->quantity = $quantity; - $this->total = $total; - } - - public function get_product_id() { - return $this->id; - } - - public function get_quantity() { - return $this->quantity; - } - - public function get_total() { - return $this->total; - } + + /** + * The product ID. + * + * @var int + */ + private $id; + + /** + * The quantity of the product. + * + * @var int + */ + private $quantity; + + /** + * The total value of the product. + * + * @var int + */ + private $total; + + /** + * Initializes the MockWCOrderItem object. + * + * @param int $id The product ID. + * @param int $quantity The quantity of the product. + * @param int $total The total value of the product. + */ + public function __construct( $id, $quantity, $total ) { + $this->id = $id; + $this->quantity = $quantity; + $this->total = $total; + } + + /** + * Retrieves the product ID of the order item. + * + * @return int The product ID. + */ + public function get_product_id() { + return $this->id; + } + + /** + * Retrieves the quantity of the order item. + * + * @return int The quantity of the order item. + */ + public function get_quantity() { + return $this->quantity; + } + + /** + * Retrieves the total value of the order item. + * + * @return int The total value of the order item. + */ + public function get_total() { + return $this->total; + } } diff --git a/__tests__/mocks/MockWCProduct.php b/__tests__/mocks/MockWCProduct.php index 6892b2d9..6af0c35d 100644 --- a/__tests__/mocks/MockWCProduct.php +++ b/__tests__/mocks/MockWCProduct.php @@ -1,47 +1,127 @@ id = $id; - $this->type = $type; - $this->title = $title; - $this->price = $price; - } - - public function get_id() { - return $this->id; - } - - public function get_sku() { - return ''; - } - - public function get_title() { - return $this->title; - } - - public function get_price() { - return $this->price; - } - public function is_type($type) { - return $this->type === $type; - } + /** + * The unique identifier of the product. + * + * @var int|null + */ + private $id = null; + + /** + * The type of the product. + * + * @var string|null + */ + private $type = null; + + /** + * The title of the product. + * + * @var string|null + */ + private $title = null; + + /** + * The price of the product. + * + * @var float|null + */ + private $price = null; + + /** + * Initializes the MockWCProduct object. + * + * @param int $id The product ID. + * @param string $type Optional. The type of the product. Default null. + * @param string $title Optional. The title of the product. Default null. + * @param float $price Optional. The price of the product. Default null. + */ + public function __construct( + $id, + $type = null, + $title = null, + $price = null +) { + $this->id = $id; + $this->type = $type; + $this->title = $title; + $this->price = $price; + } + + /** + * Retrieves the product ID. + * + * @return int The product ID. + */ + public function get_id() { + return $this->id; + } + + /** + * Retrieves the product SKU. + * + * @return string The product SKU. + */ + public function get_sku() { + return ''; + } + + /** + * Retrieves the title of the product. + * + * @return string The title of the product. + */ + public function get_title() { + return $this->title; + } + + /** + * Retrieves the price of the product. + * + * @return float The price of the product. + */ + public function get_price() { + return $this->price; + } + /** + * Checks if the product is of a certain type. + * + * @param string $type The type to check against. + * + * @return bool True if the product is of the given type, false otherwise. + */ + public function is_type( $type ) { + return $this->type === $type; + } } diff --git a/assets/event-log-head.png b/assets/event-log-head.png new file mode 100644 index 00000000..57dbb9e6 Binary files /dev/null and b/assets/event-log-head.png differ diff --git a/build.properties b/build.properties index 909e17fe..839a5a54 100644 --- a/build.properties +++ b/build.properties @@ -21,6 +21,6 @@ testdir=${basedir}/__tests__ packagebasename=facebook-pixel-for-wordpress # Files contain version number -facebookforwordpress=${builddir}/official-facebook-pixel/facebook-for-wordpress.php +facebookforwordpress=${builddir}/official-facebook-pixel/class-facebookforwordpress.php dockerimagename=wordpress-pixel-plugin-site diff --git a/build.xml b/build.xml index 0d7d3f14..a48ec6d0 100644 --- a/build.xml +++ b/build.xml @@ -8,6 +8,7 @@ + diff --git a/class-facebookforwordpress.php b/class-facebookforwordpress.php new file mode 100644 index 00000000..9e906d7e --- /dev/null +++ b/class-facebookforwordpress.php @@ -0,0 +1,139 @@ +***ATTENTION: After upgrade the plugin may be deactivated due to a known issue, to workaround please refresh this page and activate plugin.*** The Facebook pixel is an analytics tool that helps you measure the effectiveness of your advertising. You can use the Facebook pixel to understand the actions people are taking on your website and reach audiences you care about. + * Author: Facebook + * Author URI: https://www.facebook.com/ + * Version: {*VERSION_NUMBER*} + * Text Domain: official-facebook-pixel + * + * @package FacebookPixelPlugin + */ + +/* +* Copyright (C) 2017-present, Facebook, Inc. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +namespace FacebookPixelPlugin; + +defined( 'ABSPATH' ) || die( 'Direct access not allowed' ); + +require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload.php'; + +use FacebookPixelPlugin\Core\FacebookPixel; +use FacebookPixelPlugin\Core\FacebookPluginConfig; +use FacebookPixelPlugin\Core\FacebookPluginUtils; +use FacebookPixelPlugin\Core\FacebookWordpressOpenBridge; +use FacebookPixelPlugin\Core\FacebookWordpressOptions; +use FacebookPixelPlugin\Core\FacebookWordpressPixelInjection; +use FacebookPixelPlugin\Core\FacebookWordpressSettingsPage; +use FacebookPixelPlugin\Core\FacebookWordpressSettingsRecorder; +use FacebookPixelPlugin\Core\ServerEventAsyncTask; + +/** + * FacebookForWordpress root class. + */ +class FacebookForWordpress { + /** + * Plugin constructor. Initializes the plugin options, loads the translation files, + * sets up the Facebook pixel, sets up the pixel injection, and sets up the settings + * page. Also starts the server event async task. + */ + public function __construct() { + FacebookWordpressOptions::initialize(); + + load_plugin_textdomain( + FacebookPluginConfig::TEXT_DOMAIN, + false, + dirname( plugin_basename( __FILE__ ) ) . '/languages/' + ); + + $options = FacebookWordpressOptions::get_options(); + FacebookPixel::initialize( FacebookWordpressOptions::get_pixel_id() ); + + add_action( 'init', array( $this, 'register_pixel_injection' ), 0 ); + add_action( 'parse_request', array( $this, 'handle_events_request' ), 0 ); + + $this->register_settings_page(); + + new ServerEventAsyncTask(); + } + + + /** + * Registers the pixel injection. This method instantiates the + * FacebookWordpressPixelInjection and calls its inject method. + * + * The inject method is responsible for adding the necessary hooks to + * inject the Facebook pixel code into the footer of the WordPress page. + */ + public function register_pixel_injection() { + $injection_obj = new FacebookWordpressPixelInjection(); + $injection_obj->inject(); + } + + + /** + * Registers the settings page for the Facebook for WordPress plugin. This method + * instantiates the FacebookWordpressSettingsPage and FacebookWordpressSettingsRecorder + * objects. The settings page object is responsible for adding the necessary hooks + * and rendering the settings page. The settings recorder object is responsible for + * recording data about the user's settings and sending it to Meta. + */ + public function register_settings_page() { + if ( is_admin() ) { + $plugin_name = plugin_basename( __FILE__ ); + new FacebookWordpressSettingsPage( $plugin_name ); + ( new FacebookWordpressSettingsRecorder() )->init(); + } + } + + + /** + * Handles incoming events requests by checking if the request URI + * ends with the configured open bridge path and if the request + * method is POST. If both conditions are met, it decodes the JSON + * payload from the request body and forwards it to the open bridge + * request handler. Additionally, it sets CORS headers to allow + * cross-origin requests if the origin is specified in the request + * headers. + */ + public function handle_events_request() { + if ( isset( $_SERVER['REQUEST_URI'] ) ) { + $request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); + + if ( + FacebookPluginUtils::ends_with( + $request_uri, + FacebookPluginConfig::OPEN_BRIDGE_PATH + ) && + isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] + ) { + $data = json_decode( file_get_contents( 'php://input' ), true ); + if ( ! is_null( $data ) ) { + FacebookWordpressOpenBridge::get_instance()->handle_open_bridge_req( + $data + ); + } + if ( isset( $_SERVER['HTTP_ORIGIN'] ) ) { + $origin = wp_kses( wp_unslash( $_SERVER['HTTP_ORIGIN'] ), array() ); + header( "Access-Control-Allow-Origin: $origin" ); + header( 'Access-Control-Allow-Credentials: true' ); + header( 'Access-Control-Max-Age: 86400' ); + } + exit(); + } + } + } +} + +new FacebookForWordpress(); diff --git a/composer.json b/composer.json index a4f0a17d..021156c7 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,10 @@ "type": "project", "require-dev": { "phing/phing": "^3.0.0-RC3", - "10up/wp_mock": "1.0.1" + "10up/wp_mock": "1.0.1", + "squizlabs/php_codesniffer": "^3.10", + "wp-coding-standards/wpcs": "^3.1", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" }, "prefer-stable" : true, "license": "GPL", @@ -27,5 +30,10 @@ }, "replace": { "guzzlehttp/guzzle": "*" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } } diff --git a/composer.lock b/composer.lock index a4bddb32..38ccb6d2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "66f1427c8d1db057bf2d1cfcd4776981", + "content-hash": "9ff1e0e490496206016be4e5953f5ad7", "packages": [ { "name": "facebook/php-business-sdk", @@ -212,6 +212,84 @@ }, "time": "2024-02-06T09:26:11+00:00" }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, { "name": "doctrine/instantiator", "version": "1.5.0", @@ -932,6 +1010,172 @@ ], "time": "2024-05-01T18:28:58+00:00" }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.0.9", + "squizlabs/php_codesniffer": "^3.8.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T16:49:07+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.0.12", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/87b233b00daf83fb70f40c9a28692be017ea7c6c", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.10.0 || 4.0.x-dev@dev" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-05-20T13:34:27+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.32", @@ -2370,6 +2614,86 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.10.3", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-09-18T10:38:58+00:00" + }, { "name": "symfony/console", "version": "v6.0.19", @@ -3073,6 +3397,72 @@ } ], "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "time": "2024-03-25T16:39:00+00:00" } ], "aliases": [], @@ -3085,5 +3475,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/core/AAMFieldsExtractor.php b/core/AAMFieldsExtractor.php deleted file mode 100644 index 64ec7cf8..00000000 --- a/core/AAMFieldsExtractor.php +++ /dev/null @@ -1,93 +0,0 @@ -getEnableAutomaticMatching()){ - return array(); - } - - //Removing fields not enabled in AAM settings - foreach ($user_data_array as $key => $value) { - if(!in_array($key, $aam_setttings->getEnabledAutomaticMatchingFields())){ - unset($user_data_array[$key]); - } - } - - // Normalizing gender and date of birth - // According to https://developers.facebook.com/docs/facebook-pixel/advanced/advanced-matching - if( - isset($user_data_array[AAMSettingsFields::GENDER]) && - !empty($user_data_array[AAMSettingsFields::GENDER]) - ){ - $user_data_array[AAMSettingsFields::GENDER] = - $user_data_array[AAMSettingsFields::GENDER][0]; - } - if( - isset($user_data_array[AAMSettingsFields::DATE_OF_BIRTH]) - ){ - // strtotime() and date() return false for invalid parameters - $unix_timestamp = - strtotime($user_data_array[AAMSettingsFields::DATE_OF_BIRTH]); - if(!$unix_timestamp){ - unset($user_data_array[AAMSettingsFields::DATE_OF_BIRTH]); - } else { - $formatted_date = date("Ymd", $unix_timestamp); - if(!$formatted_date){ - unset($user_data_array[AAMSettingsFields::DATE_OF_BIRTH]); - } else { - $user_data_array[AAMSettingsFields::DATE_OF_BIRTH] = $formatted_date; - } - } - } - // Given that the format of advanced matching fields is the same in - // the Pixel and the Conversions API, - // we can use the business sdk for normalization - // Compare the documentation: - // https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters - // https://developers.facebook.com/docs/facebook-pixel/advanced/advanced-matching - foreach($user_data_array as $field => $data){ - try{ - if (is_array($data)){ - $res = array(); - foreach($data as $key => $value) { - $normalized_value = Normalizer::normalize($field, $value); - $res[$key] = $normalized_value; - } - $user_data_array[$field] = $res; - } else { - $normalized_value = Normalizer::normalize($field, $data); - $user_data_array[$field] = $normalized_value; - } - } - catch(\Exception $e){ - unset($user_data_array[$field]); - } - } - - return $user_data_array; - } -} diff --git a/core/AAMSettingsFields.php b/core/AAMSettingsFields.php deleted file mode 100644 index 82e0c5bd..00000000 --- a/core/AAMSettingsFields.php +++ /dev/null @@ -1,46 +0,0 @@ - - - -"; - - private static $pixelFbqCodeWithoutScript = " - fbq('%s', '%s'%s%s); - "; - - private static $pixelNoscriptCode = " - - - -"; - - public static function initialize($pixel_id = '') { - self::$pixelId = $pixel_id; - } - - /** - * Gets FB pixel ID - */ - public static function getPixelId() { - return self::$pixelId; - } - - /** - * Sets FB pixel ID - */ - public static function setPixelId($pixel_id) { - self::$pixelId = $pixel_id; - } - - /** - * Gets FB pixel base code - */ - public static function getPixelBaseCode() { - return self::$pixelBaseCode; - } - - /** - * Gets OpenBridge set config code - */ - - public static function getOpenBridgeConfigCode(){ - if (empty(self::$pixelId)) { - return; - } - - $code = " - - "; - return sprintf($code, self::$pixelId); - } - - - /** - * Gets FB pixel init code - */ - public static function getPixelInitCode($agent_string, $param = array(), $with_script_tag = true) { - if (empty(self::$pixelId)) { - return; - } - - $pixelFbqCodeWithoutScript = "fbq('%s', '%s'%s%s)"; - - $code = $with_script_tag - ? "" - : $pixelFbqCodeWithoutScript; - $param_str = $param; - if (is_array($param)) { - $param_str = json_encode($param, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT); - } - $agent_param = array('agent' => $agent_string); - return sprintf( - $code, - 'init', - self::$pixelId, - ', ' . $param_str, - ', ' . json_encode($agent_param, JSON_PRETTY_PRINT)); - } - - /** - * Gets FB pixel track code - * $param is the parameter for the pixel event. - * If it is an array, FB_INTEGRATION_TRACKING_KEY parameter with $tracking_name value will automatically - * be added into the $param. If it is a string, please append the FB_INTEGRATION_TRACKING_KEY parameter - * with its tracking name into the JS Parameter block - */ - public static function getPixelTrackCode($event, $param = array(), $tracking_name = '', $with_script_tag = true) { - if (empty(self::$pixelId)) { - return; - } - - $code = $with_script_tag - ? "" - : self::$pixelFbqCodeWithoutScript; - $param_str = $param; - if (is_array($param)) { - if (!empty($tracking_name)) { - $param[self::FB_INTEGRATION_TRACKING_KEY] = $tracking_name; - } - $param_str = json_encode($param, JSON_PRETTY_PRINT); - } - $class = new ReflectionClass(__CLASS__); - return sprintf( - $code, - $class->getConstant(strtoupper($event)) !== false ? 'track' : 'trackCustom', - $event, - ', ' . $param_str, - ''); - } - - /** - * Gets FB pixel noscript code - */ - public static function getPixelNoscriptCode($event = 'PageView', $cd = array(), $tracking_name = '') { - if (empty(self::$pixelId)) { - return; - } - - $data = ''; - foreach ($cd as $k => $v) { - $data .= '&cd[' . $k . ']=' . $v; - } - if (!empty($tracking_name)) { - $data .= '&cd[' . self::FB_INTEGRATION_TRACKING_KEY . ']=' . $tracking_name; - } - return sprintf( - self::$pixelNoscriptCode, - self::$pixelId, - $event, - $data); - } - - /** - * Gets FB pixel AddToCart code - */ - public static function getPixelAddToCartCode($param = array(), $tracking_name = '', $with_script_tag = true) { - return self::getPixelTrackCode( - self::ADDTOCART, - $param, - $tracking_name, - $with_script_tag); - } - - /** - * Gets FB pixel InitiateCheckout code - */ - public static function getPixelInitiateCheckoutCode($param = array(), $tracking_name = '', $with_script_tag = true) { - return self::getPixelTrackCode( - self::INITIATECHECKOUT, - $param, - $tracking_name, - $with_script_tag); - } - - /** - * Gets FB pixel Lead code - */ - public static function getPixelLeadCode($param = array(), $tracking_name = '', $with_script_tag = true) { - return self::getPixelTrackCode( - self::LEAD, - $param, - $tracking_name, - $with_script_tag); - } - - /** - * Gets FB pixel PageView code - */ - public static function getPixelPageViewCode($param = array(), $tracking_name = '', $with_script_tag = true) { - return self::getPixelTrackCode( - self::PAGEVIEW, - $param, - $tracking_name, - $with_script_tag); - } - - /** - * Gets FB pixel Purchase code - */ - public static function getPixelPurchaseCode($param = array(), $tracking_name = '', $with_script_tag = true) { - return self::getPixelTrackCode( - self::PURCHASE, - $param, - $tracking_name, - $with_script_tag); - } - - /** - * Gets FB pixel ViewContent code - */ - public static function getPixelViewContentCode($param = array(), $tracking_name = '', $with_script_tag = true) { - return self::getPixelTrackCode( - self::VIEWCONTENT, - $param, - $tracking_name, - $with_script_tag); - } -} diff --git a/core/FacebookPluginConfig.php b/core/FacebookPluginConfig.php deleted file mode 100644 index 75e3cc7b..00000000 --- a/core/FacebookPluginConfig.php +++ /dev/null @@ -1,113 +0,0 @@ - PLUGIN_CLASS - public static function integrationConfig() { - return array( - 'CALDERA_FORM' => 'FacebookWordpressCalderaForm', - 'CONTACT_FORM_7' => 'FacebookWordpressContactForm7', - 'EASY_DIGITAL_DOWNLOAD' => 'FacebookWordpressEasyDigitalDownloads', - 'FORMIDABLE_FORM' => 'FacebookWordpressFormidableForm', - 'MAILCHIMP_FOR_WP' => 'FacebookWordpressMailchimpForWp', - 'NINJA_FORMS' => 'FacebookWordpressNinjaForms', - 'WP_E_COMMERCE' => 'FacebookWordpressWPECommerce', - 'WOOCOMMERCE' => 'FacebookWordpressWooCommerce' - ); - } -} diff --git a/core/FacebookPluginUtils.php b/core/FacebookPluginUtils.php deleted file mode 100644 index bc1000e7..00000000 --- a/core/FacebookPluginUtils.php +++ /dev/null @@ -1,92 +0,0 @@ - $current_user->user_email, - 'first_name' => $current_user->user_firstname, - 'last_name' => $current_user->user_lastname, - 'id' => $current_user->ID, - ); - } - - public static function newGUID() - { - if (function_exists('com_create_guid') === true) - { - return trim(com_create_guid(), '{}'); - } - - return sprintf( - '%04X%04X-%04X-%04X-%04X-%04X%04X%04X', - mt_rand(0, 65535), - mt_rand(0, 65535), - mt_rand(0, 65535), - mt_rand(16384, 20479), - mt_rand(32768, 49151), - mt_rand(0, 65535), - mt_rand(0, 65535), - mt_rand(0, 65535) - ); - } - - // All standard WordPress user roles are considered internal unless they have - // the Subscriber role. - // WooCommerce uses the 'read' capability for its customer role. - // Also check for the 'upload_files' capability to account for the shop_worker - // and shop_vendor roles in Easy Digital Downloads. - // https://wordpress.org/support/article/roles-and-capabilities - public static function isInternalUser() { - return current_user_can('edit_posts') || current_user_can('upload_files'); - } - - public static function endsWith( $haystack, $needle ) { - $length = strlen( $needle ); - if( !$length ) { - return false; - } - return substr( $haystack, -$length ) === $needle; - } - - public static function string_contains($haystack, $needle) { - return (bool) strstr($haystack, $needle); - } -} diff --git a/core/FacebookServerSideEvent.php b/core/FacebookServerSideEvent.php deleted file mode 100644 index 0aefa090..00000000 --- a/core/FacebookServerSideEvent.php +++ /dev/null @@ -1,130 +0,0 @@ -trackedEvents[] = $event; - if( $sendNow ){ - do_action( 'send_server_events', - array($event), - 1 - ); - } - else{ - $this->pendingEvents[] = $event; - } - } - - public function getTrackedEvents() { - return $this->trackedEvents; - } - - public function getNumTrackedEvents(){ - return count( $this->trackedEvents ); - } - - public function getPendingEvents(){ - return $this->pendingEvents; - } - - public function setPendingPixelEvent($callback_name, $event){ - $this->pendingPixelEvents[$callback_name] = $event; - } - - public function getPendingPixelEvent($callback_name){ - if(isset($this->pendingPixelEvents[$callback_name])){ - return $this->pendingPixelEvents[$callback_name]; - } - return null; - } - - public static function send($events) { - $events = apply_filters('before_conversions_api_event_sent', $events); - if (empty($events)) { - return; - } - - $pixel_id = FacebookWordpressOptions::getPixelId(); - $access_token = FacebookWordpressOptions::getAccessToken(); - $agent = FacebookWordpressOptions::getAgentString(); - - if(self::isOpenBridgeEvent($events)){ - $agent .= '_ob'; // agent suffix is openbridge - } - - if(empty($pixel_id) || empty($access_token)){ - return; - } - try{ - $api = Api::init(null, null, $access_token); - - $request = (new EventRequest($pixel_id)) - ->setEvents($events) - ->setPartnerAgent($agent); - - $response = $request->execute(); - } catch (Exception $e) { - error_log(json_encode($e)); - } - } - - private static function isOpenBridgeEvent($events) { - if(count($events) !== 1){ - return false; - } - - $customData = $events[0]->getCustomData(); - if (!$customData) { - return false; - } - - $customProperties = $customData->getCustomProperties(); - if (!$customProperties || - !isset($customProperties['fb_integration_tracking'])) { - return false; - } - - return - $customProperties['fb_integration_tracking'] === 'wp-cloudbridge-plugin'; - } -} diff --git a/core/FacebookWordpressOpenBridge.php b/core/FacebookWordpressOpenBridge.php deleted file mode 100644 index 986af5ea..00000000 --- a/core/FacebookWordpressOpenBridge.php +++ /dev/null @@ -1,294 +0,0 @@ - $maxlifetime, - 'path' => '/', - 'domain' => $_SERVER['HTTP_HOST'], - 'secure' => $secure, - 'httponly' => $httponly, - 'samesite' => $samesite - ]); - } - - session_start(); - - $_SESSION[self::EXTERNAL_ID_COOKIE] = isset( - $_SESSION[self::EXTERNAL_ID_COOKIE] - ) ? $_SESSION[self::EXTERNAL_ID_COOKIE] - : FacebookPluginUtils::newGUID(); - } - - public function handleOpenBridgeReq($data){ - - self::startNewPhpSessionIfNeeded(); - - $event_name = $data['event_name']; - if(in_array($event_name, self::$blocked_events)){ - return; - } - $event = ServerEventFactory::safeCreateEvent( - $event_name, - array($this, 'extractFromDatabag'), - array($data), - 'wp-cloudbridge-plugin', - true - ); - $event->setEventId($data['event_id']); - FacebookServerSideEvent::send([$event]); - } - - public function extractFromDatabag($databag){ - $current_user = self::getPIIFromSession(); - - $event_data = array( - # user data - 'email' => self::getEmail($current_user, $databag), - 'first_name' => self::getFirstName($current_user, $databag), - 'last_name' => self::getLastName($current_user, $databag), - 'external_id' => self::getExternalID($current_user, $databag), - 'phone' => self::getPhone($current_user, $databag), - 'state' => self::getState($current_user, $databag), - 'country' => self::getCountry($current_user, $databag), - 'city' => self::getCity($current_user, $databag), - 'zip' => self::getZip($current_user, $databag), - 'gender' => self::getAAMField(AAMSettingsFields::GENDER, $databag), - 'date_of_birth' => - self::getAAMField(AAMSettingsFields::DATE_OF_BIRTH, $databag), - - # custom data - 'currency' => self::getCustomData('currency', $databag), - 'value' => self::getCustomData('value', $databag), - 'content_type' => self::getCustomData('content_type', $databag), - 'content_name' => self::getCustomData('content_name', $databag), - 'content_ids' => self::getCustomDataArray('content_ids', $databag), - 'content_category' => - self::getCustomData('content_category', $databag), - ); - return $event_data; - } - - private static function getPIIFromSession(){ - $current_user = array_filter( - FacebookPluginUtils::getLoggedInUserInfo() - ); - $capiPiiCachingStatus = - FacebookWordpressOptions::getCapiPiiCachingStatus(); - - if(empty($current_user) && $capiPiiCachingStatus === '1') { - - if(isset($_SESSION[AAMSettingsFields::EMAIL])){ - $current_user['email'] = $_SESSION[AAMSettingsFields::EMAIL]; - } - - if(isset($_SESSION[AAMSettingsFields::FIRST_NAME])){ - $current_user['first_name'] = - $_SESSION[AAMSettingsFields::FIRST_NAME]; - } - - if(isset($_SESSION[AAMSettingsFields::LAST_NAME])){ - $current_user['last_name'] = - $_SESSION[AAMSettingsFields::LAST_NAME]; - } - - if(isset($_SESSION[AAMSettingsFields::PHONE])){ - $current_user['phone'] = - $_SESSION[AAMSettingsFields::PHONE]; - } - - return array_filter($current_user); - } - - $user_id = get_current_user_id(); - if($user_id != 0){ - $current_user['city'] = get_user_meta( - $user_id, - 'billing_city', - true - ); - $current_user['zip'] = get_user_meta( - $user_id, - 'billing_postcode', - true - ); - $current_user['country'] = get_user_meta( - $user_id, - 'billing_country', - true - ); - $current_user['state'] = get_user_meta( - $user_id, - 'billing_state', - true - ); - $current_user['phone'] = get_user_meta( - $user_id, - 'billing_phone', - true - ); - } - return array_filter($current_user); - } - - private static function getEmail($current_user_data, $pixel_data){ - if(isset($current_user_data['email'])){ - return $current_user_data['email']; - } - return self::getAAMField(AAMSettingsFields::EMAIL, $pixel_data); - } - - private static function getFirstName($current_user_data, $pixel_data){ - if(isset($current_user_data['first_name'])){ - return $current_user_data['first_name']; - } - return self::getAAMField(AAMSettingsFields::FIRST_NAME, $pixel_data); - } - - private static function getLastName($current_user_data, $pixel_data){ - if(isset($current_user_data['last_name'])){ - return $current_user_data['last_name']; - } - return self::getAAMField(AAMSettingsFields::LAST_NAME, $pixel_data); - } - - private static function getExternalID($current_user_data, $pixel_data){ - $external_ids = array(); - - if( isset( $current_user_data['id'] ) ){ - $external_ids[] = (string) $current_user_data['id']; - } - - $temp_external_id = self::getAAMField( - AAMSettingsFields::EXTERNAL_ID, - $pixel_data - ); - - if ( $temp_external_id ) { - $external_ids[] = $temp_external_id; - } - - if (isset($_SESSION[self::EXTERNAL_ID_COOKIE])) { - $external_ids[] = $_SESSION[self::EXTERNAL_ID_COOKIE]; - } - return $external_ids; - } - - private static function getPhone($current_user_data, $pixel_data){ - if(isset($current_user_data['phone'])){ - return $current_user_data['phone']; - } - return self::getAAMField(AAMSettingsFields::PHONE, $pixel_data); - } - - private static function getCity($current_user_data, $pixel_data){ - if(isset($current_user_data['city'])){ - return $current_user_data['city']; - } - return self::getAAMField(AAMSettingsFields::CITY, $pixel_data); - } - - private static function getZip($current_user_data, $pixel_data){ - if(isset($current_user_data['zip'])){ - return $current_user_data['zip']; - } - return self::getAAMField(AAMSettingsFields::ZIP_CODE, $pixel_data); - } - - private static function getCountry($current_user_data, $pixel_data){ - if(isset($current_user_data['country'])){ - return $current_user_data['country']; - } - return self::getAAMField(AAMSettingsFields::COUNTRY, $pixel_data); - } - - private static function getState($current_user_data, $pixel_data){ - if(isset($current_user_data['state'])){ - return $current_user_data['state']; - } - return self::getAAMField(AAMSettingsFields::STATE, $pixel_data); - } - - private static function getAAMField($key, $pixel_data){ - if(!isset($pixel_data[self::ADVANCED_MATCHING_LABEL])){ - return ''; - } - if(isset($pixel_data[self::ADVANCED_MATCHING_LABEL][$key])){ - $value = $pixel_data[self::ADVANCED_MATCHING_LABEL][$key]; - $_SESSION[$key] = $value; - return $value; - } - return ''; - } - - private static function getCustomData($key, $pixel_data){ - if(!isset($pixel_data[self::CUSTOM_DATA_LABEL])){ - return ''; - } - if(isset($pixel_data[self::CUSTOM_DATA_LABEL][$key])){ - return $pixel_data[self::CUSTOM_DATA_LABEL][$key]; - } - return ''; - } - - private static function getCustomDataArray($key, $pixel_data){ - if(!isset($pixel_data[self::CUSTOM_DATA_LABEL])){ - return ''; - } - if(isset($pixel_data[self::CUSTOM_DATA_LABEL][$key])){ - return $pixel_data[self::CUSTOM_DATA_LABEL][$key]; - } - return []; - } -} diff --git a/core/FacebookWordpressOptions.php b/core/FacebookWordpressOptions.php deleted file mode 100644 index 54efbac9..00000000 --- a/core/FacebookWordpressOptions.php +++ /dev/null @@ -1,328 +0,0 @@ - - self::getDefaultExternalBusinessId(), - FacebookPluginConfig::IS_FBE_INSTALLED_KEY => - self::getDefaultIsFbeInstalled(), - ); - if( - isset($old_options[FacebookPluginConfig::OLD_ACCESS_TOKEN_KEY]) - && !empty($old_options[FacebookPluginConfig::OLD_ACCESS_TOKEN_KEY]) - ){ - self::$options[FacebookPluginConfig::ACCESS_TOKEN_KEY] = - $old_options[FacebookPluginConfig::OLD_ACCESS_TOKEN_KEY]; - } - else{ - self::$options[FacebookPluginConfig::ACCESS_TOKEN_KEY] = - self::getDefaultAccessToken(); - } - if( - isset($old_options[FacebookPluginConfig::OLD_PIXEL_ID_KEY]) - && !empty($old_options[FacebookPluginConfig::OLD_PIXEL_ID_KEY]) - && is_numeric($old_options[FacebookPluginConfig::OLD_PIXEL_ID_KEY]) - ){ - self::$options[FacebookPluginConfig::PIXEL_ID_KEY] = - $old_options[FacebookPluginConfig::OLD_PIXEL_ID_KEY]; - } - else{ - self::$options[FacebookPluginConfig::PIXEL_ID_KEY] = - self::getDefaultPixelID(); - } - } - // If no options are present, the default values are used - else{ - self::$options = \get_option( - FacebookPluginConfig::SETTINGS_KEY, - array( - FacebookPluginConfig::PIXEL_ID_KEY => self::getDefaultPixelID(), - FacebookPluginConfig::ACCESS_TOKEN_KEY => - self::getDefaultAccessToken(), - FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY => - self::getDefaultExternalBusinessId(), - FacebookPluginConfig::IS_FBE_INSTALLED_KEY => - self::getDefaultIsFbeInstalled() - ) - ); - } - } - } - - public static function getPixelId() { - if (isset(self::$options[FacebookPluginConfig::PIXEL_ID_KEY])) { - return self::$options[FacebookPluginConfig::PIXEL_ID_KEY]; - } - - return self::getDefaultPixelID(); - } - - public static function getExternalBusinessId() { - if( - isset(self::$options[FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY]) - ){ - return self::$options[FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY]; - } - - return self::getDefaultExternalBusinessId(); - } - - public static function getIsFbeInstalled(){ - if( - isset(self::$options[FacebookPluginConfig::IS_FBE_INSTALLED_KEY]) - ){ - return self::$options[FacebookPluginConfig::IS_FBE_INSTALLED_KEY]; - } - - return self::getDefaultIsFbeInstalled(); - } - - public static function getAccessToken() { - if (isset(self::$options[FacebookPluginConfig::ACCESS_TOKEN_KEY])) { - return self::$options[FacebookPluginConfig::ACCESS_TOKEN_KEY]; - } - - return self::getDefaultAccessToken(); - } - - public static function getUserInfo() { - return self::$userInfo; - } - - public static function setUserInfo() { - add_action( - 'init', - array( - 'FacebookPixelPlugin\\Core\\FacebookWordpressOptions', - 'registerUserInfo' - ), - 0); - } - - public static function registerUserInfo() { - $current_user = wp_get_current_user(); - if (0 === $current_user->ID ) { - // User not logged in - self::$userInfo = array(); - } else { - $user_info = array_filter( - array( - // Keys documented in - // https://developers.facebook.com/docs/facebook-pixel/pixel-with-ads/conversion-tracking#advanced_match - AAMSettingsFields::EMAIL => $current_user->user_email, - AAMSettingsFields::FIRST_NAME => $current_user->user_firstname, - AAMSettingsFields::LAST_NAME => $current_user->user_lastname - ), - function ($value) { return $value !== null && $value !== ''; }); - self::$userInfo = AAMFieldsExtractor::getNormalizedUserData($user_info); - } - } - - public static function getVersionInfo() { - return self::$versionInfo; - } - - public static function setVersionInfo() { - global $wp_version; - - self::$versionInfo = array( - 'pluginVersion' => FacebookPluginConfig::PLUGIN_VERSION, - 'source' => FacebookPluginConfig::SOURCE, - 'version' => $wp_version - ); - } - - public static function getAgentString() { - return sprintf( - '%s-%s-%s', - self::$versionInfo['source'], - self::$versionInfo['version'], - self::$versionInfo['pluginVersion']); - } - - public static function getAAMSettings(){ - return self::$aamSettings; - } - - private static function setFbeBasedAAMSettings(){ - $installed_pixel = self::getPixelId(); - $settings_as_array = get_transient(FacebookPluginConfig::AAM_SETTINGS_KEY); - // If AAM_SETTINGS_KEY is present in the DB and corresponds to the installed - // pixel, it is converted into an AdsPixelSettings object - if( $settings_as_array !== false ){ - $aam_settings = new AdsPixelSettings(); - $aam_settings->setPixelId($settings_as_array['pixelId']); - $aam_settings->setEnableAutomaticMatching($settings_as_array['enableAutomaticMatching']); - $aam_settings->setEnabledAutomaticMatchingFields($settings_as_array['enabledAutomaticMatchingFields']); - if($installed_pixel == $aam_settings->getPixelId()){ - self::$aamSettings = $aam_settings; - } - } - // If the settings are not present - // they are fetched from Meta domain - // and cached in WP database if they are not null - if(!self::$aamSettings){ - $refresh_interval = - self::AAM_SETTINGS_REFRESH_IN_MINUTES*MINUTE_IN_SECONDS; - $aam_settings = AdsPixelSettings::buildFromPixelId( $installed_pixel ); - if($aam_settings){ - $settings_as_array = array( - 'pixelId' => $aam_settings->getPixelId(), - 'enableAutomaticMatching' => - $aam_settings->getEnableAutomaticMatching(), - 'enabledAutomaticMatchingFields' => - $aam_settings->getEnabledAutomaticMatchingFields(), - ); - set_transient(FacebookPluginConfig::AAM_SETTINGS_KEY, - $settings_as_array, $refresh_interval); - self::$aamSettings = $aam_settings; - } - } - } - - private static function setOldAAMSettings(){ - $old_options = \get_option(FacebookPluginConfig::OLD_SETTINGS_KEY); - if($old_options - && isset($old_options[FacebookPluginConfig::OLD_USE_PII]) - && $old_options[FacebookPluginConfig::OLD_USE_PII]){ - self::$aamSettings = new AdsPixelSettings( - array( - 'enableAutomaticMatching' => true, - 'enabledAutomaticMatchingFields' => - AAMSettingsFields::getAllFields(), - ) - ); - } else { - self::$aamSettings = new AdsPixelSettings( - array( - 'enableAutomaticMatching' => false, - 'enabledAutomaticMatchingFields' => array(), - ) - ); - } - } - - private static function setAAMSettings(){ - self::$aamSettings = null; - if( empty(self::getPixelId()) ){ - return; - } - if(self::getIsFbeInstalled()){ - self::setFbeBasedAAMSettings(); - } else { - self::setOldAAMSettings(); - } - } -} diff --git a/core/FacebookWordpressPixelInjection.php b/core/FacebookWordpressPixelInjection.php deleted file mode 100644 index 80c7beb9..00000000 --- a/core/FacebookWordpressPixelInjection.php +++ /dev/null @@ -1,85 +0,0 @@ - $value) { - $class_name = 'FacebookPixelPlugin\\Integration\\'.$value; - $class_name::injectPixelCode(); - } - add_action( - 'wp_footer', - array($this, 'sendPendingEvents')); - } - } - - public function sendPendingEvents(){ - $pending_events = - FacebookServerSideEvent::getInstance()->getPendingEvents(); - if(count($pending_events) > 0){ - do_action( - 'send_server_events', - $pending_events, - count($pending_events) - ); - } - } - - public function injectPixelCode() { - $pixel_id = FacebookPixel::getPixelId(); - if ( - (isset(self::$renderCache[FacebookPluginConfig::IS_PIXEL_RENDERED]) && - self::$renderCache[FacebookPluginConfig::IS_PIXEL_RENDERED] === true) || - empty($pixel_id) - ) { - return; - } - - self::$renderCache[FacebookPluginConfig::IS_PIXEL_RENDERED] = true; - echo(FacebookPixel::getPixelBaseCode()); - $capiIntegrationStatus = - FacebookWordpressOptions::getCapiIntegrationStatus(); - if($capiIntegrationStatus === '1'){ - echo(FacebookPixel::getOpenBridgeConfigCode()); - } - echo(FacebookPixel::getPixelInitCode( - FacebookWordpressOptions::getAgentString(), - FacebookWordpressOptions::getUserInfo())); - echo(FacebookPixel::getPixelPageViewCode()); - } - - public function injectPixelNoscriptCode() { - echo(FacebookPixel::getPixelNoscriptCode()); - } -} diff --git a/core/FacebookWordpressSettingsPage.php b/core/FacebookWordpressSettingsPage.php deleted file mode 100644 index ca469579..00000000 --- a/core/FacebookWordpressSettingsPage.php +++ /dev/null @@ -1,530 +0,0 @@ -optionsPage = add_options_page( - FacebookPluginConfig::ADMIN_PAGE_TITLE, - FacebookPluginConfig::ADMIN_MENU_TITLE, - FacebookPluginConfig::ADMIN_CAPABILITY, - FacebookPluginConfig::ADMIN_MENU_SLUG, - array($this, 'addFbeBox')); - - // Adding option to save Capig Integration settings in wp_options - \add_option(FacebookPluginConfig::CAPI_INTEGRATION_STATUS, - FacebookPluginConfig::CAPI_INTEGRATION_STATUS_DEFAULT); - \add_option(FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER, - FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_DEFAULT); - - } - - public function addFbeBox(){ - if (!current_user_can(FacebookPluginConfig::ADMIN_CAPABILITY)) { - wp_die(__( - 'You do not have sufficient permissions to access this page', - FacebookPluginConfig::TEXT_DOMAIN)); - } - $pixel_id_message = $this->getPreviousPixelIdMessage(); - if($pixel_id_message){ - echo $pixel_id_message; - } - echo $this->getFbeBrowserSettings(); - wp_enqueue_script('fbe_allinone_script'); - } - - private function getPreviousPixelIdMessage(){ - if(FacebookWordpressOptions::getIsFbeInstalled()){ - return null; - } - $pixel_id = FacebookWordPressOptions::getPixelId(); - if(empty($pixel_id)){ - return null; - } - $message = - sprintf('

Reuse the pixel id from your previous setup: '. - '%s

', - $pixel_id - ); - return $message; - } - - private function getFbeBrowserSettings(){ - ob_start(); - $fbe_extras = json_encode(array( - "business_config" => array( - "business" => array( - "name" => "Solutions_Engineering_Team" - ), - ), - "setup" => array( - "external_business_id" => - FacebookWordpressOptions::getExternalBusinessId(), - "timezone" => 'America/Los_Angeles', - "currency" => "USD", - "business_vertical" => "ECOMMERCE", - "channel" => "DEFAULT" - ), - "repeat" => false, - )); - ?> -
-
-
- - - -
-
-

Ads Creation

-
-
-
-
-
-

Ads Insights

-
-
-
-
-
-
- - - FacebookPluginConfig::SAVE_FBE_SETTINGS_ACTION_NAME, - '_wpnonce' => $nonce_value - ); - return add_query_arg($args, $simple_url); - } - - public function getCapiIntegrationStatusSaveUrl() { - $nonce_value = wp_create_nonce( - FacebookPluginConfig::SAVE_CAPI_INTEGRATION_STATUS_ACTION_NAME - ); - $simple_url = admin_url('admin-ajax.php'); - $args = array( - 'action' => - FacebookPluginConfig::SAVE_CAPI_INTEGRATION_STATUS_ACTION_NAME, - '_wpnonce' => $nonce_value - ); - return add_query_arg($args, $simple_url); - } - - public function getCapiIntegrationEventsFilterSaveUrl() { - $nonce_value = wp_create_nonce( - FacebookPluginConfig::SAVE_CAPI_INTEGRATION_EVENTS_FILTER_ACTION_NAME - ); - $simple_url = admin_url('admin-ajax.php'); - $args = array( - 'action' => - FacebookPluginConfig::SAVE_CAPI_INTEGRATION_EVENTS_FILTER_ACTION_NAME, - '_wpnonce' => $nonce_value - ); - return add_query_arg($args, $simple_url); - } - - public function getCapiPiiCachingStatusSaveUrl() { - $nonce_value = wp_create_nonce( - FacebookPluginConfig::SAVE_CAPI_PII_CACHING_STATUS_ACTION_NAME - ); - $simple_url = admin_url('admin-ajax.php'); - $args = array( - 'action' => - FacebookPluginConfig::SAVE_CAPI_PII_CACHING_STATUS_ACTION_NAME, - '_wpnonce' => $nonce_value - ); - return add_query_arg($args, $simple_url); - } - - public function getDeleteFbeSettingsAjaxRoute(){ - $nonce_value = wp_create_nonce( - FacebookPluginConfig::DELETE_FBE_SETTINGS_ACTION_NAME - ); - $simple_url = admin_url('admin-ajax.php'); - $args = array( - 'action' => FacebookPluginConfig::DELETE_FBE_SETTINGS_ACTION_NAME, - '_wpnonce' => $nonce_value - ); - return add_query_arg($args, $simple_url); - } - - public function addSettingsLink($links) { - $settings = array( - 'settings' => sprintf( - '%s', - admin_url('options-general.php?page=' . - FacebookPluginConfig::ADMIN_MENU_SLUG), - 'Settings') - ); - return array_merge($settings, $links); - } - - public function registerNotices() { - $is_fbe_installed = FacebookWordpressOptions::getIsFbeInstalled(); - $current_screen_id = get_current_screen()->id; - - if (current_user_can(FacebookPluginConfig::ADMIN_CAPABILITY) && - in_array($current_screen_id, array('dashboard', 'plugins'), true)){ - if( $is_fbe_installed == '0' && !get_user_meta( - get_current_user_id(), - FacebookPluginConfig::ADMIN_IGNORE_FBE_NOT_INSTALLED_NOTICE, - true)){ - add_action('admin_notices', array($this, 'fbeNotInstalledNotice')); - } - if( $is_fbe_installed == '1' && !get_user_meta( - get_current_user_id(), - FacebookPluginConfig::ADMIN_IGNORE_PLUGIN_REVIEW_NOTICE, - true)){ - add_action('admin_notices', array($this, 'pluginReviewNotice')); - } - } - } - - public function getCustomizedFbeNotInstalledNotice(){ - $valid_pixel_id = !empty(FacebookWordPressOptions:: getPixelId()); - $valid_access_token = !empty(FacebookWordPressOptions::getAccessToken()); - $message = ''; - $plugin_name_tag = sprintf('%s', - FacebookPluginConfig::PLUGIN_NAME); - if($valid_pixel_id){ - if($valid_access_token){ - $message = sprintf('Easily manage your connection to Meta with %s.', - $plugin_name_tag); - } - else{ - $message = sprintf('%s gives you access to the Conversions API.', - $plugin_name_tag); - } - } - else{ - $message = sprintf('%s is almost ready.', $plugin_name_tag); - } - return $message.' To complete your configuration, '. - 'follow the setup steps.'; - } - - public function setNotice($notice, $dismiss_config, $notice_type) { - $url = admin_url('options-general.php?page=' . - FacebookPluginConfig::ADMIN_MENU_SLUG); - - $link = sprintf( - $notice, - esc_url($url)); - printf( - ' -
-

%s

- -
- ', - $notice_type, - $link, - esc_url(add_query_arg($dismiss_config, '')), - esc_html__( - 'Dismiss this notice.', - FacebookPluginConfig::TEXT_DOMAIN)); - } - - public function pluginReviewNotice(){ - $message = sprintf('Let us know what you think about %s. '. - 'Leave a review on this page.', - FacebookPluginConfig::PLUGIN_NAME, - FacebookPluginConfig::PLUGIN_REVIEW_PAGE - ); - $this->setNotice( - __( - $message, - FacebookPluginConfig::TEXT_DOMAIN), - FacebookPluginConfig::ADMIN_DISMISS_PLUGIN_REVIEW_NOTICE, - 'info' - ); - } - - public function fbeNotInstalledNotice() { - $message = $this->getCustomizedFbeNotInstalledNotice(); - $this->setNotice( - __( - $message, - FacebookPluginConfig::TEXT_DOMAIN), - FacebookPluginConfig::ADMIN_DISMISS_FBE_NOT_INSTALLED_NOTICE, - 'warning' - ); - } - - public function dismissNotices() { - $user_id = get_current_user_id(); - if (isset( - $_GET[FacebookPluginConfig::ADMIN_DISMISS_FBE_NOT_INSTALLED_NOTICE] - )){ - update_user_meta($user_id, - FacebookPluginConfig::ADMIN_IGNORE_FBE_NOT_INSTALLED_NOTICE, - true); - } - if (isset( - $_GET[FacebookPluginConfig::ADMIN_DISMISS_PLUGIN_REVIEW_NOTICE] - )){ - update_user_meta($user_id, - FacebookPluginConfig::ADMIN_IGNORE_PLUGIN_REVIEW_NOTICE, - true); - } - } -} diff --git a/core/FacebookWordpressSettingsRecorder.php b/core/FacebookWordpressSettingsRecorder.php deleted file mode 100644 index d7e2c570..00000000 --- a/core/FacebookWordpressSettingsRecorder.php +++ /dev/null @@ -1,187 +0,0 @@ - true, - 'msg' => $body, - ); - wp_send_json($res); - return $res; - } - - private function handleUnauthorizedRequest(){ - $res = array( - 'success' => false, - 'msg' => 'Unauthorized user', - ); - wp_send_json($res, 403); - return $res; - } - - private function handleInvalidRequest(){ - $res = array( - 'success' => false, - 'msg' => 'Invalid values', - ); - wp_send_json($res, 400); - return $res; - } - - public function saveFbeSettings(){ - if (!current_user_can('administrator')) { - return $this->handleUnauthorizedRequest(); - } - check_admin_referer( - FacebookPluginConfig::SAVE_FBE_SETTINGS_ACTION_NAME - ); - $pixel_id = sanitize_text_field($_POST['pixelId']); - $access_token = sanitize_text_field($_POST['accessToken']); - $external_business_id = sanitize_text_field( - $_POST['externalBusinessId'] - ); - if(empty($pixel_id) - || empty($access_token) - || empty($external_business_id)){ - return $this->handleInvalidRequest(); - } - $settings = array( - FacebookPluginConfig::PIXEL_ID_KEY => $pixel_id, - FacebookPluginConfig::ACCESS_TOKEN_KEY => $access_token, - FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY => - $external_business_id, - FacebookPluginConfig::IS_FBE_INSTALLED_KEY => '1' - ); - \update_option( - FacebookPluginConfig::SETTINGS_KEY, - $settings - ); - return $this->handleSuccessRequest($settings); - } - - public function saveCapiIntegrationStatus(){ - if (!current_user_can('administrator')) { - return $this->handleUnauthorizedRequest(); - } - - // Cross origin iframe and local wordpress options are not in sync. - // Thus if request is made and pixel is not available show error. - if (empty(FacebookWordPressOptions::getPixelId())) { - // Reset wp_option value - \update_option(FacebookPluginConfig::CAPI_INTEGRATION_STATUS, - FacebookPluginConfig::CAPI_INTEGRATION_STATUS_DEFAULT); - return $this->handleInvalidRequest(); - } - - check_admin_referer( - FacebookPluginConfig::SAVE_CAPI_INTEGRATION_STATUS_ACTION_NAME - ); - $val = sanitize_text_field($_POST['val']); - - if(!($val === '0' || $val === '1')){ - return $this->handleInvalidRequest(); - } - - \update_option(FacebookPluginConfig::CAPI_INTEGRATION_STATUS, $val); - return $this->handleSuccessRequest($val); - } - - public function saveCapiIntegrationEventsFilter(){ - if (!current_user_can('administrator')) { - return $this->handleUnauthorizedRequest(); - } - - // Cross origin iframe and local wordpress options are not in sync. - // Thus if request is made and pixel is not available show error. - if (empty(FacebookWordPressOptions::getPixelId())) { - // Reset wp_option value - \update_option(FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER, - FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_DEFAULT); - return $this->handleInvalidRequest(); - } - - check_admin_referer( - FacebookPluginConfig::SAVE_CAPI_INTEGRATION_EVENTS_FILTER_ACTION_NAME - ); - $val = sanitize_text_field($_POST['val']); - $constFilterPageView = - FacebookPluginConfig::CAPI_INTEGRATION_FILTER_PAGE_VIEW_EVENT; - $constKeepPageView = - FacebookPluginConfig::CAPI_INTEGRATION_KEEP_PAGE_VIEW_EVENT; - - if(!($val === $constFilterPageView || $val === $constKeepPageView)){ - return $this->handleInvalidRequest(); - } - - $pageViewFiltered = - FacebookWordpressOptions::getCapiIntegrationPageViewFiltered(); - - // If pageViewFiltered and new val are not in sync update option - if ($val === $constKeepPageView && $pageViewFiltered) { - \update_option(FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER, - FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_DEFAULT); - } else if ($val === $constFilterPageView && !$pageViewFiltered) { - \update_option(FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER, - FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_DEFAULT . - ',PageView'); - } - - return $this->handleSuccessRequest($val); - } - - public function saveCapiPiiCachingStatus(){ - if (!current_user_can('administrator')) { - return $this->handleUnauthorizedRequest(); - } - - // Cross origin iframe and local wordpress options are not in sync. - // Thus if request is made and pixel is not available show error. - if (empty(FacebookWordPressOptions::getPixelId())) { - // Reset wp_option value - \update_option(FacebookPluginConfig::CAPI_PII_CACHING_STATUS, - FacebookPluginConfig::CAPI_PII_CACHING_STATUS_DEFAULT); - return $this->handleInvalidRequest(); - } - - check_admin_referer( - FacebookPluginConfig::SAVE_CAPI_PII_CACHING_STATUS_ACTION_NAME - ); - $val = sanitize_text_field($_POST['val']); - - if(!($val === '0' || $val === '1')){ - return $this->handleInvalidRequest(); - } - - \update_option(FacebookPluginConfig::CAPI_PII_CACHING_STATUS, $val); - return $this->handleSuccessRequest($val); - } - - public function deleteFbeSettings(){ - if (!current_user_can('administrator')) { - return $this->handleUnauthorizedRequest(); - } - check_admin_referer( - FacebookPluginConfig::DELETE_FBE_SETTINGS_ACTION_NAME - ); - \delete_option( FacebookPluginConfig::SETTINGS_KEY ); - \delete_transient( FacebookPluginConfig::AAM_SETTINGS_KEY ); - // Cross origin iframe and local wordpress options are not in sync. - // Thus do not delete Capi option along with Fbe. - return $this->handleSuccessRequest('Done'); - } -} diff --git a/core/PixelRenderer.php b/core/PixelRenderer.php deleted file mode 100644 index 9067dd20..00000000 --- a/core/PixelRenderer.php +++ /dev/null @@ -1,78 +0,0 @@ -%s"; - const FBQ_EVENT_CODE = " - fbq('%s', '%s', %s, %s); - "; - const FBQ_AGENT_CODE = " - fbq('set', 'agent', '%s', '%s');"; - - public static function render($events, $fb_integration_tracking, - $script_tag = true) { - if (empty($events)) { - return ""; - } - // The first line of the injected script will set the agent - // for all the events passed in $events - $code = sprintf( - self::FBQ_AGENT_CODE, - FacebookWordpressOptions::getAgentString(), - FacebookWordpressOptions::getPixelId() - ); - foreach ($events as $event) { - $code .= self::getPixelTrackCode($event, $fb_integration_tracking); - } - return $script_tag ? sprintf(self::SCRIPT_TAG, $code) : $code; - } - - private static function getPixelTrackCode($event, $fb_integration_tracking) { - $event_data[self::EVENT_ID] = $event->getEventId(); - - $custom_data = $event->getCustomData() !== null ? - $event->getCustomData() : - new CustomData(); - - $normalized_custom_data = $custom_data->normalize(); - if (!is_null($fb_integration_tracking)) { - $normalized_custom_data[ - self::FB_INTEGRATION_TRACKING] = $fb_integration_tracking; - } - - $class = new ReflectionClass('FacebookPixelPlugin\Core\FacebookPixel'); - return sprintf( - self::FBQ_EVENT_CODE, - $class->getConstant(strtoupper($event->getEventName())) !== false - ? self::TRACK : self::TRACK_CUSTOM, - $event->getEventName(), - json_encode($normalized_custom_data, JSON_PRETTY_PRINT), - json_encode($event_data, JSON_PRETTY_PRINT) - ); - } -} diff --git a/core/ServerEventAsyncTask.php b/core/ServerEventAsyncTask.php deleted file mode 100644 index 6ee161b2..00000000 --- a/core/ServerEventAsyncTask.php +++ /dev/null @@ -1,152 +0,0 @@ - 'emails', - AAMSettingsFields::FIRST_NAME => 'first_names', - AAMSettingsFields::LAST_NAME => 'last_names', - AAMSettingsFields::GENDER => 'genders', - AAMSettingsFields::DATE_OF_BIRTH => 'dates_of_birth', - AAMSettingsFields::EXTERNAL_ID => 'external_ids', - AAMSettingsFields::PHONE => 'phones', - AAMSettingsFields::CITY => 'cities', - AAMSettingsFields::STATE => 'states', - AAMSettingsFields::ZIP_CODE => 'zip_codes', - AAMSettingsFields::COUNTRY => 'country_codes', - ]; - $user_data = array(); - foreach($user_data_normalized as $norm_key => $field){ - if(isset($norm_key_to_key[$norm_key])){ - $user_data[$norm_key_to_key[$norm_key]] = $field; - } - else{ - $user_data[$norm_key] = $field; - } - } - return $user_data; - } - - private function convert_array_to_event($event_as_array){ - $event = new Event($event_as_array); - // If user_data exists, an UserData object is created - // and set - if(isset($event_as_array['user_data'])){ - // The method convert_user_data converts the keys used in the - // normalized array to the keys used in the constructor of UserData - $user_data = new UserData($this->convert_user_data( - $event_as_array['user_data'] - )); - $event->setUserData($user_data); - } - // If custom_data exists, a CustomData object is created and set - if(isset($event_as_array['custom_data'])){ - $custom_data = new CustomData($event_as_array['custom_data']); - // If contents exists in custom_data, an array of Content is created - // and set - if(isset($event_as_array['custom_data']['contents'])){ - $contents = array(); - foreach( - $event_as_array['custom_data']['contents'] as $contents_as_array - ){ - // The normalized contents array encodes product id as id - // but the constructor of Content requires product_id - if(isset($contents_as_array['id'])){ - $contents_as_array['product_id'] = $contents_as_array['id']; - } - $contents[] = new Content($contents_as_array); - } - $custom_data->setContents($contents); - } - if(isset($event_as_array['custom_data']['fb_integration_tracking'])){ - $custom_data->addCustomProperty('fb_integration_tracking', - $event_as_array['custom_data']['fb_integration_tracking']); - } - $event->setCustomData($custom_data); - } - return $event; - } - - protected function prepare_data($data) { - try { - if (!empty($data)) { - $num_events = $data[1]; - $events = $data[0]; - // $data[0] can be a single event or an array - // We want to receive it as an array - if($num_events == 1){ - $events = array($events); - } - // Each event is casted to a php array with normalize() - $events_as_array = array(); - foreach($events as $event){ - $events_as_array[] = $event->normalize(); - } - // The array of events is converted to a JSON string - // and encoded in base 64 - return array( - 'event_data' => base64_encode(json_encode($events_as_array)), - 'num_events'=>$data[1] - ); - } - } catch (\Exception $ex) { - error_log($ex); - } - - return array(); - } - - protected function run_action() { - try { - $num_events = $_POST['num_events']; - if( $num_events == 0 ){ - return; - } - // $_POST['event_data'] is decoded from base 64, returning a JSON string - // and decoded as a php array - $events_as_array = json_decode(base64_decode($_POST['event_data']), true); - // If the passed json string is invalid, no processing is done - if(!$events_as_array){ - return; - } - $events = array(); - // Every event is a php array and casted to an Event object - foreach( $events_as_array as $event_as_array ){ - $event = $this->convert_array_to_event($event_as_array); - $events[] = $event; - } - FacebookServerSideEvent::send($events); - } - catch (\Exception $ex) { - error_log($ex); - } - } -} diff --git a/core/ServerEventFactory.php b/core/ServerEventFactory.php deleted file mode 100644 index 8178c906..00000000 --- a/core/ServerEventFactory.php +++ /dev/null @@ -1,352 +0,0 @@ -setClientIpAddress(self::getIpAddress()) - ->setClientUserAgent(self::getHttpUserAgent()) - ->setFbp(self::getFbp()) - ->setFbc(self::getFbc()); - - $event = (new Event()) - ->setEventName($event_name) - ->setEventTime(time()) - ->setEventId(EventIdGenerator::guidv4()) - ->setEventSourceUrl( - self::getRequestUri($prefer_referrer_for_event_src)) - ->setActionSource('website') - ->setUserData($user_data) - ->setCustomData(new CustomData()); - - return $event; - } - - private static function getIpAddress() { - $HEADERS_TO_SCAN = array( - 'HTTP_CLIENT_IP', - 'HTTP_X_FORWARDED_FOR', - 'HTTP_X_FORWARDED', - 'HTTP_X_CLUSTER_CLIENT_IP', - 'HTTP_FORWARDED_FOR', - 'HTTP_FORWARDED', - 'REMOTE_ADDR' - ); - - foreach ($HEADERS_TO_SCAN as $header) { - if (isset($_SERVER[$header])) { - $ip_list = explode(',', $_SERVER[$header]); - foreach($ip_list as $ip) { - $trimmed_ip = trim($ip); - if (self::isValidIpAddress($trimmed_ip)) { - return $trimmed_ip; - } - } - } - } - - return null; - } - - private static function getHttpUserAgent() { - $user_agent = null; - - if (!empty($_SERVER['HTTP_USER_AGENT'])) { - $user_agent = $_SERVER['HTTP_USER_AGENT']; - } - - return $user_agent; - } - - private static function getRequestUri($prefer_referrer_for_event_src) { - if ($prefer_referrer_for_event_src && !empty($_SERVER['HTTP_REFERER'])) { - return $_SERVER['HTTP_REFERER']; - } - - $url = "http://"; - if(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { - $url = "https://"; - } - - if (!empty($_SERVER['HTTP_HOST'])) { - $url .= $_SERVER['HTTP_HOST']; - } - - if (!empty($_SERVER['REQUEST_URI'])) { - $url .= $_SERVER['REQUEST_URI']; - } - - return $url; - } - - private static function getFbp() { - $fbp = null; - - if (!empty($_COOKIE['_fbp'])) { - $fbp = $_COOKIE['_fbp']; - } - - return $fbp; - } - - private static function getFbc() { - $fbc = null; - - if (!empty($_COOKIE['_fbc'])) { - $fbc = $_COOKIE['_fbc']; - $_SESSION['_fbc'] = $fbc; - } - - if (!$fbc && isset($_GET['fbclid'])) { - $fbclid = $_GET['fbclid']; - $cur_time = (int)(microtime(true)*1000); - $fbc = "fb.1.".$cur_time.".".rawurldecode($fbclid); - } - - if (!$fbc && isset($_SESSION['_fbc'])) { - $fbc = $_SESSION['_fbc']; - } - - if ($fbc) { - $_SESSION['_fbc'] = $fbc; - } - - return $fbc; - } - - private static function isValidIpAddress($ip_address) { - return filter_var($ip_address, - FILTER_VALIDATE_IP, - FILTER_FLAG_IPV4 - | FILTER_FLAG_IPV6 - | FILTER_FLAG_NO_PRIV_RANGE - | FILTER_FLAG_NO_RES_RANGE); - } - - /* - Given that the data extracted by the integration classes is a mix of - user data and custom data, - this function splits these fields in two arrays - and user data is formatted with the AAM field setting - */ - private static function splitUserDataAndCustomData($data){ - $user_data = array(); - $custom_data = array(); - $key_to_aam_field = array( - 'email' => AAMSettingsFields::EMAIL, - 'first_name' => AAMSettingsFields::FIRST_NAME, - 'last_name' => AAMSettingsFields::LAST_NAME, - 'phone' => AAMSettingsFields::PHONE, - 'state' => AAMSettingsFields::STATE, - 'country' => AAMSettingsFields::COUNTRY, - 'city' => AAMSettingsFields::CITY, - 'zip' => AAMSettingsFields::ZIP_CODE, - 'gender' => AAMSettingsFields::GENDER, - 'date_of_birth' => AAMSettingsFields::DATE_OF_BIRTH, - 'external_id' => AAMSettingsFields::EXTERNAL_ID, - ); - foreach( $data as $key => $value ){ - if( isset( $key_to_aam_field[$key] ) ){ - $user_data[$key_to_aam_field[$key]] = $value; - } - else{ - $custom_data[$key] = $value; - } - } - return array( - 'user_data' => $user_data, - 'custom_data' => $custom_data - ); - } - - public static function safeCreateEvent( - $event_name, - $callback, - $arguments, - $integration, - $prefer_referrer_for_event_src = false) - { - $event = self::newEvent($event_name, $prefer_referrer_for_event_src); - - try { - $data = call_user_func_array($callback, $arguments); - $data_split = self::splitUserDataAndCustomData($data); - $user_data_array = $data_split['user_data']; - $custom_data_array = $data_split['custom_data']; - $user_data_array = - AAMFieldsExtractor::getNormalizedUserData($user_data_array); - - $user_data = $event->getUserData(); - if( - isset($user_data_array[AAMSettingsFields::EMAIL]) - ){ - $user_data->setEmail( - $user_data_array[AAMSettingsFields::EMAIL] - ); - } - if( - isset($user_data_array[AAMSettingsFields::FIRST_NAME]) - ){ - $user_data->setFirstName( - $user_data_array[AAMSettingsFields::FIRST_NAME] - ); - } - if( - isset($user_data_array[AAMSettingsFields::LAST_NAME]) - ){ - $user_data->setLastName( - $user_data_array[AAMSettingsFields::LAST_NAME] - ); - } - if( - isset($user_data_array[AAMSettingsFields::GENDER]) - ){ - $user_data->setGender( - $user_data_array[AAMSettingsFields::GENDER] - ); - } - if( - isset($user_data_array[AAMSettingsFields::DATE_OF_BIRTH]) - ){ - $user_data->setDateOfBirth( - $user_data_array[AAMSettingsFields::DATE_OF_BIRTH]); - } - if( - isset($user_data_array[AAMSettingsFields::EXTERNAL_ID]) && - !is_null($user_data_array[AAMSettingsFields::EXTERNAL_ID]) - ){ - if (is_array($user_data_array[AAMSettingsFields::EXTERNAL_ID])) { - $external_ids = $user_data_array[AAMSettingsFields::EXTERNAL_ID]; - $hashed_eids = array(); - foreach($external_ids as $k => $v) { - $hashed_eids[$k] = hash("sha256", $v); - } - $user_data->setExternalIds($hashed_eids); - } else { - $user_data->setExternalId( - hash("sha256", $user_data_array[AAMSettingsFields::EXTERNAL_ID]) - ); - } - } - if( - isset($user_data_array[AAMSettingsFields::PHONE]) - ){ - $user_data->setPhone( - $user_data_array[AAMSettingsFields::PHONE] - ); - } - if( - isset($user_data_array[AAMSettingsFields::CITY]) - ){ - $user_data->setCity( - $user_data_array[AAMSettingsFields::CITY] - ); - } - if( - isset($user_data_array[AAMSettingsFields::STATE]) - ){ - $user_data->setState( - $user_data_array[AAMSettingsFields::STATE] - ); - } - if( - isset($user_data_array[AAMSettingsFields::ZIP_CODE]) - ){ - $user_data->setZipCode( - $user_data_array[AAMSettingsFields::ZIP_CODE] - ); - } - if( - isset($user_data_array[AAMSettingsFields::COUNTRY]) - ){ - $user_data->setCountryCode( - $user_data_array[AAMSettingsFields::COUNTRY] - ); - } - - $custom_data = $event->getCustomData(); - $custom_data->addCustomProperty('fb_integration_tracking', $integration); - - if (!empty($data['currency'])) { - $custom_data->setCurrency($custom_data_array['currency']); - } - - if (!empty($data['value'])) { - $custom_data->setValue($custom_data_array['value']); - } - - if (!empty($data['contents'])) { - $custom_data->setContents($custom_data_array['contents']); - } - - if (!empty($data['content_ids'])) { - $custom_data->setContentIds($custom_data_array['content_ids']); - } - - if (!empty($data['content_type'])) { - $custom_data->setContentType($custom_data_array['content_type']); - } - - if (!empty($data['num_items'])) { - $custom_data->setNumItems($custom_data_array['num_items']); - } - - if (!empty($data['content_name'])) { - $custom_data->setContentName($custom_data_array['content_name']); - } - - if (!empty($data['content_category'])){ - $custom_data->setContentCategory( - $custom_data_array['content_category'] - ); - } - } catch (\Exception $e) { - error_log(json_encode($e)); - } - - return $event; - } - - public static function splitName($name) { - $first_name = $name; - $last_name = null; - $index = strpos($name, ' '); - if ($index !== false) { - $first_name = substr($name, 0, $index); - $last_name = substr($name, $index + 1); - } - - return array($first_name, $last_name); - } -} diff --git a/core/class-aamfieldsextractor.php b/core/class-aamfieldsextractor.php new file mode 100644 index 00000000..a36cb02a --- /dev/null +++ b/core/class-aamfieldsextractor.php @@ -0,0 +1,104 @@ +getEnableAutomaticMatching() ) { + return array(); + } + + foreach ( $user_data_array as $key => $value ) { + if ( ! in_array( + $key, + $aam_setttings->getEnabledAutomaticMatchingFields(), + true + ) ) { + unset( $user_data_array[ $key ] ); + } + } + + if ( + isset( $user_data_array[ AAMSettingsFields::GENDER ] ) && + ! empty( $user_data_array[ AAMSettingsFields::GENDER ] ) + ) { + $user_data_array[ AAMSettingsFields::GENDER ] = + $user_data_array[ AAMSettingsFields::GENDER ][0]; + } + if ( + isset( $user_data_array[ AAMSettingsFields::DATE_OF_BIRTH ] ) + ) { + $unix_timestamp = + strtotime( $user_data_array[ AAMSettingsFields::DATE_OF_BIRTH ] ); + if ( ! $unix_timestamp ) { + unset( $user_data_array[ AAMSettingsFields::DATE_OF_BIRTH ] ); + } else { + $formatted_date = gmdate( 'Ymd', $unix_timestamp ); + if ( ! $formatted_date ) { + unset( $user_data_array[ AAMSettingsFields::DATE_OF_BIRTH ] ); + } else { + $user_data_array[ AAMSettingsFields::DATE_OF_BIRTH ] = + $formatted_date; + } + } + } + + foreach ( $user_data_array as $field => $data ) { + try { + if ( is_array( $data ) ) { + $res = array(); + foreach ( $data as $key => $value ) { + $normalized_value = Normalizer::normalize( $field, $value ); + $res[ $key ] = $normalized_value; + } + $user_data_array[ $field ] = $res; + } else { + $normalized_value = Normalizer::normalize( $field, $data ); + $user_data_array[ $field ] = $normalized_value; + } + } catch ( \Exception $e ) { + unset( $user_data_array[ $field ] ); + } + } + + return $user_data_array; + } +} diff --git a/core/class-aamsettingsfields.php b/core/class-aamsettingsfields.php new file mode 100644 index 00000000..8e5b8525 --- /dev/null +++ b/core/class-aamsettingsfields.php @@ -0,0 +1,66 @@ + 'string', + 'event_time' => 'integer', + 'user_data' => 'object', + 'custom_data' => 'object', + 'event_source_url' => 'string', + 'opt_out' => 'boolean', + 'event_id' => 'string', + 'action_source' => 'string', + 'data_processing' => 'string', + 'data_processing_options' => 'array', + 'data_processing_options_country' => 'integer', + 'data_processing_options_state' => 'integer', + 'app_data' => 'object', + 'extinfo' => 'object', + 'referrer_url' => 'string', + ); + + const VALID_CUSTOM_DATA = array( + 'value', + 'currency', + 'content_name', + 'content_category', + 'content_ids', + 'contents', + 'content_type', + 'order_id', + 'predicted_ltv', + 'num_items', + 'status', + 'search_string', + 'item_number', + 'delivery_category', + 'custom_properties', + ); + + /** + * Hook into WordPress's AJAX actions to handle sending a CAPI event. + */ + public function __construct() { + add_action( + 'wp_ajax_send_capi_event', + array( $this, 'send_capi_event' ) + ); + } + + /** + * Retrieves the event custom data if available. + * + * @param array $custom_data The custom data to retrieve. + * @return array The custom data array if not empty, + * otherwise an empty array. + */ + public static function get_event_custom_data( $custom_data ) { + if ( empty( $custom_data ) ) { + return array(); + } else { + return $custom_data; + } + } + + /** + * Sends a CAPI event. + * + * This function is responsible for sending a CAPI + * event to Facebook's servers. It expects the event name + * and custom data to be provided in the $_POST superglobal and + * will validate the custom data before sending the event. + * If the custom data is invalid, it will return an error message. + * + * If the event is successfully sent, it will return the + * response from Facebook's servers. + */ + public function send_capi_event() { + $nonce = isset( $_POST['nonce'] ) ? + sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : null; + if ( ! isset( $nonce ) || + ! wp_verify_nonce( $nonce, 'send_capi_event_nonce' ) ) { + wp_send_json_error( + wp_json_encode( + array( + 'error' => array( + 'message' => 'Invalid nonce', + 'error_user_msg' => 'Invalid nonce', + ), + ) + ) + ); + wp_die(); + } + + $api_version = ApiConfig::APIVersion; + $pixel_id = FacebookWordpressOptions::get_pixel_id(); + $access_token = FacebookWordpressOptions::get_access_token(); + + $url = 'https://graph.facebook.com/v' . + $api_version . '/' . $pixel_id . + '/events?access_token=' . $access_token; + + $event_name = isset( $_POST['event_name'] ) ? + sanitize_text_field( wp_unslash( $_POST['event_name'] ) ) : null; + + if ( empty( $_POST['payload'] ) && ! empty( $event_name ) ) { + $custom_data = isset( $_POST['custom_data'] ) ? + $_POST['custom_data'] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $invalid_custom_data = + self::get_invalid_event_custom_data( $custom_data ); + if ( ! empty( $invalid_custom_data ) ) { + $invalid_custom_data_msg = implode( ',', $invalid_custom_data ); + wp_send_json_error( + wp_json_encode( + array( + 'error' => array( + 'message' => 'Invalid custom_data attribute', + 'error_user_msg' => + 'Invalid custom_data attributes: ' + . $invalid_custom_data_msg, + + ), + ) + ) + ); + wp_die(); + } else { + $event = ServerEventFactory::safe_create_event( + $event_name, + array( $this, 'get_event_custom_data' ), + array( $custom_data ), + 'fb-capi-event', + true + ); + + $events = array(); + array_push( $events, $event ); + + $event_request = ( new EventRequest( $pixel_id ) ) + ->setEvents( $events ) + ->setTestEventCode( + isset( $_POST['test_event_code'] ) ? + sanitize_text_field( + wp_unslash( $_POST['test_event_code'] ) + ) : + null + ); + + $normalized_event = $event_request->normalize(); + + if ( ! empty( $_POST['user_data'] ) ) { + foreach ( $normalized_event['data'] as $key => $value ) { + $normalized_event['data'][ $key ]['user_data'] += + isset( $_POST['user_data'] ) ? + $_POST['user_data'] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash + } + } + + $payload = wp_json_encode( $normalized_event ); + } + } else { + $validated_payload = + self::validate_payload( + isset( $_POST['payload'] ) ? + $_POST['payload'] : null // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash + ); + if ( ! $validated_payload['valid'] ) { + wp_send_json_error( + wp_json_encode( + array( + 'error' => array( + 'message' => $validated_payload['message'], + 'error_user_msg' => + $validated_payload['error_user_msg'], + ), + ) + ) + ); + wp_die(); + } else { + $payload = wp_json_encode( + isset( $_POST['payload'] ) + ? $_POST['payload'] : null // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash + ); + } + } + + $args = array( + 'body' => $payload, + 'headers' => array( + 'Content-Type' => 'application/json', + 'Accept' => '*/*', + ), + 'method' => 'POST', + ); + + $response = wp_remote_post( $url, $args ); + + if ( is_wp_error( $response ) ) { + wp_send_json_error( $response->get_error_message() ); + } else { + wp_send_json_success( wp_remote_retrieve_body( $response ) ); + } + wp_die(); + } + + /** + * Given a custom data array, returns an array of + * the custom data keys which are not valid + * + * @param array $custom_data The custom data array to check. + * @return array An array of the invalid custom data keys. + */ + public function get_invalid_event_custom_data( $custom_data ) { + $invalid_custom_data = array(); + foreach ( $custom_data as $key => $value ) { + if ( ! in_array( $key, self::VALID_CUSTOM_DATA, true ) ) { + array_push( $invalid_custom_data, $key ); + } + } + return $invalid_custom_data; + } + + /** + * Validates the given payload to ensure it meets the required criteria. + * + * This function checks if the payload is non-empty and contains valid JSON. + * It further verifies that each event within the payload's data includes + * all required attributes, with appropriate data types, and that + * custom data attributes are valid. + * + * @param array $payload The payload to validate. + * @return array An associative array containing a 'valid' key indicating + * the validity of the payload, and 'message' and + * 'error_user_msg' keys providing error details if invalid. + */ + public function validate_payload( $payload ) { + $response = array( + 'valid' => true, + ); + if ( empty( $payload ) ) { + $response['valid'] = false; + $response['message'] = 'Empty payload'; + $response['error_user_msg'] = 'Payload is empty.'; + } elseif ( ! self::validate_json( $payload ) ) { + $response['valid'] = false; + $response['message'] = 'Invalid JSON in payload'; + $response['error_user_msg'] = 'Invalid JSON in payload.'; + } else { + foreach ( $payload['data'] as $event ) { + foreach ( self::REQUIRED_EVENT_DATA as $attribute ) { + if ( ! array_key_exists( $attribute, $event ) ) { + if ( ! empty( $response['message'] ) ) { + $response['error_user_msg'] .= + ", {$attribute} attribute is missing"; + } else { + $response['valid'] = false; + $response['message'] = 'Missing required attribute'; + $response['error_user_msg'] = + "{$attribute} attribute is missing"; + } + } + } + + if ( $response['valid'] ) { + $invalid_attributes = self::validate_event_attributes_type( + $event + ); + if ( ! empty( $invalid_attributes ) ) { + $invalid_attributes_msg = implode( + ',', + $invalid_attributes + ); + $response['valid'] = false; + $response['message'] = 'Invalid attribute type'; + $response['error_user_msg'] = + "Invalid attribute type: {$invalid_attributes_msg}"; + } elseif ( isset( $event['custom_data'] ) ) { + $invalid_custom_data = + self::get_invalid_event_custom_data( + $event['custom_data'] + ); + if ( ! empty( $invalid_custom_data ) ) { + $invalid_custom_data_msg = + implode( ',', $invalid_custom_data ); + $response['valid'] = false; + $response['message'] = + 'Invalid custom_data attribute'; + $response['error_user_msg'] = + 'Invalid custom_data attributes: ' + . $invalid_custom_data_msg; + } + } + } + } + } + + return $response; + } + + /** + * Validates a payload as JSON. + * + * @param array $payload The payload to validate. + * @return bool Whether the payload is valid JSON. + */ + public function validate_json( $payload ) { + $json_string = wp_json_encode( $payload ); + $regex = '/^(?:\{.*\}|\[.*\])$/s'; + + return preg_match( $regex, $json_string ); + } + + /** + * Validate the type of attributes in an event. + * + * @param array $event An event with its attributes. + * + * @return array An array of invalid attributes. + */ + public function validate_event_attributes_type( $event ) { + if ( is_numeric( $event['event_time'] ) ) { + $event['event_time'] = (int) $event['event_time']; + } + $invalid_attributes = array(); + $event = json_decode( wp_json_encode( $event ) ); + foreach ( $event as $key => $value ) { + if ( 'integer' === self::VALID_EVENT_ATTRIBUTES_TYPE[ $key ] ) { + if ( ! is_numeric( $value ) ) { + array_push( $invalid_attributes, $key ); + } + } elseif ( + gettype( $value ) !== self::VALID_EVENT_ATTRIBUTES_TYPE[ $key ] + ) { + array_push( $invalid_attributes, $key ); + } + } + return $invalid_attributes; + } +} diff --git a/core/class-facebookpixel.php b/core/class-facebookpixel.php new file mode 100644 index 00000000..d633c5bf --- /dev/null +++ b/core/class-facebookpixel.php @@ -0,0 +1,403 @@ + + + +"; + + /** + * The Facebook Pixel fbq code without script. + * + * @var string + */ + private static $pixel_fbq_code_without_script = " + fbq('%s', '%s'%s%s); + "; + + /** + * The Facebook Pixel noscript code. + * + * @var string + */ + private static $pixel_noscript_code = ' + + + +'; + + /** + * Initializes the Facebook Pixel with the given pixel ID. + * + * @param string $pixel_id The Facebook Pixel ID to be set. + * Defaults to an empty string. + */ + public static function initialize( $pixel_id = '' ) { + self::$pixel_id = $pixel_id; + } + + /** + * Gets FB pixel ID + */ + public static function get_pixel_id() { + return self::$pixel_id; + } + + /** + * Sets FB pixel ID + * + * @param string $pixel_id The Facebook Pixel ID to be set. + */ + public static function set_pixel_id( $pixel_id ) { + self::$pixel_id = $pixel_id; + } + + /** + * Gets FB pixel base code + */ + public static function get_pixel_base_code() { + return self::$pixel_base_code; + } + + /** + * Gets OpenBridge set config code + */ + public static function get_open_bridge_config_code() { + if ( empty( self::$pixel_id ) ) { + return; + } + + $code = " + + "; + return sprintf( $code, self::$pixel_id ); + } + + + /** + * Gets FB pixel init code + * + * @param string $agent_string The agent string to be used + * in the pixel init code. + * @param array $param The parameters for the pixel event. + * Defaults to an empty array. + * @param bool $with_script_tag Whether to include the script tag in + * the pixel init code. Defaults to true. + */ + public static function get_pixel_init_code( + $agent_string, + $param = array(), + $with_script_tag = true + ) { + if ( empty( self::$pixel_id ) ) { + return; + } + + $pixel_fbq_code_without_script = "fbq('%s', '%s'%s%s)"; + + $code = $with_script_tag ? "' : $pixel_fbq_code_without_script; + $param_str = $param; + if ( is_array( $param ) ) { + $param_str = wp_json_encode( + $param, + JSON_PRETTY_PRINT | JSON_FORCE_OBJECT + ); + } + $agent_param = array( 'agent' => $agent_string ); + return sprintf( + $code, + 'init', + self::$pixel_id, + ', ' . $param_str, + ', ' . wp_json_encode( $agent_param, JSON_PRETTY_PRINT ) + ); + } + + /** + * Gets FB pixel track code + * $param is the parameter for the pixel event. + * If it is an array, FB_INTEGRATION_TRACKING_KEY parameter with + * $tracking_name value will automatically + * be added into the $param. If it is a string, please append the + * FB_INTEGRATION_TRACKING_KEY parameter + * with its tracking name into the JS Parameter block + * + * @param string $event The name of the pixel event. + * @param array $param The parameters for the pixel event. + * @param string $tracking_name The tracking name for the pixel event. + * @param bool $with_script_tag Whether to include the script tag in + * the pixel track code. + * @return string The pixel track code. + */ + public static function get_pixel_track_code( + $event, + $param = array(), + $tracking_name = '', + $with_script_tag = true + ) { + if ( empty( self::$pixel_id ) ) { + return; + } + + $code = $with_script_tag ? "' : self::$pixel_fbq_code_without_script; + $param_str = $param; + if ( is_array( $param ) ) { + if ( ! empty( $tracking_name ) ) { + $param[ self::FB_INTEGRATION_TRACKING_KEY ] = $tracking_name; + } + $param_str = wp_json_encode( $param, JSON_PRETTY_PRINT ); + } + $class = new ReflectionClass( __CLASS__ ); + return sprintf( + $code, + $class->getConstant( + strtoupper( $event ) + ) !== false ? 'track' : 'trackCustom', + $event, + ', ' . $param_str, + '' + ); + } + + /** + * Gets FB pixel noscript code + * + * @param string $event The name of the pixel event. + * @param array $cd The parameters for the pixel event. + * @param string $tracking_name The tracking name for the pixel event. + */ + public static function get_pixel_noscript_code( + $event = 'PageView', + $cd = array(), + $tracking_name = '' + ) { + if ( empty( self::$pixel_id ) ) { + return; + } + + $data = ''; + foreach ( $cd as $k => $v ) { + $data .= '&cd[' . $k . ']=' . $v; + } + if ( ! empty( $tracking_name ) ) { + $data .= '&cd[' . self::FB_INTEGRATION_TRACKING_KEY . ']=' . + $tracking_name; + } + return sprintf( + self::$pixel_noscript_code, + self::$pixel_id, + $event, + $data + ); + } + + /** + * Gets FB pixel AddToCart code + * + * @param array $param The parameters for the pixel event. + * @param string $tracking_name The tracking name for the pixel event. + * @param bool $with_script_tag Whether to include the script + * tag in the pixel track code. + */ + public static function get_pixel_add_to_cart_code( + $param = array(), + $tracking_name = '', + $with_script_tag = true + ) { + return self::get_pixel_track_code( + self::ADDTOCART, + $param, + $tracking_name, + $with_script_tag + ); + } + + /** + * Gets FB pixel InitiateCheckout code + * + * @param array $param The parameters for the pixel event. + * @param string $tracking_name The tracking name for the pixel event. + * @param bool $with_script_tag Whether to include the + * script tag in the pixel track code. + */ + public static function get_pixel_initiate_checkout_code( + $param = array(), + $tracking_name = '', + $with_script_tag = true + ) { + return self::get_pixel_track_code( + self::INITIATECHECKOUT, + $param, + $tracking_name, + $with_script_tag + ); + } + + /** + * Gets FB pixel Lead code + * + * @param array $param The parameters for the pixel event. + * @param string $tracking_name The tracking name for the pixel event. + * @param bool $with_script_tag Whether to include the + * script tag in the pixel track code. + */ + public static function get_pixel_lead_code( + $param = array(), + $tracking_name = '', + $with_script_tag = true + ) { + return self::get_pixel_track_code( + self::LEAD, + $param, + $tracking_name, + $with_script_tag + ); + } + + /** + * Gets FB pixel PageView code + * + * @param array $param The parameters for the pixel event. + * @param string $tracking_name The tracking name for the pixel event. + * @param bool $with_script_tag Whether to include the script + * tag in the pixel track code. + */ + public static function get_pixel_page_view_code( + $param = array(), + $tracking_name = '', + $with_script_tag = true + ) { + return self::get_pixel_track_code( + self::PAGEVIEW, + $param, + $tracking_name, + $with_script_tag + ); + } + + /** + * Gets FB pixel Purchase code + * + * @param array $param The parameters for the pixel event. + * @param string $tracking_name The tracking name for the pixel event. + * @param bool $with_script_tag Whether to include the script + * tag in the pixel track code. + */ + public static function get_pixel_purchase_code( + $param = array(), + $tracking_name = '', + $with_script_tag = true + ) { + return self::get_pixel_track_code( + self::PURCHASE, + $param, + $tracking_name, + $with_script_tag + ); + } + + /** + * Gets FB pixel ViewContent code + * + * @param array $param The parameters for the pixel event. + * @param string $tracking_name The tracking name for the pixel event. + * @param bool $with_script_tag Whether to include the script tag in + * the pixel track code. + */ + public static function get_pixel_view_content_code( + $param = array(), + $tracking_name = '', + $with_script_tag = true + ) { + return self::get_pixel_track_code( + self::VIEWCONTENT, + $param, + $tracking_name, + $with_script_tag + ); + } +} diff --git a/core/class-facebookpluginconfig.php b/core/class-facebookpluginconfig.php new file mode 100644 index 00000000..ec715bca --- /dev/null +++ b/core/class-facebookpluginconfig.php @@ -0,0 +1,132 @@ + PLUGIN_CLASS. + * + * @return array + */ + public static function integration_config() { + return array( + 'CALDERA_FORM' => 'FacebookWordpressCalderaForm', + 'CONTACT_FORM_7' => 'FacebookWordpressContactForm7', + 'EASY_DIGITAL_DOWNLOAD' => 'FacebookWordpressEasyDigitalDownloads', + 'FORMIDABLE_FORM' => 'FacebookWordpressFormidableForm', + 'MAILCHIMP_FOR_WP' => 'FacebookWordpressMailchimpForWp', + 'NINJA_FORMS' => 'FacebookWordpressNinjaForms', + 'WP_E_COMMERCE' => 'FacebookWordpressWPECommerce', + 'WOOCOMMERCE' => 'FacebookWordpressWooCommerce', + ); + } +} diff --git a/core/class-facebookpluginutils.php b/core/class-facebookpluginutils.php new file mode 100644 index 00000000..47c529a1 --- /dev/null +++ b/core/class-facebookpluginutils.php @@ -0,0 +1,136 @@ + $current_user->user_email, + 'first_name' => $current_user->user_firstname, + 'last_name' => $current_user->user_lastname, + 'id' => $current_user->ID, + ); + } + + /** + * Generates a random GUID. + * + * @return string The generated GUID. + */ + public static function new_guid() { + if ( function_exists( 'com_create_guid' ) === true ) { + return trim( com_create_guid(), '{}' ); + } + + return sprintf( + '%04X%04X-%04X-%04X-%04X-%04X%04X%04X', + wp_rand( 0, 65535 ), + wp_rand( 0, 65535 ), + wp_rand( 0, 65535 ), + wp_rand( 16384, 20479 ), + wp_rand( 32768, 49151 ), + wp_rand( 0, 65535 ), + wp_rand( 0, 65535 ), + wp_rand( 0, 65535 ) + ); + } + + /** + * All standard WordPress user roles are considered internal + * unless they have the Subscriber role. + * WooCommerce uses the 'read' capability for its customer role. + * Also check for the 'upload_files' capability to account for the + * shop_worker and shop_vendor roles in Easy Digital Downloads. + * https://wordpress.org/support/article/roles-and-capabilities + * + * @return bool + */ + public static function is_internal_user() { + return current_user_can( 'edit_posts' ) + || current_user_can( 'upload_files' ); + } + + /** + * Checks if a string ends with a specified substring. + * + * @param string $haystack The string to search in. + * @param string $needle The substring to search for at the + * end of $haystack. + * @return bool True if $haystack ends with $needle, false otherwise. + */ + public static function ends_with( $haystack, $needle ) { + $length = strlen( $needle ); + if ( ! $length ) { + return false; + } + return substr( $haystack, -$length ) === $needle; + } + + /** + * Checks if a string contains a specified substring. + * + * @param string $haystack The string to search in. + * @param string $needle The substring to search for within $haystack. + * @return bool True if $haystack contains $needle, false otherwise. + */ + public static function string_contains( $haystack, $needle ) { + return (bool) strstr( $haystack, $needle ); + } +} diff --git a/core/class-facebookserversideevent.php b/core/class-facebookserversideevent.php new file mode 100644 index 00000000..c8adfe1b --- /dev/null +++ b/core/class-facebookserversideevent.php @@ -0,0 +1,233 @@ +tracked_events[] = $event; + if ( $send_now ) { + do_action( + 'send_server_events', + array( $event ), + 1 + ); + } else { + $this->pending_events[] = $event; + } + } + + /** + * Retrieves all the events tracked during the current request. + * + * @return array An array of tracked events. + */ + public function get_tracked_events() { + return $this->tracked_events; + } + + /** + * Retrieves the number of events tracked during the current request. + * + * @return int The number of tracked events. + */ + public function get_num_tracked_events() { + return count( $this->tracked_events ); + } + + /** + * Retrieves all the events that have not been sent yet. + * + * @return array An array of events that have not been sent yet. + */ + public function get_pending_events() { + return $this->pending_events; + } + + /** + * Stores a server event that should be sent when a specific + * callback is fired. + * + * @param string $callback_name The name of the callback + * to listen for. + * @param ServerEvent $event The server event to send when the + * callback is fired. + */ + public function set_pending_pixel_event( $callback_name, $event ) { + $this->pending_pixel_events[ $callback_name ] = $event; + } + + /** + * Retrieves a server event that should be sent when a specific + * callback is fired. + * + * @param string $callback_name The name of the callback to listen for. + * @return ServerEvent|null The server event to send when the callback + * is fired, or null if no event was stored for the callback. + */ + public function get_pending_pixel_event( $callback_name ) { + if ( isset( $this->pending_pixel_events[ $callback_name ] ) ) { + return $this->pending_pixel_events[ $callback_name ]; + } + return null; + } + + /** + * Sends a list of events to the Conversions API. + * + * This function can be used to send events to the Conversions API directly. + * It will apply the 'before_conversions_api_event_sent' + * filter to the events before sending them. + * + * @param ServerEvent[] $events The events to send to the Conversions API. + * + * @throws \Exception If there was an error sending the events to + * the Conversions API. + */ + public static function send( $events ) { + $events = apply_filters( 'before_conversions_api_event_sent', $events ); + if ( empty( $events ) ) { + return; + } + + $pixel_id = FacebookWordpressOptions::get_pixel_id(); + $access_token = FacebookWordpressOptions::get_access_token(); + $agent = FacebookWordpressOptions::get_agent_string(); + + if ( self::is_open_bridge_event( $events ) ) { + $agent .= '_ob'; // agent suffix is openbridge. + } + + if ( empty( $pixel_id ) || empty( $access_token ) ) { + return; + } + try { + $api = Api::init( null, null, $access_token ); + + $request = ( new EventRequest( $pixel_id ) ) + ->setEvents( $events ) + ->setPartnerAgent( $agent ); + + $response = $request->execute(); + } catch ( \Exception $e ) { + throw $e; + } + } + + /** + * Checks if the given event is an OpenBridge event. + * + * This function determines if the provided event array contains exactly one + * event and if that event has custom data with a 'fb_integration_tracking' + * property set to 'wp-cloudbridge-plugin'. If these conditions are met, + * the function returns true, indicating the event is an OpenBridge event. + * + * @param array $events An array of events to check. + * @return bool True if the event is an OpenBridge event, false otherwise. + */ + private static function is_open_bridge_event( $events ) { + if ( count( $events ) !== 1 ) { + return false; + } + + $custom_data = $events[0]->getCustomData(); + if ( ! $custom_data ) { + return false; + } + + $custom_properties = $custom_data->getCustomProperties(); + if ( ! $custom_properties || + ! isset( $custom_properties['fb_integration_tracking'] ) ) { + return false; + } + + return 'wp-cloudbridge-plugin' === + $custom_properties['fb_integration_tracking']; + } +} diff --git a/core/class-facebookwordpressopenbridge.php b/core/class-facebookwordpressopenbridge.php new file mode 100644 index 00000000..eb89f122 --- /dev/null +++ b/core/class-facebookwordpressopenbridge.php @@ -0,0 +1,534 @@ + $maxlifetime, + 'path' => '/', + 'domain' => isset( $_SERVER['HTTP_HOST'] ) ? + sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '', + 'secure' => $secure, + 'httponly' => $httponly, + 'samesite' => $samesite, + ) + ); + } + + session_start(); + + $_SESSION[ self::EXTERNAL_ID_COOKIE ] = isset( + $_SESSION[ self::EXTERNAL_ID_COOKIE ] + ) ? sanitize_text_field( $_SESSION[ self::EXTERNAL_ID_COOKIE ] ) : + FacebookPluginUtils::new_guid(); + } + + /** + * Handles an incoming Open Bridge request from the front-end. + * + * Starts a new PHP session if one is not already active, and extracts the + * event name, event ID, and event data from the request. If the event name + * is in the list of blocked events, the method returns early without taking + * any action. Otherwise, it creates a ServerEvent using the event name and + * data, and sends it to the Facebook pixel servers. + * + * @param array $data The event data, including the event name and ID. + * + * @return void + */ + public function handle_open_bridge_req( $data ) { + + self::start_new_php_session_if_needed(); + + $event_name = $data['event_name']; + if ( in_array( $event_name, self::$blocked_events, true ) ) { + return; + } + $event = ServerEventFactory::safe_create_event( + $event_name, + array( $this, 'extract_from_databag' ), + array( $data ), + 'wp-cloudbridge-plugin', + true + ); + $event->setEventId( $data['event_id'] ); + FacebookServerSideEvent::send( array( $event ) ); + } + + /** + * Extracts the user data and custom data from the given databag. + * + * @param array $databag The databag containing the event data. + * + * @return array The extracted data, including user data and custom data. + */ + public function extract_from_databag( $databag ) { + $current_user = self::get_pii_from_session(); + + $event_data = array( + 'email' => self::get_email( $current_user, $databag ), + 'first_name' => + self::get_first_name( $current_user, $databag ), + 'last_name' => + self::get_last_name( $current_user, $databag ), + 'external_id' => + self::get_external_id( $current_user, $databag ), + 'phone' => self::get_phone( $current_user, $databag ), + 'state' => self::get_state( $current_user, $databag ), + 'country' => self::get_country( $current_user, $databag ), + 'city' => self::get_city( $current_user, $databag ), + 'zip' => self::get_zip( $current_user, $databag ), + 'gender' => + self::get_aam_field( AAMSettingsFields::GENDER, $databag ), + 'date_of_birth' => + self::get_aam_field( AAMSettingsFields::DATE_OF_BIRTH, $databag ), + 'currency' => self::get_custom_data( 'currency', $databag ), + 'value' => self::get_custom_data( 'value', $databag ), + 'content_type' => + self::get_custom_data( 'content_type', $databag ), + 'content_name' => + self::get_custom_data( 'content_name', $databag ), + 'content_ids' => + self::get_custom_data_array( 'content_ids', $databag ), + 'content_category' => + self::get_custom_data( 'content_category', $databag ), + ); + return $event_data; + } + + /** + * Retrieves PII from the logged in user's session. + * + * This function retrieves PII data (email, first name, last name, phone + * number, city, state, zip, country) from the logged in user's session. + * If the data is not available in the session, it retrieves the data from + * the WordPress user meta table. + * + * @return array The user's PII data. + * + * @since 1.0.0 + */ + private static function get_pii_from_session() { + $current_user = array_filter( + FacebookPluginUtils::get_logged_in_user_info() + ); + $capi_pii_caching_status = + FacebookWordpressOptions::get_capi_pii_caching_status(); + + if ( empty( $current_user ) && '1' === $capi_pii_caching_status ) { + + if ( isset( $_SESSION[ AAMSettingsFields::EMAIL ] ) ) { + $current_user['email'] = sanitize_text_field( + $_SESSION[ AAMSettingsFields::EMAIL ] + ); + } + + if ( isset( $_SESSION[ AAMSettingsFields::FIRST_NAME ] ) ) { + $current_user['first_name'] = + sanitize_text_field( $_SESSION[ AAMSettingsFields::FIRST_NAME ] ); + } + + if ( isset( $_SESSION[ AAMSettingsFields::LAST_NAME ] ) ) { + $current_user['last_name'] = + sanitize_text_field( $_SESSION[ AAMSettingsFields::LAST_NAME ] ); + } + + if ( isset( $_SESSION[ AAMSettingsFields::PHONE ] ) ) { + $current_user['phone'] = + sanitize_text_field( $_SESSION[ AAMSettingsFields::PHONE ] ); + } + + return array_filter( $current_user ); + } + + $user_id = get_current_user_id(); + if ( 0 !== $user_id ) { + $current_user['city'] = get_user_meta( + $user_id, + 'billing_city', + true + ); + $current_user['zip'] = get_user_meta( + $user_id, + 'billing_postcode', + true + ); + $current_user['country'] = get_user_meta( + $user_id, + 'billing_country', + true + ); + $current_user['state'] = get_user_meta( + $user_id, + 'billing_state', + true + ); + $current_user['phone'] = get_user_meta( + $user_id, + 'billing_phone', + true + ); + } + return array_filter( $current_user ); + } + + /** + * Get the user's email address. + * + * If the user data contains an email, use that. Otherwise + * use the email from the AAM settings. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string The user's email address. + */ + private static function get_email( $current_user_data, $pixel_data ) { + if ( isset( $current_user_data['email'] ) ) { + return $current_user_data['email']; + } + return self::get_aam_field( AAMSettingsFields::EMAIL, $pixel_data ); + } + + /** + * Get the user's first name. + * + * If the user data contains a first name, use that. Otherwise use + * the first name from the AAM settings. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string The user's first name. + */ + private static function get_first_name( $current_user_data, $pixel_data ) { + if ( isset( $current_user_data['first_name'] ) ) { + return $current_user_data['first_name']; + } + return self::get_aam_field( + AAMSettingsFields::FIRST_NAME, + $pixel_data + ); + } + + /** + * Get the user's last name. + * + * If the user data contains a last name, use that. Otherwise + * use the last name from the AAM settings. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string The user's last name. + */ + private static function get_last_name( $current_user_data, $pixel_data ) { + if ( isset( $current_user_data['last_name'] ) ) { + return $current_user_data['last_name']; + } + return self::get_aam_field( AAMSettingsFields::LAST_NAME, $pixel_data ); + } + + /** + * Get the user's external ID. + * + * If the user data contains an ID, use that. Otherwise use the + * external ID from the AAM settings. + * If the external ID is set in the session, use that as well. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string[] The user's external ID. + */ + private static function get_external_id( $current_user_data, $pixel_data ) { + $external_ids = array(); + + if ( isset( $current_user_data['id'] ) ) { + $external_ids[] = (string) $current_user_data['id']; + } + + $temp_external_id = self::get_aam_field( + AAMSettingsFields::EXTERNAL_ID, + $pixel_data + ); + + if ( $temp_external_id ) { + $external_ids[] = $temp_external_id; + } + + if ( isset( $_SESSION[ self::EXTERNAL_ID_COOKIE ] ) ) { + $external_ids[] = sanitize_text_field( + $_SESSION[ self::EXTERNAL_ID_COOKIE ] + ); + } + return $external_ids; + } + + /** + * Gets the user's phone. + * + * If the phone is set in the current user data, use that. Otherwise use the + * value from the AAM settings. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string The user's phone. + */ + private static function get_phone( $current_user_data, $pixel_data ) { + if ( isset( $current_user_data['phone'] ) ) { + return $current_user_data['phone']; + } + return self::get_aam_field( AAMSettingsFields::PHONE, $pixel_data ); + } + + /** + * Gets the user's city. + * + * If the city is set in the current user data, use that. Otherwise use the + * value from the AAM settings. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string The user's city. + */ + private static function get_city( $current_user_data, $pixel_data ) { + if ( isset( $current_user_data['city'] ) ) { + return $current_user_data['city']; + } + return self::get_aam_field( AAMSettingsFields::CITY, $pixel_data ); + } + + /** + * Gets the user's zip code. + * + * If the user data contains a zip code, use that. Otherwise + * use the zip code from the AAM settings. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string The user's zip code. + */ + private static function get_zip( $current_user_data, $pixel_data ) { + if ( isset( $current_user_data['zip'] ) ) { + return $current_user_data['zip']; + } + return self::get_aam_field( AAMSettingsFields::ZIP_CODE, $pixel_data ); + } + + /** + * Gets the user's country. + * + * If the country is set in the current user data, use that. + * Otherwise use the value from the AAM settings. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string The user's country. + */ + private static function get_country( $current_user_data, $pixel_data ) { + if ( isset( $current_user_data['country'] ) ) { + return $current_user_data['country']; + } + return self::get_aam_field( AAMSettingsFields::COUNTRY, $pixel_data ); + } + + /** + * Gets the user's state. + * + * If the user data contains a state, use that. Otherwise use + * the state from the AAM settings. + * + * @param array $current_user_data The user data. + * @param array $pixel_data The AAM settings. + * + * @return string The user's state. + */ + private static function get_state( $current_user_data, $pixel_data ) { + if ( isset( $current_user_data['state'] ) ) { + return $current_user_data['state']; + } + return self::get_aam_field( AAMSettingsFields::STATE, $pixel_data ); + } + + /** + * Retrieves a value from the advanced matching settings. + * + * Retrieves a value from the advanced matching settings array and + * stores it in the session. If the key is not found in the advanced + * matching settings, an empty string is returned. + * + * @param string $key The key of the value to retrieve. + * @param array $pixel_data The array containing the + * advanced matching settings. + * + * @return string The value associated with the given key + * if found, otherwise an empty string. + */ + private static function get_aam_field( $key, $pixel_data ) { + if ( ! isset( $pixel_data[ self::ADVANCED_MATCHING_LABEL ] ) ) { + return ''; + } + if ( isset( $pixel_data[ self::ADVANCED_MATCHING_LABEL ][ $key ] ) ) { + $value = + $pixel_data[ self::ADVANCED_MATCHING_LABEL ][ $key ]; + $_SESSION[ $key ] = $value; + return $value; + } + return ''; + } + + /** + * Retrieves a custom data value from the given pixel data. + * + * @param string $key The key of the custom data value. + * @param array $pixel_data The array containing the custom data. + * + * @return string The custom data value if found, otherwise an empty string. + */ + private static function get_custom_data( $key, $pixel_data ) { + if ( ! isset( $pixel_data[ self::CUSTOM_DATA_LABEL ] ) ) { + return ''; + } + if ( isset( $pixel_data[ self::CUSTOM_DATA_LABEL ][ $key ] ) ) { + return $pixel_data[ self::CUSTOM_DATA_LABEL ][ $key ]; + } + return ''; + } + + /** + * Retrieves an array of custom data based on the provided key. + * + * This function checks if the custom data label exists + * within the pixel data. + * If the specified key is found, it returns the corresponding + * custom data array. + * If the key is not found, it returns an empty array. + * + * @param string $key The key to retrieve the custom data array for. + * @param array $pixel_data The array containing the custom data. + * + * @return array|string The custom data array if the key is + * found, otherwise an empty array. + */ + private static function get_custom_data_array( $key, $pixel_data ) { + if ( ! isset( $pixel_data[ self::CUSTOM_DATA_LABEL ] ) ) { + return ''; + } + if ( isset( $pixel_data[ self::CUSTOM_DATA_LABEL ][ $key ] ) ) { + return $pixel_data[ self::CUSTOM_DATA_LABEL ][ $key ]; + } + return array(); + } +} diff --git a/core/class-facebookwordpressoptions.php b/core/class-facebookwordpressoptions.php new file mode 100644 index 00000000..363574ce --- /dev/null +++ b/core/class-facebookwordpressoptions.php @@ -0,0 +1,615 @@ + + self::get_default_external_business_id(), + FacebookPluginConfig::IS_FBE_INSTALLED_KEY => + self::get_default_is_fbe_installed(), + ); + if ( + isset( $old_options[ FacebookPluginConfig::OLD_ACCESS_TOKEN_KEY ] ) + && ! empty( $old_options[ FacebookPluginConfig::OLD_ACCESS_TOKEN_KEY ] ) + ) { + self::$options[ FacebookPluginConfig::ACCESS_TOKEN_KEY ] = + $old_options[ FacebookPluginConfig::OLD_ACCESS_TOKEN_KEY ]; + } else { + self::$options[ FacebookPluginConfig::ACCESS_TOKEN_KEY ] = + self::get_default_access_token(); + } + if ( + isset( $old_options[ FacebookPluginConfig::OLD_PIXEL_ID_KEY ] ) + && ! empty( $old_options[ FacebookPluginConfig::OLD_PIXEL_ID_KEY ] ) + && is_numeric( $old_options[ FacebookPluginConfig::OLD_PIXEL_ID_KEY ] ) + ) { + self::$options[ FacebookPluginConfig::PIXEL_ID_KEY ] = + $old_options[ FacebookPluginConfig::OLD_PIXEL_ID_KEY ]; + } else { + self::$options[ FacebookPluginConfig::PIXEL_ID_KEY ] = + self::get_default_pixel_id(); + } + } else { + self::$options = \get_option( + FacebookPluginConfig::SETTINGS_KEY, + array( + FacebookPluginConfig::PIXEL_ID_KEY => + self::get_default_pixel_id(), + FacebookPluginConfig::ACCESS_TOKEN_KEY => + self::get_default_access_token(), + FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY => + self::get_default_external_business_id(), + FacebookPluginConfig::IS_FBE_INSTALLED_KEY => + self::get_default_is_fbe_installed(), + ) + ); + } + } + + /** + * Retrieves the Facebook pixel ID. + * + * If the pixel ID is not set in the options, the default pixel + * ID is returned. + * + * @return string The Facebook pixel ID. + */ + public static function get_pixel_id() { + if ( isset( self::$options[ FacebookPluginConfig::PIXEL_ID_KEY ] ) ) { + return self::$options[ FacebookPluginConfig::PIXEL_ID_KEY ]; + } + + return self::get_default_pixel_id(); + } + + /** + * Retrieves the external business ID. + * + * If the external business ID is not set in the options, + * the default external + * business ID is returned. + * + * @return string The external business ID. + */ + public static function get_external_business_id() { + if ( + isset( + self::$options[ FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY ] + ) + ) { + return self::$options[ FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY ]; + } + + return self::get_default_external_business_id(); + } + + /** + * Retrieves the status indicating whether the Facebook Business + * Extension is installed. + * + * If the FBE installed status is not set in the options, the + * default FBE installed status is returned. + * + * @return string The FBE installed status. + */ + public static function get_is_fbe_installed() { + if ( + isset( self::$options[ FacebookPluginConfig::IS_FBE_INSTALLED_KEY ] ) + ) { + return self::$options[ FacebookPluginConfig::IS_FBE_INSTALLED_KEY ]; + } + + return self::get_default_is_fbe_installed(); + } + + /** + * Retrieves the Facebook access token. + * + * If the access token is not set in the options, the + * default access token is returned. + * + * @return string The Facebook access token. + */ + public static function get_access_token() { + if ( isset( self::$options[ FacebookPluginConfig::ACCESS_TOKEN_KEY ] ) ) { + return self::$options[ FacebookPluginConfig::ACCESS_TOKEN_KEY ]; + } + + return self::get_default_access_token(); + } + + /** + * Retrieves the user information. + * + * This function returns the user information stored in the class. + * The user information is an array that may contain keys like 'email', + * 'first_name', and 'last_name', depending on the data available for the + * current user. + * + * @return array The user information. + */ + public static function get_user_info() { + return self::$user_info; + } + + /** + * Registers an action hook to set the user information. + * + * The 'register_user_info' method is called when the 'init' + * action is triggered. + * The method sets the user information stored in the class. + * + * @return void + */ + public static function set_user_info() { + add_action( + 'init', + array( + 'FacebookPixelPlugin\\Core\\FacebookWordpressOptions', + 'register_user_info', + ), + 0 + ); + } + + /** + * Registers the user information. + * + * This function is called when the 'init' action is triggered, and it + * sets the user information stored in the class. The user information + * is an array that may contain keys like 'email', 'first_name', and + * 'last_name', depending on the data available for the current user. + * + * @return void + */ + public static function register_user_info() { + $current_user = wp_get_current_user(); + if ( 0 === $current_user->ID ) { + self::$user_info = array(); + } else { + $user_info = array_filter( + array( + AAMSettingsFields::EMAIL => $current_user->user_email, + AAMSettingsFields::FIRST_NAME => $current_user->user_firstname, + AAMSettingsFields::LAST_NAME => $current_user->user_lastname, + ), + function ( $value ) { + return null !== $value && '' !== $value; + } + ); + self::$user_info = + AAMFieldsExtractor::get_normalized_user_data( $user_info ); + } + } + + /** + * Retrieves the version information. + * + * The version information is an array that contains the keys + * 'pluginVersion' and 'source'. The 'pluginVersion' key contains + * the current version of the plugin, and the 'source' key + * contains the source of the plugin. + * + * @return array The version information. + */ + public static function get_version_info() { + return self::$version_info; + } + + /** + * Sets the version information. + * + * The version information is an array that contains the + * keys 'pluginVersion', 'source', and 'version'. The + * 'pluginVersion' key contains the current version of the + * plugin, the 'source' key contains the source of the plugin, + * and the 'version' key contains the current version of WordPress. + * + * @return void + */ + public static function set_version_info() { + global $wp_version; + + self::$version_info = array( + 'pluginVersion' => FacebookPluginConfig::PLUGIN_VERSION, + 'source' => FacebookPluginConfig::SOURCE, + 'version' => $wp_version, + ); + } + + /** + * Constructs the agent string from version information. + * + * This function returns a formatted string that combines the source, + * WordPress version, and plugin version from the version information array. + * + * @return string The constructed agent string. + */ + public static function get_agent_string() { + return sprintf( + '%s-%s-%s', + self::$version_info['source'], + self::$version_info['version'], + self::$version_info['pluginVersion'] + ); + } + + /** + * Retrieves the AdsPixelSettings object from the AAM settings. + * + * @return AdsPixelSettings The AdsPixelSettings object + * containing the AAM settings. + */ + public static function get_aam_settings() { + return self::$aam_settings; + } + + /** + * Retrieves the AdsPixelSettings object from the AAM settings. + * + * This method first checks if there are any AAM settings cached in the + * WordPress database. If there are, they are converted into an + * AdsPixelSettings object and returned. If there are no cached + * settings, the method fetches the settings from the Facebook + * domain and caches them in the WordPress + * database if they are not null. + * + * @return void + */ + private static function set_fbe_based_aam_settings() { + $installed_pixel = self::get_pixel_id(); + $settings_as_array = + get_transient( FacebookPluginConfig::AAM_SETTINGS_KEY ); + if ( false !== $settings_as_array ) { + $aam_settings = new AdsPixelSettings(); + $aam_settings->setPixelId( $settings_as_array['pixelId'] ); + $aam_settings->setEnableAutomaticMatching( + $settings_as_array['enableAutomaticMatching'] + ); + $aam_settings->setEnabledAutomaticMatchingFields( + $settings_as_array['enabledAutomaticMatchingFields'] + ); + if ( $installed_pixel === $aam_settings->getPixelId() ) { + self::$aam_settings = $aam_settings; + } + } + if ( ! self::$aam_settings ) { + $refresh_interval = + self::AAM_SETTINGS_REFRESH_IN_MINUTES * MINUTE_IN_SECONDS; + $aam_settings = + AdsPixelSettings::buildFromPixelId( $installed_pixel ); + if ( $aam_settings ) { + $settings_as_array = array( + 'pixelId' => $aam_settings->getPixelId(), + 'enableAutomaticMatching' => + $aam_settings->getEnableAutomaticMatching(), + 'enabledAutomaticMatchingFields' => + $aam_settings->getEnabledAutomaticMatchingFields(), + ); + set_transient( + FacebookPluginConfig::AAM_SETTINGS_KEY, + $settings_as_array, + $refresh_interval + ); + self::$aam_settings = $aam_settings; + } + } + } + + /** + * If the old settings are present and the user has opted-in to use PII, + * the AAM settings are set to enable automatic matching and all its fields. + * Otherwise, the AAM settings are set to disable automatic matching + * and all its fields. + */ + private static function set_old_aam_settings() { + $old_options = \get_option( FacebookPluginConfig::OLD_SETTINGS_KEY ); + if ( $old_options + && isset( $old_options[ FacebookPluginConfig::OLD_USE_PII ] ) + && $old_options[ FacebookPluginConfig::OLD_USE_PII ] ) { + self::$aam_settings = new AdsPixelSettings( + array( + 'enableAutomaticMatching' => true, + 'enabledAutomaticMatchingFields' => + AAMSettingsFields::get_all_fields(), + ) + ); + } else { + self::$aam_settings = new AdsPixelSettings( + array( + 'enableAutomaticMatching' => false, + 'enabledAutomaticMatchingFields' => array(), + ) + ); + } + } + + /** + * Sets the AdsPixelSettings based on the installation status. + * + * This method initializes the AAM settings to null and + * checks if a pixel ID is set. + * If no pixel ID is found, the method returns early. + * If the Facebook Business Extension + * (FBE) is installed, the AAM settings are set using + * the FBE-based settings. + * Otherwise, the old AAM settings are used. + * + * @return void + */ + private static function set_aam_settings() { + self::$aam_settings = null; + if ( empty( self::get_pixel_id() ) ) { + return; + } + if ( self::get_is_fbe_installed() ) { + self::set_fbe_based_aam_settings(); + } else { + self::set_old_aam_settings(); + } + } +} diff --git a/core/class-facebookwordpresspixelinjection.php b/core/class-facebookwordpresspixelinjection.php new file mode 100644 index 00000000..b5e9f860 --- /dev/null +++ b/core/class-facebookwordpresspixelinjection.php @@ -0,0 +1,154 @@ + $value + ) { + $class_name = 'FacebookPixelPlugin\\Integration\\' . $value; + $class_name::inject_pixel_code(); + } + add_action( + 'wp_footer', + array( $this, 'send_pending_events' ) + ); + } + } + + /** + * Sends any pending Facebook server-side events. + * + * This method checks if there are any pending Facebook server-side events, + * and if so, it sends them by triggering the `send_server_events` action. + * + * @return void + */ + public function send_pending_events() { + $pending_events = + FacebookServerSideEvent::get_instance()->get_pending_events(); + if ( count( $pending_events ) > 0 ) { + do_action( + 'send_server_events', + $pending_events, + count( $pending_events ) + ); + } + } + + /** + * Injects the Facebook pixel base code, Open Bridge configuration code + * if CAI is enabled, Facebook pixel initialization code and Facebook + * pixel page view code. + * + * This method is hooked into the `wp_head` action and is responsible + * for injecting the necessary code to enable the Facebook pixel for + * the current page. It uses the `FacebookPixel` class to generate + * the necessary code and injects it into the page. + * + * @return void + */ + public function inject_pixel_code() { + $pixel_id = FacebookPixel::get_pixel_id(); + if ( + ( isset( + self::$render_cache[ FacebookPluginConfig::IS_PIXEL_RENDERED ] + ) && + true === self::$render_cache[ FacebookPluginConfig::IS_PIXEL_RENDERED ] ) + || + empty( $pixel_id ) + ) { + return; + } + + self::$render_cache[ FacebookPluginConfig::IS_PIXEL_RENDERED ] = true; + echo FacebookPixel::get_pixel_base_code(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $capi_integration_status = + FacebookWordpressOptions::get_capi_integration_status(); + if ( '1' === $capi_integration_status ) { + FacebookPixel::get_open_bridge_config_code(); + } + echo FacebookPixel::get_pixel_init_code( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + FacebookWordpressOptions::get_agent_string(), + FacebookWordpressOptions::get_user_info() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + echo FacebookPixel::get_pixel_page_view_code(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Injects the Facebook Pixel noscript code. + * + * This method is responsible for adding the noscript version of the + * Facebook Pixel code to the page. It uses the `get_pixel_noscript_code` + * method from the `FacebookPixel` class to generate the necessary code. + * + * @return void + */ + public function inject_pixel_noscript_code() { + echo FacebookPixel::get_pixel_noscript_code(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } +} diff --git a/core/class-facebookwordpresssettingspage.php b/core/class-facebookwordpresssettingspage.php new file mode 100644 index 00000000..d1b4beb9 --- /dev/null +++ b/core/class-facebookwordpresssettingspage.php @@ -0,0 +1,890 @@ +options_page = add_options_page( + FacebookPluginConfig::ADMIN_PAGE_TITLE, + FacebookPluginConfig::ADMIN_MENU_TITLE, + 'manage_options', + FacebookPluginConfig::ADMIN_MENU_SLUG, + array( $this, 'add_fbe_box' ) + ); + + \add_option( + FacebookPluginConfig::CAPI_INTEGRATION_STATUS, + FacebookPluginConfig::CAPI_INTEGRATION_STATUS_DEFAULT + ); + \add_option( + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER, + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_DEFAULT + ); + } + + /** + * Renders the Facebook Business Extension box on the settings page. + * + * This function checks if the current user has the necessary capabilities + * to access the page. If not, it terminates the script + * with an error message. + * If the user has the required permissions, it displays any previous pixel + * ID message and the FBE browser settings, and enqueues the + * necessary script + * for the page. + * + * @return void + */ + public function add_fbe_box() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( + esc_html__( + 'You do not have permissions to access this page', + 'official-facebook-pixel' + ) + ); + } + $pixel_id_message = $this->get_previous_pixel_id_message(); + if ( $pixel_id_message ) { + echo $pixel_id_message; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo $this->get_fbe_browser_settings(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + wp_enqueue_script( 'fbe_allinone_script' ); + } + + /** + * Retrieves a message regarding the previous pixel ID setup. + * + * If the Facebook Business Extension is installed, it returns null. + * Otherwise, it checks for the existence of a pixel ID and returns + * a formatted message indicating the reuse of the pixel ID from a + * previous setup. If no pixel ID is found, it returns null. + * + * @return string|null The message with the previous pixel ID or null if + * the pixel ID is not found or FBE is installed. + */ + private function get_previous_pixel_id_message() { + if ( FacebookWordpressOptions::get_is_fbe_installed() ) { + return null; + } + $pixel_id = FacebookWordpressOptions::get_pixel_id(); + if ( empty( $pixel_id ) ) { + return null; + } + $message = + sprintf( + '

Reuse the pixel id from your previous setup: ' . + '%s

', + $pixel_id + ); + return $message; + } + + /** + * Generates and returns the browser settings HTML and JavaScript + * for the Facebook Business Extension. + * + * This function constructs and outputs a set of HTML elements and + * JavaScript code necessary for configuring + * the Facebook Business Extension in the browser. It includes sections + * for advanced configuration, ads creation, + * and ads insights, as well as functionality for testing + * conversion API events. + * + * The function dynamically generates JSON-encoded configuration + * data and embeds it into the HTML via + * data attributes, allowing for the interactive configuration + * of various Facebook Business Extension features. + * + * It also enqueues the necessary scripts and styles for + * rendering the settings page and handles + * inline script parameters for AJAX communication + * and configuration management. + * + * @return string The HTML and JavaScript code for the + * Facebook Business Extension browser settings. + */ + private function get_fbe_browser_settings() { + ob_start(); + $fbe_extras = wp_json_encode( + array( + 'business_config' => array( + 'business' => array( + 'name' => 'Solutions_Engineering_Team', + ), + ), + 'setup' => array( + 'external_business_id' => + FacebookWordpressOptions::get_external_business_id(), + 'timezone' => 'America/Los_Angeles', + 'currency' => 'USD', + 'business_vertical' => 'ECOMMERCE', + 'channel' => 'DEFAULT', + ), + 'repeat' => false, + ) + ); + ?> +
+
+
+
+
Meta Advanced Configuration
+
+ + + +
+
+ Enable checkbox to filter PageView events from sending. +
+
+ + + +
+
+ When turned on, PII will be cached for non logged in users. +
+
+
+
+ +
+

Conversion API Tests

+ +
+
+

Plugin Connected to Meta Events Manager

+ +

Meta Events Manager is a tool that + enables you to view and manage your + event data. In Events Manager, you can + set up, monitor and troubleshoot issues + with your integrations, such as the + Conversions API and Meta pixel.

+

+ Visit the + Meta Events Manager + to view the events being tracked.

+
+ +
+ + +
+ + '; + ?> + +
+
+
+
+
+ ? + +

+ To obtain the Test Event Code, + visit the Test Event section in the + + + /test_events"> + Events Manager. +

+
+
+ +
+
+ + +
+ +
+ + + +
+
+ +
+ + Advanced | Edit Event Data + + + + + + + Click here to load default payload + +
+ + +
+ + +
+ +
+

Event Log

+ + + + + + + + + + +
Code/MessageEvent TypeStatus
+ +
+
+ + +

+ No events logged yet.

+ + +
+
+
+
+
+
+ + +
+
+

Ads Creation

+
+
+
+
+
+

Ads Insights

+
+
+
+
+
+
+ + + admin_url( 'admin-ajax.php' ), + 'send_capi_event_nonce' => + wp_create_nonce( 'send_capi_event_nonce' ), + 'pixelId' => + FacebookWordpressOptions::get_pixel_id(), + 'setSaveSettingsRoute' => + $this->get_fbe_save_settings_ajax_route(), + 'externalBusinessId' => + esc_html( FacebookWordpressOptions::get_external_business_id() ), + 'deleteConfigKeys' => + $this->get_delete_fbe_settings_ajax_route(), + 'installed' => + FacebookWordpressOptions::get_is_fbe_installed(), + 'systemUserName' => + esc_html( FacebookWordpressOptions::get_external_business_id() ), + 'pixelString' => + esc_html( FacebookWordpressOptions::get_pixel_id() ), + 'piiCachingStatus' => + FacebookWordpressOptions::get_capi_pii_caching_status(), + 'fbAdvConfTop' => + FacebookPluginConfig::CAPI_INTEGRATION_DIV_TOP, + 'capiIntegrationPageViewFiltered' => + wp_json_encode( + FacebookWordpressOptions::get_capi_integration_page_view_filtered() + ), + 'capiPiiCachingStatusSaveUrl' => + $this->get_capi_pii_caching_status_save_url(), + 'capiPiiCachingStatusActionName' => + FacebookPluginConfig::SAVE_CAPI_PII_CACHING_STATUS_ACTION_NAME, + 'capiPiiCachingStatusUpdateError' => + FacebookPluginConfig::CAPI_PII_CACHING_STATUS_UPDATE_ERROR, + 'capiIntegrationEventsFilterSaveUrl' => + $this->get_capi_integration_events_filter_save_url(), + 'capiIntegrationEventsFilterActionName' => + FacebookPluginConfig::SAVE_CAPI_INTEGRATION_EVENTS_FILTER_ACTION_NAME, + 'capiIntegrationEventsFilterUpdateError' => + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_UPDATE_ERROR, + ) + ), + 'before' + ); + return $initial_script; + } + + /** + * Generates the AJAX route URL for saving FBE settings. + * + * This function creates a nonce for the AJAX action to ensure + * security and constructs a URL for the admin-ajax.php endpoint + * with the required query arguments, including the action name and nonce. + * + * @return string The URL with query arguments for the AJAX action. + */ + public function get_fbe_save_settings_ajax_route() { + $nonce_value = wp_create_nonce( + FacebookPluginConfig::SAVE_FBE_SETTINGS_ACTION_NAME + ); + $simple_url = admin_url( 'admin-ajax.php' ); + $args = array( + 'action' => FacebookPluginConfig::SAVE_FBE_SETTINGS_ACTION_NAME, + '_wpnonce' => $nonce_value, + ); + return add_query_arg( $args, $simple_url ); + } + + /** + * Generates the AJAX route URL for saving CAPE integration status. + * + * This function creates a nonce for the AJAX action to ensure + * security and constructs a URL for the admin-ajax.php endpoint + * with the required query arguments, including the action name and nonce. + * + * @return string The URL with query arguments for the AJAX action. + */ + public function get_capi_integration_status_save_url() { + $nonce_value = wp_create_nonce( + FacebookPluginConfig::SAVE_CAPI_INTEGRATION_STATUS_ACTION_NAME + ); + $simple_url = admin_url( 'admin-ajax.php' ); + $args = array( + 'action' => + FacebookPluginConfig::SAVE_CAPI_INTEGRATION_STATUS_ACTION_NAME, + '_wpnonce' => $nonce_value, + ); + return add_query_arg( $args, $simple_url ); + } + + /** + * Generates the AJAX route URL for saving CAPE events filter. + * + * This function creates a nonce for the AJAX action to ensure + * security and constructs a URL for the admin-ajax.php endpoint + * with the required query arguments, including the action name and nonce. + * + * @return string The URL with query arguments for the AJAX action. + */ + public function get_capi_integration_events_filter_save_url() { + $nonce_value = wp_create_nonce( + FacebookPluginConfig::SAVE_CAPI_INTEGRATION_EVENTS_FILTER_ACTION_NAME + ); + $simple_url = admin_url( 'admin-ajax.php' ); + $args = array( + 'action' => + FacebookPluginConfig::SAVE_CAPI_INTEGRATION_EVENTS_FILTER_ACTION_NAME, + '_wpnonce' => $nonce_value, + ); + return add_query_arg( $args, $simple_url ); + } + + /** + * Generates the AJAX route URL for saving CAPI PII caching status. + * + * This function creates a nonce for the AJAX action to ensure + * security and constructs a URL for the admin-ajax.php endpoint + * with the required query arguments, including the action name and nonce. + * + * @return string The URL with query arguments for the AJAX action. + */ + public function get_capi_pii_caching_status_save_url() { + $nonce_value = wp_create_nonce( + FacebookPluginConfig::SAVE_CAPI_PII_CACHING_STATUS_ACTION_NAME + ); + $simple_url = admin_url( 'admin-ajax.php' ); + $args = array( + 'action' => + FacebookPluginConfig::SAVE_CAPI_PII_CACHING_STATUS_ACTION_NAME, + '_wpnonce' => $nonce_value, + ); + return add_query_arg( $args, $simple_url ); + } + + /** + * Generates the AJAX route URL for deleting FBE settings. + * + * This function creates a nonce for the AJAX action to ensure + * security and constructs a URL for the admin-ajax.php endpoint + * with the required query arguments, including the action name and nonce. + * + * @return string The URL with query arguments for the AJAX action. + */ + public function get_delete_fbe_settings_ajax_route() { + $nonce_value = wp_create_nonce( + FacebookPluginConfig::DELETE_FBE_SETTINGS_ACTION_NAME + ); + $simple_url = admin_url( 'admin-ajax.php' ); + $args = array( + 'action' => FacebookPluginConfig::DELETE_FBE_SETTINGS_ACTION_NAME, + '_wpnonce' => $nonce_value, + ); + return add_query_arg( $args, $simple_url ); + } + + /** + * Adds a settings link to the plugin action links. + * + * This function appends a "Settings" link to the given + * array of plugin action links. + * The link directs the user to the Facebook Business + * Extension settings page. + * + * @param array $links An array of existing plugin action links. + * @return array The modified array of plugin action + * links with the settings link added. + */ + public function add_settings_link( $links ) { + $settings = array( + 'settings' => sprintf( + '%s', + admin_url( + 'options-general.php?page=' . + FacebookPluginConfig::ADMIN_MENU_SLUG + ), + 'Settings' + ), + ); + return array_merge( $settings, $links ); + } + + /** + * Registers admin notices for the Facebook Business Extension. + * + * This function determines whether the Facebook Business + * Extension is installed + * and whether the user has dismissed the notice. If the + * extension is not installed + * and the user has not dismissed the notice, it + * registers the 'fbe_not_installed_notice' + * function to display the notice. If the extension is + * installed and the user has not + * dismissed the review notice, it registers the + * 'plugin_review_notice' function to + * display the review notice. + */ + public function register_notices() { + $is_fbe_installed = FacebookWordpressOptions::get_is_fbe_installed(); + $current_screen_id = get_current_screen()->id; + + if ( current_user_can( 'manage_options' ) && + in_array( + $current_screen_id, + array( 'dashboard', 'plugins' ), + true + ) + ) { + if ( '0' === $is_fbe_installed && ! get_user_meta( + get_current_user_id(), + FacebookPluginConfig::ADMIN_IGNORE_FBE_NOT_INSTALLED_NOTICE, + true + ) ) { + add_action( + 'admin_notices', + array( $this, 'fbe_not_installed_notice' ) + ); + } + if ( '1' === $is_fbe_installed && ! get_user_meta( + get_current_user_id(), + FacebookPluginConfig::ADMIN_IGNORE_PLUGIN_REVIEW_NOTICE, + true + ) ) { + add_action( + 'admin_notices', + array( $this, 'plugin_review_notice' ) + ); + } + } + } + + /** + * Returns a customized message for the Facebook Business + * Extension not installed notice. + * + * This function determines if a valid pixel ID and access + * token are set. If both are set, it + * suggests using the plugin to manage the connection to Meta. + * If only the pixel ID is set, it + * highlights the Conversions API feature. If neither is set, + * it suggests completing the setup + * steps. + * + * @return string The customized message. + */ + public function get_customized_fbe_not_installed_notice() { + $valid_pixel_id = ! empty( + FacebookWordpressOptions::get_pixel_id() + ); + $valid_access_token = ! empty( + FacebookWordPressOptions::get_access_token() + ); + $message = ''; + $plugin_name_tag = sprintf( + '%s', + FacebookPluginConfig::PLUGIN_NAME + ); + if ( $valid_pixel_id ) { + if ( $valid_access_token ) { + $message = sprintf( + 'Easily manage your connection to Meta with %s.', + $plugin_name_tag + ); + } else { + $message = sprintf( + '%s gives you access to the Conversions API.', + $plugin_name_tag + ); + } + } else { + $message = sprintf( '%s is almost ready.', $plugin_name_tag ); + } + return $message . ' To complete your configuration, ' . + 'follow the setup steps.'; + } + + /** + * Displays a WordPress admin notice with a dismiss button. + * + * This function generates an HTML notice with the + * specified content and type, + * which can be dismissed by the user. It constructs a URL for the settings + * page and includes a button to dismiss the notice. + * + * @param string $notice The content of the notice, + * with a placeholder for the settings URL. + * @param array $dismiss_config Configuration for + * the dismissal URL query arguments. + * @param string $notice_type The type of notice + * to display (e.g., 'warning', 'info'). + */ + public function set_notice( $notice, $dismiss_config, $notice_type ) { + $url = admin_url( + 'options-general.php?page=' . + FacebookPluginConfig::ADMIN_MENU_SLUG + ); + + $link = sprintf( + $notice, + esc_url( $url ) + ); + printf( + ' +
+

%s

+ +
+ ', + esc_html( $notice_type ), + wp_kses_post( $link ), + esc_url( add_query_arg( $dismiss_config, '' ) ), + esc_html__( + 'Dismiss this notice.', + 'official-facebook-pixel' + ) + ); + } + + /** + * Displays a notice asking the user to leave a review for the plugin. + * + * If the user has not dismissed the review notice, this function generates + * an HTML notice with a link to the plugin's + * review page and a dismiss button. + * + * @since 3.0.0 + */ + public function plugin_review_notice() { + $message = sprintf( + /* translators: %1$s: Plugin name, %2$s: Review page URL */ + __( + 'Let us know what you think about %1$s. + Leave a review on + this page.', + 'official-facebook-pixel' + ), + FacebookPluginConfig::PLUGIN_NAME, + FacebookPluginConfig::PLUGIN_REVIEW_PAGE + ); + $this->set_notice( + $message, + FacebookPluginConfig::ADMIN_DISMISS_PLUGIN_REVIEW_NOTICE, + 'info' + ); + } + + /** + * Displays a notice indicating that the Facebook + * Business Extension is not installed. + * + * This function retrieves a customized message for the + * Facebook Business Extension not installed notice + * and uses it to generate an HTML admin notice. + * The notice is dismissible and marked as a warning type. + * + * @since 3.0.0 + */ + public function fbe_not_installed_notice() { + $message = $this->get_customized_fbe_not_installed_notice(); + $this->set_notice( + $message, + FacebookPluginConfig::ADMIN_DISMISS_FBE_NOT_INSTALLED_NOTICE, + 'warning' + ); + } + + /** + * Handles dismissals of admin notices. + * + * This function checks for the presence of + * query arguments that indicate a notice + * should be dismissed. If such an argument + * is present, it updates the corresponding + * user meta value to true, indicating that + * the notice should no longer be shown. + * + * @since 3.0.0 + */ + public function dismiss_notices() { + $user_id = get_current_user_id(); + if ( isset( + $_GET[ FacebookPluginConfig::ADMIN_DISMISS_FBE_NOT_INSTALLED_NOTICE ] // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) ) { + update_user_meta( + $user_id, + FacebookPluginConfig::ADMIN_IGNORE_FBE_NOT_INSTALLED_NOTICE, + true + ); + } + if ( isset( + $_GET[ FacebookPluginConfig::ADMIN_DISMISS_PLUGIN_REVIEW_NOTICE ] // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) ) { + update_user_meta( + $user_id, + FacebookPluginConfig::ADMIN_IGNORE_PLUGIN_REVIEW_NOTICE, + true + ); + } + } +} diff --git a/core/class-facebookwordpresssettingsrecorder.php b/core/class-facebookwordpresssettingsrecorder.php new file mode 100644 index 00000000..44904c4c --- /dev/null +++ b/core/class-facebookwordpresssettingsrecorder.php @@ -0,0 +1,327 @@ + true, + 'msg' => $body, + ); + wp_send_json( $res ); + return $res; + } + + /** + * Handles unauthorized request by sending a 403 response + * with 'success' => false and 'msg' => 'Unauthorized user'. + * + * @return array response data + */ + private function handle_unauthorized_request() { + $res = array( + 'success' => false, + 'msg' => 'Unauthorized user', + ); + wp_send_json( $res, 403 ); + return $res; + } + + /** + * Handles invalid request by sending a 400 response + * with 'success' => false and 'msg' => 'Invalid values'. + * + * @return array response data + */ + private function handle_invalid_request() { + $res = array( + 'success' => false, + 'msg' => 'Invalid values', + ); + wp_send_json( $res, 400 ); + return $res; + } + + /** + * Handles saving Facebook Business Extension settings. + * + * This function handles saving the Facebook Business Extension settings, + * such as the pixel ID, access token, and external business ID. + * It checks if the current user is an administrator, and if not, + * it will return an unauthorized request response. + * If the request is valid, it will save the settings to the WordPress + * options table and return a success response. + * + * @return array response data + */ + public function save_fbe_settings() { + if ( ! current_user_can( 'manage_options' ) ) { + return $this->handle_unauthorized_request(); + } + check_admin_referer( + FacebookPluginConfig::SAVE_FBE_SETTINGS_ACTION_NAME + ); + $pixel_id = sanitize_text_field( + isset( $_POST['pixelId'] ) ? + wp_unslash( $_POST['pixelId'] ) : '' + ); + $access_token = sanitize_text_field( + isset( $_POST['accessToken'] ) ? + wp_unslash( $_POST['accessToken'] ) : '' + ); + $external_business_id = sanitize_text_field( + isset( $_POST['externalBusinessId'] ) ? + wp_unslash( $_POST['externalBusinessId'] ) : '' + ); + if ( empty( $pixel_id ) + || empty( $access_token ) + || empty( $external_business_id ) ) { + return $this->handle_invalid_request(); + } + $settings = array( + FacebookPluginConfig::PIXEL_ID_KEY => $pixel_id, + FacebookPluginConfig::ACCESS_TOKEN_KEY => $access_token, + FacebookPluginConfig::EXTERNAL_BUSINESS_ID_KEY => + $external_business_id, + FacebookPluginConfig::IS_FBE_INSTALLED_KEY => '1', + ); + \update_option( + FacebookPluginConfig::SETTINGS_KEY, + $settings + ); + return $this->handle_success_request( $settings ); + } + + /** + * Handles saving the CAPI integration status. + * + * This function handles saving the CAPI integration status, which indicates + * whether the CAPI integration is enabled or disabled. + * It checks if the current user is an administrator, and if not, + * it will return an unauthorized request response. + * If the request is valid, it will save the status to the WordPress + * options table and return a success response. + * + * @return array response data + */ + public function save_capi_integration_status() { + if ( ! current_user_can( 'manage_options' ) ) { + return $this->handle_unauthorized_request(); + } + + if ( empty( FacebookWordpressOptions::get_pixel_id() ) ) { + \update_option( + FacebookPluginConfig::CAPI_INTEGRATION_STATUS, + FacebookPluginConfig::CAPI_INTEGRATION_STATUS_DEFAULT + ); + return $this->handle_invalid_request(); + } + + check_admin_referer( + FacebookPluginConfig::SAVE_CAPI_INTEGRATION_STATUS_ACTION_NAME + ); + $val = sanitize_text_field( + isset( $_POST['val'] ) ? + wp_unslash( $_POST['val'] ) : '' + ); + + if ( ! ( '0' === $val || '1' === $val ) ) { + return $this->handle_invalid_request(); + } + + \update_option( FacebookPluginConfig::CAPI_INTEGRATION_STATUS, $val ); + return $this->handle_success_request( $val ); + } + + /** + * Handles saving the CAPI integration events filter. + * + * This function handles saving the CAPI integration events filter, which + * determines which events are sent to the CAPI integration. It checks if + * the current user is an administrator, and if not, it will return an + * unauthorized request response. + * If the request is valid, it will save the filter to the WordPress + * options table and return a success response. + * + * @return array response data + */ + public function save_capi_integration_events_filter() { + if ( ! current_user_can( 'manage_options' ) ) { + return $this->handle_unauthorized_request(); + } + + if ( empty( FacebookWordpressOptions::get_pixel_id() ) ) { + \update_option( + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER, + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_DEFAULT + ); + return $this->handle_invalid_request(); + } + + check_admin_referer( + FacebookPluginConfig::SAVE_CAPI_INTEGRATION_EVENTS_FILTER_ACTION_NAME + ); + $val = sanitize_text_field( + isset( $_POST['val'] ) ? + wp_unslash( $_POST['val'] ) : '' + ); + $const_filter_page_view = + FacebookPluginConfig::CAPI_INTEGRATION_FILTER_PAGE_VIEW_EVENT; + $const_keep_page_view = + FacebookPluginConfig::CAPI_INTEGRATION_KEEP_PAGE_VIEW_EVENT; + + if ( ! ( $val === $const_filter_page_view + || $val === $const_keep_page_view ) ) { + return $this->handle_invalid_request(); + } + + $page_view_filtered = + FacebookWordpressOptions::get_capi_integration_page_view_filtered(); + + if ( $val === $const_keep_page_view && $page_view_filtered ) { + \update_option( + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER, + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_DEFAULT + ); + } elseif ( $val === $const_filter_page_view && ! $page_view_filtered ) { + \update_option( + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER, + FacebookPluginConfig::CAPI_INTEGRATION_EVENTS_FILTER_DEFAULT . + ',PageView' + ); + } + + return $this->handle_success_request( $val ); + } + + /** + * Handles saving the CAPI PII caching status. + * + * This function handles saving the CAPI PII caching status, which indicates + * whether the CAPI PII caching is enabled or disabled. + * It checks if the current user is an administrator, and if not, + * it will return an unauthorized request response. + * If the request is valid, it will save the status to the WordPress + * options table and return a success response. + * + * @return array response data + */ + public function save_capi_pii_caching_status() { + if ( ! current_user_can( 'manage_options' ) ) { + return $this->handle_unauthorized_request(); + } + + if ( empty( FacebookWordpressOptions::get_pixel_id() ) ) { + \update_option( + FacebookPluginConfig::CAPI_PII_CACHING_STATUS, + FacebookPluginConfig::CAPI_PII_CACHING_STATUS_DEFAULT + ); + return $this->handle_invalid_request(); + } + + check_admin_referer( + FacebookPluginConfig::SAVE_CAPI_PII_CACHING_STATUS_ACTION_NAME + ); + $val = sanitize_text_field( + isset( $_POST['val'] ) ? + wp_unslash( $_POST['val'] ) : '' + ); + + if ( ! ( '0' === $val || '1' === $val ) ) { + return $this->handle_invalid_request(); + } + + \update_option( FacebookPluginConfig::CAPI_PII_CACHING_STATUS, $val ); + return $this->handle_success_request( $val ); + } + + /** + * Deletes the Facebook Business Extension settings. + * + * This function deletes the Facebook Business + * Extension settings from the WordPress + * options table. It checks if the current user + * is an administrator, and if not, + * it will return an unauthorized request response. + * If the request is valid, it will delete the + * settings and return a success response. + * + * @return array response data + */ + public function delete_fbe_settings() { + if ( ! current_user_can( 'manage_options' ) ) { + return $this->handle_unauthorized_request(); + } + check_admin_referer( + FacebookPluginConfig::DELETE_FBE_SETTINGS_ACTION_NAME + ); + \delete_option( FacebookPluginConfig::SETTINGS_KEY ); + \delete_transient( FacebookPluginConfig::AAM_SETTINGS_KEY ); + + return $this->handle_success_request( 'Done' ); + } +} diff --git a/core/class-pixelrenderer.php b/core/class-pixelrenderer.php new file mode 100644 index 00000000..06ea01bc --- /dev/null +++ b/core/class-pixelrenderer.php @@ -0,0 +1,121 @@ +%s"; + const FBQ_EVENT_CODE = "fbq('%s', '%s', %s, %s);"; + const FBQ_AGENT_CODE = "fbq('set', 'agent', '%s', '%s');"; + + /** + * Render the pixel events + * + * @param array $events The array of events, each event is an + * array with the following keys: + * - event_name: the name of the event + * - event_id: the id of the event (optional) + * - custom_data: the custom data + * for the event (optional). + * @param bool $fb_integration_tracking Whether to track the + * event as a Facebook integration. + * @param bool $script_tag Whether to wrap the + * generated code with a script tag. + * + * @return string The rendered pixel events + */ + public static function render( + $events, + $fb_integration_tracking, + $script_tag = true + ) { + if ( empty( $events ) ) { + return ''; + } + $code = sprintf( + self::FBQ_AGENT_CODE, + FacebookWordpressOptions::get_agent_string(), + FacebookWordpressOptions::get_pixel_id() + ); + foreach ( $events as $event ) { + $code .= self::get_pixel_track_code( $event, $fb_integration_tracking ); + } + return $script_tag ? sprintf( self::SCRIPT_TAG, $code ) : $code; + } + + + /** + * Generate the Facebook Pixel track code for an event + * + * @param \FacebookPixelPlugin\Core\Event $event The event to + * generate the track code for. + * @param bool $fb_integration_tracking Whether + * to track the event as a Facebook integration. + * + * @return string The generated track code + */ + private static function get_pixel_track_code( + $event, + $fb_integration_tracking + ) { + $event_data[ self::EVENT_ID ] = $event->getEventId(); + + $custom_data = $event->getCustomData() !== null ? + $event->getCustomData() : new CustomData(); + + $normalized_custom_data = $custom_data->normalize(); + if ( ! is_null( $fb_integration_tracking ) ) { + $normalized_custom_data[ self::FB_INTEGRATION_TRACKING ] = + $fb_integration_tracking; + } + + $class = new ReflectionClass( + 'FacebookPixelPlugin\Core\FacebookPixel' + ); + return sprintf( + self::FBQ_EVENT_CODE, + $class->getConstant( strtoupper( $event->getEventName() ) ) !== false ? + self::TRACK : self::TRACK_CUSTOM, + $event->getEventName(), + wp_json_encode( $normalized_custom_data, JSON_PRETTY_PRINT ), + wp_json_encode( $event_data, JSON_PRETTY_PRINT ) + ); + } +} diff --git a/core/class-servereventasynctask.php b/core/class-servereventasynctask.php new file mode 100644 index 00000000..a3433c73 --- /dev/null +++ b/core/class-servereventasynctask.php @@ -0,0 +1,213 @@ + value pairs + * where the key is a normalized version of the user data field name + * and the value is the value of the field. + * + * This function takes that array and converts it to + * the format used by UserData. + * + * @param array $user_data_normalized The normalized user data. + * + * @return array The converted user data. + */ + private function convert_user_data( $user_data_normalized ) { + $norm_key_to_key = array( + AAMSettingsFields::EMAIL => 'emails', + AAMSettingsFields::FIRST_NAME => 'first_names', + AAMSettingsFields::LAST_NAME => 'last_names', + AAMSettingsFields::GENDER => 'genders', + AAMSettingsFields::DATE_OF_BIRTH => 'dates_of_birth', + AAMSettingsFields::EXTERNAL_ID => 'external_ids', + AAMSettingsFields::PHONE => 'phones', + AAMSettingsFields::CITY => 'cities', + AAMSettingsFields::STATE => 'states', + AAMSettingsFields::ZIP_CODE => 'zip_codes', + AAMSettingsFields::COUNTRY => 'country_codes', + ); + $user_data = array(); + foreach ( $user_data_normalized as $norm_key => $field ) { + if ( isset( $norm_key_to_key[ $norm_key ] ) ) { + $user_data[ $norm_key_to_key[ $norm_key ] ] = $field; + } else { + $user_data[ $norm_key ] = $field; + } + } + return $user_data; + } + + /** + * Converts an array representation of an event into an Event object. + * + * This function takes an array that represents an event and + * converts it into + * an instance of the `Event` class. If the array contains `user_data`, a + * `UserData` object is created and associated with the event. Similarly, if + * `custom_data` is present, a `CustomData` object is created and + * associated. + * This includes handling nested `contents` and additional custom properties + * like `fb_integration_tracking`. + * + * @param array $event_as_array The array representing the event. + * @return Event The constructed Event object. + */ + private function convert_array_to_event( $event_as_array ) { + $event = new Event( $event_as_array ); + if ( isset( $event_as_array['user_data'] ) ) { + $user_data = new UserData( + $this->convert_user_data( + $event_as_array['user_data'] + ) + ); + $event->setUserData( $user_data ); + } + if ( isset( $event_as_array['custom_data'] ) ) { + $custom_data = new CustomData( $event_as_array['custom_data'] ); + if ( isset( $event_as_array['custom_data']['contents'] ) ) { + $contents = array(); + foreach ( + $event_as_array['custom_data']['contents'] as $contents_as_array + ) { + if ( isset( $contents_as_array['id'] ) ) { + $contents_as_array['product_id'] = $contents_as_array['id']; + } + $contents[] = new Content( $contents_as_array ); + } + $custom_data->setContents( $contents ); + } + if ( isset( + $event_as_array['custom_data']['fb_integration_tracking'] + ) ) { + $custom_data->addCustomProperty( + 'fb_integration_tracking', + $event_as_array['custom_data']['fb_integration_tracking'] + ); + } + $event->setCustomData( $custom_data ); + } + return $event; + } + + /** + * Prepares event data for processing. + * + * This function takes an input array of events, normalizes each event, + * and encodes the data into a base64 JSON string. If the input data + * contains only a single event, it is wrapped in an array for consistency. + * + * @param array $data The input data containing events and the number + * of events. The format is expected to be an array + * where the first element is the event(s) and the + * second element is the number of events. + * + * @return array An associative array containing 'event_data', a base64 + * encoded JSON string of the events, and 'num_events', + * the number of events processed. + * + * @throws \Exception If there was an preprocessing error. + */ + protected function prepare_data( $data ) { + try { + if ( ! empty( $data ) ) { + $num_events = $data[1]; + $events = $data[0]; + if ( 1 === $num_events ) { + $events = array( $events ); + } + $events_as_array = array(); + foreach ( $events as $event ) { + $events_as_array[] = $event->normalize(); + } + return array( + 'event_data' => + base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + wp_json_encode( $events_as_array ) + ), + 'num_events' => $data[1], + ); + } + } catch ( \Exception $ex ) { + throw $ex; + } + + return array(); + } + + /** + * Process the events sent via the AJAX action. + * + * This function decodes the JSON string sent in the $_POST['event_data'] + * and processes the events as an array of Event objects. + * + * @see FacebookServerSideEvent::send() + * + * @throws \Exception If there was an preprocessing error. + */ + protected function run_action() { + try { + $num_events = isset( $_POST['num_events'] ) ? // phpcs:ignore WordPress.Security.NonceVerification.Missing + sanitize_text_field( + wp_unslash( $_POST['num_events'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing + ) : null; + if ( 0 === $num_events ) { + return; + } + $events_as_array = json_decode( + base64_decode( // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + isset( $_POST['event_data'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + ? $_POST['event_data'] : null // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + ), + true + ); + if ( ! $events_as_array ) { + return; + } + $events = array(); + foreach ( $events_as_array as $event_as_array ) { + $event = $this->convert_array_to_event( $event_as_array ); + $events[] = $event; + } + FacebookServerSideEvent::send( $events ); + } catch ( \Exception $ex ) { + throw $ex; + } + } +} diff --git a/core/class-servereventfactory.php b/core/class-servereventfactory.php new file mode 100644 index 00000000..efa2e5da --- /dev/null +++ b/core/class-servereventfactory.php @@ -0,0 +1,475 @@ +setClientIpAddress( self::get_ip_address() ) + ->setClientUserAgent( self::get_http_user_agent() ) + ->setFbp( self::get_fbp() ) + ->setFbc( self::get_fbc() ); + + $event = ( new Event() ) + ->setEventName( $event_name ) + ->setEventTime( time() ) + ->setEventId( EventIdGenerator::guidv4() ) + ->setEventSourceUrl( + self::get_request_uri( $prefer_referrer_for_event_src ) + ) + ->setActionSource( 'website' ) + ->setUserData( $user_data ) + ->setCustomData( new CustomData() ); + + return $event; + } + + /** + * Scans the HTTP headers for the first valid IP address it can find. + * + * @return string|null The first valid IP address found, or null if none + * were found. + */ + private static function get_ip_address() { + $headers_to_scan = array( + 'HTTP_CLIENT_IP', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED', + 'HTTP_X_CLUSTER_CLIENT_IP', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + 'REMOTE_ADDR', + ); + + foreach ( $headers_to_scan as $header ) { + if ( isset( $_SERVER[ $header ] ) ) { + $ip_list = explode( ',', $_SERVER[ $header ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + foreach ( $ip_list as $ip ) { + $trimmed_ip = trim( $ip ); + if ( self::is_valid_ip_address( $trimmed_ip ) ) { + return $trimmed_ip; + } + } + } + } + + return null; + } + + /** + * Retrieves the User-Agent string from the HTTP request headers. + * + * @return string|null The User-Agent string, or null if it was not found. + */ + private static function get_http_user_agent() { + $user_agent = null; + + if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) { + $user_agent = sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + } + + return $user_agent; + } + + /** + * Retrieves the request URI for the current HTTP request. + * + * This function constructs the full request URI by considering + * the protocol, host, and request path. If the + * $prefer_referrer_for_event_src parameter is true and a referrer + * URL is present in the HTTP headers, it returns the referrer URL instead. + * + * @param boolean $prefer_referrer_for_event_src Whether to + * prefer the referrer URL over the current request URL. + * + * @return string The constructed request URI or the referrer + * URL if preferred. + */ + private static function get_request_uri( $prefer_referrer_for_event_src ) { + if ( $prefer_referrer_for_event_src + && ! empty( $_SERVER['HTTP_REFERER'] ) ) { + return sanitize_text_field( $_SERVER['HTTP_REFERER'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + } + + $url = 'http://'; + if ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] ) { + $url = 'https://'; + } + + if ( ! empty( $_SERVER['HTTP_HOST'] ) ) { + $url .= sanitize_text_field( $_SERVER['HTTP_HOST'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + } + + if ( ! empty( $_SERVER['REQUEST_URI'] ) ) { + $url .= sanitize_text_field( $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + } + + return $url; + } + + /** + * Retrieves the Facebook Browser ID (FBP) cookie. + * + * This function returns the value of the FBP cookie, which + * is a unique identifier assigned to a user by Facebook. + * The FBP cookie is used by the Facebook pixel to track + * user behavior across multiple sites and sessions. + * + * @return string|null The value of the FBP cookie, or + * null if the cookie is not present. + */ + private static function get_fbp() { + $fbp = null; + + if ( ! empty( $_COOKIE['_fbp'] ) ) { + $fbp = sanitize_text_field( $_COOKIE['_fbp'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + } + + return $fbp; + } + + /** + * Retrieves the Facebook Click ID (FBC) cookie or session variable. + * + * This function returns the value of the FBC cookie or session variable, + * which is a unique identifier assigned to a user by Facebook. The FBC + * cookie is used by the Facebook pixel to track user behavior across + * multiple sites and sessions. If the FBC cookie is not present, the + * function will attempt to generate an FBC value from the fbclid query + * parameter, if present. + * + * @return string|null The value of the FBC cookie or session variable, or + * null if neither is present. + */ + private static function get_fbc() { + $fbc = null; + + if ( ! empty( $_COOKIE['_fbc'] ) ) { + $fbc = sanitize_text_field( + wp_unslash( $_COOKIE['_fbc'] ) + ); + $_SESSION['_fbc'] = $fbc; + } + + if ( ! $fbc && isset( $_GET['fbclid'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $fbclid = sanitize_text_field( wp_unslash( $_GET['fbclid'] ) ); // phpcs:ignore WordPress.Security.NonceVerification + $cur_time = (int) ( microtime( true ) * 1000 ); + $fbc = 'fb.1.' . $cur_time . '.' . rawurldecode( $fbclid ); + } + + if ( ! $fbc && isset( $_SESSION['_fbc'] ) ) { + $fbc = sanitize_text_field( $_SESSION['_fbc'] ); + } + + if ( $fbc ) { + $_SESSION['_fbc'] = $fbc; + } + + return $fbc; + } + + /** + * Validates an IP address. + * + * This function takes an IP address and returns true if it is valid, + * false otherwise. The function uses the filter_var function to validate + * the IP address, and it filters out private and reserved IP addresses. + * + * @param string $ip_address The IP address to validate. + * @return bool True if the IP address is valid, false otherwise. + */ + private static function is_valid_ip_address( $ip_address ) { + return filter_var( + $ip_address, + FILTER_VALIDATE_IP, + FILTER_FLAG_IPV4 | + FILTER_FLAG_IPV6 | + FILTER_FLAG_NO_PRIV_RANGE | + FILTER_FLAG_NO_RES_RANGE + ); + } + + /** + * Given that the data extracted by the integration classes is a mix of + * user data and custom data, + * this function splits these fields in two arrays + * and user data is formatted with the AAM field setting + * + * @param array $data Data extracted by the integration. + * @return array + */ + private static function split_user_data_and_custom_data( $data ) { + $user_data = array(); + $custom_data = array(); + $key_to_aam_field = array( + 'email' => AAMSettingsFields::EMAIL, + 'first_name' => AAMSettingsFields::FIRST_NAME, + 'last_name' => AAMSettingsFields::LAST_NAME, + 'phone' => AAMSettingsFields::PHONE, + 'state' => AAMSettingsFields::STATE, + 'country' => AAMSettingsFields::COUNTRY, + 'city' => AAMSettingsFields::CITY, + 'zip' => AAMSettingsFields::ZIP_CODE, + 'gender' => AAMSettingsFields::GENDER, + 'date_of_birth' => AAMSettingsFields::DATE_OF_BIRTH, + 'external_id' => AAMSettingsFields::EXTERNAL_ID, + ); + foreach ( $data as $key => $value ) { + if ( isset( $key_to_aam_field[ $key ] ) ) { + $user_data[ $key_to_aam_field[ $key ] ] = $value; + } else { + $custom_data[ $key ] = $value; + } + } + return array( + 'user_data' => $user_data, + 'custom_data' => $custom_data, + ); + } + + /** + * Given a callback and its arguments, it calls the callback + * with the arguments and extracts the user data and custom + * data from the result. + * + * It uses the AAM setting to normalize the user data and the custom data + * is used as is. + * + * If an exception is thrown in the callback, it's caught and logged, + * and the function returns an empty Event object. + * + * @param string $event_name The name of the event. + * @param callable $callback The callback to call. + * @param array $arguments The arguments to pass to the callback. + * @param string $integration The integration name. + * @param boolean $prefer_referrer_for_event_src Whether to prefer + * the referrer URL over the current request URL as the event source URL. + * + * @return Event The event object. + * + * @throws \Exception If there was an preprocessing error. + */ + public static function safe_create_event( + $event_name, + $callback, + $arguments, + $integration, + $prefer_referrer_for_event_src = false + ) { + $event = self::new_event( $event_name, $prefer_referrer_for_event_src ); + + try { + $data = call_user_func_array( $callback, $arguments ); + $data_split = self::split_user_data_and_custom_data( $data ); + $user_data_array = $data_split['user_data']; + $custom_data_array = $data_split['custom_data']; + $user_data_array = AAMFieldsExtractor::get_normalized_user_data( + $user_data_array + ); + + $user_data = $event->getUserData(); + if ( + isset( $user_data_array[ AAMSettingsFields::EMAIL ] ) + ) { + $user_data->setEmail( + $user_data_array[ AAMSettingsFields::EMAIL ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::FIRST_NAME ] ) + ) { + $user_data->setFirstName( + $user_data_array[ AAMSettingsFields::FIRST_NAME ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::LAST_NAME ] ) + ) { + $user_data->setLastName( + $user_data_array[ AAMSettingsFields::LAST_NAME ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::GENDER ] ) + ) { + $user_data->setGender( + $user_data_array[ AAMSettingsFields::GENDER ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::DATE_OF_BIRTH ] ) + ) { + $user_data->setDateOfBirth( + $user_data_array[ AAMSettingsFields::DATE_OF_BIRTH ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::EXTERNAL_ID ] ) && + ! is_null( $user_data_array[ AAMSettingsFields::EXTERNAL_ID ] ) + ) { + if ( is_array( $user_data_array[ AAMSettingsFields::EXTERNAL_ID ] ) ) { + $external_ids = $user_data_array[ AAMSettingsFields::EXTERNAL_ID ]; + $hashed_eids = array(); + foreach ( $external_ids as $k => $v ) { + $hashed_eids[ $k ] = hash( 'sha256', $v ); + } + $user_data->setExternalIds( $hashed_eids ); + } else { + $user_data->setExternalId( + hash( + 'sha256', + $user_data_array[ AAMSettingsFields::EXTERNAL_ID ] + ) + ); + } + } + if ( + isset( $user_data_array[ AAMSettingsFields::PHONE ] ) + ) { + $user_data->setPhone( + $user_data_array[ AAMSettingsFields::PHONE ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::CITY ] ) + ) { + $user_data->setCity( + $user_data_array[ AAMSettingsFields::CITY ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::STATE ] ) + ) { + $user_data->setState( + $user_data_array[ AAMSettingsFields::STATE ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::ZIP_CODE ] ) + ) { + $user_data->setZipCode( + $user_data_array[ AAMSettingsFields::ZIP_CODE ] + ); + } + if ( + isset( $user_data_array[ AAMSettingsFields::COUNTRY ] ) + ) { + $user_data->setCountryCode( + $user_data_array[ AAMSettingsFields::COUNTRY ] + ); + } + + $custom_data = $event->getCustomData(); + $custom_data->addCustomProperty( + 'fb_integration_tracking', + $integration + ); + + if ( ! empty( $data['currency'] ) ) { + $custom_data->setCurrency( $custom_data_array['currency'] ); + } + + if ( ! empty( $data['value'] ) ) { + $custom_data->setValue( $custom_data_array['value'] ); + } + + if ( ! empty( $data['contents'] ) ) { + $custom_data->setContents( $custom_data_array['contents'] ); + } + + if ( ! empty( $data['content_ids'] ) ) { + $custom_data->setContentIds( $custom_data_array['content_ids'] ); + } + + if ( ! empty( $data['content_type'] ) ) { + $custom_data->setContentType( $custom_data_array['content_type'] ); + } + + if ( ! empty( $data['num_items'] ) ) { + $custom_data->setNumItems( $custom_data_array['num_items'] ); + } + + if ( ! empty( $data['content_name'] ) ) { + $custom_data->setContentName( $custom_data_array['content_name'] ); + } + + if ( ! empty( $data['content_category'] ) ) { + $custom_data->setContentCategory( + $custom_data_array['content_category'] + ); + } + } catch ( \Exception $e ) { + throw $e; + } + + return $event; + } + + /** + * Split a full name string into an array containing the first name + * and last name. + * + * If the name contains a space, it will be split into a first name and + * last name. Otherwise, the entire name will be considered the first + * name and the last name will be null. + * + * @param string $name The full name to split. + * @return array An array containing the first name and last name. + */ + public static function split_name( $name ) { + $first_name = $name; + $last_name = null; + $index = strpos( $name, ' ' ); + if ( false !== $index ) { + $first_name = substr( $name, 0, $index ); + $last_name = substr( $name, $index + 1 ); + } + + return array( $first_name, $last_name ); + } +} diff --git a/css/admin.css b/css/admin.css index 58173323..de4312ba 100644 --- a/css/admin.css +++ b/css/admin.css @@ -14,6 +14,11 @@ top: 540px; position: absolute; left: 20px; + display: none; +} + +.fb-capi-ef { + display: none; } .fb-adv-conf-title { @@ -45,3 +50,446 @@ padding-top: 5px; padding-bottom: 5px; } + +.bg-white { + background-color: white; +} + +/* Events Manager Section */ +.events-manager-wrapper.hidden { + display: none; +} + +.events-manager-container { + display: flex; + flex-direction: column; + gap: 36px; + padding: 36px 20px; + background-color: #ffffff; + position: relative; +} + +.events-manager-container h3 { + font-size: 18px; + margin-top: 0; +} + +.events-manager-container p { + font-size: 14px; + max-width: 600px; +} + +.events-manager-container label, +.events-manager-container h4 { + font-size: 16px; + font-weight: 600; +} + +.events-manager-container input, +.events-manager-container select, +.events-manager-container textarea.open { + padding: 6px 12px; + font-size: 14px; + border-radius: 6px; + box-shadow: 3px 2px 17px 0px rgba(0, 0, 0, 0.25); + border: none; +} + +.events-manager-container textarea { + height: 0; + resize: none; + margin-bottom: 10px; + opacity: 0; + border: none; + pointer-events: none; + transition: all 0.5s ease; +} + +.events-manager-container textarea.open { + height: auto; + opacity: 1; + pointer-events: initial; +} + +.events-manager-container input:disabled { + background-color: #e2e5e9; + color: #00000050; + box-shadow: none; + cursor: not-allowed; +} + +.events-manager-container .meta-event-manager { + margin-bottom: 0px; +} + +.events-manager-block { + display: flex; + flex-direction: column; + padding: 16px; + border-radius: 6px; + box-shadow: 3px 2px 17px 0px rgba(0, 0, 0, 0.25); +} + +.pixel-block { + display: flex; + flex-direction: column; + gap: 10px; + width: 378px; +} + +.pixel-block a, +.test-form button { + padding: 10px 6px; + text-align: center; + font-size: 16px; + font-weight: 600; + color: #ffffff; + background-color: #1b74e4; + border-radius: 6px; + border: none; + text-decoration: none; + cursor: pointer; + transition: 0.3s; +} + +.pixel-block a:hover, +.test-form button:hover { + box-shadow: 3px 2px 17px 0px rgba(0, 0, 0, 0.25); +} + +.test-events-block { + display: flex; + flex-direction: row; + width: fit-content; + gap: 30px; + background: #fff; + z-index: 3; +} + +.test-form { + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.test-hints__wrapper { + background-color: #bfdcff; +} + +.event-hints__wrapper { + position: relative; + margin-top: 10px; + background-color: #e2e5e9; +} + +.test-hints__wrapper, +.event-hints__wrapper { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + border-radius: 6px; +} + +.test-hints__wrapper span, +.event-hints__wrapper span { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + font-size: 14px; + font-weight: 600; + border-radius: 50%; + background-color: #000000; +} + +.test-hints__wrapper span { + color: #bfdcff; +} + +.event-hints__wrapper span { + color: #e2e5e9; +} + +.event-hints__wrapper .event-hints__close-icon { + position: absolute; + right: 12px; + cursor: pointer; + font-size: 12px; +} + +.event-hints__close-icon.hidden { + display: none; +} + +.test-hints { + margin-bottom: 10px; +} + +.test-hints p, +.event-hints p { + max-width: 50ch; + margin: 0; + color: #000000; +} + +.test-form-field-wrapper { + display: flex; + flex-direction: column; + gap: 10px; +} + +.text-form-inputs { + display: flex; + gap: 10px; +} + +.text-form-inputs div { + display: flex; + flex-direction: column; + gap: 10px; +} + +.text-form-inputs div:first-child { + width: 320px; +} + +.text-form-inputs select { + width: 218px; +} + +.test-event-code-wrapper input::placeholder { + color: #cccccc; +} + +.test-form button { + width: 100%; +} + +.test-form-img { + position: absolute; + top: 130px; + left: 35%; + width: 600px; + height: 370px; + z-index: 2; +} + +.advanced-edit-toggle { + display: flex; + align-items: center; + font-size: 14px; + font-weight: 600; + cursor: pointer; + width: max-content; +} + +.advanced-edit-toggle-arrow { + height: 6px; + margin-left: 8px; + margin-top: 3px; + transform: rotate(-180deg); + transition: all 0.5s ease; +} + +.advanced-edit-toggle-arrow.open { + transform: rotate(0); +} + +.advanced-payload-controls-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 15px; +} + +#populate-payload-button { + font-size: 14px; + font-weight: 600; + cursor: pointer; + text-decoration: underline; + opacity: 0; + pointer-events: none; + transition: opacity 0.5s ease; +} + +#populate-payload-button.show { + opacity: 1; + pointer-events: initial; +} + +.event-log-block { + width: 50%; +} + +.event-log-block h4 { + margin: 0 0 3px 0px; +} + +.event-log-block table { + display: block; + max-width: 550px; +} + +.event-log-block tbody { + display: grid; +} + +.event-log-block table tr { + display: grid; + grid-template-columns: minmax(250px, auto) 150px 95px; + grid-template-rows: 42px auto; + gap: 0 10px; + align-items: center; + height: 100%; + min-height: 40px; + border-bottom: 1px solid #e2e5e9; +} + +.event-log-block .event-log-block__head tr { + border-bottom: 1px solid #717375; + align-items: self-end; + text-align: center; +} + +.event-log-block .event-log-block__head tr td:first-child { + text-align: left; +} + +.event-log-block .event-log-block__head tr td { + margin-bottom: 5px; +} + +.event-log-block table tbody tr td { + display: block; + color: #000000; + font-size: 14px; +} + +.event-log-block table tbody tr td:last-child { + justify-self: end; +} + +.event-log-block__head { + display: block; + height: 40px; +} + +.test-event-td, +.test-event-pill { + font-size: 12px; +} + +.test-event-pill { + display: block; + width: 80px; + padding: 1px 6px; + border-radius: 5px; + font-weight: 700; + text-align: center; + background-color: #ececec; +} + +.test-event-pill--success { + color: #007e59; + background-color: #daf2c2; +} + +.test-event-pill--error { + color: #991700; + background-color: #f2bab0; +} + +.event-log-block table .test-event-msg--error { + grid-column: 1 / 4; + display: block; + min-height: 0; + width: 97.5%; + margin-bottom: 10px; + font-weight: 100; + text-align: left; + font-size: 12px; + background: #ececec; + overflow: hidden; + transition: all 0.2s ease-out; + padding: 7px; + opacity: 1; + border-radius: 6px; + z-index: 2; +} + +.event-log-block table .test-event-msg--error.hidden { + height: 0; + opacity: 0; + padding: 0; + margin: 0; +} + +.test-event-button--error { + display: flex; + align-items: center; + justify-content: center; + background-color: #de3f2466; + color: #991700; +} + +.show-error-icon { + margin-left: 5px; + margin-top: 1px; + width: 10px; + height: 10px; + transform: rotate(-180deg); + transition: all 0.5s ease; +} + +.show-error-icon.open { + transform: rotate(0); + margin-top: 2px; +} + +.show-error-icon path { + fill: #991700; +} + +.event-log-block table tbody tr .test-event-pill--type { + background-color: #ececec; + color: #797979; + min-width: fit-content; + width: auto; + font-size: 12px; +} + +.test-event-button--error:hover { + cursor: pointer; +} + +@media screen and (max-width: 1360px) { + .pixel-block { + width: auto; + } + + .test-events-block { + flex-direction: column; + width: auto; + } + + .test-form, + .event-log-block { + width: 100%; + } + + .test-form button, + .test-hints, + .event-hints { + max-width: 550px; + } + + .test-form-img { + display: none; + } + + .event-log-block table { + max-width: auto; + } +} + diff --git a/facebook-for-wordpress.php b/facebook-for-wordpress.php deleted file mode 100644 index 7befa678..00000000 --- a/facebook-for-wordpress.php +++ /dev/null @@ -1,115 +0,0 @@ -***ATTENTION: After upgrade the plugin may be deactivated due to a known issue, to workaround please refresh this page and activate plugin.*** The Facebook pixel is an analytics tool that helps you measure the effectiveness of your advertising. You can use the Facebook pixel to understand the actions people are taking on your website and reach audiences you care about. - * Author: Facebook - * Author URI: https://www.facebook.com/ - * Version: {*VERSION_NUMBER*} - * Text Domain: official-facebook-pixel - */ - -/* -* Copyright (C) 2017-present, Facebook, Inc. -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; version 2 of the License. -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -*/ - -/** - * @package FacebookPixelPlugin - */ - -namespace FacebookPixelPlugin; - -defined('ABSPATH') or die('Direct access not allowed'); - -require_once plugin_dir_path(__FILE__).'vendor/autoload.php'; - -use FacebookPixelPlugin\Core\FacebookPixel; -use FacebookPixelPlugin\Core\FacebookPluginConfig; -use FacebookPixelPlugin\Core\FacebookPluginUtils; -use FacebookPixelPlugin\Core\FacebookWordpressOpenBridge; -use FacebookPixelPlugin\Core\FacebookWordpressOptions; -use FacebookPixelPlugin\Core\FacebookWordpressPixelInjection; -use FacebookPixelPlugin\Core\FacebookWordpressSettingsPage; -use FacebookPixelPlugin\Core\FacebookWordpressSettingsRecorder; -use FacebookPixelPlugin\Core\ServerEventAsyncTask; - -class FacebookForWordpress { - public function __construct() { - // initialize options - FacebookWordpressOptions::initialize(); - - // load textdomain - load_plugin_textdomain( - FacebookPluginConfig::TEXT_DOMAIN, - false, - dirname(plugin_basename(__FILE__)) . '/languages/'); - - // initialize pixel - $options = FacebookWordpressOptions::getOptions(); - FacebookPixel::initialize(FacebookWordpressOptions::getPixelId()); - // Register WordPress pixel injection controlling where to fire pixel - add_action('init', array($this, 'registerPixelInjection'), 0); - - // Listen on /events to parse pixel fired events - add_action('parse_request', array($this, 'handle_events_request'), 0); - - // initialize admin page config - $this->registerSettingsPage(); - - // initialize the s2s event async task - new ServerEventAsyncTask(); - } - - /** - * Helper function for registering pixel injection. - */ - public function registerPixelInjection() { - $injectionObj = new FacebookWordpressPixelInjection(); - $injectionObj->inject(); - } - - /** - * Helper function for registering the settings page. - */ - public function registerSettingsPage() { - if (is_admin()) { - $plugin_name = plugin_basename(__FILE__); - new FacebookWordpressSettingsPage($plugin_name); - (new FacebookWordpressSettingsRecorder())->init(); - } - } - - public function handle_events_request(){ - $request_uri = $_SERVER['REQUEST_URI']; - - if( - FacebookPluginUtils::endsWith( - $request_uri, - FacebookPluginConfig::OPEN_BRIDGE_PATH) && - $_SERVER['REQUEST_METHOD'] == 'POST' - ) { - $data = json_decode(file_get_contents('php://input'), true); - if (!is_null($data)) { - FacebookWordpressOpenBridge::getInstance()->handleOpenBridgeReq( - $data - ); - } - if (isset($_SERVER['HTTP_ORIGIN'])) { - header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}"); - header('Access-Control-Allow-Credentials: true'); - header('Access-Control-Max-Age: 86400'); - } - exit(); - } - } -} - -$WP_FacebookForWordpress = new FacebookForWordpress(); diff --git a/integration/EDDUtils.php b/integration/EDDUtils.php deleted file mode 100644 index ee4b294f..00000000 --- a/integration/EDDUtils.php +++ /dev/null @@ -1,30 +0,0 @@ -cart->get_total(); - } -} diff --git a/integration/FacebookWordpressCalderaForm.php b/integration/FacebookWordpressCalderaForm.php deleted file mode 100644 index 8b6afb18..00000000 --- a/integration/FacebookWordpressCalderaForm.php +++ /dev/null @@ -1,120 +0,0 @@ -track($server_event); - - $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME); - $code = sprintf(" - - %s - - ", - $code); - - $out['html'] .= $code; - return $out; - } - - public static function readFormData($form) { - if (empty($form)) { - return array(); - } - return array( - 'email' => self::getEmail($form), - 'first_name' => self::getFirstName($form), - 'last_name' => self::getLastName($form), - 'phone' => self::getPhone($form), - 'state' => self::getState($form) - ); - } - - private static function getEmail($form) { - return self::getFieldValue($form, 'type', 'email'); - } - - private static function getFirstName($form) { - return self::getFieldValue($form, 'slug', 'first_name'); - } - - private static function getLastName($form) { - return self::getFieldValue($form, 'slug', 'last_name'); - } - - private static function getState($form){ - return self::getFieldValue($form, 'type', 'states'); - } - - private static function getPhone($form) { - // Extract phone number from the better version first, fallback to the basic - // version if it's null - $phone = self::getFieldValue($form, 'type', 'phone_better'); - return empty($phone) ? self::getFieldValue($form, 'type', 'phone') - : $phone; - } - - private static function getFieldValue($form, $attr, $attr_value) { - if (empty($form['fields'])) { - return null; - } - - foreach ($form['fields'] as $field) { - if (isset($field[$attr]) && $field[$attr] == $attr_value) { - return $_POST[$field['ID']]; - } - } - - return null; - } -} diff --git a/integration/FacebookWordpressContactForm7.php b/integration/FacebookWordpressContactForm7.php deleted file mode 100644 index b2f47a6e..00000000 --- a/integration/FacebookWordpressContactForm7.php +++ /dev/null @@ -1,171 +0,0 @@ - - - - - track($server_event); - - add_action( - 'wpcf7_feedback_response', - array(__CLASS__, 'injectLeadEvent'), - 20, 2); - - return $result; - } - - public static function injectLeadEvent($response, $result) { - if (FacebookPluginUtils::isInternalUser()) { - return $response; - } - - $events = FacebookServerSideEvent::getInstance()->getTrackedEvents(); - if( count($events) == 0 ){ - return $response; - } - $event_id = $events[0]->getEventId(); - $fbq_calls = PixelRenderer::render($events, self::TRACKING_NAME, false); - $code = sprintf(" -if( typeof window.pixelLastGeneratedLeadEvent === 'undefined' - || window.pixelLastGeneratedLeadEvent != '%s' ){ - window.pixelLastGeneratedLeadEvent = '%s'; - %s -} - ", - $event_id , - $event_id , - $fbq_calls); - - $response['fb_pxl_code'] = $code; - return $response; - } - - public static function readFormData($form) { - if (empty($form)) { - return array(); - } - - $form_tags = $form->scan_form_tags(); - $name = self::getName($form_tags); - - return array( - 'email' => self::getEmail($form_tags), - 'first_name' => $name[0], - 'last_name' => $name[1], - 'phone' => self::getPhone($form_tags) - ); - } - - private static function getEmail($form_tags) { - if (empty($form_tags)) { - return null; - } - - foreach ($form_tags as $tag) { - if ($tag->basetype == "email") { - return $_POST[$tag->name]; - } - } - - return null; - } - - private static function getName($form_tags) { - if (empty($form_tags)) { - return null; - } - - foreach ($form_tags as $tag) { - if ($tag->basetype === "text" - && strpos(strtolower($tag->name), 'name') !== false) { - return ServerEventFactory::splitName($_POST[$tag->name]); - } - } - - return null; - } - - private static function getPhone($form_tags) { - if (empty($form_tags)) { - return null; - } - - foreach ($form_tags as $tag) { - if ($tag->basetype === "tel") { - return $_POST[$tag->name]; - } - } - - return null; - } - -} diff --git a/integration/FacebookWordpressEasyDigitalDownloads.php b/integration/FacebookWordpressEasyDigitalDownloads.php deleted file mode 100644 index a7d35bac..00000000 --- a/integration/FacebookWordpressEasyDigitalDownloads.php +++ /dev/null @@ -1,340 +0,0 @@ - 0 ) { - value = _this.data('price'); - } - } - - var param = { - 'content_ids': [download], - 'content_type': 'product', - 'currency': currency, - '%s': '%s', - 'value': value - }; - fbq('set', 'agent', '%s', '%s'); - if(event_id){ - fbq('track', 'AddToCart', param, {'eventID': event_id}); - } - else{ - fbq('track', 'AddToCart', param); - } - }); -}); -"; - - public static function injectPixelCode() { - // AddToCart JS listener - add_action( - 'edd_after_download_content', - array(__CLASS__, 'injectAddToCartListener') - ); - add_action( - 'edd_downloads_list_after', - array(__CLASS__, 'injectAddToCartListener') - ); - - //Hooks to AddToCart ajax requests - add_action( - 'wp_ajax_edd_add_to_cart', - array(__CLASS__, 'injectAddToCartEventAjax'), - 5 - ); - - add_action( - 'wp_ajax_nopriv_edd_add_to_cart', - array(__CLASS__, 'injectAddToCartEventAjax'), - 5 - ); - - //Injects a hidden field with event id to send it in AddToCart ajax request - add_action( - 'edd_purchase_link_top', - array(__CLASS__, 'injectAddToCartEventId') - ); - - // InitiateCheckout - self::addPixelFireForHook(array( - 'hook_name' => 'edd_after_checkout_cart', - 'classname' => __CLASS__, - 'inject_function' => 'injectInitiateCheckoutEvent')); - - // Purchase - add_action( - 'edd_payment_receipt_after', - array(__CLASS__, 'trackPurchaseEvent'), - 10, 2); - - // ViewContent - add_action( - 'edd_after_download_content', - array(__CLASS__, 'injectViewContentEvent'), - 40, 1 - ); - } - - public static function injectAddToCartEventId(){ - if(FacebookPluginUtils::isInternalUser()){ - return; - } - $eventId = EventIdGenerator::guidv4(); - printf("", - $eventId); - } - - public static function injectAddToCartEventAjax(){ - if( isset($_POST['nonce']) && isset($_POST['download_id']) - && isset($_POST['post_data'])){ - $download_id = absint( $_POST['download_id'] ); - //Adding form validations - $nonce = sanitize_text_field( $_POST['nonce'] ); - if( wp_verify_nonce($nonce, 'edd-add-to-cart-'.$download_id) === false ){ - return; - } - //Getting form data - parse_str( $_POST['post_data'], $post_data ); - if(isset($post_data['facebook_event_id'])){ - //Starting Conversions API event creation - $event_id = $post_data['facebook_event_id']; - $server_event = ServerEventFactory::safeCreateEvent( - 'AddToCart', - array(__CLASS__, 'createAddToCartEvent'), - array($download_id), - self::TRACKING_NAME - ); - $server_event->setEventId($event_id); - FacebookServerSideEvent::getInstance()->track($server_event); - } - } - } - - public static function injectAddToCartListener($download_id) { - if (FacebookPluginUtils::isInternalUser()) { - return; - } - - $listener_code = sprintf( - self::$addToCartJS, - FacebookPixel::FB_INTEGRATION_TRACKING_KEY, - self::TRACKING_NAME, - FacebookWordpressOptions::getAgentString(), - FacebookWordpressOptions::getPixelId() - ); - - printf(" - - - - ", - $listener_code); - } - - public static function injectInitiateCheckoutEvent() { - if (FacebookPluginUtils::isInternalUser() || !function_exists('EDD')) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'InitiateCheckout', - array(__CLASS__, 'createInitiateCheckoutEvent'), - array(), - self::TRACKING_NAME - ); - FacebookServerSideEvent::getInstance()->track($server_event); - - $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME); - printf(" - -%s - - ", - $code); - } - - public static function trackPurchaseEvent($payment, $edd_receipt_args) { - if (FacebookPluginUtils::isInternalUser() || empty($payment->ID)) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'Purchase', - array(__CLASS__, 'createPurchaseEvent'), - array($payment), - self::TRACKING_NAME - ); - FacebookServerSideEvent::getInstance()->track($server_event); - - add_action( - 'wp_footer', - array(__CLASS__, 'injectPurchaseEvent'), - 20 - ); - } - - public static function injectPurchaseEvent() { - $events = FacebookServerSideEvent::getInstance()->getTrackedEvents(); - $code = PixelRenderer::render($events, self::TRACKING_NAME); - - printf(" - -%s - - ", - $code); - } - - public static function injectViewContentEvent($download_id) { - if (FacebookPluginUtils::isInternalUser() || empty($download_id)) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'ViewContent', - array(__CLASS__, 'createViewContentEvent'), - array($download_id), - self::TRACKING_NAME - ); - - FacebookServerSideEvent::getInstance()->track($server_event); - - $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME); - printf(" - -%s - - ", - $code); - } - - public static function createInitiateCheckoutEvent() { - $event_data = FacebookPluginUtils::getLoggedInUserInfo(); - $event_data['currency'] = EDDUtils::getCurrency(); - $event_data['value'] = EDDUtils::getCartTotal(); - - return $event_data; - } - - public static function createPurchaseEvent($payment) { - $event_data = array(); - - $payment_meta = \edd_get_payment_meta($payment->ID); - if (empty($payment_meta)) { - return $event_data; - } - - $event_data['email'] = $payment_meta['email']; - $event_data['first_name'] = $payment_meta['user_info']['first_name']; - $event_data['last_name'] = $payment_meta['user_info']['last_name']; - - $content_ids = array(); - $value = 0; - foreach ($payment_meta['cart_details'] as $item) { - $content_ids[] = $item['id']; - $value += $item['price']; - } - - $event_data['currency'] = $payment_meta['currency']; - $event_data['value'] = $value; - $event_data['content_ids'] = $content_ids; - $event_data['content_type'] = 'product'; - - return $event_data; - } - - public static function createViewContentEvent($download_id){ - $event_data = FacebookPluginUtils::getLoggedInUserInfo(); - $currency = EDDUtils::getCurrency(); - $download = edd_get_download($download_id); - $title = $download ? $download->post_title : ''; - - if (get_post_meta($download_id, '_variable_pricing', true)) { - $prices = get_post_meta($download_id, 'edd_variable_prices', true); - $price = array_shift($prices); - $value = $price['amount']; - } else { - $value = get_post_meta($download_id, 'edd_price', true); - } - if (!$value) { - $value = 0; - } - $event_data['content_ids'] = [(string)$download_id]; - $event_data['content_type'] = 'product'; - $event_data['currency'] = $currency; - $event_data['value'] = floatval($value); - $event_data['content_name'] = $title; - return $event_data; - } - - public static function createAddToCartEvent($download_id){ - $event_data = FacebookPluginUtils::getLoggedInUserInfo(); - $currency = EDDUtils::getCurrency(); - $download = edd_get_download($download_id); - $title = $download ? $download->post_title : ''; - if ( get_post_meta($download_id, '_variable_pricing', true) ) { - $prices = get_post_meta($download_id, 'edd_variable_prices', true); - $price = array_shift($prices); - $value = $price['amount']; - } else { - $value = get_post_meta($download_id, 'edd_price', true); - } - if (!$value) { - $value = 0; - } - $event_data['content_ids'] = [(string)$download_id]; - $event_data['content_type'] = 'product'; - $event_data['currency'] = $currency; - $event_data['value'] = $value; - $event_data['content_name'] = $title; - return $event_data; - } -} diff --git a/integration/FacebookWordpressFormidableForm.php b/integration/FacebookWordpressFormidableForm.php deleted file mode 100644 index 0f126f76..00000000 --- a/integration/FacebookWordpressFormidableForm.php +++ /dev/null @@ -1,173 +0,0 @@ -track($server_event); - - add_action( - 'wp_footer', - array(__CLASS__, 'injectLeadEvent'), - 20 - ); - } - - public static function injectLeadEvent() { - if (FacebookPluginUtils::isInternalUser()) { - return; - } - - $events = FacebookServerSideEvent::getInstance()->getTrackedEvents(); - $code = PixelRenderer::render($events, self::TRACKING_NAME); - - printf(" - -%s - - ", - $code); - } - - public static function readFormData($entry_id) { - if (empty($entry_id)) { - return array(); - } - - $entry_values = - IntegrationUtils::getFormidableFormsEntryValues($entry_id); - - $field_values = $entry_values->get_field_values(); - if (!empty($field_values)) { - $user_data = array( - 'email' => self::getEmail($field_values), - 'first_name' => self::getFirstName($field_values), - 'last_name' => self::getLastName($field_values), - 'phone' => self::getPhone($field_values) - ); - $address_data = self::getAddressInformation($field_values); - return array_merge($user_data, $address_data); - } - - return array(); - } - - private static function getEmail($field_values) { - return self::getFieldValueByType($field_values, 'email'); - } - - private static function getFirstName($field_values) { - return self::getFieldValue($field_values, 'text', 'Name', 'First'); - } - - private static function getLastName($field_values) { - return self::getFieldValue($field_values, 'text', 'Last', 'Last'); - } - - private static function getPhone($field_values) { - return self::getFieldValueByType($field_values, 'phone'); - } - - private static function getAddressInformation($field_values){ - $address_saved_value = self::getFieldValueByType($field_values, 'address'); - $address_data = array(); - if($address_saved_value){ - if(isset($address_saved_value['city'])){ - $address_data['city'] = $address_saved_value['city']; - } - if(isset($address_saved_value['state'])){ - $address_data['state'] = $address_saved_value['state']; - } - // Validating ISO code - // In current version, country field saves the full name - if( - isset($address_saved_value['country']) - && strlen($address_saved_value['country']) == 2 - ){ - $address_data['country'] = $address_saved_value['country']; - } - if(isset($address_saved_value['zip'])){ - $address_data['zip'] = $address_saved_value['zip']; - } - } - return $address_data; - } - - private static function getFieldValueByType($field_values, $type){ - foreach ($field_values as $field_value) { - $field = $field_value->get_field(); - if ($field->type == $type) { - return $field_value->get_saved_value(); - } - } - - return null; - } - - private static function getFieldValue( - $field_values, - $type, - $name, - $description) - { - foreach ($field_values as $field_value) { - $field = $field_value->get_field(); - if ($field->type == $type && - $field->name == $name && - $field->description == $description) { - return $field_value->get_saved_value(); - } - } - - return null; - } -} diff --git a/integration/FacebookWordpressIntegrationBase.php b/integration/FacebookWordpressIntegrationBase.php deleted file mode 100644 index 5630de7f..00000000 --- a/integration/FacebookWordpressIntegrationBase.php +++ /dev/null @@ -1,63 +0,0 @@ -getNumberOfParameters(); - $argv = $reflection->getParameters(); - - $callback = function () use ($user_function, $argv) { - $hook_wp_footer = function () use ($user_function, $argv) { - \call_user_func_array($user_function, $argv); - }; - add_action( - 'wp_footer', - $hook_wp_footer, - 11); - }; - - add_action($hook_name, $callback, $priority, $argc); - } -} diff --git a/integration/FacebookWordpressMailchimpForWp.php b/integration/FacebookWordpressMailchimpForWp.php deleted file mode 100644 index 3c98fdf8..00000000 --- a/integration/FacebookWordpressMailchimpForWp.php +++ /dev/null @@ -1,107 +0,0 @@ - 'mc4wp_form_subscribed', - 'classname' => __CLASS__, - 'inject_function' => 'injectLeadEvent')); - } - - public static function injectLeadEvent() { - if (FacebookPluginUtils::isInternalUser()) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'Lead', - array(__CLASS__, 'readFormData'), - array(), - self::TRACKING_NAME, - true - ); - FacebookServerSideEvent::getInstance()->track($server_event); - - $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME); - printf(" - - %s - - ", - $code); - } - - public static function readFormData() { - $event_data = array(); - if (!empty($_POST['EMAIL'])) { - $event_data['email'] = $_POST['EMAIL']; - } - - if (!empty($_POST['FNAME'])) { - $event_data['first_name'] = $_POST['FNAME']; - } - - if (!empty($_POST['LNAME'])) { - $event_data['last_name'] = $_POST['LNAME']; - } - - if(!empty($_POST['PHONE'])){ - $event_data['phone'] = $_POST['PHONE']; - } - - if(!empty($_POST['ADDRESS'])){ - $address_data = $_POST['ADDRESS']; - - if(!empty($address_data['city'])){ - $event_data['city'] = $address_data['city']; - } - - if(!empty($address_data['state'])){ - $event_data['state'] = $address_data['state']; - } - - if(!empty($address_data['zip'])){ - $event_data['zip'] = $address_data['zip']; - } - - // You can edit the country field in the plugin editor - // and do not use ISO code - // Validating this case - if( - !empty($address_data['country']) - && strlen($address_data['country'])==2 - ){ - $event_data['country'] = $address_data['country']; - } - } - return $event_data; - } -} diff --git a/integration/FacebookWordpressNinjaForms.php b/integration/FacebookWordpressNinjaForms.php deleted file mode 100644 index 2af3689e..00000000 --- a/integration/FacebookWordpressNinjaForms.php +++ /dev/null @@ -1,171 +0,0 @@ - $action) { - if (!isset($action['settings']) || !isset($action['settings']['type'])) { - continue; - } - - $type = $action['settings']['type']; - if (!is_string($type)) { - continue; - } - - // inject code when form is submitted successfully - if ($type == 'successmessage') { - $event = ServerEventFactory::safeCreateEvent( - 'Lead', - array(__CLASS__, 'readFormData'), - array($form_data), - self::TRACKING_NAME, - true - ); - FacebookServerSideEvent::getInstance()->track($event); - - $pixel_code = PixelRenderer::render(array($event), self::TRACKING_NAME); - $code = sprintf(" - -%s - - ", $pixel_code); - - $action['settings']['success_msg'] .= $code; - $actions[$key] = $action; - } - } - - return $actions; - } - - public static function readFormData($form_data) { - if (empty($form_data)) { - return array(); - } - - $event_data = array(); - $name = self::getName($form_data); - if( $name ){ - $event_data['first_name'] = $name[0]; - $event_data['last_name'] = $name[1]; - } - else{ - $event_data['first_name'] = self::getFirstName($form_data); - $event_data['last_name'] = self::getLastName($form_data); - } - $event_data['email'] = self::getEmail($form_data); - $event_data['phone'] = self::getPhone($form_data); - $event_data['city'] = self::getCity($form_data); - $event_data['zip'] = self::getZipCode($form_data); - $event_data['state'] = self::getState($form_data); - $event_data['country'] = self::getCountry($form_data); - $event_data['gender'] = self::getGender($form_data); - - return $event_data; - } - - private static function getEmail($form_data) { - return self::getField($form_data, 'email'); - } - - private static function getName($form_data) { - $name = self::getField($form_data, 'name'); - if($name){ - return ServerEventFactory::splitName($name); - } - return null; - } - - private static function getFirstName($form_data){ - return self::getField($form_data, 'firstname'); - } - - private static function getLastName($form_data){ - return self::getField($form_data, 'lastname'); - } - - private static function getPhone($form_data) { - return self::getField($form_data, 'phone'); - } - - private static function getCity($form_data) { - return self::getField($form_data, 'city'); - } - - private static function getZipCode($form_data) { - return self::getField($form_data, 'zip'); - } - - private static function getState($form_data) { - return self::getField($form_data, 'liststate'); - } - - private static function getCountry($form_data) { - return self::getField($form_data, 'listcountry'); - } - - private static function getGender($form_data) { - return self::getField($form_data, 'gender'); - } - - private static function hasPrefix($string, $prefix){ - $len = strlen($prefix); - return substr($string, 0, $len) === $prefix; - } - - private static function getField($form_data, $key) { - if (empty($form_data['fields'])) { - return null; - } - - foreach ($form_data['fields'] as $field) { - if ( self::hasPrefix( $field['key'], $key) ) { - return $field['value']; - } - } - - return null; - } -} diff --git a/integration/FacebookWordpressWPECommerce.php b/integration/FacebookWordpressWPECommerce.php deleted file mode 100644 index fe16a2e5..00000000 --- a/integration/FacebookWordpressWPECommerce.php +++ /dev/null @@ -1,189 +0,0 @@ - 'wpsc_before_shopping_cart_page', - 'classname' => __CLASS__, - 'inject_function' => 'injectInitiateCheckoutEvent')); - - // Purchase - add_action( - 'wpsc_transaction_results_shutdown', - array(__CLASS__, 'injectPurchaseEvent'), 11, 3); - } - - // Event hook for AddToCart. - public static function injectAddToCartEvent($response) { - if (FacebookPluginUtils::isInternalUser()) { - return $response; - } - - $product_id = $response['product_id']; - $server_event = ServerEventFactory::safeCreateEvent( - 'AddToCart', - array(__CLASS__, 'createAddToCartEvent'), - array($product_id), - self::TRACKING_NAME - ); - FacebookServerSideEvent::getInstance()->track($server_event); - - $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME); - $code = sprintf(" - - %s - - ", - $code); - $response['widget_output'] .= $code; - return $response; - } - - public static function injectInitiateCheckoutEvent() { - if (FacebookPluginUtils::isInternalUser()) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'InitiateCheckout', - array(__CLASS__, 'createInitiateCheckoutEvent'), - array(), - self::TRACKING_NAME - ); - FacebookServerSideEvent::getInstance()->track($server_event); - - $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME); - printf(" - -%s - - ", - $code); - } - - public static function injectPurchaseEvent( - $purchase_log_object, - $session_id, - $display_to_screen) - { - if (FacebookPluginUtils::isInternalUser() || !$display_to_screen) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'Purchase', - array(__CLASS__, 'createPurchaseEvent'), - array($purchase_log_object), - self::TRACKING_NAME - ); - FacebookServerSideEvent::getInstance()->track($server_event); - - $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME); - - printf(" - -%s - - ", - $code); - } - - public static function createPurchaseEvent($purchase_log_object) { - $event_data = FacebookPluginUtils::getLoggedInUserInfo(); - - $cart_items = $purchase_log_object->get_items(); - $total_price = $purchase_log_object->get_total(); - $currency = function_exists('\wpsc_get_currency_code') - ? \wpsc_get_currency_code() : ''; - - $item_ids = array(); - foreach ($cart_items as $item) { - // This is for backwards compatibility - $item_array = (array) $item; - $item_ids[] = $item_array['prodid']; - } - - $event_data['content_ids'] = $item_ids; - $event_data['content_type'] = 'product'; - $event_data['currency'] = $currency; - $event_data['value'] = $total_price; - - return $event_data; - } - - public static function createAddToCartEvent($product_id) { - $event_data = FacebookPluginUtils::getLoggedInUserInfo(); - - global $wpsc_cart; - $cart_items = $wpsc_cart->get_items(); - foreach ($cart_items as $item) { - if ($item->product_id === $product_id) { - $unit_price = $item->unit_price; - break; - } - } - - $event_data['content_ids'] = array($product_id); - $event_data['content_type'] = 'product'; - $event_data['currency'] = - function_exists('\wpsc_get_currency_code') - ? \wpsc_get_currency_code() : ''; - $event_data['value'] = $unit_price; - - return $event_data; - } - - public static function createInitiateCheckoutEvent() { - $event_data = FacebookPluginUtils::getLoggedInUserInfo(); - $content_ids = array(); - - $value = 0; - global $wpsc_cart; - $cart_items = $wpsc_cart->get_items(); - foreach ($cart_items as $item) { - $content_ids[] = $item->product_id; - $value += $item->unit_price; - } - - $event_data['currency'] = - function_exists('\wpsc_get_currency_code') - ? \wpsc_get_currency_code() : ''; - $event_data['value'] = $value; - $event_data['content_ids'] = $content_ids; - - return $event_data; - } -} diff --git a/integration/FacebookWordpressWPForms.php b/integration/FacebookWordpressWPForms.php deleted file mode 100644 index c92ed8fb..00000000 --- a/integration/FacebookWordpressWPForms.php +++ /dev/null @@ -1,187 +0,0 @@ -track($server_event); - - add_action( - 'wp_footer', - array(__CLASS__, 'injectLeadEvent'), - 20 - ); - } - - public static function injectLeadEvent() { - if (FacebookPluginUtils::isInternalUser()) { - return; - } - - $events = FacebookServerSideEvent::getInstance()->getTrackedEvents(); - $pixel_code = PixelRenderer::render($events, self::TRACKING_NAME); - - printf(" - -%s - - ", - $pixel_code); - } - - public static function readFormData($entry, $form_data) { - if (empty($entry) || empty($form_data)) { - return array(); - } - - $name = self::getName($entry, $form_data); - - $event_data = array( - 'email' => self::getEmail($entry, $form_data), - 'first_name' => !empty($name) ? $name[0] : null, - 'last_name' => !empty($name) ? $name[1] : null, - 'phone' => self::getPhone($entry, $form_data) - ); - - $event_data = array_merge( - $event_data, - self::getAddress($entry, $form_data) - ); - - return $event_data; - } - - private static function getPhone($entry, $form_data) { - return self::getField($entry, $form_data, 'phone'); - } - - private static function getEmail($entry, $form_data) { - return self::getField($entry, $form_data, 'email'); - } - - private static function getAddress($entry, $form_data){ - $address_field_data = self::getField($entry, $form_data, 'address'); - if($address_field_data == null){ - return array(); - } - $address_data = array(); - if(isset($address_field_data['city'])){ - $address_data['city'] = $address_field_data['city']; - } - if(isset($address_field_data['state'])){ - $address_data['state'] = $address_field_data['state']; - } - //Country values are sent in ISO format - if(isset($address_field_data['country'])){ - $address_data['country'] = $address_field_data['country']; - } - else{ - // When country is not present, it could be that address scheme is us - // so country will be US - $address_scheme = self::getAddressScheme($form_data); - if( $address_scheme == 'us'){ - $address_data['country'] = 'US'; - } - } - if(isset($address_field_data['postal'])){ - $address_data['zip'] = $address_field_data['postal']; - } - return $address_data; - } - - private static function getName($entry, $form_data) { - if (empty($form_data['fields']) || empty($entry['fields'])) { - return null; - } - - $entries = $entry['fields']; - foreach ($form_data['fields'] as $field) { - if ($field['type'] == 'name') { - if ($field['format'] == 'simple') { - return ServerEventFactory::splitName($entries[$field['id']]); - } else if ($field['format'] == 'first-last') { - return array( - $entries[$field['id']]['first'], - $entries[$field['id']]['last'] - ); - } - } - } - - return null; - } - - private static function getField($entry, $form_data, $type) { - if (empty($form_data['fields']) || empty($entry['fields'])) { - return null; - } - - foreach ($form_data['fields'] as $field) { - if ($field['type'] == $type) { - return $entry['fields'][$field['id']]; - } - } - - return null; - } - - private static function getAddressScheme($form_data){ - foreach ($form_data['fields'] as $field) { - if ($field['type'] == 'address') { - if(isset($field['scheme'])){ - return $field['scheme']; - } - } - } - return null; - } -} diff --git a/integration/FacebookWordpressWooCommerce.php b/integration/FacebookWordpressWooCommerce.php deleted file mode 100644 index f1066e21..00000000 --- a/integration/FacebookWordpressWooCommerce.php +++ /dev/null @@ -1,390 +0,0 @@ -" - .$content.""; - } - - public static function trackViewContentEvent(){ - if(FacebookPluginUtils::isInternalUser()){ - return; - } - - global $post; - if (!isset($post->ID)){ - return; - } - - $product = wc_get_product($post->ID); - if(!$product){ - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'ViewContent', - array(__CLASS__, 'createViewContentEvent'), - array($product), - self::TRACKING_NAME - ); - - FacebookServerSideEvent::getInstance()->track($server_event, false); - - self::enqueuePixelCode($server_event); - } - - public static function createViewContentEvent($product){ - $event_data = self::getPIIFromSession(); - - $product_id = self::getProductId($product); - $content_type = $product->is_type('variable') ? 'product_group' : 'product'; - - $event_data['content_type'] = $content_type; - $event_data['currency'] = \get_woocommerce_currency(); - $event_data['value'] = $product->get_price(); - $event_data['content_ids'] = array($product_id); - $event_data['content_name'] = $product->get_title(); - $event_data['content_category'] = - self::getProductCategory($product->get_id()); - - return array_filter($event_data); - } - - private static function getProductCategory($product_id){ - $categories = get_the_terms( - $product_id, - 'product_cat' - ); - - return (is_array($categories) && count($categories) > 0) ? $categories[0]->name : null; - } - - public static function trackPurchaseEvent($order_id) { - if (FacebookPluginUtils::isInternalUser()) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'Purchase', - array(__CLASS__, 'createPurchaseEvent'), - array($order_id), - self::TRACKING_NAME - ); - - FacebookServerSideEvent::getInstance()->track($server_event); - - self::enqueuePixelCode($server_event); - } - - public static function createPurchaseEvent($order_id) { - $order = wc_get_order($order_id); - - $content_type = 'product'; - $product_ids = array(); - $contents = array(); - - foreach ($order->get_items() as $item) { - $product = wc_get_product($item->get_product_id()); - if ('product_group' !== $content_type - && $product->is_type('variable')) - { - $content_type = 'product_group'; - } - - $quantity = $item->get_quantity(); - $product_id = self::getProductId($product); - - $content = new Content(); - $content->setProductId($product_id); - $content->setQuantity($quantity); - $content->setItemPrice($item->get_total() / $quantity); - - $contents[] = $content; - $product_ids[] = $product_id; - } - - $event_data = self::getPiiFromBillingInformation($order); - $event_data['content_type'] = $content_type; - $event_data['currency'] = \get_woocommerce_currency(); - $event_data['value'] = $order->get_total(); - $event_data['content_ids'] = $product_ids; - $event_data['contents'] = $contents; - - return $event_data; - } - - public static function trackAddToCartEvent( - $cart_item_key, $product_id, $quantity, $variation_id) { - if (FacebookPluginUtils::isInternalUser()) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'AddToCart', - array(__CLASS__, 'createAddToCartEvent'), - array($cart_item_key, $product_id, $quantity), - self::TRACKING_NAME - ); - - // When AJAX is used to add an item to the cart - // The Conversions API event should be sent inmediately - // because the wp_footer action is not executed - $is_ajax_request = wp_doing_ajax(); - - FacebookServerSideEvent::getInstance()->track($server_event, - $is_ajax_request); - - // If it is not an ajax request - // We show the pixel call in the footer - // Otherwise we add a filter - // to modify the ajax response - // and show the fbq call in the div #fb-pxl-ajax-code - if(!$is_ajax_request){ - self::enqueuePixelCode($server_event); - } - else{ - FacebookServerSideEvent::getInstance()->setPendingPixelEvent( - 'addPixelCodeToAddToCartFragment', - $server_event - ); - add_filter('woocommerce_add_to_cart_fragments', - array(__CLASS__, 'addPixelCodeToAddToCartFragment')); - } - } - - public static function addPixelCodeToAddToCartFragment($fragments) { - $server_event = - FacebookServerSideEvent::getInstance() - ->getPendingPixelEvent('addPixelCodeToAddToCartFragment'); - if( !is_null($server_event) ){ - $pixel_code = self::generatePixelCode($server_event, true); - $fragments['#'.self::DIV_ID_FOR_AJAX_PIXEL_EVENTS] = - self::getDivForAjaxPixelEvent($pixel_code); - } - return $fragments; - } - - public static function createAddToCartEvent( - $cart_item_key, $product_id, $quantity) - { - $event_data = self::getPIIFromSession(); - $event_data['content_type'] = 'product'; - $event_data['currency'] = \get_woocommerce_currency(); - - $cart_item = self::getCartItem($cart_item_key); - if (!empty($cart_item_key)) { - $event_data['content_ids'] = - array(self::getProductId($cart_item['data'])); - $event_data['value'] = self::getAddToCartValue($cart_item, $quantity); - } - - return $event_data; - } - - public static function trackInitiateCheckout() { - if (FacebookPluginUtils::isInternalUser()) { - return; - } - - $server_event = ServerEventFactory::safeCreateEvent( - 'InitiateCheckout', - array(__CLASS__, 'createInitiateCheckoutEvent'), - array(), - self::TRACKING_NAME - ); - - FacebookServerSideEvent::getInstance()->track($server_event); - - self::enqueuePixelCode($server_event); - } - - public static function createInitiateCheckoutEvent() { - $event_data = self::getPIIFromSession(); - $event_data['content_type'] = 'product'; - $event_data['currency'] = \get_woocommerce_currency(); - - if ($cart = WC()->cart) { - $event_data['num_items'] = $cart->get_cart_contents_count(); - $event_data['value'] = $cart->total; - $event_data['content_ids'] = self::getContentIds($cart); - $event_data['contents'] = self::getContents($cart); - } - - return $event_data; - } - - private static function getPiiFromBillingInformation($order) { - $pii = array(); - - $pii['first_name'] = $order->get_billing_first_name(); - $pii['last_name'] = $order->get_billing_last_name(); - $pii['email'] = $order->get_billing_email(); - $pii['zip'] = $order->get_billing_postcode(); - $pii['state'] = $order->get_billing_state(); - $pii['country'] = $order->get_billing_country(); - $pii['city'] = $order->get_billing_city(); - $pii['phone'] = $order->get_billing_phone(); - - return $pii; - } - - private static function getAddToCartValue($cart_item, $quantity) { - if (!empty($cart_item)) { - $price = $cart_item['line_total'] / $cart_item['quantity']; - return $quantity * $price; - } - - return null; - } - - private static function getCartItem($cart_item_key) { - if (WC()->cart) { - $cart = WC()->cart->get_cart(); - if (!empty($cart) && !empty($cart[$cart_item_key])) { - return $cart[$cart_item_key]; - } - } - - return null; - } - - private static function getContentIds($cart) { - $product_ids = []; - foreach ($cart->get_cart() as $item) { - if (!empty($item['data'])) { - $product_ids[] = self::getProductId($item['data']); - } - } - - return $product_ids; - } - - private static function getContents($cart) { - $contents = []; - foreach ($cart->get_cart() as $item) { - if (!empty($item['data']) && !empty($item['quantity'])) { - $content = new Content(); - $content->setProductId(self::getProductId($item['data'])); - $content->setQuantity($item['quantity']); - $content->setItemPrice($item['line_total'] / $item['quantity']); - - $contents[] = $content; - } - } - - return $contents; - } - - private static function getProductId($product) { - $woo_id = $product->get_id(); - - return $product->get_sku() ? - $product->get_sku() . '_' . $woo_id - : self::FB_ID_PREFIX . $woo_id; - } - - private static function getPIIFromSession(){ - $event_data = FacebookPluginUtils::getLoggedInUserInfo(); - $user_id = get_current_user_id(); - if($user_id != 0){ - $event_data['city'] = get_user_meta($user_id, 'billing_city', true); - $event_data['zip'] = get_user_meta($user_id, 'billing_postcode', true); - $event_data['country'] = get_user_meta($user_id, 'billing_country', true); - $event_data['state'] = get_user_meta($user_id, 'billing_state', true); - $event_data['phone'] = get_user_meta($user_id, 'billing_phone', true); - } - return array_filter($event_data); - } - - private static function isFacebookForWooCommerceActive() { - return in_array( - 'facebook-for-woocommerce/facebook-for-woocommerce.php', - get_option('active_plugins')); - } - - public static function generatePixelCode($server_event, $script_tag = false){ - $code = PixelRenderer::render(array($server_event), self::TRACKING_NAME, - $script_tag); - $code = sprintf(" - -%s - - ", - $code); - return $code; - } - - public static function enqueuePixelCode($server_event){ - $code = self::generatePixelCode($server_event, false); - wc_enqueue_js($code); - return $code; - } -} diff --git a/integration/IntegrationUtils.php b/integration/IntegrationUtils.php deleted file mode 100644 index fb219665..00000000 --- a/integration/IntegrationUtils.php +++ /dev/null @@ -1,26 +0,0 @@ -cart->get_total(); + } +} diff --git a/integration/class-facebookwordpresscalderaform.php b/integration/class-facebookwordpresscalderaform.php new file mode 100644 index 00000000..5010eda1 --- /dev/null +++ b/integration/class-facebookwordpresscalderaform.php @@ -0,0 +1,225 @@ +track( $server_event ); + + $code = PixelRenderer::render( + array( $server_event ), + self::TRACKING_NAME + ); + $code = sprintf( + ' + + %s + + ', + $code + ); + + $out['html'] .= $code; + return $out; + } + + /** + * Reads the form data from the Caldera Forms submission. + * + * @param array $form The form data. + * + * @return array The form data in the format + * expected by the `FacebookServerSideEvent` class. + */ + public static function readFormData( $form ) { + if ( empty( $form ) ) { + return array(); + } + return array( + 'email' => self::getEmail( $form ), + 'first_name' => self::getFirstName( $form ), + 'last_name' => self::getLastName( $form ), + 'phone' => self::getPhone( $form ), + 'state' => self::getState( $form ), + ); + } + + /** + * Get the email address from the form data. + * + * @param array $form The form data. + * + * @return string The email address. + */ + private static function getEmail( $form ) { + return self::getFieldValue( $form, 'type', 'email' ); + } + + /** + * Get the first name from the form data. + * + * @param array $form The form data. + * + * @return string The first name. + */ + private static function getFirstName( $form ) { + return self::getFieldValue( $form, 'slug', 'first_name' ); + } + + /** + * Get the last name from the form data. + * + * @param array $form The form data. + * + * @return string The last name. + */ + private static function getLastName( $form ) { + return self::getFieldValue( $form, 'slug', 'last_name' ); + } + + /** + * Get the state from the form data. + * + * @param array $form The form data. + * + * @return string|null The state, or null if not found. + */ + private static function getState( $form ) { + return self::getFieldValue( $form, 'type', 'states' ); + } + + /** + * Get the phone number from the form data. + * + * Attempts to extract the phone number using the 'phone_better' type first. + * If not found, falls back to using the 'phone' type. + * + * @param array $form The form data. + * + * @return string|null The phone number, or null if not found. + */ + private static function getPhone( $form ) { + $phone = self::getFieldValue( $form, 'type', 'phone_better' ); + return empty( $phone ) ? + self::getFieldValue( $form, 'type', 'phone' ) : $phone; + } + + /** + * Retrieves the value of a field from the form data. + * + * Searches through the form's fields to find a field with the specified + * attribute and attribute value. If a match is found, returns the value + * from the $_POST array using the field's ID. + * + * @param array $form The form data containing fields. + * @param string $attr The attribute to match against in the field. + * @param string $attr_value The value of the attribute to look for. + * + * @return mixed|null The value of the field from $_POST, or null + * if not found. + */ + private static function getFieldValue( $form, $attr, $attr_value ) { + if ( empty( $form['fields'] ) ) { + return null; + } + + foreach ( $form['fields'] as $field ) { + if ( isset( $field[ $attr ] ) && $field[ $attr ] === $attr_value ) { + return sanitize_text_field( + wp_unslash( + $_POST[ $field['ID'] ] ?? '' // phpcs:ignore WordPress.Security.NonceVerification.Missing + ) + ); + } + } + + return null; + } +} diff --git a/integration/class-facebookwordpresscontactform7.php b/integration/class-facebookwordpresscontactform7.php new file mode 100644 index 00000000..6c459202 --- /dev/null +++ b/integration/class-facebookwordpresscontactform7.php @@ -0,0 +1,272 @@ + + + + + track( $server_event ); + + add_action( + 'wpcf7_feedback_response', + array( __CLASS__, 'injectLeadEvent' ), + 20, + 2 + ); + + return $result; + } + + /** + * Injects the Pixel code into the Contact Form 7 response. + * + * Hooks into the `wpcf7_feedback_response` action and checks if the form + * is submitted successfully and if the user is not an internal user. + * If conditions are met, it renders the Pixel code using the `PixelRenderer` class + * and appends the code to the form response. + * + * @param array $response The Contact Form 7 response. + * @param array $result The form data. + * + * @return array The modified Contact Form 7 response. + */ + public static function injectLeadEvent( $response, $result ) { + if ( FacebookPluginUtils::is_internal_user() ) { + return $response; + } + + $events = FacebookServerSideEvent::get_instance()->get_tracked_events(); + if ( count( $events ) === 0 ) { + return $response; + } + $event_id = $events[0]->getEventId(); + $fbq_calls = PixelRenderer::render( + $events, + self::TRACKING_NAME, + false + ); + $code = sprintf( + " + if( typeof window.pixelLastGeneratedLeadEvent === 'undefined' + || window.pixelLastGeneratedLeadEvent != '%s' ){ + window.pixelLastGeneratedLeadEvent = '%s'; + %s + } + ", + $event_id, + $event_id, + $fbq_calls + ); + + $response['fb_pxl_code'] = $code; + return $response; + } + + /** + * Reads the form data from the Contact Form 7 submission. + * + * @param object $form The Contact Form 7 form object. + * + * @return array The form data in the format expected + * by the `FacebookServerSideEvent` class. + */ + public static function readFormData( $form ) { + if ( empty( $form ) ) { + return array(); + } + + $form_tags = $form->scan_form_tags(); + $name = self::getName( $form_tags ); + + return array( + 'email' => self::getEmail( $form_tags ), + 'first_name' => $name[0], + 'last_name' => $name[1], + 'phone' => self::getPhone( $form_tags ), + ); + } + + /** + * Retrieves the email address from the form submission. + * + * @param array $form_tags The form tags. + * + * @return string|null The email address, or null if no email tag found. + */ + private static function getEmail( $form_tags ) { + if ( empty( $form_tags ) ) { + return null; + } + + foreach ( $form_tags as $tag ) { + if ( 'email' === $tag->basetype && isset( $_POST[ $tag->name ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + return sanitize_text_field( wp_unslash( $_POST[ $tag->name ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + } + + return null; + } + + /** + * Retrieves the first and last name from the form submission. + * + * @param array $form_tags The form tags. + * + * @return array|null An array containing the first and + * last name, or null if no name tag found. + */ + private static function getName( $form_tags ) { + if ( empty( $form_tags ) ) { + return null; + } + + foreach ( $form_tags as $tag ) { + if ( 'text' === $tag->basetype + && strpos( strtolower( $tag->name ), 'name' ) !== false ) { + return ServerEventFactory::split_name( + sanitize_text_field( + wp_unslash( $_POST[ $tag->name ] ?? null ) // phpcs:ignore WordPress.Security.NonceVerification.Missing + ) + ); + } + } + + return null; + } + + /** + * Retrieves the phone number from the form submission. + * + * @param array $form_tags The form tags. + * + * @return string|null The phone number, or null if no phone tag found. + */ + private static function getPhone( $form_tags ) { + if ( empty( $form_tags ) ) { + return null; + } + + foreach ( $form_tags as $tag ) { + if ( 'tel' === $tag->basetype ) { + return isset( $_POST[ $tag->name ] ) ? // phpcs:ignore WordPress.Security.NonceVerification.Missing + sanitize_text_field( + wp_unslash( $_POST[ $tag->name ] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing + ) : null; + } + } + + return null; + } +} diff --git a/integration/class-facebookwordpresseasydigitaldownloads.php b/integration/class-facebookwordpresseasydigitaldownloads.php new file mode 100644 index 00000000..e60f0898 --- /dev/null +++ b/integration/class-facebookwordpresseasydigitaldownloads.php @@ -0,0 +1,462 @@ + 'edd_after_checkout_cart', + 'classname' => __CLASS__, + 'inject_function' => 'injectInitiateCheckoutEvent', + ) + ); + + add_action( + 'edd_payment_receipt_after', + array( __CLASS__, 'trackPurchaseEvent' ), + 10, + 2 + ); + + add_action( + 'edd_after_download_content', + array( __CLASS__, 'injectViewContentEvent' ), + 40, + 1 + ); + } + + /** + * Injects a hidden field with a unique event ID into the AddToCart form. + * + * The event ID is used to identify the AddToCart event + * for a given download. + * + * @return void + */ + public static function injectAddToCartEventId() { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + $event_id = EventIdGenerator::guidv4(); + printf( + '', + esc_attr( $event_id ) + ); + } + + /** + * Triggers the AddToCart event for Easy Digital Downloads. + * + * The `edd-add-to-cart` nonce check is performed to ensure that the request + * comes from a valid EDD form submission. The event + * ID is verified to ensure that + * it is a valid Event ID. + * + * @since 1.0.0 + */ + public static function injectAddToCartEventAjax() { + if ( isset( $_POST['nonce'] ) && isset( $_POST['download_id'] ) + && isset( $_POST['post_data'] ) ) { + $download_id = absint( $_POST['download_id'] ); + $nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ) ); + if ( wp_verify_nonce( $nonce, 'edd-add-to-cart-' . $download_id ) + === false ) { + return; + } + parse_str( $_POST['post_data'], $post_data ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + if ( isset( $post_data['facebook_event_id'] ) ) { + $event_id = $post_data['facebook_event_id']; + $server_event = ServerEventFactory::safe_create_event( + 'AddToCart', + array( __CLASS__, 'createAddToCartEvent' ), + array( $download_id ), + self::TRACKING_NAME + ); + $server_event->setEventId( $event_id ); + FacebookServerSideEvent::get_instance()->track( $server_event ); + } + } + } + + /** + * Injects a JavaScript listener for the AddToCart event + * for Easy Digital Downloads. + * + * This method enqueues a JavaScript file that listens for + * the `edd_add_to_cart` + * event, and sends a server-side event to Facebook + * for the AddToCart pixel event. + * + * @param int $download_id The ID of the download item. + * + * @since 1.0.0 + */ + public static function injectAddToCartListener( $download_id ) { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + wp_register_script( + 'facebook-pixel-add-to-cart', + plugins_url( '../js/facebook_pixel_add_to_cart.js', __FILE__ ), + array( 'jquery' ), + '1.0', + true + ); + + wp_localize_script( + 'facebook-pixel-add-to-cart', + 'facebookPixelData', + array( + 'fbIntegrationKey' => FacebookPixel::FB_INTEGRATION_TRACKING_KEY, + 'trackingName' => self::TRACKING_NAME, + 'agentString' => FacebookWordpressOptions::get_agent_string(), + 'pixelId' => FacebookWordpressOptions::get_pixel_id(), + ) + ); + + wp_enqueue_script( 'facebook-pixel-add-to-cart' ); + } + + /** + * Injects a Meta Pixel InitiateCheckout event. + * + * This method is a callback for the `edd_purchase_link_top` action hook. + * It injects a Meta Pixel InitiateCheckout event into + * the page whenever a purchase link is rendered. + * + * @since 1.0.0 + */ + public static function injectInitiateCheckoutEvent() { + if ( FacebookPluginUtils::is_internal_user() || + ! function_exists( 'EDD' ) ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'InitiateCheckout', + array( __CLASS__, 'createInitiateCheckoutEvent' ), + array(), + self::TRACKING_NAME + ); + FacebookServerSideEvent::get_instance()->track( $server_event ); + + $code = PixelRenderer::render( + array( $server_event ), + self::TRACKING_NAME + ); + printf( + ' + +%s + + ', + $code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Tracks a Meta Pixel Purchase event. + * + * This method is a callback for the `edd_complete_purchase` action hook. + * It tracks a Meta Pixel Purchase event whenever a purchase is completed. + * + * @param object $payment The payment object. + * @param array $edd_receipt_args The receipt arguments. + * + * @since 1.0.0 + */ + public static function trackPurchaseEvent( $payment, $edd_receipt_args ) { + if ( FacebookPluginUtils::is_internal_user() || empty( $payment->ID ) ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'Purchase', + array( __CLASS__, 'createPurchaseEvent' ), + array( $payment ), + self::TRACKING_NAME + ); + FacebookServerSideEvent::get_instance()->track( $server_event ); + + add_action( + 'wp_footer', + array( __CLASS__, 'injectPurchaseEvent' ), + 20 + ); + } + + /** + * Injects a Meta Pixel Purchase event. + * + * This method is a callback for the `wp_footer` action hook. + * It injects a Meta Pixel Purchase event + * into the page whenever a purchase is completed. + * + * @since 1.0.0 + */ + public static function injectPurchaseEvent() { + $events = FacebookServerSideEvent::get_instance()->get_tracked_events(); + $code = PixelRenderer::render( $events, self::TRACKING_NAME ); + + printf( + ' + +%s + + ', + $code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Injects a Meta Pixel ViewContent event. + * + * This method is a callback for the + * `edd_download_before_content` action hook. + * It injects a Meta Pixel ViewContent event + * into the page whenever a download + * item is viewed. + * + * @param int $download_id The ID of the download item. + * + * @since 1.0.0 + */ + public static function injectViewContentEvent( $download_id ) { + if ( FacebookPluginUtils::is_internal_user() || empty( $download_id ) ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'ViewContent', + array( __CLASS__, 'createViewContentEvent' ), + array( $download_id ), + self::TRACKING_NAME + ); + + FacebookServerSideEvent::get_instance()->track( $server_event ); + + $code = PixelRenderer::render( array( $server_event ), self::TRACKING_NAME ); + printf( + ' + +%s + + ', + $code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Creates a Meta Pixel InitiateCheckout event data. + * + * The InitiateCheckout event is fired when a customer initiates a checkout. + * It is typically sent when a customer clicks a "checkout" button + * or submits an order. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createInitiateCheckoutEvent() { + $event_data = + FacebookPluginUtils::get_logged_in_user_info(); + $event_data['currency'] = EDDUtils::get_currency(); + $event_data['value'] = EDDUtils::get_cart_total(); + + return $event_data; + } + + /** + * Creates a Meta Pixel Purchase event data. + * + * The Purchase event is fired when a customer completes a purchase. + * It is typically sent when a customer submits an order. + * + * @param \EDD_Payment $payment The payment object. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createPurchaseEvent( $payment ) { + $event_data = array(); + + $payment_meta = \edd_get_payment_meta( $payment->ID ); + if ( empty( $payment_meta ) ) { + return $event_data; + } + + $event_data['email'] = $payment_meta['email']; + $event_data['first_name'] = $payment_meta['user_info']['first_name']; + $event_data['last_name'] = $payment_meta['user_info']['last_name']; + + $content_ids = array(); + $value = 0; + foreach ( $payment_meta['cart_details'] as $item ) { + $content_ids[] = $item['id']; + $value += $item['price']; + } + + $event_data['currency'] = $payment_meta['currency']; + $event_data['value'] = $value; + $event_data['content_ids'] = $content_ids; + $event_data['content_type'] = 'product'; + + return $event_data; + } + + /** + * Creates a Meta Pixel ViewContent event data. + * + * The ViewContent event is fired when a customer views a product. + * It is typically sent when a customer views a product page. + * + * @param int $download_id The download ID. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createViewContentEvent( $download_id ) { + $event_data = FacebookPluginUtils::get_logged_in_user_info(); + $currency = EDDUtils::get_currency(); + $download = edd_get_download( $download_id ); + $title = $download ? $download->post_title : ''; + + if ( get_post_meta( $download_id, '_variable_pricing', true ) ) { + $prices = get_post_meta( $download_id, 'edd_variable_prices', true ); + $price = array_shift( $prices ); + $value = $price['amount']; + } else { + $value = get_post_meta( $download_id, 'edd_price', true ); + } + if ( ! $value ) { + $value = 0; + } + $event_data['content_ids'] = array( (string) $download_id ); + $event_data['content_type'] = 'product'; + $event_data['currency'] = $currency; + $event_data['value'] = floatval( $value ); + $event_data['content_name'] = $title; + return $event_data; + } + + /** + * Creates a Meta Pixel AddToCart event data. + * + * The AddToCart event is fired when a customer adds a product to their + * cart. It is typically sent when a customer adds a product to their + * cart. + * + * @param int $download_id The download ID. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createAddToCartEvent( $download_id ) { + $event_data = FacebookPluginUtils::get_logged_in_user_info(); + $currency = EDDUtils::get_currency(); + $download = edd_get_download( $download_id ); + $title = $download ? $download->post_title : ''; + if ( get_post_meta( $download_id, '_variable_pricing', true ) ) { + $prices = get_post_meta( $download_id, 'edd_variable_prices', true ); + $price = array_shift( $prices ); + $value = $price['amount']; + } else { + $value = get_post_meta( $download_id, 'edd_price', true ); + } + if ( ! $value ) { + $value = 0; + } + $event_data['content_ids'] = array( (string) $download_id ); + $event_data['content_type'] = 'product'; + $event_data['currency'] = $currency; + $event_data['value'] = $value; + $event_data['content_name'] = $title; + return $event_data; + } +} diff --git a/integration/class-facebookwordpressformidableform.php b/integration/class-facebookwordpressformidableform.php new file mode 100644 index 00000000..d9063845 --- /dev/null +++ b/integration/class-facebookwordpressformidableform.php @@ -0,0 +1,318 @@ +track( $server_event ); + + add_action( + 'wp_footer', + array( __CLASS__, 'injectLeadEvent' ), + 20 + ); + } + + /** + * Injects lead event code into the footer. + * + * This method retrieves tracked events from the FacebookServerSideEvent + * instance and renders them into pixel code using the PixelRenderer. + * The resulting code is printed into the footer section of the page. + * If the user is an internal user, the method returns without injecting + * any code. + * + * @return void + */ + public static function injectLeadEvent() { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + $events = FacebookServerSideEvent::get_instance()->get_tracked_events(); + $code = PixelRenderer::render( $events, self::TRACKING_NAME ); + + printf( + ' + +%s + + ', + $code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Reads form data for a given entry ID. + * + * This method retrieves the entry values from Formidable + * Forms using the provided + * entry ID. It extracts specific user information such as + * email, first name, + * last name, and phone, and combines this information with address data. + * + * @param int $entry_id The ID of the form entry. + * @return array An associative array containing user and + * address information, + * or an empty array if the entry ID is + * empty or no data is found. + */ + public static function readFormData( $entry_id ) { + if ( empty( $entry_id ) ) { + return array(); + } + + $entry_values = + IntegrationUtils::get_formidable_forms_entry_values( $entry_id ); + + $field_values = $entry_values->get_field_values(); + if ( ! empty( $field_values ) ) { + $user_data = array( + 'email' => self::getEmail( $field_values ), + 'first_name' => self::getFirstName( $field_values ), + 'last_name' => self::getLastName( $field_values ), + 'phone' => self::getPhone( $field_values ), + ); + $address_data = self::getAddressInformation( $field_values ); + return array_merge( $user_data, $address_data ); + } + + return array(); + } + + /** + * Retrieves the email address from the form data. + * + * @param array $field_values An associative array of field values. + * @return string|null The email address, or null if + * no email field is found. + */ + private static function getEmail( $field_values ) { + return self::getFieldValueByType( $field_values, 'email' ); + } + + /** + * Retrieves the first name from the form data. + * + * This method extracts the first name field from the provided field values. + * + * @param array $field_values An associative array of field values. + * @return string|null The first name, or null if no first + * name field is found. + */ + private static function getFirstName( $field_values ) { + return self::getFieldValue( $field_values, 'text', 'Name', 'First' ); + } + + /** + * Retrieves the last name from the form data. + * + * This method extracts the last name field from the provided field values. + * + * @param array $field_values An associative array of field values. + * @return string|null The last name, or null if no + * last name field is found. + */ + private static function getLastName( $field_values ) { + return self::getFieldValue( $field_values, 'text', 'Last', 'Last' ); + } + + /** + * Retrieves the phone number from the form data. + * + * This method extracts the phone field from the provided field values. + * + * @param array $field_values An associative array of field values. + * @return string|null The phone number, or null if no phone field is found. + */ + private static function getPhone( $field_values ) { + return self::getFieldValueByType( $field_values, 'phone' ); + } + + /** + * Retrieves address information from the form data. + * + * This method extracts address information + * (city, state, country, and zip code) + * from the provided field values. It returns an + * associative array containing + * the extracted address information, or an empty + * array if no address information + * is found. + * + * @param array $field_values An associative array of field values. + * @return array An associative array containing address information. + */ + private static function getAddressInformation( $field_values ) { + $address_saved_value = self::getFieldValueByType( + $field_values, + 'address' + ); + $address_data = array(); + if ( $address_saved_value ) { + if ( isset( $address_saved_value['city'] ) ) { + $address_data['city'] = $address_saved_value['city']; + } + if ( isset( $address_saved_value['state'] ) ) { + $address_data['state'] = $address_saved_value['state']; + } + if ( + isset( $address_saved_value['country'] ) + && strlen( $address_saved_value['country'] ) === 2 + ) { + $address_data['country'] = $address_saved_value['country']; + } + if ( isset( $address_saved_value['zip'] ) ) { + $address_data['zip'] = $address_saved_value['zip']; + } + } + return $address_data; + } + + /** + * Retrieves the saved value of a specific field type + * from the given field values. + * + * This method iterates through the provided field values to find a field + * that matches the specified type. If a matching field is found, it returns + * the saved value of that field. If no matching field + * is found, it returns null. + * + * @param array $field_values An array of field values. + * @param string $type The type of the field to retrieve the value for. + * @return mixed|null The saved value of the field, + * or null if no matching field is found. + */ + private static function getFieldValueByType( $field_values, $type ) { + foreach ( $field_values as $field_value ) { + $field = $field_value->get_field(); + if ( $field->type === $type ) { + return $field_value->get_saved_value(); + } + } + + return null; + } + + /** + * Retrieves the saved value of a field that matches the given criteria. + * + * This method iterates through the provided field + * values to find a field that + * matches the specified type, name, and description. + * If a matching field is found, + * it returns the saved value of that field. If no + * matching field is found, it returns + * null. + * + * @param array $field_values An array of field values. + * @param string $type The type of the field to retrieve the value for. + * @param string $name The name of the field to retrieve the value for. + * @param string $description The description of the + * field to retrieve the value for. + * @return mixed|null The saved value of the field, + * or null if no matching field is found. + */ + private static function getFieldValue( + $field_values, + $type, + $name, + $description + ) { + foreach ( $field_values as $field_value ) { + $field = $field_value->get_field(); + if ( $field->type === $type && + $field->name === $name && + $field->description === $description ) { + return $field_value->get_saved_value(); + } + } + + return null; + } +} diff --git a/integration/class-facebookwordpressintegrationbase.php b/integration/class-facebookwordpressintegrationbase.php new file mode 100644 index 00000000..357b438e --- /dev/null +++ b/integration/class-facebookwordpressintegrationbase.php @@ -0,0 +1,113 @@ +getNumberOfParameters(); + $argv = $reflection->getParameters(); + + $callback = function () use ( $user_function, $argv ) { + $hook_wp_footer = function () use ( $user_function, $argv ) { + \call_user_func_array( $user_function, $argv ); + }; + add_action( + 'wp_footer', + $hook_wp_footer, + 11 + ); + }; + + add_action( $hook_name, $callback, $priority, $argc ); + } +} diff --git a/integration/class-facebookwordpressmailchimpforwp.php b/integration/class-facebookwordpressmailchimpforwp.php new file mode 100644 index 00000000..3191003a --- /dev/null +++ b/integration/class-facebookwordpressmailchimpforwp.php @@ -0,0 +1,162 @@ + 'mc4wp_form_subscribed', + 'classname' => __CLASS__, + 'inject_function' => 'injectLeadEvent', + ) + ); + } + + /** + * Injects Facebook Pixel events for the MailChimp for WP plugin. + * + * This method sets up WordPress actions to inject Facebook Pixel events + * for different stages of the MailChimp for WP plugin process. + * + * @return void + */ + public static function injectLeadEvent() { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'Lead', + array( __CLASS__, 'readFormData' ), + array(), + self::TRACKING_NAME, + true + ); + FacebookServerSideEvent::get_instance()->track( $server_event ); + + $code = PixelRenderer::render( + array( $server_event ), + self::TRACKING_NAME + ); + printf( + ' + + %s + + ', + $code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Reads form data from the $_POST global array. + * + * This function extracts user-related data + * such as email, first name, last name, + * phone number, and address details from the + * $_POST array, commonly used in form + * submissions. The extracted data includes: + * - 'email': The user's email address. + * - 'first_name': The user's first name. + * - 'last_name': The user's last name. + * - 'phone': The user's phone number. + * - 'city', 'state', 'zip', 'country': Address details, + * where the country must + * be specified using a 2-letter code. + * + * The function returns an associative array containing the extracted data. + * + * @return array An associative array of form data. + */ + public static function readFormData() { + $event_data = array(); + if ( ! empty( $_POST['EMAIL'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $event_data['email'] = sanitize_email( wp_unslash( $_POST['EMAIL'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( ! empty( $_POST['FNAME'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $event_data['first_name'] = sanitize_text_field( wp_unslash( $_POST['FNAME'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( ! empty( $_POST['LNAME'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $event_data['last_name'] = sanitize_text_field( wp_unslash( $_POST['LNAME'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( ! empty( $_POST['PHONE'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $event_data['phone'] = sanitize_text_field( wp_unslash( $_POST['PHONE'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( ! empty( $_POST['ADDRESS'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $address_data = sanitize_text_field( wp_unslash( $_POST['ADDRESS'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + + if ( ! empty( $address_data['city'] ) ) { + $event_data['city'] = sanitize_text_field( $address_data['city'] ); + } + + if ( ! empty( $address_data['state'] ) ) { + $event_data['state'] = + sanitize_text_field( $address_data['state'] ); + } + + if ( ! empty( $address_data['zip'] ) ) { + $event_data['zip'] = sanitize_text_field( $address_data['zip'] ); + } + + if ( + ! empty( $address_data['country'] ) + && strlen( $address_data['country'] ) === 2 + ) { + $event_data['country'] = $address_data['country']; + } + } + return $event_data; + } +} diff --git a/integration/class-facebookwordpressninjaforms.php b/integration/class-facebookwordpressninjaforms.php new file mode 100644 index 00000000..ce484be7 --- /dev/null +++ b/integration/class-facebookwordpressninjaforms.php @@ -0,0 +1,326 @@ + $action ) { + if ( ! isset( $action['settings'] ) || + ! isset( $action['settings']['type'] ) ) { + continue; + } + + $type = $action['settings']['type']; + if ( ! is_string( $type ) ) { + continue; + } + + if ( 'successmessage' === $type ) { + $event = ServerEventFactory::safe_create_event( + 'Lead', + array( __CLASS__, 'readFormData' ), + array( $form_data ), + self::TRACKING_NAME, + true + ); + FacebookServerSideEvent::get_instance()->track( $event ); + + $pixel_code = PixelRenderer::render( + array( $event ), + self::TRACKING_NAME + ); + $code = sprintf( + ' + +%s + + ', + $pixel_code + ); + + $action['settings']['success_msg'] .= $code; + $actions[ $key ] = $action; + } + } + + return $actions; + } + + /** + * Reads form data from the $_POST global array. + * + * This function extracts user-related data such as email, + * first name, last name, + * phone number, and address details from the $_POST array, + * commonly used in form + * submissions. The extracted data includes: + * - 'email': The user's email address. + * - 'first_name': The user's first name. + * - 'last_name': The user's last name. + * - 'phone': The user's phone number. + * - 'city', 'state', 'zip', 'country': Address + * details, where the country must + * be specified using a 2-letter code. + * + * The function returns an associative array containing the extracted data. + * + * @param array $form_data The form data as an associative array. + * @return array An associative array of form data. + */ + public static function readFormData( $form_data ) { + if ( empty( $form_data ) ) { + return array(); + } + + $event_data = array(); + $name = self::getName( $form_data ); + if ( $name ) { + $event_data['first_name'] = $name[0]; + $event_data['last_name'] = $name[1]; + } else { + $event_data['first_name'] = self::getFirstName( $form_data ); + $event_data['last_name'] = self::getLastName( $form_data ); + } + $event_data['email'] = self::getEmail( $form_data ); + $event_data['phone'] = self::getPhone( $form_data ); + $event_data['city'] = self::getCity( $form_data ); + $event_data['zip'] = self::getZipCode( $form_data ); + $event_data['state'] = self::getState( $form_data ); + $event_data['country'] = self::getCountry( $form_data ); + $event_data['gender'] = self::getGender( $form_data ); + + return $event_data; + } + + /** + * Retrieves the user's email address from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's email address. + */ + private static function getEmail( $form_data ) { + return self::getField( $form_data, 'email' ); + } + + /** + * Retrieves the user's full name from the form data and splits it into + * first name and last name. + * + * @param array $form_data The form data as an associative array. + * @return array|null An array containing first name and last name, or null + * if no name field is found. + */ + private static function getName( $form_data ) { + $name = self::getField( $form_data, 'name' ); + if ( $name ) { + return ServerEventFactory::split_name( $name ); + } + return null; + } + + /** + * Retrieves the user's first name from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's first name. + */ + private static function getFirstName( $form_data ) { + return self::getField( $form_data, 'firstname' ); + } + + /** + * Retrieves the user's last name from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's last name. + */ + private static function getLastName( $form_data ) { + return self::getField( $form_data, 'lastname' ); + } + + /** + * Retrieves the user's phone number from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's phone number. + */ + private static function getPhone( $form_data ) { + return self::getField( $form_data, 'phone' ); + } + + /** + * Retrieves the user's city from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's city. + */ + private static function getCity( $form_data ) { + return self::getField( $form_data, 'city' ); + } + + /** + * Retrieves the user's zip code from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's zip code. + */ + private static function getZipCode( $form_data ) { + return self::getField( $form_data, 'zip' ); + } + + /** + * Retrieves the user's state from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's state. + */ + private static function getState( $form_data ) { + return self::getField( $form_data, 'liststate' ); + } + + /** + * Retrieves the user's country from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's country. + */ + private static function getCountry( $form_data ) { + return self::getField( $form_data, 'listcountry' ); + } + + /** + * Retrieves the user's gender from the form data. + * + * @param array $form_data The form data as an associative array. + * @return string The user's gender. + */ + private static function getGender( $form_data ) { + return self::getField( $form_data, 'gender' ); + } + + /** + * Checks if a given string starts with a given prefix. + * + * @param string $text The string to check. + * @param string $prefix The prefix to check for. + * @return boolean True if the string starts with the + * prefix, false otherwise. + */ + private static function hasPrefix( $text, $prefix ) { + $len = strlen( $prefix ); + return substr( $text, 0, $len ) === $prefix; + } + + /** + * Retrieves the value of a field from the form data by its key. + * + * The key is searched for in the form data as a prefix of the field's key. + * If a matching field is found, its value is returned. + * If not, null is returned. + * + * @param array $form_data The form data as an associative array. + * @param string $key The key of the field to retrieve the value for. + * @return string|null The value of the field, or null + * if no matching field is found. + */ + private static function getField( $form_data, $key ) { + if ( empty( $form_data['fields'] ) ) { + return null; + } + + foreach ( $form_data['fields'] as $field ) { + if ( self::hasPrefix( $field['key'], $key ) ) { + return $field['value']; + } + } + + return null; + } +} diff --git a/integration/class-facebookwordpresswoocommerce.php b/integration/class-facebookwordpresswoocommerce.php new file mode 100644 index 00000000..81055e6f --- /dev/null +++ b/integration/class-facebookwordpresswoocommerce.php @@ -0,0 +1,758 @@ + array( + 'id' => array(), + ), + ) + ); + } + + /** + * Generates a div element with a specific ID for + * AJAX-triggered pixel events. + * + * This method returns a div element with the ID defined + * by DIV_ID_FOR_AJAX_PIXEL_EVENTS. + * The function allows for optional content to be included inside the div. + * + * @param string $content Optional content to be placed inside the div. + * @return string HTML div element as a string. + */ + public static function getDivForAjaxPixelEvent( $content = '' ) { + return "
" + . $content . '
'; + } + + /** + * Injects a ViewContent event into the page, but only if the user is not an + * internal user (i.e. an admin user). + * + * The event is only injected if a valid product + * object can be retrieved from + * the current post ID. If not, the method simply exits. + * + * The ViewContent event is generated by calling + * the createViewContentEvent method + * and passing in the product object as an argument. + * The event is then tracked + * using the FacebookServerSideEvent singleton, + * and the pixel code is enqueued + * for output. + * + * @return void + */ + public static function trackViewContentEvent() { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + global $post; + if ( ! isset( $post->ID ) ) { + return; + } + + $product = wc_get_product( $post->ID ); + if ( ! $product ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'ViewContent', + array( __CLASS__, 'createViewContentEvent' ), + array( $product ), + self::TRACKING_NAME + ); + + FacebookServerSideEvent::get_instance()->track( $server_event, false ); + + self::enqueuePixelCode( $server_event ); + } + + /** + * Generates a ViewContent event data. + * + * The ViewContent event is generated by + * setting fields such as content_type, + * currency, value, content_ids, content_name and content_category. + * + * @param WC_Product $product Product object. + * @return array The event data. + */ + public static function createViewContentEvent( $product ) { + $event_data = self::getPIIFromSession(); + + $product_id = self::getProductId( $product ); + $content_type = $product->is_type( 'variable' ) ? + 'product_group' : 'product'; + + $event_data['content_type'] = $content_type; + $event_data['currency'] = \get_woocommerce_currency(); + $event_data['value'] = $product->get_price(); + $event_data['content_ids'] = array( $product_id ); + $event_data['content_name'] = $product->get_title(); + $event_data['content_category'] = self::getProductCategory( + $product->get_id() + ); + + return array_filter( $event_data ); + } + + /** + * Returns the first category name of a given product ID. + * + * This method gets all the categories associated with the given product ID + * and returns the first category name. If no categories are associated with + * the product, the method returns null. + * + * @param int $product_id Product ID. + * @return string|null First category name + * associated with the product, or null. + */ + private static function getProductCategory( $product_id ) { + $categories = get_the_terms( + $product_id, + 'product_cat' + ); + return count( $categories ) > 0 ? $categories[0]->name : null; + } + + /** + * Tracks a Meta Pixel Purchase event. + * + * This method is a callback for the `woocommerce_thankyou` action hook. + * It tracks a Meta Pixel Purchase event whenever a purchase is completed. + * + * @param int $order_id The order ID. + * + * @since 1.0.0 + */ + public static function trackPurchaseEvent( $order_id ) { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'Purchase', + array( __CLASS__, 'createPurchaseEvent' ), + array( $order_id ), + self::TRACKING_NAME + ); + + FacebookServerSideEvent::get_instance()->track( $server_event ); + + self::enqueuePixelCode( $server_event ); + } + + /** + * Generates a Meta Pixel Purchase event data. + * + * The Purchase event is fired when a customer completes a purchase. + * It is typically sent when a customer submits an order. + * + * The method loops through the items in the order and creates a + * Meta Pixel Content object for each item. The method then sets the + * content_type, currency, value, content_ids and contents fields in + * the event data. + * + * @param int $order_id The order ID. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createPurchaseEvent( $order_id ) { + $order = wc_get_order( $order_id ); + + $content_type = 'product'; + $product_ids = array(); + $contents = array(); + + foreach ( $order->get_items() as $item ) { + $product = wc_get_product( $item->get_product_id() ); + if ( 'product_group' !== $content_type + && $product->is_type( 'variable' ) ) { + $content_type = 'product_group'; + } + + $quantity = $item->get_quantity(); + $product_id = self::getProductId( $product ); + + $content = new Content(); + $content->setProductId( $product_id ); + $content->setQuantity( $quantity ); + $content->setItemPrice( $item->get_total() / $quantity ); + + $contents[] = $content; + $product_ids[] = $product_id; + } + + $event_data = self::getPiiFromBillingInformation( + $order + ); + $event_data['content_type'] = $content_type; + $event_data['currency'] = \get_woocommerce_currency(); + $event_data['value'] = $order->get_total(); + $event_data['content_ids'] = $product_ids; + $event_data['contents'] = $contents; + + return $event_data; + } + + /** + * Generates a Meta Pixel AddToCart event data. + * + * The AddToCart event is fired when a customer + * adds a product to their cart. + * It is typically sent when a customer submits a + * form to add a product to their cart. + * + * The method loops through the items in the cart and creates a + * Meta Pixel Content object for each item. The method then sets the + * content_type, currency, value, content_ids and contents fields in + * the event data. + * + * @param string $cart_item_key The cart item key. + * @param int $product_id The product ID. + * @param int $quantity The quantity of the item in the cart. + * @param int $variation_id The variation ID. + * + * @since 1.0.0 + */ + public static function trackAddToCartEvent( + $cart_item_key, + $product_id, + $quantity, + $variation_id + ) { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'AddToCart', + array( __CLASS__, 'createAddToCartEvent' ), + array( $cart_item_key, $product_id, $quantity ), + self::TRACKING_NAME + ); + + $is_ajax_request = wp_doing_ajax(); + + FacebookServerSideEvent::get_instance()->track( + $server_event, + $is_ajax_request + ); + + if ( ! $is_ajax_request ) { + self::enqueuePixelCode( $server_event ); + } else { + FacebookServerSideEvent::get_instance()->set_pending_pixel_event( + 'addPixelCodeToAddToCartFragment', + $server_event + ); + add_filter( + 'woocommerce_add_to_cart_fragments', + array( __CLASS__, 'addPixelCodeToAddToCartFragment' ) + ); + } + } + + /** + * Modifies the WooCommerce "add to cart" AJAX fragment response + * to include the Meta Pixel code. + * + * This method is used to inject the Meta Pixel code into the + * page after the user has added a product to their cart. + * + * @param array $fragments The response array passed to the + * "woocommerce_add_to_cart_fragments" filter. + * + * @return array The modified response array. + * + * @since 1.0.0 + */ + public static function addPixelCodeToAddToCartFragment( $fragments ) { + $server_event = FacebookServerSideEvent::get_instance() + ->get_pending_pixel_event( 'addPixelCodeToAddToCartFragment' ); + if ( ! is_null( $server_event ) ) { + $pixel_code = self::generatePixelCode( $server_event, true ); + $fragments[ '#' . self::DIV_ID_FOR_AJAX_PIXEL_EVENTS ] = + self::getDivForAjaxPixelEvent( $pixel_code ); + } + return $fragments; + } + + /** + * Creates a Meta Pixel AddToCart event data. + * + * The AddToCart event is fired when a customer adds a product to their + * cart. It is typically sent when a customer adds a product to their + * cart. + * + * @param string $cart_item_key The cart item key. + * @param int $product_id The product ID. + * @param int $quantity The quantity. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createAddToCartEvent( + $cart_item_key, + $product_id, + $quantity + ) { + $event_data = self::getPIIFromSession(); + $event_data['content_type'] = 'product'; + $event_data['currency'] = \get_woocommerce_currency(); + + $cart_item = self::getCartItem( $cart_item_key ); + if ( ! empty( $cart_item_key ) ) { + $event_data['content_ids'] = array( + self::getProductId( + $cart_item['data'] + ), + ); + $event_data['value'] = self::getAddToCartValue( + $cart_item, + $quantity + ); + } + + return $event_data; + } + + /** + * Tracks a Meta Pixel InitiateCheckout event. + * + * This method is a wrapper of FacebookServerSideEvent::track() method. + * It creates a Meta Pixel InitiateCheckout event data with the data + * from the WooCommerce session, and then tracks the event. + * + * @since 1.0.0 + */ + public static function trackInitiateCheckout() { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'InitiateCheckout', + array( __CLASS__, 'createInitiateCheckoutEvent' ), + array(), + self::TRACKING_NAME + ); + + FacebookServerSideEvent::get_instance()->track( $server_event ); + + self::enqueuePixelCode( $server_event ); + } + + /** + * Creates a Meta Pixel InitiateCheckout event data. + * + * The InitiateCheckout event is triggered when + * a customer initiates the checkout process. + * This method gathers personal identifiable + * information (PII) from the session and + * retrieves cart details such as the number + * of items, total value, content IDs, + * and contents of the cart. The event data + * is then returned for tracking purposes. + * + * @return array The event data including + * user PII, cart details, and currency information. + * + * @since 1.0.0 + */ + public static function createInitiateCheckoutEvent() { + $event_data = self::getPIIFromSession(); + $event_data['content_type'] = 'product'; + $event_data['currency'] = \get_woocommerce_currency(); + + if ( WC()->cart ) { + $cart = WC()->cart; + + $event_data['num_items'] = $cart->get_cart_contents_count(); + $event_data['value'] = $cart->total; + $event_data['content_ids'] = self::getContentIds( $cart ); + $event_data['contents'] = self::getContents( $cart ); + } + + return $event_data; + } + + /** + * Retrieves personally identifiable + * information (PII) from a WooCommerce order. + * + * This method extracts billing details + * from the given WooCommerce order object, + * including the first name, last name, + * email, postal code, state, country, city, + * and phone number. The PII is + * returned as an associative array. + * + * @param WC_Order $order The WooCommerce + * order object containing billing information. + * + * @return array An associative array containing the extracted PII. + * + * @since 1.0.0 + */ + private static function getPiiFromBillingInformation( $order ) { + $pii = array(); + + $pii['first_name'] = $order->get_billing_first_name(); + $pii['last_name'] = $order->get_billing_last_name(); + $pii['email'] = $order->get_billing_email(); + $pii['zip'] = $order->get_billing_postcode(); + $pii['state'] = $order->get_billing_state(); + $pii['country'] = $order->get_billing_country(); + $pii['city'] = $order->get_billing_city(); + $pii['phone'] = $order->get_billing_phone(); + + return $pii; + } + + /** + * Calculates the total value for adding a specified + * quantity of a cart item to the cart. + * + * This method computes the total cost based on the + * line total and quantity of the provided + * cart item, and multiplies it by the specified quantity. + * + * @param array $cart_item An associative array + * representing the cart item, containing + * 'line_total' and 'quantity' keys among others. + * @param int $quantity The quantity of + * the item to calculate the total value for. + * + * @return float|null The calculated total value for + * the specified quantity of the item, + * or null if the cart item is empty. + */ + private static function getAddToCartValue( $cart_item, $quantity ) { + if ( ! empty( $cart_item ) ) { + $price = $cart_item['line_total'] / $cart_item['quantity']; + return $quantity * $price; + } + + return null; + } + + /** + * Retrieves a cart item from the WooCommerce cart by its key. + * + * This method accesses the current WooCommerce cart and returns the + * cart item associated with the provided cart item key. If the cart + * or the specified cart item is not found, the method returns null. + * + * @param string $cart_item_key The key for identifying the cart item. + * + * @return array|null An associative array representing the cart item, + * or null if the cart item is not found. + */ + private static function getCartItem( $cart_item_key ) { + if ( WC()->cart ) { + $cart = WC()->cart->get_cart(); + if ( ! empty( $cart ) && ! empty( $cart[ $cart_item_key ] ) ) { + return $cart[ $cart_item_key ]; + } + } + + return null; + } + + /** + * Retrieves an array of product IDs from the given WooCommerce cart object. + * + * @param WC_Cart $cart The WooCommerce cart object. + * + * @return array An array of product IDs. + * + * @since 1.0.0 + */ + private static function getContentIds( $cart ) { + $product_ids = array(); + foreach ( $cart->get_cart() as $item ) { + if ( ! empty( $item['data'] ) ) { + $product_ids[] = self::getProductId( $item['data'] ); + } + } + + return $product_ids; + } + + /** + * Retrieves an array of Content objects from + * the given WooCommerce cart object. + * + * Each Content object represents an item in the cart and includes + * the product ID, quantity, and item price. + * + * @param WC_Cart $cart The WooCommerce cart object. + * + * @return Content[] An array of Content + * objects representing the items in the cart. + * + * @since 1.0.0 + */ + private static function getContents( $cart ) { + $contents = array(); + foreach ( $cart->get_cart() as $item ) { + if ( ! empty( $item['data'] ) && ! empty( $item['quantity'] ) ) { + $content = new Content(); + $content->setProductId( self::getProductId( $item['data'] ) ); + $content->setQuantity( $item['quantity'] ); + $content->setItemPrice( $item['line_total'] / $item['quantity'] ); + + $contents[] = $content; + } + } + + return $contents; + } + + /** + * Retrieves a unique product ID from the given WooCommerce product object. + * + * If the product has a SKU, the ID is in the format of "sku_woo_id". + * Otherwise, the ID is in the format of + * "fb_woo_id" where "fb_" is a prefix. + * + * @param WC_Product $product The WooCommerce product object. + * + * @return string The unique product ID. + * + * @since 1.0.0 + */ + private static function getProductId( $product ) { + $woo_id = $product->get_id(); + + return $product->get_sku() ? $product->get_sku() . '_' . + $woo_id : self::FB_ID_PREFIX . $woo_id; + } + + /** + * Retrieves PII from the logged in user's session. + * + * @return array The user's PII data. + * + * @since 1.0.0 + */ + private static function getPIIFromSession() { + $event_data = FacebookPluginUtils::get_logged_in_user_info(); + $user_id = get_current_user_id(); + if ( 0 !== $user_id ) { + $event_data['city'] = get_user_meta( + $user_id, + 'billing_city', + true + ); + $event_data['zip'] = get_user_meta( + $user_id, + 'billing_postcode', + true + ); + $event_data['country'] = get_user_meta( + $user_id, + 'billing_country', + true + ); + $event_data['state'] = get_user_meta( + $user_id, + 'billing_state', + true + ); + $event_data['phone'] = get_user_meta( + $user_id, + 'billing_phone', + true + ); + } + return array_filter( $event_data ); + } + + /** + * Checks if Facebook for WooCommerce plugin is active. + * + * @return bool True if Facebook for WooCommerce is active, false otherwise. + * + * @since 1.0.0 + */ + private static function isFacebookForWooCommerceActive() { + return in_array( + 'facebook-for-woocommerce/facebook-for-woocommerce.php', + get_option( 'active_plugins' ), + true + ); + } + + /** + * Generates the pixel code for a given server event. + * + * @param FacebookServerSideEvent $server_event The server event + * to generate the pixel code for. + * @param bool $script_tag Whether to wrap + * the pixel code in a script tag. Default to false. + * + * @return string The pixel code for the given server event. + * + * @since 1.0.0 + */ + public static function generatePixelCode( + $server_event, + $script_tag = false +) { + $code = PixelRenderer::render( + array( $server_event ), + self::TRACKING_NAME, + $script_tag + ); + $code = sprintf( + ' + +%s + + ', + $code + ); + return $code; + } + + /** + * Enqueues a Meta Pixel event code for a given server event. + * + * This method renders the Meta Pixel code for the given server event, + * and then enqueues it using the WooCommerce JavaScript enqueueing + * system. + * + * @param FacebookServerSideEvent $server_event The server + * event to enqueue the pixel code for. + * + * @return string The Meta Pixel code for the given server event. + * + * @since 1.0.0 + */ + public static function enqueuePixelCode( $server_event ) { + $code = self::generatePixelCode( $server_event, false ); + wc_enqueue_js( $code ); + return $code; + } +} diff --git a/integration/class-facebookwordpresswpecommerce.php b/integration/class-facebookwordpresswpecommerce.php new file mode 100644 index 00000000..80494626 --- /dev/null +++ b/integration/class-facebookwordpresswpecommerce.php @@ -0,0 +1,335 @@ + 'wpsc_before_shopping_cart_page', + 'classname' => __CLASS__, + 'inject_function' => 'injectInitiateCheckoutEvent', + ) + ); + + add_action( + 'wpsc_transaction_results_shutdown', + array( __CLASS__, 'injectPurchaseEvent' ), + 11, + 3 + ); + } + + /** + * Injects Facebook Pixel code for add to cart events. + * + * This method is called from the + * `wpsc_add_to_cart_json_response` action hook. + * It creates an "AddToCart" event and tracks + * it using the Facebook server-side + * API. It then injects the Facebook Pixel code into the response. + * + * @param array $response The JSON response + * after an item is added to the cart. + * @return array The modified response with the Facebook Pixel code added. + */ + public static function injectAddToCartEvent( $response ) { + if ( FacebookPluginUtils::is_internal_user() ) { + return $response; + } + + $product_id = $response['product_id']; + $server_event = ServerEventFactory::safe_create_event( + 'AddToCart', + array( __CLASS__, 'createAddToCartEvent' ), + array( $product_id ), + self::TRACKING_NAME + ); + FacebookServerSideEvent::get_instance()->track( $server_event ); + + $code = PixelRenderer::render( + array( $server_event ), + self::TRACKING_NAME + ); + $code = sprintf( + ' + + %s + + ', + $code + ); + $response['widget_output'] .= $code; + return $response; + } + + /** + * Injects a Meta Pixel InitiateCheckout event. + * + * This method is called from the + * `wpsc_before_shopping_cart_page` action hook. + * It injects a Meta Pixel InitiateCheckout + * event into the page whenever a shopping cart is rendered. + * + * @since 1.0.0 + */ + public static function injectInitiateCheckoutEvent() { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'InitiateCheckout', + array( __CLASS__, 'createInitiateCheckoutEvent' ), + array(), + self::TRACKING_NAME + ); + FacebookServerSideEvent::get_instance()->track( $server_event ); + + $code = PixelRenderer::render( + array( + $server_event, + ), + self::TRACKING_NAME + ); + printf( + ' + +%s + + ', + $code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Injects a Meta Pixel Purchase event. + * + * This method is triggered by the + * `wpsc_transaction_results_shutdown` action hook. + * It creates and tracks a "Purchase" + * event using the Facebook server-side API, + * injecting the Facebook Pixel code + * into the page if the user is not internal and + * the display_to_screen flag is true. + * + * @param object $purchase_log_object The + * purchase log object containing transaction details. + * @param mixed $session_id The session + * ID for the current user session. + * @param bool $display_to_screen Flag + * indicating whether to display the Pixel code on screen. + * + * @since 1.0.0 + */ + public static function injectPurchaseEvent( + $purchase_log_object, + $session_id, + $display_to_screen + ) { + if ( FacebookPluginUtils::is_internal_user() || ! $display_to_screen ) { + return; + } + + $server_event = ServerEventFactory::safe_create_event( + 'Purchase', + array( __CLASS__, 'createPurchaseEvent' ), + array( $purchase_log_object ), + self::TRACKING_NAME + ); + FacebookServerSideEvent::get_instance()->track( $server_event ); + + $code = PixelRenderer::render( + array( + $server_event, + ), + self::TRACKING_NAME + ); + + printf( + ' + +%s + + ', + $code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Generates a Meta Pixel Purchase event data. + * + * The Purchase event is fired when a customer completes a purchase. + * It is typically sent when a customer submits an order. + * + * The method loops through the items in the order and creates a + * Meta Pixel Content object for each item. The method then sets the + * content_type, currency, value, content_ids and contents fields in + * the event data. + * + * @param object $purchase_log_object The + * purchase log object containing transaction details. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createPurchaseEvent( $purchase_log_object ) { + $event_data = FacebookPluginUtils::get_logged_in_user_info(); + + $cart_items = $purchase_log_object->get_items(); + $total_price = $purchase_log_object->get_total(); + $currency = function_exists( '\wpsc_get_currency_code' ) ? + \wpsc_get_currency_code() : ''; + + $item_ids = array(); + foreach ( $cart_items as $item ) { + $item_array = (array) $item; + $item_ids[] = $item_array['prodid']; + } + + $event_data['content_ids'] = $item_ids; + $event_data['content_type'] = 'product'; + $event_data['currency'] = $currency; + $event_data['value'] = $total_price; + + return $event_data; + } + + /** + * Generates a Meta Pixel AddToCart event data. + * + * The AddToCart event is fired when a customer adds a product to their + * cart. It is typically sent when a customer adds a product to their + * cart. + * + * The method loops through the items in the cart and creates a Meta Pixel + * Content object for the product that was added to the cart. The method + * then sets the content_type, currency, value, content_ids and contents + * fields in the event data. + * + * @param int $product_id The product ID. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createAddToCartEvent( $product_id ) { + $event_data = FacebookPluginUtils::get_logged_in_user_info(); + + global $wpsc_cart; + $cart_items = $wpsc_cart->get_items(); + foreach ( $cart_items as $item ) { + if ( $item->product_id === $product_id ) { + $unit_price = $item->unit_price; + break; + } + } + + $event_data['content_ids'] = array( $product_id ); + $event_data['content_type'] = 'product'; + $event_data['currency'] = + function_exists( '\wpsc_get_currency_code' ) ? + \wpsc_get_currency_code() : ''; + $event_data['value'] = $unit_price; + + return $event_data; + } + + /** + * Generates a Meta Pixel InitiateCheckout event data. + * + * The InitiateCheckout event is fired when a customer initiates a checkout. + * It is typically sent when a customer clicks a "checkout" button + * or submits an order. + * + * The method loops through the items in the cart and creates a Meta Pixel + * Content object for each item. The method then sets the content_type, + * currency, value, content_ids and contents fields in the event data. + * + * @return array The event data. + * + * @since 1.0.0 + */ + public static function createInitiateCheckoutEvent() { + $event_data = FacebookPluginUtils::get_logged_in_user_info(); + $content_ids = array(); + + $value = 0; + global $wpsc_cart; + $cart_items = $wpsc_cart->get_items(); + foreach ( $cart_items as $item ) { + $content_ids[] = $item->product_id; + $value += $item->unit_price; + } + + $event_data['currency'] = + function_exists( '\wpsc_get_currency_code' ) ? + \wpsc_get_currency_code() : ''; + $event_data['value'] = $value; + $event_data['content_ids'] = $content_ids; + + return $event_data; + } +} diff --git a/integration/class-facebookwordpresswpforms.php b/integration/class-facebookwordpresswpforms.php new file mode 100644 index 00000000..993ad786 --- /dev/null +++ b/integration/class-facebookwordpresswpforms.php @@ -0,0 +1,338 @@ +track( $server_event ); + + add_action( + 'wp_footer', + array( __CLASS__, 'injectLeadEvent' ), + 20 + ); + } + + /** + * Injects lead event code into the footer. + * + * This method retrieves tracked events from the FacebookServerSideEvent + * instance and renders them into pixel code using the PixelRenderer. + * The resulting code is printed into the footer section of the page. + * If the user is an internal user, the method returns without injecting + * any code. + * + * @return void + */ + public static function injectLeadEvent() { + if ( FacebookPluginUtils::is_internal_user() ) { + return; + } + + $events = + FacebookServerSideEvent::get_instance()->get_tracked_events(); + $pixel_code = PixelRenderer::render( $events, self::TRACKING_NAME ); + + printf( + ' + +%s + + ', + $pixel_code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Reads the form submission data and extracts user information. + * + * This method processes the form entry and form + * data to extract user-related + * information such as email, first name, last name, + * and phone number. It also + * retrieves the address data, including city, + * state, country, and postal code. + * + * If either the form entry or form data is + * empty, an empty array is returned. + * + * @param array $entry The form entry data. + * @param array $form_data The form schema data. + * + * @return array An associative array + * containing user and address information + * extracted from the form entry. + */ + public static function readFormData( $entry, $form_data ) { + if ( empty( $entry ) || empty( $form_data ) ) { + return array(); + } + + $name = self::getName( $entry, $form_data ); + + $event_data = array( + 'email' => self::getEmail( $entry, $form_data ), + 'first_name' => ! empty( $name ) ? $name[0] : null, + 'last_name' => ! empty( $name ) ? $name[1] : null, + 'phone' => self::getPhone( $entry, $form_data ), + ); + + $event_data = array_merge( + $event_data, + self::getAddress( $entry, $form_data ) + ); + + return $event_data; + } + + /** + * Retrieves the phone number from the form data. + * + * This method extracts the phone number field from the provided form entry + * and form data. + * + * @param array $entry The form entry data. + * @param array $form_data The form schema data. + * + * @return string|null The phone number, or null if no phone field is found. + */ + private static function getPhone( $entry, $form_data ) { + return self::getField( $entry, $form_data, 'phone' ); + } + + /** + * Retrieves the email address from the form data. + * + * This method extracts the email address field from the provided form entry + * and form data. + * + * @param array $entry The form entry data. + * @param array $form_data The form schema data. + * + * @return string|null The email address, or null + * if no email field is found. + */ + private static function getEmail( $entry, $form_data ) { + return self::getField( $entry, $form_data, 'email' ); + } + + /** + * Retrieves the address data from the form data. + * + * This method extracts the address data (city, state, country, and zip) + * from the provided form entry + * and form data. The country is sent in ISO format. + * + * Note that if the address scheme is 'us' and country + * is not present, 'US' is used as the country. + * + * @param array $entry The form entry data. + * @param array $form_data The form schema data. + * + * @return array The address data. + */ + private static function getAddress( $entry, $form_data ) { + $address_field_data = self::getField( $entry, $form_data, 'address' ); + if ( is_null( $address_field_data ) ) { + return array(); + } + $address_data = array(); + if ( isset( $address_field_data['city'] ) ) { + $address_data['city'] = $address_field_data['city']; + } + if ( isset( $address_field_data['state'] ) ) { + $address_data['state'] = $address_field_data['state']; + } + + if ( isset( $address_field_data['country'] ) ) { + $address_data['country'] = $address_field_data['country']; + } else { + $address_scheme = self::getAddressScheme( $form_data ); + if ( 'us' === $address_scheme ) { + $address_data['country'] = 'US'; + } + } + if ( isset( $address_field_data['postal'] ) ) { + $address_data['zip'] = $address_field_data['postal']; + } + return $address_data; + } + + /** + * Retrieves the user's name from the form data. + * + * This method extracts the name field from the provided form entry + * and form data. It supports two formats: + * - 'simple': where the name is a single string, + * split into first and last name. + * - 'first-last': where the name is provided as separate + * 'first' and 'last' fields. + * + * @param array $entry The form entry data. + * @param array $form_data The form schema data. + * + * @return array|null An array containing the first and + * last name, or null if no name field is found. + */ + private static function getName( $entry, $form_data ) { + if ( empty( $form_data['fields'] ) || empty( $entry['fields'] ) ) { + return null; + } + + $entries = $entry['fields']; + foreach ( $form_data['fields'] as $field ) { + if ( 'name' === $field['type'] ) { + if ( 'simple' === $field['format'] ) { + return ServerEventFactory::split_name( + $entries[ $field['id'] ] + ); + } elseif ( 'first-last' === $field['format'] ) { + return array( + $entries[ $field['id'] ]['first'], + $entries[ $field['id'] ]['last'], + ); + } + } + } + + return null; + } + + /** + * Retrieves the value of a specific field type from the form entry data. + * + * This method searches through the form schema data to find a field of + * the specified type and returns the corresponding value from the form + * entry data. + * + * @param array $entry The form entry data. + * @param array $form_data The form schema data. + * @param string $type The type of the field to retrieve. + * + * @return mixed|null The value of the field, or null if no + * field of the specified type is found. + */ + private static function getField( $entry, $form_data, $type ) { + if ( empty( $form_data['fields'] ) || empty( $entry['fields'] ) ) { + return null; + } + + foreach ( $form_data['fields'] as $field ) { + if ( $field['type'] === $type ) { + return $entry['fields'][ $field['id'] ]; + } + } + + return null; + } + + /** + * Retrieves the address scheme from the form data. + * + * This method searches through the form schema data to find the first + * 'address' field and returns its 'scheme' value, which is either 'us' or + * 'international'. If no address field is found, or if the address field + * does not have a scheme, this method returns null. + * + * @param array $form_data The form schema data. + * + * @return string|null The address scheme, or + * null if no address field is found. + */ + private static function getAddressScheme( $form_data ) { + foreach ( $form_data['fields'] as $field ) { + if ( 'address' === $field['type'] ) { + if ( isset( $field['scheme'] ) ) { + return $field['scheme']; + } + } + } + return null; + } +} diff --git a/integration/class-integrationutils.php b/integration/class-integrationutils.php new file mode 100644 index 00000000..420665d6 --- /dev/null +++ b/integration/class-integrationutils.php @@ -0,0 +1,49 @@ + 0) { + value = _this.data('price'); + } + } + + var param = { + 'content_ids': [download], + 'content_type': 'product', + 'currency': currency, + 'fb_integration_key': facebookPixelData.fb_integration_key, + 'value': value + }; + + fbq('set', 'agent', facebookPixelData.agent_string, facebookPixelData.pixel_id); + if (event_id) { + fbq('track', 'AddToCart', param, { 'eventID': event_id }); + } else { + fbq('track', 'AddToCart', param); + } + }); +}); diff --git a/js/fbe_allinone.js b/js/fbe_allinone.js index f10f1936..b746be6a 100644 --- a/js/fbe_allinone.js +++ b/js/fbe_allinone.js @@ -255,6 +255,7 @@ var FBEFlowContainer = React.createClass({ let msg = ''; if (response.success) { _this.setState({pixelId: pixelId}); + _this.showEventsManagerSection(pixelId); msg = "The Meta Pixel with ID: " + pixelId + " is now installed on your website."; } else { msg = "There was a problem saving the pixel. Please try again"; @@ -274,6 +275,7 @@ var FBEFlowContainer = React.createClass({ success: function onSuccess(data, _textStatus, _jqXHR) { let msg = ''; if(data.success) { + _this.hideEventsManagerSection(); msg = data.message; }else { msg = data.error_message; @@ -327,6 +329,14 @@ var FBEFlowContainer = React.createClass({ } catch (err) { console.error(err); } + }, + hideEventsManagerSection: function hideEventsManagerSection() { + jQuery(".events-manager-wrapper").hide(); + jQuery(".events-manager-wrapper input#pixel-id").val(''); + }, + showEventsManagerSection: function showEventsManagerSection(pixelId) { + jQuery(".events-manager-wrapper").show(); + jQuery(".events-manager-wrapper input#pixel-id").val(pixelId); } }); diff --git a/js/settings_page.js b/js/settings_page.js new file mode 100644 index 00000000..76284851 --- /dev/null +++ b/js/settings_page.js @@ -0,0 +1,381 @@ +window.fbAsyncInit = function () { + FB.init({ + appId: "221646389321681", + autoLogAppEvents: true, + xfbml: true, + version: "v13.0", + }); +}; + +window.facebookBusinessExtensionConfig = { + pixelId: meta_wc_params.pixelId, + popupOrigin: "https://business.facebook.com", + setSaveSettingsRoute: meta_wc_params.setSaveSettingsRoute, + externalBusinessId: meta_wc_params.externalBusinessId, + fbeLoginUrl: "https://business.facebook.com/fbe-iframe-get-started/?", + deleteConfigKeys: meta_wc_params.deleteConfigKeys, + appId: "221646389321681", + timeZone: "America/Los_Angeles", + installed: meta_wc_params.installed, + systemUserName: meta_wc_params.systemUserName + "_system_user", + businessVertical: "ECOMMERCE", + version: "v8.0", + currency: "USD", + businessName: "Solutions Engineering Team", + debug: true, + channel: "DEFAULT", +}; + +var pixelString = meta_wc_params.pixelString; + +if (!pixelString.trim()) { + jQuery("#fb-adv-conf").hide(); +} else { + // Set advanced configuration top relative to fbe iframe + setFbAdvConfTop(); + jQuery("#fb-adv-conf").show(); + jQuery("#fb-capi-ef").show(); + + var enablePiiCachingCheckbox = document.getElementById("capi-ch"); + var piiCachingStatus = meta_wc_params.piiCachingStatus; + updateCapiPiiCachingCheckbox(piiCachingStatus); + enablePiiCachingCheckbox.addEventListener("change", function () { + if (this.checked) { + saveCapiPiiCachingStatus("1"); + } else { + saveCapiPiiCachingStatus("0"); + } + }); + function setFbAdvConfTop() { + var fbeIframeTop = 0; + // Add try catch to handle any error and avoid breaking js + try { + fbeIframeTop = jQuery("#fbe-iframe")[0].getBoundingClientRect().top; + } catch (e) {} + + var fbAdvConfTop = meta_wc_params.fbAdvConfTop + fbeIframeTop; + jQuery("#fb-adv-conf").css({ top: fbAdvConfTop + "px" }); + } + + var enablePageViewFilterCheckBox = document.getElementById("capi-ef"); + var capiIntegrationPageViewFiltered = + meta_wc_params.capiIntegrationPageViewFiltered === "true" ? "1" : "0"; + updateCapiIntegrationEventsFilter(capiIntegrationPageViewFiltered); + enablePageViewFilterCheckBox.addEventListener("change", function () { + saveCapiIntegrationEventsFilter(this.checked ? "1" : "0"); + }); + function updateCapiPiiCachingCheckbox(val) { + if (val === "1") { + enablePiiCachingCheckbox.checked = true; + } else { + enablePiiCachingCheckbox.checked = false; + } + } + function updateCapiIntegrationEventsFilter(val) { + enablePageViewFilterCheckBox.checked = val === "1" ? true : false; + } + function saveCapiPiiCachingStatus(new_val) { + jQuery + .ajax({ + type: "post", + dataType: "json", + url: meta_wc_params.capiPiiCachingStatusSaveUrl, + data: { + action: meta_wc_params.capiPiiCachingStatusActionName, + val: new_val, + }, + success: function (response) { + updateCapiPiiCachingCheckbox(new_val); + }, + }) + .fail(function (jqXHR, textStatus, error) { + jQuery("#fb-capi-se").text( + meta_wc_params.capiPiiCachingStatusUpdateError + ); + jQuery("#fb-capi-se").show().delay(3000).fadeOut(); + updateCapiPiiCachingCheckbox(new_val === "1" ? "0" : "1"); + }); + } + function saveCapiIntegrationEventsFilter(new_val) { + jQuery + .ajax({ + type: "post", + dataType: "json", + url: meta_wc_params.capiIntegrationEventsFilterSaveUrl, + data: { + action: meta_wc_params.capiIntegrationEventsFilterActionName, + val: new_val, + }, + success: function (response) {}, + }) + .fail(function (jqXHR, textStatus, error) { + jQuery("#fb-capi-ef-se").text( + meta_wc_params.capiIntegrationEventsFilterUpdateError + ); + jQuery("#fb-capi-ef-se").show().delay(3000).fadeOut(); + updateCapiIntegrationEventsFilter(new_val === "1" ? "0" : "1"); + }); + } +} +var currentFBEInstalledStatus = meta_wc_params.installed; +jQuery("#ad-creation-plugin-iframe").attr("data-fbe-extras", getFBEExtras()); +jQuery("#ad-insights-plugin-iframe").attr("data-fbe-extras", getFBEExtras()); +updateAdInsightsPlugin(currentFBEInstalledStatus); + +function getFBEExtras() { + $fbeConfig = window.facebookBusinessExtensionConfig; + return JSON.stringify({ + business_config: { + business: { + name: $fbeConfig.businessName, + }, + }, + setup: { + external_business_id: $fbeConfig.externalBusinessId, + timezone: $fbeConfig.timeZone, + currency: $fbeConfig.currency, + business_vertical: $fbeConfig.businessVertical, + channel: $fbeConfig.channel, + }, + repeat: false, + }); +} +function updateAdInsightsPlugin(isFBEInstalled) { + if (isFBEInstalled) { + jQuery("#meta-ads-plugin").show(); + } else { + jQuery("#meta-ads-plugin").hide(); + } +} + +function sendTestEvent(e) { + e.preventDefault(); + var advancedPayloadElement = document.getElementById("advanced-payload"); + var testEventCode = ""; + var testEventName = ""; + var data = ""; + if (advancedPayloadElement.classList.contains("open")) { + if (!advancedPayloadElement.value) { + alert("You must enter payload."); + return; + } + advancedPayload = advancedPayloadElement.value; + try { + data = JSON.parse(advancedPayload); + if (data.test_event_code) { + testEventCode = data.test_event_code; + } + testEventName += data.data + .map((event) => event.event_name) + .join(", "); + } catch (e) { + alert("Invalid JSON in payload."); + return; + } + } else { + testEventCode = document.getElementById("event-test-code").value; + testEventName = document.getElementById("test-event-name").value; + } + + if (!testEventCode) { + alert("You must enter test event code."); + return; + } + + jQuery.ajax({ + type: "POST", + url: meta_wc_params.ajax_url, + data: { + action: "send_capi_event", + nonce: meta_wc_params.send_capi_event_nonce, + event_name: testEventName, + test_event_code: testEventCode, + payload: data, + custom_data: setCustomData(data, testEventName), + user_data: { + ph: "254aa248acb47dd654ca3ea53f48c2c26d641d23d7e2e93a1ec56258df7674c4", + em: "309a0a5c3e211326ae75ca18196d301a9bdbd1a882a4d2569511033da23f0abd", + }, + }, + success: function (response) { + data = JSON.parse(response.data); + if (!data.error) { + document + .querySelector(".event-log-block>table>tbody") + .insertAdjacentHTML( + "beforeend", + `${testEventCode}${testEventName}Success` + ); + } else { + let tableRow = ` + + + ${data.error.message} + + ${testEventName} + + + Error + + + + + + + ${data.error.error_user_msg} + + + `; + + document + .querySelector(".event-log-block>table>tbody") + .insertAdjacentHTML("beforeend", tableRow); + + const testErrorButton = document.querySelectorAll( + ".test-event-button--error" + ); + testErrorButton.forEach((button) => { + button.addEventListener("click", handleErrorMessageToggle); + }); + } + + const eventHintsText = document.querySelector(".event-hints__text"); + + if (eventHintsText.classList.contains("initial-text")) { + const noteCloseButton = document.querySelector( + ".event-hints__close-icon" + ); + + noteCloseButton.addEventListener("click", () => { + document + .querySelector(".event-hints") + .classList.add("hidden"); + }); + eventHintsText.textContent = + "Note that events can take up to a few minutes to appear in the Events Manager."; + eventHintsText.classList.remove("initial-text"); + noteCloseButton.classList.remove("hidden"); + } + }, + + error: function (error) { + console.log(error); + }, + }); +} + +function setCustomData(data, testEventName) { + if (data) { + return; + } else { + if ( + [ + "Purchase", + "AddToCart", + "InitiateCheckout", + "ViewContent", + "Search", + "AddPaymentInfo", + "AddToWishlist", + ].includes(testEventName) + ) { + return { + value: 123.321, + currency: "USD", + content_type: "product", + }; + } else { + return null; + } + } +} + +function handleErrorMessageToggle() { + const errorRow = this.closest(".test-event--error"); + + if (!errorRow) { + return; + } + + this.firstElementChild.classList.toggle("open"); + const errorMessage = errorRow.querySelector(".test-event-msg--error"); + + if (errorMessage) { + toggleHeight(errorMessage); + } +} + +function toggleAdvancedPayload() { + document.getElementById("advanced-payload").classList.toggle("open"); + document.getElementById("populate-payload-button").classList.toggle("show"); + document + .querySelector(".advanced-edit-toggle-arrow") + .classList.toggle("open"); + + if ( + !document.getElementById("advanced-payload").value && + document.getElementById("advanced-payload").classList.contains("open") + ) { + populateAdvancedEvent(); + } +} + +function populateAdvancedEvent() { + testEventName = document.getElementById("test-event-name").value; + testEventCode = document.getElementById("event-test-code").value; + var exampleEvent = { + data: [ + { + event_name: testEventName, + event_time: Math.floor(Date.now() / 1000), + event_id: "event.id." + Math.floor(Math.random() * 901 + 100), + event_source_url: window.location.origin, + action_source: "website", + user_data: { + em: [ + "309a0a5c3e211326ae75ca18196d301a9bdbd1a882a4d2569511033da23f0abd", + ], + ph: [ + "254aa248acb47dd654ca3ea53f48c2c26d641d23d7e2e93a1ec56258df7674c4", + ], + }, + custom_data: { + value: 100.2, + currency: "USD", + content_ids: ["product.id.123"], + content_type: "product", + }, + }, + ], + test_event_code: testEventCode ? testEventCode : "TEST4039", + }; + if ( + ![ + "Purchase", + "AddToCart", + "InitiateCheckout", + "ViewContent", + "Search", + "AddPaymentInfo", + "AddToWishlist", + ].includes(testEventName) + ) { + delete exampleEvent.data[0].custom_data; + } + document.getElementById("advanced-payload").value = JSON.stringify( + exampleEvent, + null, + 2 + ); +} + +// Function to toggle height with transition +function toggleHeight(element) { + if (element.style.height === "0px" || element.style.height === "") { + element.style.height = `fit-content`; + element.classList.remove("hidden"); + } else { + element.style.height = "0"; + element.classList.add("hidden"); + } +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..dc2a31dc --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,65 @@ + + + PHP CodeSniffer ruleset for WordPress plugin development with additional style guide rules + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + warning + 4 + + + + + + + + + + . + + + + + + + + + vendor/* + node_modules/* + task/* + __tests__/* + diff --git a/phpcs.xml.dist b/phpcs.xml.dist deleted file mode 100644 index 29a11b47..00000000 --- a/phpcs.xml.dist +++ /dev/null @@ -1,23 +0,0 @@ - - - Coding standards for WordPress development. - - - ./ - - - vendor/* - node_modules/* - - - - - - - - - - - - - diff --git a/task/BaseTask.php b/task/BaseTask.php deleted file mode 100644 index 9eba364a..00000000 --- a/task/BaseTask.php +++ /dev/null @@ -1,26 +0,0 @@ -path = $path; - } - - public function main() { - $this->setABSPATH(); - $version = FacebookPluginConfig::PLUGIN_VERSION; - echo "The current version is {$version} \n"; - $file_contents = file_get_contents($this->path); - $file_contents = str_replace("{*VERSION_NUMBER*}", $version, $file_contents); - file_put_contents($this->path, $file_contents); - } -} diff --git a/task/FacebookWordpressGetVersionNumberTask.php b/task/FacebookWordpressGetVersionNumberTask.php deleted file mode 100644 index 4b102ec5..00000000 --- a/task/FacebookWordpressGetVersionNumberTask.php +++ /dev/null @@ -1,34 +0,0 @@ -versionprop = $versionprop; - } - - public function main() { - $version = FacebookPluginConfig::PLUGIN_VERSION; - $this->project->setProperty($this->versionprop, $version); - } -} diff --git a/task/class-basetask.php b/task/class-basetask.php new file mode 100644 index 00000000..d34010c5 --- /dev/null +++ b/task/class-basetask.php @@ -0,0 +1,53 @@ +path = $path; + } + + /** + * Main function to update the version number in the specified file. + * + * This function retrieves the current plugin version and replaces the + * placeholder '{*VERSION_NUMBER*}' in the file at the specified path + * with the actual version number. + * + * @return void + */ + public function main() { + $this->setABSPATH(); + $version = FacebookPluginConfig::PLUGIN_VERSION; + echo 'The current version is ' . $version . " \n"; + $file_contents = file_get_contents( $this->path ); + $file_contents = str_replace( '{*VERSION_NUMBER*}', $version, $file_contents ); + file_put_contents( $this->path, $file_contents ); + } +} diff --git a/task/class-facebookwordpressgetversionnumbertask.php b/task/class-facebookwordpressgetversionnumbertask.php new file mode 100644 index 00000000..d8de9aa2 --- /dev/null +++ b/task/class-facebookwordpressgetversionnumbertask.php @@ -0,0 +1,66 @@ +versionprop = $versionprop; + } + + /** + * Sets the project property specified in + * $versionprop to the current version of the plugin. + * + * @return void + */ + public function main() { + $version = FacebookPluginConfig::PLUGIN_VERSION; + $this->project->setProperty( $this->versionprop, $version ); + } +} diff --git a/uninstall.php b/uninstall.php index 94ba40d6..068bbeb3 100644 --- a/uninstall.php +++ b/uninstall.php @@ -1,4 +1,12 @@