<?php

abstract class wfAuditLogObserversWordPressCoreSite extends wfAuditLog {
	const SITE_DATA_EXPORTED = 'site.data.exported';
	const SITE_RECOVERY_MODE_KEY_GENERATED = 'site.recovery-mode.key-generated';
	
	const SITE_MAIL_SEND_FAILED = 'site.mail.send-failed';
	const SITE_MAIL_SENT = 'site.mail.sent';
	
	const SITE_OPTION_ACTIVE_PLUGINS = 'site.option.active-plugins';
	const SITE_OPTION_ADMIN_EMAIL = 'site.option.admin-email';
	const SITE_OPTION_ANONYMOUS_COMMENTS = 'site.option.anonymous-comments';
	const SITE_OPTION_COMMENT_MODERATION = 'site.option.comment-moderation';
	const SITE_OPTION_DEFAULT_COMMENT_STATUS = 'site.option.default-comment-status';
	const SITE_OPTION_DEFAULT_ROLE = 'site.option.default-role';
	const SITE_OPTION_HOME_URL = 'site.option.home-url';
	const SITE_OPTION_SITE_URL = 'site.option.site-url';
	const SITE_OPTION_STYLESHEET = 'site.option.stylesheet';
	const SITE_OPTION_TEMPLATE = 'site.option.template';
	const SITE_OPTION_USER_REGISTRATION = 'site.option.user-registration';
	
	const SITE_PERMISSIONS_ROLE_CAPABILITIES = 'site.permissions.role-capabilities';
	const SITE_PERMISSIONS_ADMIN_PAGE_DENIED = 'site.permissions.admin-page.denied';
	
	const SITE_PLUGIN_INSTALLED = 'site.plugin.installed';
	const SITE_PLUGIN_DELETED = 'site.plugin.deleted';
	const SITE_PLUGIN_ACTIVATED = 'site.plugin.activated';
	const SITE_PLUGIN_DEACTIVATED = 'site.plugin.deactivated';
	
	const SITE_THEME_INSTALLED = 'site.theme.installed';
	const SITE_THEME_DELETED = 'site.theme.deleted';
	const SITE_THEME_SWITCHED = 'site.theme.switched';
	const SITE_THEME_CUSTOMIZED = 'site.theme.customized';
	const SITE_THEME_SIDEBAR_UPDATED = 'site.theme.sidebar.updated';
	
	const SITE_UPDATE_AUTOMATIC_COMPLETED = 'site.update.automatic-completed';
	const SITE_UPDATE_CORE = 'site.update.core';
	const SITE_UPDATE_PLUGIN = 'site.update.plugin';
	const SITE_UPDATE_THEME = 'site.update.theme';
	
	public static function immediateSendEvents() {
		return array(
			self::SITE_OPTION_ACTIVE_PLUGINS,
			self::SITE_OPTION_DEFAULT_ROLE,
			self::SITE_PERMISSIONS_ROLE_CAPABILITIES,
			self::SITE_PLUGIN_ACTIVATED,
			self::SITE_THEME_INSTALLED,
		);
	}
	
