PluginProbe
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity / 3.3.10
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity v3.3.10
3.3.10 3.3.9 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 All 68 releases
logtivity / logtivity.php

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

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