escape($blog_id) . '_') : ''; $table_name = $wpdb->base_prefix . $suffix . 'activity_log'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $table_name ( log_id bigint(20) unsigned NOT NULL auto_increment, user_id bigint(20) unsigned NOT NULL, time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, logger varchar(255) default NULL, object_id varchar(255) default '' NOT NULL, object_type varchar(20) default NULL, log_code bigint(20) unsigned NOT NULL, logmeta LONGTEXT default NULL, PRIMARY KEY (log_id), KEY user_id (user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); if($blog_id) { update_blog_option($blog_id, 'cookspin_log_db_version', CK_LOG_DB_VERSION); update_blog_option($blog_id, 'cookspin_has_log', 1); } else { update_option('cookspin_log_db_version', CK_LOG_DB_VERSION); update_option('cookspin_has_log', 1); } } } add_action('init', 'cookspin_log_setup', 10); # Allow definition of constants in functions.php to override this settings function cookspin_log_setup() { # Define default time difference (in seconds) that will cause our activity log to ignore repeat actions # Note: activity will still be logged, but won't be displayed until these settings change if(!defined('CK_LOG_TIME_IGNORE')) { define('CK_LOG_TIME_IGNORE', 60); # Logs 60 seconds apart will be treated as the same action (same type, same logger only) } # Define maximum number of records per table if(!defined('CK_LOG_MAX_ROWS')) { define('CK_LOG_MAX_ROWS', 0); # 0 means no limit; } # Define default log limit if(!defined('CK_LOG_DEFAULT_LIMIT')) { define('CK_LOG_DEFAULT_LIMIT', 25); # 25 rows are fetched at a time } } function cookspin_log_uninstall($blog_id = false) { global $wpdb; $table = !$blog_id ? $wpdb->base_prefix . 'activity_log' : $wpdb->base_prefix . $wpdb->escape($blog_id) . '_activity_log'; $wpdb->query("DROP TABLE IF EXISTS $table"); if($blog_id) { delete_blog_option($blog_id, 'cookspin_log_db_version'); delete_blog_option($blog_id, 'cookspin_has_log'); } else { delete_option('cookspin_log_db_version'); delete_option('cookspin_has_log'); } } class CK_Logger { private $name; function __construct($name, $category, $args) { $defaults = array( 'hook' => false, 'priority' => 10, 'n_params' => 1, 'cb' => false, 'print_cb' => false, 'hook_to_filter' => false, 'ignore_cmp' => array() ); $args = wp_parse_args($args, $defaults); extract($args, EXTR_SKIP); $this->name = $name; $this->category = $category; $this->hook = $hook; $this->priority = $priority; $this->n_params = $n_params; $this->cb = $cb; $this->print_cb = $print_cb; $this->hook_to_filter = $hook_to_filter; $this->ignore_cmp = (array) $ignore_cmp; $this->register(); } function register() { # Require name, hook and logging callback in order to register if(!$this->name || !$this->hook || !$this->cb) { return; } add_action('cookspin_activity_log_init', array(&$this, 'init')); # Add to global array of loggers global $cookspin_loggers; $cookspin_loggers[$this->name] = $this; } function init() { if($this->hook_to_filter) { add_filter($this->hook, array(&$this, 'hook_to_filter'), $this->priority, $this->n_params); return; } add_action($this->hook, array(&$this, 'add_log'), $this->priority, $this->n_params); } function deregister() { global $cookspin_loggers; if(!isset($cookspin_loggers[$this->name])) { return false; } if(did_action('cookspin_activity_log_init')) { if($this->hook_to_filter) { remove_filter($this->hook, array(&$this, 'hook_to_filter'), $this->priority); } else { remove_action($this->hook, array(&$this, 'add_log'), $this->priority); } } else { remove_action('cookspin_activity_log_init', array(&$this, 'init')); } unset($cookspin_loggers[$this->name]); return true; } function countrows($table_name) { global $wpdb; $query = "SELECT COUNT(1) FROM $table_name"; $count = $wpdb->get_results($query, ARRAY_N); return $count[0][0]; } function purge($table_name, $limit) { global $wpdb; $limit = $wpdb->escape($limit); $query = " DELETE FROM $table_name WHERE log_id NOT IN ( SELECT * FROM ( SELECT log_id FROM $table_name ORDER BY log_id DESC LIMIT 0, $limit ) as t);"; $wpdb->query($query); } function insert_row($args) { global $wpdb; $current_user = wp_get_current_user(); $defaults = array( 'user_id' => $current_user->ID, 'time' => current_time('mysql'), 'object_id' => false, 'object_type' => false, 'log_code' => 1, 'logmeta' => null, 'blog_id' => false, 'log_to_main' => false, ); $args = wp_parse_args($args, $defaults); extract($args, EXTR_SKIP); if(!$object_id || !$object_type) { return; } # Only allow certain loggers to add logs to the main site $loggers_log_to_main = array( 'delete_blog', 'user_create', 'user_add_to_blog', 'user_edit', 'update_profile', 'remove_user' ); if(!in_array($this->name, apply_filters('cookspin_loggers_log_to_main', $loggers_log_to_main))) { $log_to_main = false; } $suffix = $blog_id ? $wpdb->escape($blog_id) . '_' : ''; $table_name = $wpdb->escape(($log_to_main ? $wpdb->base_prefix : $wpdb->prefix . $suffix) . 'activity_log'); # Check current amount of records, and purge if we've reached the set limit if(CK_LOG_MAX_ROWS > 0 && $this->countrows($table_name) >= CK_LOG_MAX_ROWS) { $this->purge($table_name, (CK_LOG_MAX_ROWS - 1)); # -1 to make room for the new entry } $wpdb->insert( $table_name, array( 'logger' => $this->name, 'user_id' => $user_id, 'time' => $time, 'object_id' => $object_id, 'object_type' => $object_type, 'log_code' => $log_code, 'logmeta' => maybe_serialize($logmeta) ), array( '%s', '%d', '%s', '%s', '%s', '%d', '%s' ) ); } function hook_to_filter() { $cb_params = func_get_args(); call_user_func_array(array($this, 'add_log'), $cb_params); return $cb_params[0]; } # Note: for a logger function to abort adding a log row, it can return false or an empty array function add_log() { $cb_params = func_get_args(); # Logger callback is expected to be an array of args (or an array of arrays in case of multiple insertions) $insert = apply_filters("cookspin_log_add_log_{$this->name}", call_user_func_array($this->cb, $cb_params), $this->name); do_action('cookspin_log_add_log', $insert, $this->name, $this->category); if($insert && sizeof($insert) > 0) { foreach($insert as $args) { $this->insert_row($args); } } } function print_log($log, $previous_log = false, $wrap = false, $context = 'profile') { if(function_exists($this->print_cb)) { # If logs are closer together than what we're set to ignore, check similarities between logs if($previous_log && (date('U', strtotime($previous_log->time) - date('U', strtotime($log->time)))) < CK_LOG_TIME_IGNORE) { # If key parameters are the same, assume user corrected his actions; no need to show twice # Note: users can override each check (besides the logger), as long as they pass the 'ignore_cmp' argument when registering # the logger (as a string, or array of strings, which correspond(s) to the class property to be ignored) if( $previous_log->logger == $log->logger && ($previous_log->object_type == $log->object_type || in_array('object_type', $this->ignore_cmp)) && ($previous_log->object_id == $log->object_id || in_array('object_id', $this->ignore_cmp)) && ($previous_log->log_code == $log->log_code || in_array('log_code', $this->ignore_cmp)) && ($previous_log->user_id == $log->user_id || in_array('user_id', $this->ignore_cmp)) ) { return $log; } } # Append category in case plugins wish to check it $log->category = $this->category; # Allow plugins to establish their own rules for displaying a certain log: returning false on a filter will abort printing if(!apply_filters('cookspin_pre_print_log', true, $log, $context)) { return $log; } $current_user = wp_get_current_user(); $df = get_option('date_format'); $tf = get_option('time_format'); $wrap = esc_attr(apply_filters('cookspin_print_log_wrap', $wrap, $context)); $date = date_i18n($df, strtotime($log->time)); $previous_date = $previous_log ? date_i18n($df, strtotime($previous_log->time)) : false; if($previous_date != $date) { if($date == date_i18n($df, strtotime('today'))) { $date = __('Today', 'ck_activity'); } echo($wrap ? '<' . $wrap . ' class="activity_date_header' . ($previous_date == false ? ' first' : '') . '">' . $date . '' : $date . ' '); } $classes = array( 'log_item', 'log_category_' . esc_attr($this->category), 'logger_' . esc_attr($log->logger), 'log_type_' . esc_attr($log->object_type), 'log_code_' . esc_attr($log->log_code) ); if($current_user->ID == $log->user_id) { $classes[] = 'log_item_self'; } # Log item classes echo($wrap ? '<' . $wrap . ' id="log_item_' . esc_attr($log->log_id) . '" class="' . esc_attr(implode(' ', apply_filters('cookspin_log_item_classes', $classes))) . '">' : ''); # Log time echo(($wrap ? '' : '') . date_i18n($tf, strtotime($log->time)) . ($wrap ? '' : ' ')); # Log icon echo($wrap ? '' : ''); $user = get_userdata($log->user_id); if($user) { $user_display = is_super_admin() && $GLOBALS['blog_id'] == '1' ? '' . $user->display_name . '' : $user->display_name; } else { $user_display = sprintf(__('User #%s', 'ck_activity'), $log->user_id); } echo($user_display . ' '); $log->logmeta = maybe_unserialize($log->logmeta); echo(apply_filters("cookspin_print_log_{$log->logger}", call_user_func_array($this->print_cb, array($log, $user)), $log, $user)); echo($wrap ? '' : ''); return $log; } } } function cookspin_register_logger($name, $category, $args) { $logger = new CK_Logger($name, $category, $args); global $cookspin_loggers; return isset($cookspin_loggers[$name]); } function cookspin_deregister_logger($name) { global $cookspin_loggers; if(isset($cookspin_loggers[$name])) { return $cookspin_loggers[$name]->deregister(); } return false; } function cookspin_get_log_categories() { global $cookspin_loggers; $categories = array(); foreach($cookspin_loggers as $logger => $obj) { $categories[$obj->category][] = $logger; } if(!is_super_admin()) { unset($categories['blogs']); } if(!current_user_can('edit_theme_options')) { unset($categories['preferences']); unset($categories['appearance']); unset($categories['plugins']); unset($categories['tools']); } if(!current_user_can('upload_files')) { unset($categories['media']); } return apply_filters('cookspin_get_log_categories', $categories); } function cookspin_get_loggers($category = false) { $categories = cookspin_get_log_categories(); if($category) { return isset($categories[$category]) ? $categories[$category] : array(); } foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($categories)) as $value) { $loggers[] = $value; } return $loggers; } function cookspin_get_log_categories_labels() { $default_labels = array( 'blogs' => __('Sites', 'ck_activity'), 'posts' => __('Posts', 'ck_activity'), 'media' => __('Media', 'ck_activity'), 'users' => __('Users', 'ck_activity'), 'preferences' => __('Settings', 'ck_activity'), 'comments' => __('Comments', 'ck_activity'), 'appearance' => __('Appearance', 'ck_activity'), 'plugins' => __('Plugins', 'ck_activity'), 'tools' => __('Tools', 'ck_activity') ); return apply_filters('cookspin_get_log_categories_labels', $default_labels); } function cookspin_get_log_category_label($category) { $categories = cookspin_get_log_categories_labels(); if(isset($categories[$category])) { return $categories[$category]; } return false; } function cookspin_fetch_log($id, $prefix = false) { global $wpdb; if(is_numeric($id)) { if(!$prefix) { $prefix = $wpdb->prefix; } $table_name = $wpdb->escape($prefix . 'activity_log'); return $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE log_id = %d", $id)); } } # Output activity log function cookspin_log_output_activity($args) { $current_user = wp_get_current_user(); $limit = get_user_option('cookspin_activity_log_limit', $current_user->ID); if(!$limit) { $limit = CK_LOG_DEFAULT_LIMIT; } $defaults = array( 'context' => 'profile', 'wrap' => false, 'previous_log' => false, 'limit' => $limit, 'filter' => false ); $args = wp_parse_args($args, $defaults); extract($args, EXTR_SKIP); global $wpdb; $prefix = $wpdb->prefix; if(is_super_admin() && isset($_REQUEST['activity_blog_id']) && is_numeric($_REQUEST['activity_blog_id'])) { $prefix = 'wp_' . $wpdb->escape($_REQUEST['activity_blog_id']) . '_'; } if($previous_log) { $previous_log = cookspin_fetch_log($previous_log, $prefix); } $query = "SELECT * FROM " . $prefix . 'activity_log logs WHERE 1=1'; $query .= apply_filters('cookspin_pre_log_query', ''); if($context == 'profile' || !current_user_can('delete_others_posts')) { # Check for Editor privileges $query .= " AND logs.user_id = $current_user->ID"; } if($previous_log) { $query .= " AND logs.log_id < $previous_log->log_id"; } if((isset($_REQUEST['activity_log_filter'])) || $filter) { $categories = cookspin_get_log_categories(); $filter = $filter ? $filter : $_REQUEST['activity_log_filter']; $filters = call_user_func_array('array_merge', array_map(create_function('$a', 'return cookspin_get_loggers($a);'), explode(',', $filter))); $query .= ' AND ('; foreach($filters as $f) { $query .= "logs.logger = %s"; $params[] = $f; if($f != end($filters)) { $query .= ' OR '; } } $query .= ')'; } $params[] = $limit; $logs = $wpdb->get_results($wpdb->prepare($query . " ORDER BY logs.log_id DESC LIMIT %d", $params)); if(sizeof($logs) > 0) { global $cookspin_loggers; foreach($logs as $log) { if(is_object($cookspin_loggers[$log->logger])) { $previous_log = $cookspin_loggers[$log->logger]->print_log($log, $previous_log, $wrap, $context); } } } elseif(!$previous_log) { echo('' . __('No activity to display', 'ck_activity') . ''); } if($wrap && $limit && sizeof($logs) == $limit) { ?>
' . $title . '' : ''); ?>
ID); if(isset($_POST['activity_log_limit']) && $_POST['activity_log_limit'] != $limit && $_POST['activity_log_limit'] > 0) { $limit = $_POST['activity_log_limit']; if(!$current_user || !intval($limit)) { return; } # Remember the user's choice update_user_option($current_user->ID, 'cookspin_activity_log_limit', $limit, true); } elseif(isset($_POST['activity_log_limit'])) { $limit = CK_LOG_DEFAULT_LIMIT; delete_user_option($current_user->ID, 'cookspin_activity_log_limit', true); } elseif(!$limit) { $limit = CK_LOG_DEFAULT_LIMIT; } $output = '
' . __('Filter activity logs', 'ck_activity') . '
'; foreach(cookspin_get_log_categories() as $category => $loggers) { $checked = in_array($category, $active_filters) || sizeof($active_filters) == 0 ? 'checked="checked"' : ''; $output .= '
'; } if(is_network_admin()) { $output .= '
' . __('Review activity of another network site:', 'ck_activity') . '
'; } $output .= '
' . __('Logs to fetch at a time', 'ck_activity') . '
'; echo($output); } add_action('init', 'cookspin_activity_log_persist_filters'); function cookspin_activity_log_persist_filters() { if(isset($_POST['activity_log_widget_control'])) { add_filter('wp_redirect', 'cookspin_append_activity_log_filters_to_url'); } } function cookspin_append_activity_log_filters_to_url($url) { # Clean up URL $url = remove_query_arg(array('activity_log_filter'), $url); $url = add_query_arg(array('activity_log' => ''), $url); if(isset($_POST['activity_blog_id'])) { $url = add_query_arg(array('activity_blog_id' => $_POST['activity_blog_id']), $url); } if(isset($_POST['activity_log_filter'])) { $url = add_query_arg(array('activity_log_filter' => implode(',', $_REQUEST['activity_log_filter'])), $url); } return $url; } # Dashboard widget add_action('wp_dashboard_setup', 'cookspin_dashboard_activity_widget', 1); function cookspin_dashboard_activity_widget() { # Check for editor (or above) role if(current_user_can('delete_others_posts')) { wp_add_dashboard_widget('cookspin_blog_activity', __('Site activity', 'ck_activity'), 'cookspin_blog_activity_widget', 'cookspin_blog_activity_widget_control'); } } # Network admin dashboard widget add_action('wp_network_dashboard_setup', 'cookspin_network_admin_dashboard_activity_widget'); function cookspin_network_admin_dashboard_activity_widget() { $other = false; if(isset($_REQUEST['activity_blog_id']) && is_numeric($_REQUEST['activity_blog_id'])) { $blog = get_blog_details($_REQUEST['activity_blog_id']); if($blog) { $other = true; } else { add_action('network_admin_notices', 'cookspin_activity_invalid_blog'); } } $name = $other ? sprintf(__('Site activity: %1$s (%2$s)', 'ck_activity'), $blog->blogname, $blog->blog_id) : __('Network activity', 'ck_activity'); wp_add_dashboard_widget('cookspin_network_activity', $name, 'cookspin_blog_activity_widget', 'cookspin_blog_activity_widget_control'); } function cookspin_activity_invalid_blog() { echo('

' . __('The submitted ID does not correspond to a blog in this network.', 'ck_activity') . '

'); } add_action('init', 'cookspin_activity_log_loader'); function cookspin_activity_log_loader() { wp_register_style('ck_activity', plugins_url('css/activity.css' , __FILE__), array(), CK_LOG_VERSION); wp_register_script('ck_activity', plugins_url('js/activity.min.js' , __FILE__), array('jquery'), CK_LOG_VERSION, true); global $pagenow; $activity_pages = apply_filters('cookspin_activity_log_pages', array('index.php')); if(is_admin() && in_array($pagenow, $activity_pages)) { wp_enqueue_style('ck_activity'); wp_enqueue_script('ck_activity'); wp_localize_script('ck_activity', 'ck_activity', array( 'ajaxURL' => apply_filters('cookspin_acitvity_log_ajax_url', admin_url('admin-ajax.php')), 'context' => apply_filters('cookspin_acitvity_log_context', 'blog') )); } } add_action('plugins_loaded', 'cookspin_activity_load_textdomain'); function cookspin_activity_load_textdomain() { load_plugin_textdomain('ck_activity', false, dirname(plugin_basename(__FILE__)) . '/languages/'); } # Function to load more entries to the Activity log # Note: doesn't need to check for permissions because they are checked by the cookspin_log_output_activity() function; if a logged user # doesn't have proper permissions he will get his profile activity only, regardless of request; add_action('wp_ajax_cookspin_load_more_logs', 'cookspin_load_more_logs'); function cookspin_load_more_logs() { # Define log constants cookspin_log_setup(); if(check_ajax_referer('more_logs_nonce', 'nonce')) { $args = array( 'context' => $_POST['context'], 'previous_log' => $_POST['last_log'], 'wrap' => $_POST['wrap'] ); if(isset($_POST['filter']) && $_POST['filter'] != null) { $args['filter'] = $_POST['filter']; } cookspin_log_output_activity($args); } exit; } ?>