	public static function eventCategories() {
		return array(
			wfAuditLog::AUDIT_LOG_CATEGORY_SITE_SETTINGS => array(
				self::SITE_DATA_EXPORTED,
				
				self::SITE_OPTION_ADMIN_EMAIL,
				self::SITE_OPTION_ANONYMOUS_COMMENTS,
				self::SITE_OPTION_COMMENT_MODERATION,
				self::SITE_OPTION_DEFAULT_COMMENT_STATUS,
				self::SITE_OPTION_DEFAULT_ROLE,
				self::SITE_OPTION_HOME_URL,
				self::SITE_OPTION_SITE_URL,
				self::SITE_OPTION_USER_REGISTRATION,
			),
			wfAuditLog::AUDIT_LOG_CATEGORY_AUTHENTICATION => array(
				self::SITE_RECOVERY_MODE_KEY_GENERATED,
			),
			wfAuditLog::AUDIT_LOG_CATEGORY_CONTENT => array(
				self::SITE_MAIL_SEND_FAILED,
				self::SITE_MAIL_SENT,
			),
			wfAuditLog::AUDIT_LOG_CATEGORY_USER_PERMISSIONS => array(
				self::SITE_PERMISSIONS_ROLE_CAPABILITIES,
				self::SITE_PERMISSIONS_ADMIN_PAGE_DENIED,
			),
			wfAuditLog::AUDIT_LOG_CATEGORY_PLUGINS_THEMES_UPDATES => array(
				self::SITE_OPTION_ACTIVE_PLUGINS,
				self::SITE_OPTION_STYLESHEET,
				self::SITE_OPTION_TEMPLATE,
				
				self::SITE_PLUGIN_INSTALLED,
				self::SITE_PLUGIN_DELETED,
				self::SITE_PLUGIN_ACTIVATED,
				self::SITE_PLUGIN_DEACTIVATED,
				
				self::SITE_THEME_INSTALLED,
				self::SITE_THEME_DELETED,
				self::SITE_THEME_SWITCHED,
				self::SITE_THEME_CUSTOMIZED,
				self::SITE_THEME_SIDEBAR_UPDATED,
				
				self::SITE_UPDATE_AUTOMATIC_COMPLETED,
				self::SITE_UPDATE_CORE,
				self::SITE_UPDATE_PLUGIN,
				self::SITE_UPDATE_THEME,
			),
		);
	}
	
	public static function eventNames() {
		return array(
			self::SITE_DATA_EXPORTED => __('Site Data Exported', 'wordfence'),
			self::SITE_RECOVERY_MODE_KEY_GENERATED => __('Recovery Key Generated', 'wordfence'),
			
			self::SITE_MAIL_SEND_FAILED => __('Mail Send Failed', 'wordfence'),
			self::SITE_MAIL_SENT => __('Mail Sent', 'wordfence'),
			
			self::SITE_OPTION_ACTIVE_PLUGINS => __('Active Plugins Option Changed', 'wordfence'),
			self::SITE_OPTION_ADMIN_EMAIL => __('Admin Email Option Changed', 'wordfence'),
			self::SITE_OPTION_ANONYMOUS_COMMENTS => __('Anonymous Comments Allowed Option Changed', 'wordfence'),
			self::SITE_OPTION_COMMENT_MODERATION => __('Comment Moderation Default Option Changed', 'wordfence'),
			self::SITE_OPTION_DEFAULT_COMMENT_STATUS => __('Default Comment Status Option Changed', 'wordfence'),
			self::SITE_OPTION_DEFAULT_ROLE => __('Default User Role Option Changed', 'wordfence'),
			self::SITE_OPTION_HOME_URL => __('Home URL Option Changed', 'wordfence'),
			self::SITE_OPTION_SITE_URL => __('Site URL Option Changed', 'wordfence'),
			self::SITE_OPTION_STYLESHEET => __('Child Theme Option Changed', 'wordfence'),
			self::SITE_OPTION_TEMPLATE => __('Parent Theme Option Changed', 'wordfence'),
			self::SITE_OPTION_USER_REGISTRATION => __('User Registration Permission Option Changed', 'wordfence'),
			
			self::SITE_PERMISSIONS_ROLE_CAPABILITIES => __('Role Capabilities Changed', 'wordfence'),
			self::SITE_PERMISSIONS_ADMIN_PAGE_DENIED => __('Admin Page View Denied', 'wordfence'),
			
			self::SITE_PLUGIN_INSTALLED => __('Plugin Installed', 'wordfence'),
			self::SITE_PLUGIN_DELETED => __('Plugin Deleted', 'wordfence'),
			self::SITE_PLUGIN_ACTIVATED => __('Plugin Activated', 'wordfence'),
			self::SITE_PLUGIN_DEACTIVATED => __('Plugin Deactivated', 'wordfence'),
			
			self::SITE_THEME_INSTALLED => __('Theme Installed', 'wordfence'),
			self::SITE_THEME_DELETED => __('Theme Deleted', 'wordfence'),
			self::SITE_THEME_SWITCHED => __('Theme Switched', 'wordfence'),
			self::SITE_THEME_CUSTOMIZED => __('Theme Customized', 'wordfence'),
			self::SITE_THEME_SIDEBAR_UPDATED => __('Theme Sidebar Updated', 'wordfence'),
			
			self::SITE_UPDATE_AUTOMATIC_COMPLETED => __('Automatic Updates Completed', 'wordfence'),
			self::SITE_UPDATE_CORE => __('Core Update Completed', 'wordfence'),
			self::SITE_UPDATE_PLUGIN => __('Plugin Update Completed', 'wordfence'),
			self::SITE_UPDATE_THEME => __('Theme Update Completed', 'wordfence'),
		);
	}
	
