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
← All changes | logtivity.php +486 -171 1.17.0 → 3.3.10 View file →
@@ -1,206 +1,521 @@
1 1 <?php
2 2
3 3 /**
4 - * Plugin Name: Logtivity
5 - * Plugin URI: https://logtivity.io
6 - * Description: Dedicated Event Monitoring for WordPress using Logtivity.io.
7 - * Version: 1.17.0
8 - * Author: Logtivity
9 - * Text Domain: logtivity
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
10 14 */
11 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 +
12 45 class Logtivity
13 46 {
14 - protected $version = '1.17.0';
47 + public const ACCESS_LOGS = 'view_logs';
48 + public const ACCESS_SETTINGS = 'view_log_settings';
15 49
16 - /**
17 - * List all classes here with their file paths. Keep class names the same as filenames.
18 - *
19 - * @var array
20 - */
21 - private $dependancies = [
22 - 'Helpers/Helpers',
23 - 'Helpers/Logtivity_Wp_User',
24 - 'Admin/Logtivity_Log_Index_Controller',
25 - 'Admin/Logtivity_Dismiss_Notice_Controller',
26 - 'Admin/Logtivity_Options',
27 - 'Admin/Logtivity_Admin',
28 - 'Services/Logtivity_Api',
29 - 'Services/Logtivity_Logger',
30 - 'Services/Logtivity_Register_Site',
31 - 'Helpers/Logtivity_Log_Global_Function',
32 - 'Logs/Logtivity_Abstract_Logger',
33 - 'Services/Logtivity_Check_For_Disabled_Individual_Logs',
34 - ];
50 + /**
51 + * @var string
52 + */
53 + protected string $version = LOGTIVITY_VERSION;
35 54
36 - /**
37 - *
38 - * Log classes
39 - *
40 - */
41 - private $logClasses = [
42 - 'Logs/Core/Logtivity_Post',
43 - 'Logs/Core/Logtivity_User',
44 - 'Logs/Core/Logtivity_Core',
45 - 'Logs/Core/Logtivity_Theme',
46 - 'Logs/Core/Logtivity_Plugin',
47 - 'Logs/Core/Logtivity_Comment',
48 - 'Logs/Core/Logtivity_Term',
49 - 'Logs/Core/Logtivity_Meta',
50 - ];
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 + ];
51 70
52 - /**
53 - * List all integration dependancies
54 - *
55 - * @var array
56 - */
57 - private $integrationDependancies = [
58 - 'WP_DLM' => [
59 - 'Logs/Download_Monitor/Logtivity_Download_Monitor',
60 - ],
61 - 'MeprCtrlFactory' => [
62 - 'Logs/Memberpress/Logtivity_Memberpress',
63 - ],
64 - 'Easy_Digital_Downloads' => [
65 - 'Logs/Easy_Digital_Downloads/Logtivity_Abstract_Easy_Digital_Downloads',
66 - 'Logs/Easy_Digital_Downloads/Logtivity_Easy_Digital_Downloads'
67 - ],
68 - 'EDD_Software_Licensing' => [
69 - 'Logs/Easy_Digital_Downloads/Logtivity_Easy_Digital_Downloads_Software_Licensing'
70 - ],
71 - 'EDD_Recurring' => [
72 - 'Logs/Easy_Digital_Downloads/Logtivity_Easy_Digital_Downloads_Recurring'
73 - ],
74 - 'FrmHooksController' => [
75 - 'Logs/Formidable/Logtivity_FrmEntryFormatter',
76 - 'Logs/Formidable/Logtivity_Formidable'
77 - ],
78 - 'PMXI_Plugin' => [
79 - 'Logs/WP_All_Import/Logtivity_WP_All_Import',
80 - ],
81 - ];
71 + /**
72 + * @var bool
73 + */
74 + protected bool $coreLoaded = false;
82 75
83 - public function __construct()
84 - {
85 - $this->loadDependancies();
76 + public function __construct()
77 + {
78 + $this->loadCore();
79 + $this->activateLoggers();
86 80
87 - add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), [$this, 'addSettingsLinkFromPluginsPage'] );
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']);
88 87
89 - register_activation_hook( __FILE__, [$this, 'activated']);
88 + add_filter('plugin_action_links_' . plugin_basename(__FILE__), [$this, 'addSettingsLinkFromPluginsPage']);
90 89
91 - add_action( 'admin_notices', [$this, 'welcomeMessage']);
92 -
93 - add_action( 'admin_notices', [$this, 'checkForSiteUrlChange']);
90 + register_activation_hook(__FILE__, [$this, 'activated']);
91 + }
94 92
95 - add_action('admin_enqueue_scripts', [$this, 'loadScripts']);
96 - }
93 + /**
94 + * @return self
95 + */
96 + public static function init(): self
97 + {
98 + return new static();
99 + }
97 100
98 - public static function log($action = null, $meta = null, $user_id = null)
99 - {
100 - return Logtivity_Logger::log($action, $meta, $user_id);
101 - }
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 + }
102 114
103 - public function loadDependancies()
104 - {
105 - foreach ($this->dependancies as $filePath)
106 - {
107 - $this->loadFile($filePath);
108 - }
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 + }
109 127
110 - $this->maybeLoadLogClasses();
128 + $this->coreLoaded = true;
129 + }
130 + }
111 131
112 - $this->loadIntegrationDependancies();
113 - }
132 + /**
133 + * @return void
134 + */
135 + protected function activateLoggers(): void
136 + {
137 + add_action('plugins_loaded', function () {
138 + $this->loadCore();
139 + $this->updateCheck();
114 140
115 - public function maybeLoadLogClasses()
116 - {
117 - if ($this->defaultLoggingDisabled()) {
118 - return;
119 - }
141 + if ($this->defaultLoggingDisabled() == false) {
142 + $this->loadCoreLoggers();
143 + $this->loadIntegrations();
144 + }
145 + });
120 146
121 - foreach ($this->logClasses as $filePath)
122 - {
123 - $this->loadFile($filePath);
124 - }
125 - }
147 + }
126 148
127 - /**
128 - * Is the default Event logging from within the plugin enabled
129 - *
130 - * @return bool
131 - */
132 - public function defaultLoggingDisabled()
133 - {
134 - return (new Logtivity_Options)->getOption('logtivity_disable_default_logging');
135 - }
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 + }
136 165
137 - public function loadIntegrationDependancies()
138 - {
139 - add_action('plugins_loaded', function() {
140 - foreach ($this->integrationDependancies as $key => $value)
141 - {
142 - if (class_exists($key)) {
143 - foreach ($value as $filePath)
144 - {
145 - $this->loadFile($filePath);
146 - }
147 - }
148 - }
149 - });
150 - }
166 + $list = [];
167 + foreach ($files as $file) {
168 + if ($file->isFile()) {
169 + if ($file->getExtension() == $extension) {
170 + $list[] = $file->getRealPath();
171 + }
151 172
152 - public function loadFile($filePath)
153 - {
154 - require_once plugin_dir_path( __FILE__ ) . $filePath .'.php';
155 - }
173 + } elseif ($recurse) {
174 + $list = array_merge($list, $this->getFiles($file->getRealPath(), $recurse, $extension));
175 + }
176 + }
156 177
157 - public function addSettingsLinkFromPluginsPage($links)
158 - {
159 - if (apply_filters('logtivity_hide_settings_page', false)) {
160 - return $links;
161 - }
178 + return $list;
179 + }
162 180
163 - $settings_links = array(
164 - '<a href="' . admin_url( 'admin.php?page=logtivity-settings' ) . '">Settings</a>',
165 - );
166 -
167 - return array_merge($settings_links, $links);
168 - }
181 + /**
182 + * Review updates based on version
183 + *
184 + * @return void
185 + */
186 + public function updateCheck(): void
187 + {
188 + $currentVersion = get_option('logtivity_version');
169 189
170 - public function activated()
171 - {
172 - if (apply_filters('logtivity_hide_settings_page', false)) {
173 - return;
174 - }
190 + if (version_compare($currentVersion, '3.1.6', '<=')) {
191 + $this->checkCapabilities();
192 + }
175 193
176 - set_transient( 'logtivity-welcome-notice', true, 5 );
177 - }
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 + }
178 198
179 - public function welcomeMessage()
180 - {
181 - if(get_transient( 'logtivity-welcome-notice') ) {
182 - echo logtivity_view('activation');
199 + update_option('logtivity_version', $this->version);
200 + }
183 201
184 - delete_transient( 'logtivity-welcome-notice' );
185 - }
186 - }
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 + );
187 215
188 - public function checkForSiteUrlChange()
189 - {
190 - if ( !current_user_can( 'manage_options' ) ) {
191 - return;
192 - }
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 + }
193 221
194 - if(logtivity_has_site_url_changed() && !get_transient( 'dismissed-logtivity-site-url-has-changed-notice')) {
195 - echo logtivity_view('site-url-changed-notice');
196 - }
197 - }
222 + if (array_search(Logtivity::ACCESS_SETTINGS, $capabilities) === false) {
223 + $administrator->add_cap(Logtivity::ACCESS_SETTINGS);
224 + }
225 + }
226 + }
198 227
199 - public function loadScripts()
200 - {
201 - wp_enqueue_style( 'logtivity_admin_css', plugin_dir_url(__FILE__) . 'assets/admin.css', false, $this->version );
202 - wp_enqueue_script( 'logtivity_admin_js', plugin_dir_url(__FILE__) . 'assets/app.js', false, $this->version );
203 - }
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 + }
204 519 }
205 520
206 -$logtivity = new Logtivity;
521 +Logtivity::init();