Skip to content

Commit

Permalink
Merge pull request #2684 from Automattic/add/logging-settings-save
Browse files Browse the repository at this point in the history
Add logging for settings update
  • Loading branch information
alexsanford committed Jun 3, 2019
2 parents ca0fc75 + a8dcc3b commit 810e5f0
Show file tree
Hide file tree
Showing 4 changed files with 365 additions and 0 deletions.
90 changes: 90 additions & 0 deletions includes/class-sensei-settings.php
Expand Up @@ -39,6 +39,8 @@ public function __construct() {
$this->register_hook_listener();
$this->get_settings();

// Log when settings are updated by the user.
add_action( 'update_option_sensei-settings', [ $this, 'log_settings_update' ], 10, 2 );
} // End __construct()

/**
Expand Down Expand Up @@ -705,6 +707,94 @@ public static function flush_rewrite_rules() {
}

}//end flush_rewrite_rules()

/**
* Logs settings update from the Settings form.
*
* @access private
* @since 2.1.0
*
* @param array $old_value The old settings value.
* @param array $value The new settings value.
*/
public function log_settings_update( $old_value, $value ) {
// Only process user-initiated settings updates.
if ( ! ( 'POST' === $_SERVER['REQUEST_METHOD'] && 'options' === get_current_screen()->id ) ) {
return;
}

// Find changed fields.
$changed = [];
foreach ( $this->fields as $field => $field_config ) {
// Handle absent fields.
$old_field_value = isset( $old_value[ $field ] ) ? $old_value[ $field ] : '';
$new_field_value = isset( $value[ $field ] ) ? $value[ $field ] : '';

if ( $new_field_value !== $old_field_value ) {
// Create an array for this section of settings if needed.
$section = $field_config['section'];
if ( ! isset( $changed[ $section ] ) ) {
$changed[ $section ] = [];
}

// Get changed setting values to be logged. In most cases, this
// will be an array containing only the name of the field.
$changed_values = $this->get_changed_setting_values(
$field,
$new_field_value,
$old_field_value
);
$changed[ $section ] = array_merge( $changed[ $section ], $changed_values );
}
}

// Log changed sections.
foreach ( $changed as $section => $fields ) {
sensei_log_event(
'settings_update',
[
'view' => $section,
'settings' => implode( ',', $fields ),
]
);
}
}

/**
* Get an array of setting values which were changed. In most cases, this
* will simply be the name of the setting. However, if the setting is an
* array of strings, then this will return an array of the string values
* that were changed. These values returned will be of the form
* "field_name[value_name]".
*
* @since 2.1.0
*
* @param string $field The name of the setting field.
* @param array $new_value The new value.
* @param array $old_value The old value.
*
* @return array The array of strings representing the field that was
* changed, or an array containing the field name.
*/
private function get_changed_setting_values( $field, $new_value, $old_value ) {
// If the old and new values are not arrays, return the field name.
if ( ! is_array( $new_value ) || ! is_array( $old_value ) ) {
return [ $field ];
}

// Now, make sure they are both string arrays.
foreach ( array_merge( $new_value, $old_value ) as $value ) {
if ( ! is_string( $value ) ) {
return [ $field ];
}
}

// We have two string arrays. Return the difference in their values.
$added = array_diff( $new_value, $old_value );
$removed = array_diff( $old_value, $new_value );

return array_filter( array_merge( $added, $removed ) );
}
} // End Class

/**
Expand Down
4 changes: 4 additions & 0 deletions tests/bootstrap.php
Expand Up @@ -62,6 +62,10 @@ public function includes() {
// factories
require_once $this->tests_dir . '/framework/factories/class-sensei-factory.php';
require_once $this->tests_dir . '/framework/factories/class-wp-unittest-factory-for-post-sensei.php';

// Testing setup for event logging.
require_once $this->tests_dir . '/framework/class-sensei-test-events.php';
Sensei_Test_Events::init();
}
/**
* Get the single class instance.
Expand Down
75 changes: 75 additions & 0 deletions tests/framework/class-sensei-test-events.php
@@ -0,0 +1,75 @@
<?php
/**
* File with class for testing events.
*
* @package sensei-tests
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* Test class for event logging.
*
* @author Automattic
*
* @since 2.1.0
*/
class Sensei_Test_Events {

/**
* The internal list of logged events.
*
* @var array
*/
private static $_logged_events = [];

/**
* Intialize the event logging test class.
*/
public static function init() {
// Set up filter to intercept event logging.
add_filter(
'pre_http_request',
function( $preempt, $r, $url ) {
$host = wp_parse_url( $url, PHP_URL_HOST );
$path = wp_parse_url( $url, PHP_URL_PATH );
if ( 'pixel.wp.com' === $host && '/t.gif' === $path ) {
// Log event.
$args = [];
parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $args );
array_push(
self::$_logged_events,
[
'event_name' => $args['_en'],
'url_args' => $args,
]
);
return new WP_Error();
}
return $preempt;
},
10,
3
);

// Ensure event logging is enabled.
Sensei()->settings->set( Sensei_Usage_Tracking::SENSEI_SETTING_NAME, true );
Sensei()->settings->get_settings();
}

/**
* Get the list of events that have been logged.
*/
public static function get_logged_events() {
return self::$_logged_events;
}

/**
* Clear the list of events that have been logged.
*/
public static function reset() {
self::$_logged_events = [];
}
}
196 changes: 196 additions & 0 deletions tests/unit-tests/test-class-sensei-settings.php
@@ -0,0 +1,196 @@
<?php
/**
* File with class for testing Sensei Settings.
*
* @package sensei-tests
*/

