PluginProbe
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity / 3.3.9
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity v3.3.9
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 1.6.0 All 67 releases
← All changes | logtivity.php +478 -174 1.20.0 → 3.3.9 View file →
@@ -1,209 +1,513 @@
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.20.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.9
9 + * Text Domain: logtivity
10 + * Requires at least: 6.6
11 + * Requires PHP: 7.4
10 12 */
11 13
14 +/**
15 + * @package Logtivity
16 + * @contact logtivity.io, [email protected]
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.9');
41 +}
42 +
12 43 class Logtivity
13 44 {
14 - protected $version = '1.20.0';
45 + public const ACCESS_LOGS = 'view_logs';
46 + public const ACCESS_SETTINGS = 'view_log_settings';
15 47
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 - ];
48 + /**
49 + * @var string
50 + */
51 + protected string $version = LOGTIVITY_VERSION;
35 52
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 - ];
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 + ];
51 68
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 - '\Code_Snippets\Plugin' => [
82 - 'Logs/Code_Snippets/Logtivity_Code_Snippets',
83 - ]
84 - ];
69 + /**
70 + * @var bool
71 + */
72 + protected bool $coreLoaded = false;
85 73
86 - public function __construct()
87 - {
88 - $this->loadDependancies();
74 + public function __construct()
75 + {
76 + $this->loadCore();
77 + $this->activateLoggers();
89 78
90 - add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), [$this, 'addSettingsLinkFromPluginsPage'] );
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']);
91 85
92 - register_activation_hook( __FILE__, [$this, 'activated']);
86 + add_filter('plugin_action_links_' . plugin_basename(__FILE__), [$this, 'addSettingsLinkFromPluginsPage']);
93 87
94 - add_action( 'admin_notices', [$this, 'welcomeMessage']);
95 -
96 - add_action( 'admin_notices', [$this, 'checkForSiteUrlChange']);
88 + register_activation_hook(__FILE__, [$this, 'activated']);
89 + }
97 90
98 - add_action('admin_enqueue_scripts', [$this, 'loadScripts']);
99 - }
91 + /**
92 + * @return self
93 + */
94 + public static function init(): self
95 + {
96 + return new static();
97 + }
100 98
101 - public static function log($action = null, $meta = null, $user_id = null)
102 - {
103 - return Logtivity_Logger::log($action, $meta, $user_id);
104 - }
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 + }
105 112
106 - public function loadDependancies()
107 - {
108 - foreach ($this->dependancies as $filePath)
109 - {
110 - $this->loadFile($filePath);
111 - }
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 + }
112 125
113 - add_action('plugins_loaded', function() {
114 - $this->maybeLoadLogClasses();
126 + $this->coreLoaded = true;
127 + }
128 + }
115 129
116 - $this->loadIntegrationDependancies();
117 - });
118 - }
130 + /**
131 + * @return void
132 + */
133 + protected function activateLoggers(): void
134 + {
135 + add_action('plugins_loaded', function () {
136 + $this->loadCore();
137 + $this->updateCheck();
119 138
120 - public function maybeLoadLogClasses()
121 - {
122 - if ($this->defaultLoggingDisabled()) {
123 - return;
124 - }
139 + if ($this->defaultLoggingDisabled() == false) {
140 + $this->loadCoreLoggers();
141 + $this->loadIntegrations();
142 + }
143 + });
125 144
126 - foreach ($this->logClasses as $filePath)
127 - {
128 - $this->loadFile($filePath);
129 - }
130 - }
145 + }
131 146
132 - /**
133 - * Is the default Event logging from within the plugin enabled
134 - *
135 - * @return bool
136 - */
137 - public function defaultLoggingDisabled()
138 - {
139 - return (new Logtivity_Options)->getOption('logtivity_disable_default_logging');
140 - }
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 + }
141 163
142 - public function loadIntegrationDependancies()
143 - {
144 - foreach ($this->integrationDependancies as $key => $value)
145 - {
146 - if (class_exists($key)) {
147 - foreach ($value as $filePath)
148 - {
149 - $this->loadFile($filePath);
150 - }
151 - }
152 - }
153 - }
164 + $list = [];
165 + foreach ($files as $file) {
166 + if ($file->isFile()) {
167 + if ($file->getExtension() == $extension) {
168 + $list[] = $file->getRealPath();
169 + }
154 170
155 - public function loadFile($filePath)
156 - {
157 - require_once plugin_dir_path( __FILE__ ) . $filePath .'.php';
158 - }
171 + } elseif ($recurse) {
172 + $list = array_merge($list, $this->getFiles($file->getRealPath(), $recurse, $extension));
173 + }
174 + }
159 175
160 - public function addSettingsLinkFromPluginsPage($links)
161 - {
162 - if (apply_filters('logtivity_hide_settings_page', false)) {
163 - return $links;
164 - }
176 + return $list;
177 + }
165 178
166 - $settings_links = array(
167 - '<a href="' . admin_url( 'admin.php?page=logtivity-settings' ) . '">Settings</a>',
168 - );
169 -
170 - return array_merge($settings_links, $links);
171 - }
179 + /**
180 + * Review updates based on version
181 + *
182 + * @return void
183 + */
184 + public function updateCheck(): void
185 + {
186 + $currentVersion = get_option('logtivity_version');
172 187
173 - public function activated()
174 - {
175 - if (apply_filters('logtivity_hide_settings_page', false)) {
176 - return;
177 - }
188 + if (version_compare($currentVersion, '3.1.6', '<=')) {
189 + $this->checkCapabilities();
190 + }
178 191
179 - set_transient( 'logtivity-welcome-notice', true, 5 );
180 - }
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 + }
181 196
182 - public function welcomeMessage()
183 - {
184 - if(get_transient( 'logtivity-welcome-notice') ) {
185 - echo logtivity_view('activation');
197 + update_option('logtivity_version', $this->version);
198 + }
186 199
187 - delete_transient( 'logtivity-welcome-notice' );
188 - }
189 - }
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 + );
190 213
191 - public function checkForSiteUrlChange()
192 - {
193 - if ( !current_user_can( 'manage_options' ) ) {
194 - return;
195 - }
214 + if ($administrator = get_role('administrator')) {
215 + if (array_search(Logtivity::ACCESS_LOGS, $capabilities) === false) {
216 + $administrator->add_cap(Logtivity::ACCESS_LOGS);
217 + }
196 218
197 - if(logtivity_has_site_url_changed() && !get_transient( 'dismissed-logtivity-site-url-has-changed-notice')) {
198 - echo logtivity_view('site-url-changed-notice');
199 - }
200 - }
219 + if (array_search(Logtivity::ACCESS_SETTINGS, $capabilities) === false) {
220 + $administrator->add_cap(Logtivity::ACCESS_SETTINGS);
221 + }
222 + }
223 + }
201 224
202 - public function loadScripts()
203 - {
204 - wp_enqueue_style( 'logtivity_admin_css', plugin_dir_url(__FILE__) . 'assets/admin.css', false, $this->version );
205 - wp_enqueue_script( 'logtivity_admin_js', plugin_dir_url(__FILE__) . 'assets/app.js', false, $this->version );
206 - }
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 + ['jquery'],
500 + $this->version,
501 + true
502 + );
503 + wp_localize_script('logtivity_admin_js', 'logtivityAdminA11y', [
504 + 'loadingLogs' => __('Loading logs...', 'logtivity'),
505 + 'loadLogsError' => __('Unable to load logs. Please try again.', 'logtivity'),
506 + ]);
507 + wp_localize_script('logtivity_admin_js', 'logtivityAdmin', [
508 + 'dismissNonce' => wp_create_nonce('logtivity_dismiss_notice'),
509 + ]);
510 + }
207 511 }
208 512
209 -$logtivity = new Logtivity;
513 +Logtivity::init();