	public static function eventRateLimiters() {
		return array(
			self::SITE_PERMISSIONS_ROLE_CAPABILITIES => function($auditLog, $payload) {
				$hash = self::_normalizedPayloadHash($payload);
				if (self::_rateLimiterCheck(self::SITE_PERMISSIONS_ROLE_CAPABILITIES, $hash)) {
					self::_rateLimiterConsume(self::SITE_PERMISSIONS_ROLE_CAPABILITIES, $hash);
					return true;
				}
				return false;
			},
		);
	}
	
	/**
	 * Registers the observers for this class's chunk of functionality.
	 * 
	 * @param wfAuditLog $auditLog
	 */
	protected static function _registerObservers($auditLog) {
		$auditLog->_addObserver('export_wp', function($args) use ($auditLog) { //Exported WP data
			$auditLog->_recordAction(self::SITE_DATA_EXPORTED, array('settings' => $args));
		});
		
		if ($auditLog->mode() == self::AUDIT_LOG_MODE_ALL) {
			$auditLog->_addObserver('wp_mail_succeeded', function($args) use ($auditLog) { //Mail sent
				$payload = array(
					'to_count' => isset($args['to']) ? count($args['to']) : 0,
					'subject' => isset($args['subject']) ? $args['subject'] : null,
					'attachment_count' => isset($args['attachments']) ? count($args['attachments']) : 0,
				);
				$auditLog->_recordAction(self::SITE_MAIL_SENT, $payload);
			});
			
			$auditLog->_addObserver('wp_mail_failed', function($error /** @var WP_Error $error */) use ($auditLog) { //Mail failed sending
				$args = $error->get_error_data();
				$payload = array(
					'to_count' => isset($args['to']) ? count($args['to']) : 0,
					'subject' => isset($args['subject']) ? $args['subject'] : null,
					'attachment_count' => isset($args['attachments']) ? count($args['attachments']) : 0,
					'error' => $error->get_error_message(),
				);
				$auditLog->_recordAction(self::SITE_MAIL_SEND_FAILED, $payload);
			});
		}
		
		$auditLog->_addObserver('update_option_comment_registration', function($old_value, $value, $option) use ($auditLog) { //Comment registration required enabled/disabled
			$auditLog->_recordAction(self::SITE_OPTION_ANONYMOUS_COMMENTS, array('state' => wfUtils::truthyToBoolean($value)));
		});
		
		$auditLog->_addObserver('update_option_default_role', function($old_value, $value, $option) use ($auditLog) { //Default role on user registration
			$auditLog->_recordAction(self::SITE_OPTION_DEFAULT_ROLE, array('state' => $value));
		});
		
		$auditLog->_addObserver('update_option_users_can_register', function($old_value, $value, $option) use ($auditLog) { //User registration allowed
			$auditLog->_recordAction(self::SITE_OPTION_USER_REGISTRATION, array('state' => wfUtils::truthyToBoolean($value)));
		});
		
		$auditLog->_addObserver('update_option_siteurl', function($old_value, $value, $option) use ($auditLog) { //Site URL
			$auditLog->_recordAction(self::SITE_OPTION_SITE_URL, array('url' => $value));
		});
		
		$auditLog->_addObserver('update_option_home', function($old_value, $value, $option) use ($auditLog) { //Home URL
			$auditLog->_recordAction(self::SITE_OPTION_HOME_URL, array('url' => $value));
		});
		
		$auditLog->_addObserver('update_option_admin_email', function($old_value, $value, $option) use ($auditLog) { //Admin email
			$auditLog->_recordAction(self::SITE_OPTION_ADMIN_EMAIL, array('email' => $value));
		});
		
		$auditLog->_addObserver('update_option_default_comment_status', function($old_value, $value, $option) use ($auditLog) { //Default comment status
			$auditLog->_recordAction(self::SITE_OPTION_DEFAULT_COMMENT_STATUS, array('status' => $value));
		});
		
		$auditLog->_addObserver('update_option_comment_moderation', function($old_value, $value, $option) use ($auditLog) { //Comment moderation enabled/disabled
			$auditLog->_recordAction(self::SITE_OPTION_COMMENT_MODERATION, array('state' => wfUtils::truthyToBoolean($value)));
		});
		
		$auditLog->_addObserver('update_option_template', function($old_value, $value, $option) use ($auditLog) { //Theme selected, this is the parent theme value
			$auditLog->_recordAction(self::SITE_OPTION_TEMPLATE, array('theme' => $value));
		});
		
		$auditLog->_addObserver('update_option_stylesheet', function($old_value, $value, $option) use ($auditLog) { //Theme selected, this is the child theme value
			$auditLog->_recordAction(self::SITE_OPTION_STYLESHEET, array('theme' => $value));
		});
		
		$auditLog->_addObserver('admin_page_access_denied', function() use ($auditLog) { //Admin page view denied
			$auditLog->_recordAction(self::SITE_PERMISSIONS_ADMIN_PAGE_DENIED, array());
		});
		
		$auditLog->_addObserver('activated_plugin', function($relative_path, $network_wide) use ($auditLog) { //Plugin activated
			$path = trailingslashit(WP_PLUGIN_DIR) . $relative_path;
			if (is_readable($path)) {
				$plugin = $auditLog->_getPlugin($path);
				if ($plugin) {
					$auditLog->_recordAction(self::SITE_PLUGIN_ACTIVATED, array('plugin' => $plugin, 'network' => $network_wide));
				}
			}
		});
		
		$auditLog->_addObserver('deactivated_plugin', function($relative_path, $network_wide) use ($auditLog) { //Plugin deactivated
			$path = trailingslashit(WP_PLUGIN_DIR) . $relative_path;
			if (is_readable($path)) {
				$plugin = $auditLog->_getPlugin($path);
				if ($plugin) {
					$auditLog->_recordAction(self::SITE_PLUGIN_DEACTIVATED, array('plugin' => $plugin, 'network' => $network_wide));
				}
			}
		});
		
		$auditLog->_addObserver('deleted_plugin', function($relative_path, $deleted) use ($auditLog) { //Plugin deleted
			if ($deleted && $auditLog->_hasState('delete_plugin.plugin')) {
				$auditLog->_recordAction(self::SITE_PLUGIN_DELETED, array('plugin' => $auditLog->_getState('delete_plugin.plugin')));
			}
		});
		
		$auditLog->_addObserver('switch_theme', function($new_name, $new_theme, $old_theme) use ($auditLog) { //Theme switched
			$auditLog->_recordAction(self::SITE_THEME_SWITCHED, array('from' => $auditLog->_getTheme($old_theme), 'to' => $auditLog->_getTheme($new_theme)));
		});
		
		$auditLog->_addObserver('deleted_theme', function($stylesheet, $deleted) use ($auditLog) { //Theme deleted
			if ($deleted && $auditLog->_hasState('delete_theme.theme')) {
				$auditLog->_recordAction(self::SITE_THEME_DELETED, array('theme' => $auditLog->_getState('delete_theme.theme')));
			}
		});
		
		$auditLog->_addObserver('customize_save_after', function($manager /** @var WP_Customize_Manager $manager */) use ($auditLog) { //Theme customized
			$auditLog->_recordAction(self::SITE_THEME_CUSTOMIZED, array('theme' => $auditLog->_getTheme($manager->theme())));
		});
		
		$auditLog->_addObserver('upgrader_process_complete', function($upgrader, $hook_extra) use ($auditLog) { //Updates completed
			$afterVersions = $auditLog->_installedVersions();
			
			//Core
			if (is_array($hook_extra) && isset($hook_extra['type']) && $hook_extra['type'] == 'core' && isset($hook_extra['action']) && $hook_extra['action'] == 'update') {
				$payload = array(
					'core' => $afterVersions['core'],
				);
				$payload['previous_version'] = self::$initialCoreVersion;
				$auditLog->_recordAction(self::SITE_UPDATE_CORE, $payload);
			}
			
			//Plugins/themes
			if ($auditLog->_hasState('upgrader_post_install.pending', 0)) {
				$pending = $auditLog->_getState('upgrader_post_install.pending', 0);
				
				foreach ($pending as $p) {
					if ($p['action'] == self::SITE_PLUGIN_INSTALLED || $p['action'] == self::SITE_UPDATE_PLUGIN) {
						$relativePath = preg_replace('/^' . preg_quote(WP_PLUGIN_DIR, '/') . '/', '', $p['path']);
						if (!(validate_file($relativePath) === 0 //this conditional matches the plugin loader's requirements
							&& preg_match('/\.php$/i', $relativePath)
							&& file_exists(WP_PLUGIN_DIR . '/' . $relativePath)
							&& is_readable($p['path']))) {
							continue;
						}
						
						$plugin = $auditLog->_getPlugin($p['path']);
						if ($plugin) {
							$auditLog->_recordAction($p['action'], array('plugin' => $plugin));
						}
					}
					else if ($p['action'] == self::SITE_THEME_INSTALLED || $p['action'] == self::SITE_UPDATE_THEME) {
						if (!is_readable($p['path'])) {
							continue;
						}
						
						$theme = $auditLog->_getTheme($p['path']);
						if ($theme) {
							$auditLog->_recordAction($p['action'], array('theme' => $theme));
						}
					}
				}
				
				$auditLog->_trackState('upgrader_post_install.pending', array(), 0);
			}
		});
		
		$auditLog->_addObserver('automatic_updates_complete', function($update_results) use ($auditLog) { //Automatic updates complete
			$auditLog->_recordAction(self::SITE_UPDATE_AUTOMATIC_COMPLETED, array('results' => $update_results));
		});
		
		$auditLog->_addObserver('generate_recovery_mode_key', function($token, $key) use ($auditLog) { //Recovery key generated
			$auditLog->_recordAction(self::SITE_RECOVERY_MODE_KEY_GENERATED, array());
		});
	}
	