/**
* Class for testing Sensei Settings.
*/
class Sensei_Settings_Test extends WP_UnitTestCase {
/**
* Set up for tests.
*/
public function setUp() {
Sensei_Test_Events::reset();
$this->resetSimulateSettingsRequest();

parent::setUp();
}

/**
* Tear down after tests.
*/
public function tearDown() {
parent::tearDown();

Sensei_Test_Events::reset();
$this->resetSimulateSettingsRequest();
}

/**
* Test logging of changed settings.
*
* @covers Sensei_Settings::log_settings_update
*/
public function testLogChangedSettings() {
$settings = Sensei()->settings;
$new = $settings->get_settings();

// Change some settings in General.
$new['sensei_delete_data_on_uninstall'] = ! $new['sensei_delete_data_on_uninstall'];
$new['sensei_video_embed_html_sanitization_disable'] = ! $new['sensei_video_embed_html_sanitization_disable'];

// Change some settings in Courses section.
$new['course_archive_featured_enable'] = ! $new['course_archive_featured_enable'];
$new['course_archive_more_link_text'] = $new['course_archive_more_link_text'] . '...';

// Trigger update with new setting values. Ensure that we are simulating an update from the wp-admin UI.
$this->simulateSettingsRequest();
$old = $settings->get_settings();
$settings->log_settings_update( $old, $new );

// Ensure events were logged.
$events = Sensei_Test_Events::get_logged_events();
$this->assertCount( 2, $events );

// Ensure General settings were logged.
$this->assertEquals( 'sensei_settings_update', $events[0]['event_name'] );
$this->assertEquals( 'default-settings', $events[0]['url_args']['view'] );
$changed = explode( ',', $events[0]['url_args']['settings'] );
sort( $changed );
$this->assertEquals( [ 'sensei_delete_data_on_uninstall', 'sensei_video_embed_html_sanitization_disable' ], $changed );

// Ensure Course settings were logged.
$this->assertEquals( 'sensei_settings_update', $events[1]['event_name'] );
$this->assertEquals( 'course-settings', $events[1]['url_args']['view'] );
$changed = explode( ',', $events[1]['url_args']['settings'] );
sort( $changed );
$this->assertEquals( [ 'course_archive_featured_enable', 'course_archive_more_link_text' ], $changed );
}

/**
* Test logging of settings only on user update.
*
* @covers Sensei_Settings::log_settings_update
*/
public function testOnlyLogSettingsOnUserUpdate() {
$settings = Sensei()->settings;
$new = $settings->get_settings();

// Change a setting.
$new['sensei_delete_data_on_uninstall'] = ! $new['sensei_delete_data_on_uninstall'];

// Trigger update with new setting values.
$old = $settings->get_settings();
$settings->log_settings_update( $old, $new );

// Ensure no events were logged.
$events = Sensei_Test_Events::get_logged_events();
$this->assertCount( 0, $events );
}

/**
* Test logging of array settings.
*
* @covers Sensei_Settings::log_settings_update
*/
public function testLogArraySettings() {
$settings = Sensei()->settings;

// Set "email_teachers" setting.
$settings->set( 'email_teachers', [ 'teacher-started-course', 'teacher-completed-course' ] );

// Get current settings.
$new = $settings->get_settings();

// Change the "email_teachers" setting.
$new['email_teachers'] = [ '', 'teacher-started-course', 'teacher-completed-lesson' ];

// Trigger update with new setting values.
$this->simulateSettingsRequest();
$old = $settings->get_settings();
$settings->log_settings_update( $old, $new );

// Ensure array setting change was logged.
$events = Sensei_Test_Events::get_logged_events();
$this->assertCount( 1, $events );

$changed = explode( ',', $events[0]['url_args']['settings'] );
sort( $changed );
$this->assertEquals(
[
'teacher-completed-course',
'teacher-completed-lesson',
],
$changed
);
}

/**
* Test logging of non-string array settings.
*
* @covers Sensei_Settings::log_settings_update
*/
public function testLogNonStringArraySettings() {
$settings = Sensei()->settings;

// Set an array setting with non-string values.
$settings->set( 'email_learners', [ 'a string', [ 'a sub-array' ] ] );

// Get current settings.
$new = $settings->get_settings();

// Change the array setting.
$new['email_learners'] = [ 'a different string', [ 'a different sub-array' ] ];

// Trigger update with new setting values.
$this->simulateSettingsRequest();
$old = $settings->get_settings();
$settings->log_settings_update( $old, $new );

// Ensure array setting change was logged without values.
$events = Sensei_Test_Events::get_logged_events();
$this->assertCount( 1, $events );
$changed = explode( ',', $events[0]['url_args']['settings'] );
sort( $changed );
$this->assertEquals( [ 'email_learners' ], $changed );
}

/**
* Simulate Sensei settings update request.
*/
private function simulateSettingsRequest() {
global $current_screen;

$this->original_request_method = $_SERVER['REQUEST_METHOD'];
$this->original_screen = $current_screen;

// Simulate the request.
$_SERVER['REQUEST_METHOD'] = 'POST';
set_current_screen( 'options' );
}

/**
* Reset values from simulating Sensei settings update request.
*/
private function resetSimulateSettingsRequest() {
global $current_screen;

if ( $this->original_request_method ) {
$_SERVER['REQUEST_METHOD'] = $this->original_request_method;
} else {
$_SERVER['REQUEST_METHOD'] = 'GET';
}

if ( $this->original_screen ) {
$current_screen = $this->original_screen;
} else {
$current_screen = null;
}

$this->original_request_method = null;
$this->original_screen = null;
}
}

0 comments on commit 810e5f0

Please sign in to comment.