PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / trunk
Defender Security – Malware Scanner, Login Security & Firewall vtrunk
6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 3.1.1 All 140 releases
defender-security / src / controller / class-activity-log.php

class-activity-log.php in Defender Security – Malware Scanner, Login Security & Firewall trunk, at src/controller/class-activity-log.php

286 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The activity logs class.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use Calotes\Component\Request;
11 use Calotes\Component\Response;
12 use WP_Defender\Controller;
13
14 /**
15 * Activites log.
16 *
17 * Class Activity_Log
18 */
19 class Activity_Log extends Controller {
20 /**
21 * Notification data key.
22 *
23 * @var string
24 */
25 private static $notification_data_key = 'wp_defender_notifications';
26
27 /**
28 * Maximum number of notifications.
29 *
30 * @var int
31 */
32 private static $max_notification = 50;
33
34 /**
35 * Registers routes.
36 */
37 public function __construct() {
38 $this->register_routes();
39 }
40
41 /**
42 * Remove settings.
43 */
44 public function remove_settings() {
45 delete_site_option( self::$notification_data_key );
46 }
47
48 /**
49 * Delete all the data & the cache.
50 */
51 public function remove_data() {
52 }
53
54 /**
55 * Get data for frontend.
56 *
57 * @return array
58 */
59 public function data_frontend(): array {
60 return array_merge(
61 array(
62 'notifications' => $this->get_notifications_for_ui(),
63 ),
64 $this->dump_routes_and_nonces()
65 );
66 }
67
68 /**
69 * Export to array.
70 *
71 * @return array
72 */
73 public function to_array(): array {
74 return array();
75 }
76
77 /**
78 * Import data.
79 *
80 * @param array $data The data to import.
81 */
82 public function import_data( $data ) {}
83
84 /**
85 * Export strings
86 *
87 * @return array
88 */
89 public function export_strings() {
90 return array();
91 }
92
93 /**
94 * Record a notification atomically.
95 *
96 * Uses a single JSON_ARRAY_APPEND database query so concurrent background
97 * processes never overwrite each other's entries.
98 *
99 * @param mixed $notification Notification data.
100 *
101 * @return bool True if the notification was successfully stored, false otherwise.
102 */
103 public function record_log( $notification ) {
104 $sanitized_notification = $this->sanitize_notification( $notification );
105 if ( array() === $sanitized_notification ) {
106 return false;
107 }
108
109 $notifications = $this->get_notifications();
110 $notifications[] = $sanitized_notification;
111
112 $result = update_site_option(
113 self::$notification_data_key,
114 $notifications
115 );
116
117 return false !== $result;
118 }
119
120 /**
121 * Add a notification from the UI.
122 *
123 * @param Request $request The request object containing filter parameters.
124 *
125 * @return Response
126 * @defender_route
127 */
128 public function add_notification( Request $request ): Response {
129 $data = $request->get_data(
130 array(
131 'notification' => array(
132 'type' => 'array',
133 'sanitize' => 'sanitize_text_field',
134 ),
135 )
136 );
137
138 if ( ! is_array( $data['notification'] ) ) {
139 return new Response(
140 false,
141 array(
142 'message' => esc_html__( 'Invalid notification data.', 'defender-security' ),
143 )
144 );
145 }
146
147 $success = $this->record_log( $data['notification'] );
148
149 if ( ! $success ) {
150 return new Response(
151 false,
152 array(
153 'message' => esc_html__( 'Failed to save notification.', 'defender-security' ),
154 )
155 );
156 }
157
158 return new Response(
159 true,
160 array(
161 'notifications' => $this->get_notifications_for_ui(),
162 )
163 );
164 }
165
166 /**
167 * Get the notifications.
168 *
169 * Reads directly from the database (bypasses object cache) to ensure
170 * writes from other background processes are visible.
171 *
172 * @return array
173 */
174 public function get_notifications(): array {
175 $notifications = get_site_option( self::$notification_data_key, array() );
176
177 return is_array( $notifications ) ? $notifications : array();
178 }
179
180 /**
181 * Sanitize a notification.
182 *
183 * @param mixed $notification Notification data.
184 *
185 * @return array{id: string, timestamp: string|int, type: string, content: string, url: string}
186 */
187 private function sanitize_notification( $notification ): array {
188 if ( ! isset( $notification['content'] ) || '' === $notification['content'] ) {
189 return array();
190 }
191 // Sanitize and ensure all expected fields exist.
192 $sanitized = array();
193 $sanitized['id'] = isset( $notification['id'] ) ? sanitize_text_field( $notification['id'] ) : uniqid( 'defender_notification_' );
194 $sanitized['timestamp'] = isset( $notification['timestamp'] ) ? sanitize_text_field( $notification['timestamp'] ) : microtime( true );
195 $sanitized['type'] = isset( $notification['type'] ) ? sanitize_text_field( $notification['type'] ) : 'info';
196 $sanitized['module'] = isset( $notification['module'] ) ? sanitize_text_field( $notification['module'] ) : '';
197 $sanitized['content'] = isset( $notification['content'] ) ? sanitize_text_field( $notification['content'] ) : '';
198 $sanitized['url'] = isset( $notification['url'] ) ? sanitize_text_field( $notification['url'] ) : '';
199
200 return $sanitized;
201 }
202
203 /**
204 * Sanitize a notification for display in the UI.
205 * Translates the notification content.
206 *
207 * @param array $notification Notification data.
208 *
209 * @return array Sanitized notification data.
210 */
211 private function sanitize_notification_for_ui( $notification ) {
212 $sanitized = $this->sanitize_notification( $notification );
213 if ( ! isset( $sanitized['content'] ) || '' === $sanitized['content'] ) {
214 return array();
215 }
216
217 return $sanitized;
218 }
219
220 /**
221 * Get notifications.
222 *
223 * @return array
224 */
225 private function get_notifications_for_ui(): array {
226 $notifications = $this->get_notifications();
227
228 if ( array() === $notifications ) {
229 $this->record_log(
230 array(
231 'module' => 'defender',
232 'content' => esc_html__( 'Welcome to Defender', 'defender-security' ),
233 )
234 );
235 $notifications = $this->get_notifications();
236 } else {
237 $notifications = $this->get_limited_notifications( $notifications );
238 }
239
240 return array_map( array( $this, 'sanitize_notification_for_ui' ), $notifications );
241 }
242
243 /**
244 * Fetch the latest notifications for the UI.
245 * Called after a long-running background process completes or dies on-page,
246 * so the frontend can replace optimistic entries with server-written ones.
247 *
248 * @return Response
249 * @defender_route
250 */
251 public function fetch_notifications(): Response {
252 return new Response(
253 true,
254 array(
255 'notifications' => $this->get_notifications_for_ui(),
256 )
257 );
258 }
259
260 /**
261 * Get the stored list to the maximum allowed size, keeping the most recent entries.
262 *
263 * @param array $notifications Notification data.
264 *
265 * @return array
266 */
267 private function get_limited_notifications( array $notifications ): array {
268 // Keep only valid notification entries (arrays with expected keys).
269 $notifications = array_values( array_filter( $notifications, 'is_array' ) );
270
271 if ( count( $notifications ) <= self::$max_notification ) {
272 return $notifications;
273 }
274
275 // Sort newest-first, then keep only the allowed maximum.
276 usort(
277 $notifications,
278 function ( $a, $b ) {
279 return ( $b['timestamp'] ?? 0 ) <=> ( $a['timestamp'] ?? 0 );
280 }
281 );
282
283 return array_slice( $notifications, 0, self::$max_notification );
284 }
285 }
286