	/**
	 * Registers the data gatherers for this class's chunk of functionality.
	 *
	 * @param wfAuditLog $auditLog
	 */
	protected static function _registerDataGatherers($auditLog) {
		$auditLog->_addObserver('update_option', function($option, $old_value, $value) use ($auditLog) { //User role capabilities changed
			if (preg_match('/user_roles$/i', $option)) { //For some reason this option is stored prefixed inside a table that is already prefixed on multisite, so we have to treat it special
				if (!$auditLog->_hasState('update_option_wp_user_roles.old', $auditLog->_extractMultisiteID($option, 'user_roles'))) {
					$auditLog->_trackState('update_option_wp_user_roles.old', $old_value, $auditLog->_extractMultisiteID($option, 'user_roles'));
				}
				
				$auditLog->_trackState('update_option_wp_user_roles.new', $value, $auditLog->_extractMultisiteID($option, 'user_roles'));
				
				$auditLog->_needsDestruct();
			}
		});
		
		$auditLog->_addObserver('update_option_active_plugins', function($old_value, $value, $option) use ($auditLog) { //Active plugins changed
			if (!$auditLog->_hasState('update_option_active_plugins.old', get_current_blog_id())) {
				$auditLog->_trackState('update_option_active_plugins.old', $old_value, get_current_blog_id());
			}
			
			$auditLog->_trackState('update_option_active_plugins.new', $value, get_current_blog_id());
			
			$auditLog->_needsDestruct();
		});
		
		$auditLog->_addObserver('delete_plugin', function($relative_path) use ($auditLog) { //Plugin will be deleted
			$path = trailingslashit(WP_PLUGIN_DIR) . $relative_path;
			if (is_readable($path)) {
				$plugin = $auditLog->_getPlugin($path);
				if ($plugin) {
					$auditLog->_trackState('delete_plugin.plugin', $plugin);
				}
			}
		});
		
		$auditLog->_addObserver('delete_theme', function($stylesheet) use ($auditLog) { //Theme will be deleted
			$theme = $auditLog->_getTheme(wp_get_theme($stylesheet));
			if ($theme) {
				$auditLog->_trackState('delete_theme.theme', $theme);
			}
		});
		
		$auditLog->_addObserver('upgrader_pre_install', function($response, $hook_extra) use ($auditLog) { //Plugin/theme/core will be installed/updated, capture initial versions
			if (!$auditLog->_hasState('upgrader_pre_install.versions', 0)) {
				$auditLog->_trackState('upgrader_pre_install.versions', $auditLog->_installedVersions(), 0);
			}
		}, 'filter');
		
		$auditLog->_addObserver('upgrader_post_install', function($response, $hook_extra, $result) use ($auditLog) { //Plugin/theme installed/updated
			if ($response && !is_wp_error($result)) {
				$pending = array();
				if ($auditLog->_hasState('upgrader_post_install.pending', 0)) {
					$pending = $auditLog->_getState('upgrader_post_install.pending', 0);
				}
				
				/*
				 * $hook_extra install example:
				 * 
				 * array (
				 *	  'type' => 'plugin',
				 *	  'action' => 'install',
				 *	)
				 * 
				 * 
				 * $hook_extra update example:
				 * 
				 * array (
				 *	  'plugin' => 'wordfence/wordfence.php',
				 *	  'temp_backup' => 
				 *	  array (
				 *		'slug' => 'wordfence',
				 *		'src' => '/path/to/wp-content/plugins',
				 *		'dir' => 'plugins',
				 *	  ),
				 *	)
				 */
				
				/*
				 * $result example:
				 * 
				 * array (
				 *	  'source' => '/path/to/wp-content/upgrade/wordfence.8.0.0/wordfence/',
				 *	  'source_files' => 
				 *	  array (
				 *		0 => 'LICENSE.txt',
				 *		1 => 'readme.txt',
				 *		2 => 'wordfence.php',
				 *		3 => ...
				 *	  ),
				 *	  'destination' => '/path/to/wp-content/plugins/wordfence/',
				 *	  'destination_name' => 'wordfence',
				 *	  'local_destination' => '/path/to/wp-content/plugins',
				 *	  'remote_destination' => '/path/to/plugins/wordfence/',
				 *	  'clear_destination' => false,
				 *	)
				 */
				
				if (isset($hook_extra['action']) && isset($hook_extra['type']) && isset($result['source']) && isset($result['destination'])) { //Install
					if ($hook_extra['action'] == 'install') {
						if ($hook_extra['type'] == 'plugin') {
							$path = $auditLog->_resolvePlugin(untrailingslashit($result['destination']));
							if ($path) {
								$pending[] = array('action' => self::SITE_PLUGIN_INSTALLED, 'path' => $path);
							}
						}
						else if ($hook_extra['type'] == 'theme') {
							$path = $result['destination'];
							$pending[] = array('action' => self::SITE_THEME_INSTALLED, 'path' => $path); //Can't record here since version data hasn't refreshed yet
						}
					}
				}
				else if (isset($hook_extra['plugin']) && isset($result['source']) && isset($result['destination'])) { //Plugin update
					$path = $auditLog->_resolvePlugin(trailingslashit(WP_PLUGIN_DIR) . $hook_extra['plugin']);
					if ($path) {
						$pending[] = array('action' => self::SITE_UPDATE_PLUGIN, 'path' => $path);
					}
				}
				else if (isset($hook_extra['theme']) && isset($result['source']) && isset($result['destination'])) { //Theme update
					$path = trailingslashit(get_theme_root()) . $hook_extra['theme'];
					$pending[] = array('action' => self::SITE_UPDATE_THEME, 'path' => $path);
				}
				
				$auditLog->_trackState('upgrader_post_install.pending', $pending, 0);
			}
			
			return $response;
		}, 'filter');
	}
	
