*/ public static function abilities(): array { return [ new Ability( 'sarcio/activity-log', __('Look at what happened on the site', 'sarcio'), Ability::READ, 'Recent activity: from Simple History or Stream when installed (who did what, when), else the pages, posts and products published or updated. days (7 by default, up to 90) and limit (50 by default).', ['properties' => ['days' => ['maximum' => 90, 'minimum' => 1, 'type' => 'integer'], 'limit' => ['maximum' => 200, 'minimum' => 1, 'type' => 'integer']], 'required' => [], 'type' => 'object'], static fn (array $args): bool => current_user_can('edit_posts'), static fn (array $args): array => self::recent((int) ($args['days'] ?? 7), (int) ($args['limit'] ?? 50)), ), ]; } public static function source(): ?string { global $wpdb; if (class_exists('\Simple_History\Simple_History') || defined('SIMPLE_HISTORY_VERSION')) { return 'simple-history'; } if (class_exists('\WP_Stream\Plugin') || defined('WP_STREAM_VERSION')) { return 'stream'; } return null; } /** @return array{entries:array,source:string} */ public static function recent(int $days, int $limit): array { global $wpdb; $since = gmdate('Y-m-d H:i:s', time() - $days * 86400); $limit = max(1, min(200, $limit)); $source = self::source(); $entries = []; if ($source === 'simple-history') { $table = $wpdb->prefix . 'simple_history'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- the log plugin's own table, read for a listing. $rows = $wpdb->get_results($wpdb->prepare("SELECT date, logger, level, message, initiator FROM {$table} WHERE date >= %s ORDER BY date DESC LIMIT %d", $since, $limit), ARRAY_A); foreach (is_array($rows) ? $rows : [] as $row) { $entries[] = ['at' => (string) $row['date'], 'by' => (string) ($row['initiator'] ?? ''), 'text' => mb_substr(wp_strip_all_tags((string) $row['message']), 0, 300), 'type' => (string) $row['logger']]; } } elseif ($source === 'stream') { $table = $wpdb->prefix . 'stream'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- the log plugin's own table, read for a listing. $rows = $wpdb->get_results($wpdb->prepare("SELECT created, summary, connector, context, action, user_id FROM {$table} WHERE created >= %s ORDER BY created DESC LIMIT %d", $since, $limit), ARRAY_A); foreach (is_array($rows) ? $rows : [] as $row) { $user = (int) ($row['user_id'] ?? 0) > 0 ? get_userdata((int) $row['user_id']) : null; $entries[] = ['at' => (string) $row['created'], 'by' => is_object($user) && isset($user->display_name) ? (string) $user->display_name : '', 'text' => mb_substr(wp_strip_all_tags((string) $row['summary']), 0, 300), 'type' => (string) $row['connector'] . '/' . (string) $row['action']]; } } else { $source = 'wordpress'; $posts = get_posts(['date_query' => [['after' => $since, 'column' => 'post_modified_gmt']], 'numberposts' => $limit, 'orderby' => 'modified', 'post_status' => ['publish', 'future', 'draft'], 'post_type' => ['page', 'post', 'product']]); foreach (is_array($posts) ? $posts : [] as $post) { $author = get_userdata((int) ($post->post_author ?? 0)); $entries[] = [ 'at' => (string) ($post->post_modified_gmt ?? $post->post_modified ?? ''), 'by' => is_object($author) && isset($author->display_name) ? (string) $author->display_name : '', 'text' => sprintf( /* translators: 1: the title, 2: the status. */ __('ā€œ%1$sā€ (%2$s)', 'sarcio'), (string) $post->post_title, (string) $post->post_status, ), 'type' => (string) $post->post_type . ((string) ($post->post_date ?? '') === (string) ($post->post_modified ?? '') ? '/published' : '/updated'), ]; } } return ['entries' => $entries, 'source' => $source]; } /** Counts by type for the report, over a month. */ public static function counts(string $fromUtc, string $toUtc): array { $days = max(1, (int) ceil(((int) strtotime($toUtc . ' UTC') - (int) strtotime($fromUtc . ' UTC')) / 86400)); $recent = self::recent($days, 200); $counts = []; foreach ($recent['entries'] as $entry) { if ($entry['at'] < $fromUtc || $entry['at'] >= $toUtc) { continue; } $counts[$entry['type']] = ($counts[$entry['type']] ?? 0) + 1; } arsort($counts); return ['byType' => array_slice($counts, 0, 8, true), 'source' => $recent['source']]; } }