PluginProbe
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity / trunk
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity vtrunk
3.3.8 trunk 1.0 1.1.0 1.10.0 1.11.0 1.11.1 1.12.0 1.13.0 1.14.0 1.15.0 1.16.0 1.17.0 1.17.1 1.18.0 1.19.0 1.2.0 1.20.0 1.20.1 1.3.0 1.3.1 1.4.0 1.5.0 1.6.0 1.6.1 All 66 releases
logtivity / logtivity.php

logtivity.php in Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity trunk, at logtivity.php

510 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plugin Name: Logtivity
5 * Plugin URI: https://logtivity.io
6 * Description: Record activity logs and errors logs across all your WordPress sites.
7 * Author: Logtivity
8 * Version: 3.3.8
9 * Text Domain: logtivity
10 * Requires at least: 4.7
11 * Requires PHP: 7.4
12 */
13
14 /**
15 * @package Logtivity
16 * @contact logtivity.io, hello@logtivity.io
17 * @copyright 2024-2026 Logtivity. All rights reserved
18 * @license https://www.gnu.org/licenses/gpl.html GNU/GPL
19 *
20 * This file is part of Logtivity.
21 *
22 * Logtivity is free software: you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation, either version 2 of the License, or
25 * (at your option) any later version.
26 *
27 * Logtivity is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with Logtivity. If not, see <https://www.gnu.org/licenses/>.
34 */
35
36 // phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols
37 // phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
38
39 if (!defined('LOGTIVITY_VERSION')) {
40 define('LOGTIVITY_VERSION', '3.3.8');
41 }
42
43 class Logtivity
44 {
45 public const ACCESS_LOGS = 'view_logs';
46 public const ACCESS_SETTINGS = 'view_log_settings';
47
48 /**
49 * @var string
50 */
51 protected string $version = LOGTIVITY_VERSION;
52
53 /**
54 * Integrations with other plugins
55 *
56 * @var array[]
57 */
58 protected array $integrations = [
59 WP_DLM::class => 'Download_Monitor',
60 MeprCtrlFactory::class => 'Memberpress',
61 Easy_Digital_Downloads::class => 'Easy_Digital_Downloads',
62 EDD_Software_Licensing::class => 'Easy_Digital_Downloads/Licensing',
63 EDD_Recurring::class => 'Easy_Digital_Downloads/Recurring',
64 FrmHooksController::class => 'Formidable',
65 PMXI_Plugin::class => 'WP_All_Import',
66 \Code_Snippets\Plugin::class => 'Code_Snippets',
67 ];
68
69 /**
70 * @var bool
71 */
72 protected bool $coreLoaded = false;
73
74 public function __construct()
75 {
76 $this->loadCore();
77 $this->activateLoggers();
78
79 add_action('upgrader_process_complete', [$this, 'upgradeProcessComplete'], 10, 2);
80 add_action('activated_plugin', [$this, 'setLogtivityToLoadFirst']);
81 add_action('admin_notices', [$this, 'welcomeMessage']);
82 add_action('admin_notices', [$this, 'checkForSiteUrlChange']);
83 add_action('admin_enqueue_scripts', [$this, 'loadScripts']);
84 add_action('admin_init', [$this, 'redirectOnActivate']);
85
86 add_filter('plugin_action_links_' . plugin_basename(__FILE__), [$this, 'addSettingsLinkFromPluginsPage']);
87
88 register_activation_hook(__FILE__, [$this, 'activated']);
89 }
90
91 /**
92 * @return self
93 */
94 public static function init(): self
95 {
96 return new static();
97 }
98
99 /**
100 * @return void
101 */
102 protected function loadCore(): void
103 {
104 if ($this->coreLoaded == false) {
105 $requires = array_merge(
106 $this->getFiles(__DIR__ . '/functions'),
107 $this->getFiles(__DIR__ . '/Base')
108 );
109 foreach ($requires as $file) {
110 require_once $file;
111 }
112
113 $coreFiles = $this->getFiles(__DIR__ . '/Core');
114 $initClasses = [];
115 foreach ($coreFiles as $file) {
116 require_once $file;
117 $className = basename($file, '.php');
118 if (is_callable([$className, 'init'])) {
119 $initClasses[] = $className;
120 }
121 }
122 foreach ($initClasses as $class) {
123 call_user_func([$class, 'init']);
124 }
125
126 $this->coreLoaded = true;
127 }
128 }
129
130 /**
131 * @return void
132 */
133 protected function activateLoggers(): void
134 {
135 add_action('plugins_loaded', function () {
136 $this->loadCore();
137 $this->updateCheck();
138
139 if ($this->defaultLoggingDisabled() == false) {
140 $this->loadCoreLoggers();
141 $this->loadIntegrations();
142 }
143 });
144
145 }
146
147 /**
148 * @param string $path
149 * @param bool $recurse
150 * @param string $extension
151 *
152 * @return array
153 */
154 protected function getFiles(string $path, bool $recurse = true, string $extension = 'php'): array
155 {
156 if (is_dir($path)) {
157 $files = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
158 } elseif (is_file($path)) {
159 return [realpath($path)];
160 } else {
161 return [];
162 }
163
164 $list = [];
165 foreach ($files as $file) {
166 if ($file->isFile()) {
167 if ($file->getExtension() == $extension) {
168 $list[] = $file->getRealPath();
169 }
170
171 } elseif ($recurse) {
172 $list = array_merge($list, $this->getFiles($file->getRealPath(), $recurse, $extension));
173 }
174 }
175
176 return $list;
177 }
178
179 /**
180 * Review updates based on version
181 *
182 * @return void
183 */
184 public function updateCheck(): void
185 {
186 $currentVersion = get_option('logtivity_version');
187
188 if (version_compare($currentVersion, '3.1.6', '<=')) {
189 $this->checkCapabilities();
190 }
191
192 if ($currentVersion && version_compare($currentVersion, '3.1.7', '<=')) {
193 // Default for updating sites should be no behavior change
194 update_option('logtivity_app_verify_url', 0);
195 }
196
197 update_option('logtivity_version', $this->version);
198 }
199
200 /**
201 * Custom capabilities added prior to v3.1.7
202 *
203 * @return void
204 */
205 protected function checkCapabilities(): void
206 {
207 $capabilities = array_filter(
208 array_keys(logtivity_get_capabilities()),
209 function (string $capability): bool {
210 return in_array($capability, [Logtivity::ACCESS_LOGS, Logtivity::ACCESS_SETTINGS]);
211 }
212 );
213
214 if ($administrator = get_role('administrator')) {
215 if (array_search(Logtivity::ACCESS_LOGS, $capabilities) === false) {
216 $administrator->add_cap(Logtivity::ACCESS_LOGS);
217 }
218
219 if (array_search(Logtivity::ACCESS_SETTINGS, $capabilities) === false) {
220 $administrator->add_cap(Logtivity::ACCESS_SETTINGS);
221 }
222 }
223 }
224
225 /**
226 * Is the default Event logging from within the plugin enabled
227 *
228 * @return bool
229 */
230 protected function defaultLoggingDisabled(): bool
231 {
232 return (bool)(new Logtivity_Options())->getOption('logtivity_disable_default_logging');
233 }
234
235 /**
236 * @return void
237 */
238 protected function loadCoreLoggers(): void
239 {
240 $coreLoggers = $this->getFiles(__DIR__ . '/Loggers/Core');
241 foreach ($coreLoggers as $logger) {
242 require_once $logger;
243 }
244 }
245
246 /**
247 * @return void
248 */
249 protected function loadIntegrations(): void
250 {
251 $loggerFolder = __DIR__ . '/Loggers/';
252
253 foreach ($this->integrations as $key => $folder) {
254 $integrationFolder = $loggerFolder . $folder;
255 if (class_exists($key)) {
256 if (is_dir($integrationFolder . '/Base')) {
257 // Load any base classes
258 $baseFiles = $this->getFiles($integrationFolder . '/Base');
259 foreach ($baseFiles as $file) {
260 require_once $file;
261 }
262 }
263
264 $files = $this->getFiles($integrationFolder, false);
265 foreach ($files as $file) {
266 require_once $file;
267 }
268 }
269 }
270 }
271
272 /**
273 * Main entry for registering a site using the team API Key
274 *
275 * @param ?string $teamApi
276 * @param ?string $teamName
277 * @param ?string $siteName
278 * @param ?string $url
279 *
280 * @return null|Logtivity_Response|WP_Error
281 */
282 public static function registerSite(
283 ?string $teamApi,
284 ?string $teamName = null,
285 ?string $siteName = null,
286 ?string $url = null
287 ) {
288 $logtivityOptions = new Logtivity_Options();
289
290 if ($logtivityOptions->getApiKey()) {
291 $response = new WP_Error(
292 'logtivity_register_site_error',
293 __('You have already entered an API Key for this site.', 'logtivity')
294 );
295
296 } elseif ($teamApi) {
297 $request = [
298 'method' => 'POST',
299 'timeout' => 6,
300 'blocking' => true,
301 'body' => [
302 'team_name' => $teamName,
303 'name' => $siteName ?: get_bloginfo('name'),
304 'url' => $url ?: home_url(),
305 ],
306 'cookies' => [],
307 ];
308
309 $response = new Logtivity_Response($teamApi, '/sites', $request);
310 if ($response->code == 200 && $response->error == false) {
311 $apikey = $response->body['api_key'] ?? null;
312 $teamName = $response->body['team_name'] ?? '*unknown*';
313 $created = $response->body['created_at'] ?? null;
314 $isNew = $response->body['is_new'] ?? null;
315
316 if ($apikey) {
317 $logtivityOptions->update(['logtivity_site_api_key' => $apikey]);
318
319 if ($isNew) {
320 $response->message = sprintf(
321 'This site has been created on <a href="%s" target="_blank">Logtivity</a> for team \'%s\'. Logging is now enabled.',
322 logtivity_get_app_url(),
323 $teamName
324 );
325
326 } else {
327 if ($created) {
328 $createdTimestamp = strtotime($created);
329 $creationText = sprintf(
330 'It was created on %s at %s ',
331 wp_date(get_option('date_format'), $createdTimestamp),
332 wp_date(get_option('time_format'), $createdTimestamp)
333 );
334 }
335 $response->message = sprintf(
336 'This site was found on <a href="%s" target="_blank">Logtivity</a>. %sfor the team \'%s\'. Logging is now enabled.',
337 logtivity_get_app_url(),
338 $creationText ?? '',
339 $teamName
340 );
341 }
342 }
343 }
344
345 } else {
346 $response = new WP_Error('logtivity_missing_data', 'Team API Key is required.');
347 }
348
349 return $response;
350 }
351
352 /**
353 * @param ?string $action
354 * @param ?array $meta
355 * @param ?int $userId
356 *
357 * @return Logtivity_Logger
358 */
359 public static function log(?string $action = null, ?array $meta = null, ?int $userId = null): Logtivity_Logger
360 {
361 return Logtivity_Logger::log($action, $meta, $userId);
362 }
363
364
365 /**
366 * @param WP_Upgrader $upgraderObject
367 * @param array $options
368 *
369 * @return void
370 */
371 public function upgradeProcessComplete(WP_Upgrader $upgraderObject, array $options): void
372 {
373 $type = $options['type'] ?? null;
374 $action = $options['action'] ?? null;
375
376 if ($type == 'plugin' && $action == 'update') {
377 $this->setLogtivityToLoadFirst();
378 }
379 }
380
381 /**
382 * @return void
383 */
384 public function setLogtivityToLoadFirst(): void
385 {
386 $path = str_replace(WP_PLUGIN_DIR . '/', '', __FILE__);
387
388 if ($plugins = get_option('active_plugins')) {
389 if ($key = array_search($path, $plugins)) {
390 array_splice($plugins, $key, 1);
391 array_unshift($plugins, $path);
392 update_option('active_plugins', $plugins);
393 }
394 }
395 }
396
397 /**
398 * @param array $links
399 *
400 * @return string[]
401 */
402 public function addSettingsLinkFromPluginsPage(array $links): array
403 {
404 if (apply_filters('logtivity_hide_settings_page', false)) {
405 return $links;
406 }
407
408 return array_merge(
409 [
410 sprintf('<a href="%s">Settings</a>', admin_url('admin.php?page=logtivity-settings')),
411 ],
412 $links
413 );
414 }
415
416 /**
417 * @return void
418 */
419 public function activated(): void
420 {
421 add_option('logtivity_activate', true);
422
423 $this->checkCapabilities();
424
425 if (apply_filters('logtivity_hide_settings_page', false)) {
426 return;
427 }
428
429 set_transient('logtivity-welcome-notice', true, 5);
430 }
431
432 /**
433 * Redirect to Settings page
434 *
435 * @return void
436 * @since 3.1.11
437 *
438 */
439 public function redirectOnActivate()
440 {
441 if (get_option('logtivity_activate')) {
442 delete_option('logtivity_activate');
443
444 if (!isset($_GET['activate-multi'])) {
445 $page = (new Logtivity_Options())->isWhiteLabelMode() ? 'lgtvy-logs' : 'logtivity';
446 wp_redirect(admin_url('admin.php?page=' . $page));
447 exit;
448 }
449 }
450 }
451
452 /**
453 * @return void
454 */
455 public function welcomeMessage(): void
456 {
457 if (get_transient('logtivity-welcome-notice')) {
458 echo logtivity_view('activation');
459
460 delete_transient('logtivity-welcome-notice');
461 }
462 }
463
464 /**
465 * @return void
466 */
467 public function checkForSiteUrlChange(): void
468 {
469 if (
470 current_user_can(static::ACCESS_SETTINGS)
471 && logtivity_has_site_url_changed()
472 && (new Logtivity_Options())->isWhiteLabelMode() == false
473 && !get_transient('dismissed-logtivity-site-url-has-changed-notice')
474 ) {
475 echo logtivity_view('site-url-changed-notice');
476 }
477 }
478
479 /**
480 * @return void
481 */
482 public function loadScripts(): void
483 {
484 wp_enqueue_style(
485 'logtivity_google_font_admin_css',
486 'https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,500',
487 false,
488 $this->version
489 );
490 wp_enqueue_style(
491 'logtivity_admin_css',
492 plugin_dir_url(__FILE__) . 'assets/admin.css',
493 ['logtivity_google_font_admin_css'],
494 $this->version
495 );
496 wp_enqueue_script(
497 'logtivity_admin_js',
498 plugin_dir_url(__FILE__) . 'assets/app.js',
499 false,
500 $this->version
501 );
502 wp_localize_script('logtivity_admin_js', 'logtivityAdminA11y', [
503 'loadingLogs' => __('Loading logs...', 'logtivity'),
504 'loadLogsError' => __('Unable to load logs. Please try again.', 'logtivity'),
505 ]);
506 }
507 }
508
509 Logtivity::init();
510