	/**
	 * Registers the coalescers for this class's chunk of functionality.
	 *
	 * @param wfAuditLog $auditLog
	 */
	protected static function _registerCoalescers($auditLog) {
		$auditLog->_addCoalescer(function() use ($auditLog) { //Role capabilities changed
			$old = $auditLog->_getAllStates('update_option_wp_user_roles.old');
			if (!count($old)) {
				return;
			}
			
			if (count($old) > 1) {
				$payload = array();
				foreach ($old as $blog_id => $o) {
					$new = $auditLog->_getState('update_option_wp_user_roles.new', $blog_id);
					$diff = wfUtils::array_diff($o, $new);
					if (!empty($diff['added']) || !empty($diff['removed'])) {
						$payload[] = array('capabilities' => $new, 'diff' => $diff, 'multisite_blog_id' => $blog_id);
					}
				}
				if (count($payload)) {
					$auditLog->_recordAction(self::SITE_PERMISSIONS_ROLE_CAPABILITIES, array('changes' => $payload));
				}
			}
			else {
				$blog_id = wfUtils::array_key_first($old);
				$old = $old[$blog_id];
				$new = $auditLog->_getState('update_option_wp_user_roles.new', $blog_id);
				$diff = wfUtils::array_diff($old, $new);
				if (!empty($diff['added']) || !empty($diff['removed'])) {
					$auditLog->_recordAction(self::SITE_PERMISSIONS_ROLE_CAPABILITIES, array('capabilities' => $new, 'diff' => $diff));
				}
			}
		});
		
		$auditLog->_addCoalescer(function() use ($auditLog) { //Active plugins changed
			$old = $auditLog->_getAllStates('update_option_active_plugins.old');
			if (!count($old)) {
				return;
			}
			
			if (count($old) > 1) {
				$payload = array();
				foreach ($old as $blog_id => $o) {
					$new = $auditLog->_getState('update_option_active_plugins.new', $blog_id);
					$diff = wfUtils::array_diff($o, $new);
					if (!empty($diff['added']) || !empty($diff['removed'])) {
						$payload[] = array('plugins' => $new, 'diff' => $diff, 'multisite_blog_id' => $blog_id);
					}
				}
				if (count($payload)) {
					$auditLog->_recordAction(self::SITE_OPTION_ACTIVE_PLUGINS, array('changes' => $payload));
				}
			}
			else {
				$blog_id = wfUtils::array_key_first($old);
				$old = $old[$blog_id];
				$new = $auditLog->_getState('update_option_active_plugins.new', $blog_id);
				$diff = wfUtils::array_diff($old, $new);
				if (!empty($diff['added']) || !empty($diff['removed'])) {
					$auditLog->_recordAction(self::SITE_OPTION_ACTIVE_PLUGINS, array('plugins' => $new, 'diff' => $diff));
				}
			}
		});
	}
}