diff --git a/.gitignore b/.gitignore
index 651787857..b472ff1b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
/stream.zip
/stream-*.zip
npm-debug.log
+debug.log
package.lock
.phpunit.result.cache
diff --git a/classes/class-connector.php b/classes/class-connector.php
index d2f112396..863d9a6f8 100644
--- a/classes/class-connector.php
+++ b/classes/class-connector.php
@@ -294,4 +294,14 @@ function ( $value ) {
public function is_dependency_satisfied() {
return true;
}
+
+ /**
+ * Escape % characters in a string to avoid Uncaught ValueErrors in $this->log().
+ *
+ * @param string $value The string value to be escaped.
+ * @return string The escaped string.
+ */
+ public function escape_percentages( $value ) {
+ return str_replace( '%', '%%', $value );
+ }
}
diff --git a/composer.json b/composer.json
index 960eb9105..29ad75cd3 100644
--- a/composer.json
+++ b/composer.json
@@ -31,6 +31,7 @@
"wpackagist-plugin/easy-digital-downloads": "2.9.23",
"wpackagist-plugin/jetpack": "10.0",
"wpackagist-plugin/user-switching": "1.5.5",
+ "wpackagist-plugin/wordpress-seo": "23.0",
"wpackagist-theme/twentytwentythree": "^1.0",
"xwp/wait-for": "^0.0.1",
"yoast/phpunit-polyfills": "^1.1"
@@ -77,6 +78,10 @@
"phpunit --coverage-text",
"php local/scripts/make-clover-relative.php ./tests/reports/clover.xml"
],
+ "test-one": [
+ "phpunit",
+ "WP_MULTISITE=1 phpunit"
+ ],
"test-multisite": [
"WP_MULTISITE=1 phpunit --coverage-text",
"php local/scripts/make-clover-relative.php ./tests/reports/clover.xml"
diff --git a/composer.lock b/composer.lock
index 02ca51ddd..cfdaff9a5 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": "bea10976f57ef34a97f336ac8d1172da",
+ "content-hash": "8e415ec47436f6af387de15534224042",
"packages": [
{
"name": "composer/installers",
@@ -1203,22 +1203,22 @@
},
{
"name": "guzzlehttp/guzzle",
- "version": "7.8.1",
+ "version": "7.9.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
+ "reference": "84ac2b2afc44e40d3e8e658a45d68d6d20437612"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
- "reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/84ac2b2afc44e40d3e8e658a45d68d6d20437612",
+ "reference": "84ac2b2afc44e40d3e8e658a45d68d6d20437612",
"shasum": ""
},
"require": {
"ext-json": "*",
- "guzzlehttp/promises": "^1.5.3 || ^2.0.1",
- "guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
+ "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
+ "guzzlehttp/psr7": "^2.7.0",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
@@ -1229,9 +1229,9 @@
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
- "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
+ "guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1",
- "phpunit/phpunit": "^8.5.36 || ^9.6.15",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
@@ -1309,7 +1309,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/7.8.1"
+ "source": "https://github.com/guzzle/guzzle/tree/7.9.0"
},
"funding": [
{
@@ -1325,20 +1325,20 @@
"type": "tidelift"
}
],
- "time": "2023-12-03T20:35:24+00:00"
+ "time": "2024-07-18T11:52:56+00:00"
},
{
"name": "guzzlehttp/promises",
- "version": "2.0.2",
+ "version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223"
+ "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223",
- "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+ "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
"shasum": ""
},
"require": {
@@ -1346,7 +1346,7 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "phpunit/phpunit": "^8.5.36 || ^9.6.15"
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"type": "library",
"extra": {
@@ -1392,7 +1392,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/2.0.2"
+ "source": "https://github.com/guzzle/promises/tree/2.0.3"
},
"funding": [
{
@@ -1408,20 +1408,20 @@
"type": "tidelift"
}
],
- "time": "2023-12-03T20:19:20+00:00"
+ "time": "2024-07-18T10:29:17+00:00"
},
{
"name": "guzzlehttp/psr7",
- "version": "2.6.2",
+ "version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221"
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221",
- "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
"shasum": ""
},
"require": {
@@ -1436,8 +1436,8 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "http-interop/http-factory-tests": "^0.9",
- "phpunit/phpunit": "^8.5.36 || ^9.6.15"
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
@@ -1508,7 +1508,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.6.2"
+ "source": "https://github.com/guzzle/psr7/tree/2.7.0"
},
"funding": [
{
@@ -1524,7 +1524,7 @@
"type": "tidelift"
}
],
- "time": "2023-12-03T20:05:35+00:00"
+ "time": "2024-07-18T11:15:46+00:00"
},
{
"name": "humanmade/mercator",
@@ -8408,6 +8408,24 @@
"type": "wordpress-plugin",
"homepage": "https://wordpress.org/plugins/user-switching/"
},
+ {
+ "name": "wpackagist-plugin/wordpress-seo",
+ "version": "23.0",
+ "source": {
+ "type": "svn",
+ "url": "https://plugins.svn.wordpress.org/wordpress-seo/",
+ "reference": "tags/23.0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://downloads.wordpress.org/plugin/wordpress-seo.23.0.zip"
+ },
+ "require": {
+ "composer/installers": "^1.0 || ^2.0"
+ },
+ "type": "wordpress-plugin",
+ "homepage": "https://wordpress.org/plugins/wordpress-seo/"
+ },
{
"name": "wpackagist-theme/twentytwentythree",
"version": "1.5",
@@ -8538,5 +8556,5 @@
"platform-overrides": {
"php": "7.4"
},
- "plugin-api-version": "2.6.0"
+ "plugin-api-version": "2.3.0"
}
diff --git a/connectors/class-connector-gravityforms.php b/connectors/class-connector-gravityforms.php
index 8485b96ed..034bbcda0 100644
--- a/connectors/class-connector-gravityforms.php
+++ b/connectors/class-connector-gravityforms.php
@@ -221,20 +221,23 @@ public function register() {
* @param bool $is_new Is this a new form?.
*/
public function callback_gform_after_save_form( $form, $is_new ) {
- $title = $form['title'];
- $id = $form['id'];
+ $id = $form['id'];
$this->log(
sprintf(
/* translators: %1$s a form title, %2$s a status (e.g. "Contact Form", "created") */
__( '"%1$s" form %2$s', 'stream' ),
- $title,
- $is_new ? esc_html__( 'created', 'stream' ) : esc_html__( 'updated', 'stream' )
+ $this->get_form_title_for_message( $form ),
+ $this->get_status_for_message(
+ $is_new,
+ esc_html__( 'created', 'stream' ),
+ esc_html__( 'updated', 'stream' )
+ )
),
array(
'action' => $is_new,
'id' => $id,
- 'title' => $title,
+ 'title' => $form['title'],
),
$id,
'forms',
@@ -258,9 +261,13 @@ public function callback_gform_pre_confirmation_save( $confirmation, $form, $is_
sprintf(
/* translators: %1$s: a confirmation name, %2$s: a status, %3$s: a form title (e.g. "Email", "created", "Contact Form") */
__( '"%1$s" confirmation %2$s for "%3$s"', 'stream' ),
- $confirmation['name'],
- $is_new ? esc_html__( 'created', 'stream' ) : esc_html__( 'updated', 'stream' ),
- $form['title']
+ $this->get_name_for_message( $confirmation ),
+ $this->get_status_for_message(
+ $is_new,
+ esc_html__( 'created', 'stream' ),
+ esc_html__( 'updated', 'stream' )
+ ),
+ $this->get_form_title_for_message( $form )
),
array(
'is_new' => $is_new,
@@ -291,9 +298,13 @@ public function callback_gform_pre_notification_save( $notification, $form, $is_
sprintf(
/* translators: %1$s: a notification name, %2$s: a status, %3$s: a form title (e.g. "Email", "created", "Contact Form") */
__( '"%1$s" notification %2$s for "%3$s"', 'stream' ),
- $notification['name'],
- $is_new ? esc_html__( 'created', 'stream' ) : esc_html__( 'updated', 'stream' ),
- $form['title']
+ $this->get_name_for_message( $notification ),
+ $this->get_status_for_message(
+ $is_new,
+ esc_html__( 'created', 'stream' ),
+ esc_html__( 'updated', 'stream' )
+ ),
+ $this->get_form_title_for_message( $form )
),
array(
'is_update' => $is_new,
@@ -318,8 +329,8 @@ public function callback_gform_pre_notification_deleted( $notification, $form )
sprintf(
/* translators: %1$s: a notification name, %2$s: a form title (e.g. "Email", "Contact Form") */
__( '"%1$s" notification deleted from "%2$s"', 'stream' ),
- $notification['name'],
- $form['title']
+ $this->get_name_for_message( $notification ),
+ $this->get_form_title_for_message( $form )
),
array(
'form_id' => $form['id'],
@@ -342,8 +353,8 @@ public function callback_gform_pre_confirmation_deleted( $confirmation, $form )
sprintf(
/* translators: %1$s: a confirmation name, %2$s: a form title (e.g. "Email", "Contact Form") */
__( '"%1$s" confirmation deleted from "%2$s"', 'stream' ),
- $confirmation['name'],
- $form['title']
+ $this->get_name_for_message( $confirmation ),
+ $this->get_form_title_for_message( $form )
),
array(
'form_id' => $form['id'],
@@ -367,9 +378,13 @@ public function callback_gform_confirmation_status( $confirmation, $form, $is_ac
sprintf(
/* translators: %1$s: a confirmation name, %2$s: a status, %3$s: a form title (e.g. "Email", "activated", "Contact Form") */
__( '"%1$s" confirmation %2$s from "%3$s"', 'stream' ),
- $confirmation['name'],
- $is_active ? esc_html__( 'activated', 'stream' ) : esc_html__( 'deactivated', 'stream' ),
- $form['title']
+ $this->get_name_for_message( $confirmation ),
+ $this->get_status_for_message(
+ $is_active,
+ esc_html__( 'activated', 'stream' ),
+ esc_html__( 'deactivated', 'stream' )
+ ),
+ $this->get_form_title_for_message( $form )
),
array(
'form_id' => $form['id'],
@@ -394,9 +409,13 @@ public function callback_gform_notification_status( $notification, $form, $is_ac
sprintf(
/* translators: %1$s: a notification name, %2$s: a status, %3$s: a form title (e.g. "Email", "activated", "Contact Form") */
__( '"%1$s" notification %2$s from "%3$s"', 'stream' ),
- $notification['name'],
- $is_active ? esc_html__( 'activated', 'stream' ) : esc_html__( 'deactivated', 'stream' ),
- $form['title']
+ $this->get_name_for_message( $notification ),
+ $this->get_status_for_message(
+ $is_active,
+ esc_html__( 'activated', 'stream' ),
+ esc_html__( 'deactivated', 'stream' )
+ ),
+ $this->get_form_title_for_message( $form )
),
array(
'form_id' => $form['id'],
@@ -514,7 +533,11 @@ public function check_rg_gforms_key( $old_value, $new_value ) {
sprintf(
/* translators: %s: a status (e.g. "updated") */
__( 'Gravity Forms license key %s', 'stream' ),
- $is_update ? esc_html__( 'updated', 'stream' ) : esc_html__( 'deleted', 'stream' )
+ $this->get_status_for_message(
+ $is_update,
+ esc_html__( 'updated', 'stream' ),
+ esc_html__( 'deleted', 'stream' )
+ )
),
compact( 'option', 'old_value', 'new_value' ),
null,
@@ -755,8 +778,8 @@ public function callback_gform_update_status( $lead_id, $status, $prev = '' ) {
/* translators: %1$d: an ID, %2$s: a status, %3$s: a form title (e.g. "42", "activated", "Contact Form") */
__( 'Lead #%1$d %2$s on "%3$s" form', 'stream' ),
$lead_id,
- $actions[ $status ],
- $form['title']
+ $this->escape_percentages( $actions[ $status ] ),
+ $this->get_form_title_for_message( $form )
),
array(
'lead_id' => $lead_id,
@@ -780,18 +803,21 @@ public function callback_gform_update_status( $lead_id, $status, $prev = '' ) {
* @param string $status Status.
*/
public function callback_gform_update_is_read( $lead_id, $status ) {
- $lead = $this->get_lead( $lead_id );
- $form = $this->get_form( $lead['form_id'] );
- $status = ( ! empty( $status ) ) ? esc_html__( 'read', 'stream' ) : esc_html__( 'unread', 'stream' );
+ $lead = $this->get_lead( $lead_id );
+ $form = $this->get_form( $lead['form_id'] );
$this->log(
sprintf(
/* translators: %1$d: a lead ID, %2$s: a status, %3$s: a form ID, %4$s: a form title (e.g. "42", "unread", "Contact Form") */
__( 'Entry #%1$d marked as %2$s on form #%3$d ("%4$s")', 'stream' ),
$lead_id,
- $status,
+ $this->get_status_for_message(
+ ! empty( $status ),
+ esc_html__( 'read', 'stream' ),
+ esc_html__( 'unread', 'stream' )
+ ),
$form['id'],
- $form['title']
+ $this->get_form_title_for_message( $form )
),
array(
'lead_id' => $lead_id,
@@ -814,19 +840,23 @@ public function callback_gform_update_is_read( $lead_id, $status ) {
* @param int $status Status.
*/
public function callback_gform_update_is_starred( $lead_id, $status ) {
- $lead = $this->get_lead( $lead_id );
- $form = $this->get_form( $lead['form_id'] );
- $status = ( ! empty( $status ) ) ? esc_html__( 'starred', 'stream' ) : esc_html__( 'unstarred', 'stream' );
- $action = $status;
+ $lead = $this->get_lead( $lead_id );
+ $form = $this->get_form( $lead['form_id'] );
+ $status_for_message = $this->get_status_for_message(
+ ! empty( $status ),
+ esc_html__( 'starred', 'stream' ),
+ esc_html__( 'unstarred', 'stream' )
+ );
+ $action = $status_for_message;
$this->log(
sprintf(
/* translators: %1$d: an ID, %2$s: a status, %3$d: a form title (e.g. "42", "starred", "Contact Form") */
__( 'Entry #%1$d %2$s on form #%3$d ("%4$s")', 'stream' ),
$lead_id,
- $status,
+ $status_for_message, // This has been escaped above.
$form['id'],
- $form['title']
+ $this->get_form_title_for_message( $form )
),
array(
'lead_id' => $lead_id,
@@ -945,8 +975,8 @@ public function log_form_action( $form_id, $action ) {
/* translators: %1$d: an ID, %2$s: a form title, %3$s: a status (e.g. "42", "Contact Form", "Activated") */
__( 'Form #%1$d ("%2$s") %3$s', 'stream' ),
$form_id,
- $form['title'],
- strtolower( $actions[ $action ] )
+ $this->get_form_title_for_message( $form ),
+ strtolower( $this->escape_percentages( $actions[ $action ] ) )
),
array(
'form_id' => $form_id,
@@ -976,4 +1006,47 @@ private function get_lead( $lead_id ) {
private function get_form( $form_id ) {
return \GFFormsModel::get_form_meta( $form_id );
}
+
+ /**
+ * Get the name from an associative array with percentages escaped.
+ * To be used when calling $this->log().
+ *
+ * @param array $data_array The array with a 'name' key to be escaped.
+ * @return string The name value with percentages escaped.
+ */
+ private function get_name_for_message( $data_array ) {
+ if ( empty( $data_array['name'] ) ) {
+ return $this->escape_percentages( __( 'This does not have a name key', 'stream' ) );
+ }
+
+ return $this->escape_percentages( $data_array['name'] );
+ }
+
+ /**
+ * Get the form title from a form array with percentages escaped.
+ * To be used when calling $this->log().
+ *
+ * @param array $form The form data array.
+ * @return string The title value with percentages escaped.
+ */
+ private function get_form_title_for_message( $form ) {
+ if ( empty( $form['title'] ) ) {
+ return $this->escape_percentages( __( 'This does not have a title key', 'stream' ) );
+ }
+
+ return $this->escape_percentages( $form['title'] );
+ }
+
+ /**
+ * Get the status to use in a message with percentages in translation escaped.
+ *
+ * @param bool $conditional Whether or not it's a new form.
+ * @param string $true_string The string to return when the conditional is true.
+ * @param string $false_string The string to return when the conditional is false.
+ * @return string The status with percentages escaped.
+ */
+ private function get_status_for_message( $conditional, $true_string, $false_string ) {
+ $status = $conditional ? $true_string : $false_string;
+ return $this->escape_percentages( $status );
+ }
}
diff --git a/connectors/class-connector-wordpress-seo.php b/connectors/class-connector-wordpress-seo.php
index 573f653b7..7c52a0864 100644
--- a/connectors/class-connector-wordpress-seo.php
+++ b/connectors/class-connector-wordpress-seo.php
@@ -419,9 +419,9 @@ private function meta( $object_id, $meta_key, $meta_value ) {
sprintf(
/* translators: %1$s: a meta field title, %2$s: a post title, %3$s: a post type (e.g. "Description", "Hello World", "Post") */
__( 'Updated "%1$s" of "%2$s" %3$s', 'stream' ),
- $field['title'],
- $post->post_title,
- $post_type_label
+ $this->escape_percentages( $field['title'] ),
+ $this->escape_percentages( $post->post_title ),
+ $this->escape_percentages( $post_type_label )
),
array(
'meta_key' => $meta_key,
diff --git a/local/public/wp-config.php b/local/public/wp-config.php
index 42734f680..ae947c5ec 100644
--- a/local/public/wp-config.php
+++ b/local/public/wp-config.php
@@ -25,6 +25,7 @@
$table_prefix = 'wp_';
define( 'WP_DEBUG', true );
+define( 'WP_DEBUG_LOG', true );
define( 'JETPACK_DEV_DEBUG', true );
// Keep the wp-contents outside of WP core directory.
diff --git a/package.json b/package.json
index 554454068..b47c791ba 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"test": "npm-run-all test:*",
"test:php": "npm run cli -- composer test --working-dir=wp-content/plugins/stream-src",
"test:php-multisite": "npm run cli -- composer test-multisite --working-dir=wp-content/plugins/stream-src",
+ "test:php-one": "npm run cli -- composer test-one --working-dir=wp-content/plugins/stream-src --",
"test-report": "npm run cli -- composer test-report --working-dir=wp-content/plugins/stream-src",
"build-containers": "docker compose --file docker-compose.build.yml build",
"push-containers": "docker compose --file docker-compose.build.yml push",
diff --git a/phpunit.xml b/phpunit.xml
index f37a7bf54..1254c9721 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -10,7 +10,7 @@
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index e232ec3a8..94575196c 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -104,6 +104,7 @@ function () {
define( 'EDD_USE_PHP_SESSIONS', false );
define( 'WP_USE_THEMES', false );
activate_plugin( 'easy-digital-downloads/easy-digital-downloads.php' );
+activate_plugin( 'wordpress-seo/wp-seo.php' );
wp_stream_install_edd();
require __DIR__ . '/testcase.php';
diff --git a/tests/testcase.php b/tests/testcase.php
index 6dd1ed89b..95c89cef1 100644
--- a/tests/testcase.php
+++ b/tests/testcase.php
@@ -16,6 +16,13 @@ class WP_StreamTestCase extends \WP_Ajax_UnitTestCase {
*/
protected $action_prefix = 'wp_stream_test_';
+ /**
+ * Holds the mocked class.
+ *
+ * @var MockBuilder
+ */
+ protected $mock;
+
/**
* PHP unit setup function
*
diff --git a/tests/tests/connectors/test-class-connector-wordpress-seo.php b/tests/tests/connectors/test-class-connector-wordpress-seo.php
new file mode 100644
index 000000000..246b53bd9
--- /dev/null
+++ b/tests/tests/connectors/test-class-connector-wordpress-seo.php
@@ -0,0 +1,84 @@
+plugin->connectors->unload_connectors();
+
+ // Make partial of Connector_WordPress_SEO class, with mocked "log" function.
+ $this->mock = $this->getMockBuilder( Connector_WordPress_SEO::class )
+ ->onlyMethods( array( 'log' ) )
+ ->getMock();
+
+ // Register connector.
+ $this->mock->register();
+ }
+
+ /**
+ * Confirm that WordPress SEO is installed and active.
+ */
+ public function test_wordpress_seo_installed_and_activated() {
+ $this->assertTrue( defined( 'YOAST_ENVIRONMENT' ) );
+ }
+
+ /**
+ * Tests "added_post_meta" callback function.
+ * callback_added_post_meta( $meta_id, $object_id, $meta_key, $meta_value )
+ */
+ public function test_callback_added_post_meta() {
+
+ // Set expected calls for the Mock.
+ $this->mock->expects( $this->once() )
+ ->method( 'log' )
+ ->with(
+ $this->equalTo(
+ __( 'Updated "SEO title" of "Test post %%!" Post', 'stream' )
+ ),
+ $this->equalTo(
+ array(
+ 'meta_key' => $this->title_meta_key,
+ 'meta_value' => 'Test meta %!',
+ 'post_type' => 'post',
+ )
+ ),
+ $this->greaterThan( 0 ),
+ 'wpseo_meta',
+ 'updated'
+ );
+
+ // Create post for later use.
+ $post_id = wp_insert_post(
+ array(
+ 'post_title' => 'Test post %!',
+ 'post_content' => 'Lorem ipsum dolor...',
+ 'post_status' => 'publish',
+ )
+ );
+
+ update_post_meta( $post_id, $this->title_meta_key, 'Test meta %!' );
+
+ // Confirm callback execution.
+ $this->assertGreaterThan( 0, did_action( $this->action_prefix . 'callback_added_post_meta' ) );
+ }
+}
diff --git a/tests/tests/test-class-connector.php b/tests/tests/test-class-connector.php
index 2edfd021b..c9342359d 100644
--- a/tests/tests/test-class-connector.php
+++ b/tests/tests/test-class-connector.php
@@ -235,4 +235,17 @@ public function test_get_changed_keys() {
public function test_is_dependency_satisfied() {
$this->assertTrue( $this->connector->is_dependency_satisfied() );
}
+
+ /**
+ * Test that percentages are escaped.
+ *
+ * @return void
+ */
+ public function test_escape_percentages() {
+ $escaped_value = $this->connector->escape_percentages( 'This is a message with a % sign' );
+ $this->assertEquals(
+ 'This is a message with a %% sign',
+ $escaped_value
+ );
+ }
}