PluginProbe
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity / 3.3.2
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity v3.3.2
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 3.3.2, at logtivity.php

511 lines 14.9 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.2
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-2025 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 class Logtivity
40 {
41 public const ACCESS_LOGS = 'view_logs';
42 public const ACCESS_SETTINGS = 'view_log_settings';
43
44 /**
45 * @var string
46 */
47 protected string $version = '3.3.2';
48
49 /**
50 * Integrations with other plugins
51 *
52 * @var array[]
53 */
54 protected array $integrations = [
55 WP_DLM::class => 'Download_Monitor',
56 MeprCtrlFactory::class => 'Memberpress',
57 Easy_Digital_Downloads::class => 'Easy_Digital_Downloads',
58 EDD_Software_Licensing::class => 'Easy_Digital_Downloads/Licensing',
59 EDD_Recurring::class => 'Easy_Digital_Downloads/Recurring',
60 FrmHooksController::class => 'Formidable',
61 PMXI_Plugin::class => 'WP_All_Import',
62 \Code_Snippets\Plugin::class => 'Code_Snippets',
63 ];
64
65 /**
66 * @var bool
67 */
68 protected bool $coreLoaded = false;
69
70 public function __construct()
71 {
72 $this->loadCore();
73 $this->activateLoggers();
74
75 add_action('upgrader_process_complete', [$this, 'upgradeProcessComplete'], 10, 2);
76 add_action('activated_plugin', [$this, 'setLogtivityToLoadFirst']);
77 add_action('admin_notices', [$this, 'welcomeMessage']);
78 add_action('admin_notices', [$this, 'checkForSiteUrlChange']);
79 add_action('admin_enqueue_scripts', [$this, 'loadScripts']);
80 add_action('admin_init', [$this, 'redirectOnActivate']);
81
82 add_filter('plugin_action_links_' . plugin_basename(__FILE__), [$this, 'addSettingsLinkFromPluginsPage']);
83
84 register_activation_hook(__FILE__, [$this, 'activated']);
85 }
86
87 /**
88 * @return self
89 */
90 public static function init(): self
91 {
92 return new static();
93 }
94
95 /**
96 * @return void
97 */
98 protected function loadCore(): void
99 {
100 if ($this->coreLoaded == false) {
101 $requires = array_merge(
102 $this->getFiles(__DIR__ . '/functions'),
103 $this->getFiles(__DIR__ . '/Base')
104 );
105 foreach ($requires as $file) {
106 require_once $file;
107 }
108
109 $coreFiles = $this->getFiles(__DIR__ . '/Core');
110 $initClasses = [];
111 foreach ($coreFiles as $file) {
112 require_once $file;
113 $className = basename($file, '.php');
114 if (is_callable([$className, 'init'])) {
115 $initClasses[] = $className;
116 }
117 }
118 foreach ($initClasses as $class) {
119 call_user_func([$class, 'init']);
120 }
121
122 $this->coreLoaded = true;
123 }
124 }
125
126 /**
127 * @return void
128 */
129 protected function activateLoggers(): void
130 {
131 add_action('plugins_loaded', function () {
132 $this->loadCore();
133 $this->updateCheck();
134
135 if ($this->defaultLoggingDisabled() == false) {
136 $this->loadCoreLoggers();
137 $this->loadIntegrations();
138 }
139 });
140
141 }
142
143 /**
144 * @param string $path
145 * @param bool $recurse
146 * @param string $extension
147 *
148 * @return array
149 */
150 protected function getFiles(string $path, bool $recurse = true, string $extension = 'php'): array
151 {
152 if (is_dir($path)) {
153 $files = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
154 } elseif (is_file($path)) {
155 return [realpath($path)];
156 } else {
157 return [];
158 }
159
160 $list = [];
161 foreach ($files as $file) {
162 if ($file->isFile()) {
163 if ($file->getExtension() == $extension) {
164 $list[] = $file->getRealPath();
165 }
166
167 } elseif ($recurse) {
168 $list = array_merge($list, $this->getFiles($file->getRealPath(), $recurse, $extension));
169 }
170 }
171
172 return $list;
173 }
174
175 /**
176 * Review updates based on version
177 *
178 * @return void
179 */
180 public function updateCheck(): void
181 {
182 $currentVersion = get_option('logtivity_version');
183
184 if (version_compare($currentVersion, '3.1.6', '<=')) {
185 $this->checkCapabilities();
186 }
187
188 if ($currentVersion && version_compare($currentVersion, '3.1.7', '<=')) {
189 // Default for updating sites should be no behavior change
190 update_option('logtivity_app_verify_url', 0);
191 }
192
193 update_option('logtivity_version', $this->version);
194 }
195
196 /**
197 * Custom capabilities added prior to v3.1.7
198 *
199 * @return void
200 */
201 protected function checkCapabilities(): void
202 {
203 $capabilities = array_filter(
204 array_keys(logtivity_get_capabilities()),
205 function (string $capability): bool {
206 return in_array($capability, [Logtivity::ACCESS_LOGS, Logtivity::ACCESS_SETTINGS]);
207 }
208 );
209
210 if ($administrator = get_role('administrator')) {
211 if (array_search(Logtivity::ACCESS_LOGS, $capabilities) === false) {
212 $administrator->add_cap(Logtivity::ACCESS_LOGS);
213 }
214
215 if (array_search(Logtivity::ACCESS_SETTINGS, $capabilities) === false) {
216 $administrator->add_cap(Logtivity::ACCESS_SETTINGS);
217 }
218 }
219 }
220
221 /**
222 * Is the default Event logging from within the plugin enabled
223 *
224 * @return bool
225 */
226 protected function defaultLoggingDisabled(): bool
227 {
228 return (bool)(new Logtivity_Options())->getOption('logtivity_disable_default_logging');
229 }
230
231 /**
232 * @return void
233 */
234 protected function loadCoreLoggers(): void
235 {
236 $coreLoggers = $this->getFiles(__DIR__ . '/Loggers/Core');
237 foreach ($coreLoggers as $logger) {
238 require_once $logger;
239 }
240 }
241
242 /**
243 * @return void
244 */
245 protected function loadIntegrations(): void
246 {
247 $loggerFolder = __DIR__ . '/Loggers/';
248
249 foreach ($this->integrations as $key => $folder) {
250 $integrationFolder = $loggerFolder . $folder;
251 if (class_exists($key)) {
252 if (is_dir($integrationFolder . '/Base')) {
253 // Load any base classes
254 $baseFiles = $this->getFiles($integrationFolder . '/Base');
255 foreach ($baseFiles as $file) {
256 require_once $file;
257 }
258 }
259
260 $files = $this->getFiles($integrationFolder, false);
261 foreach ($files as $file) {
262 require_once $file;
263 }
264 }
265 }
266 }
267
268 /**
269 * Main entry for registering a site using the team API Key
270 *
271 * @param ?string $teamApi
272 * @param ?string $teamName
273 * @param ?string $siteName
274 * @param ?string $url
275 *
276 * @return null|Logtivity_Response|WP_Error
277 */
278 public static function registerSite(
279 ?string $teamApi,
280 ?string $teamName = null,
281 ?string $siteName = null,
282 ?string $url = null
283 ) {
284 $logtivityOptions = new Logtivity_Options();
285
286 if ($logtivityOptions->getApiKey()) {
287 $response = new WP_Error(
288 'logtivity_register_site_error',
289 __('You have already entered an API Key for this site.', 'logtivity')
290 );
291
292 } elseif ($teamApi) {
293 $request = [
294 'method' => 'POST',
295 'timeout' => 6,
296 'blocking' => true,
297 'body' => [
298 'team_name' => $teamName,
299 'name' => $siteName ?: get_bloginfo('name'),
300 'url' => $url ?: home_url(),
301 ],
302 'cookies' => [],
303 ];
304
305 $response = new Logtivity_Response($teamApi, '/sites', $request);
306 if ($response->code == 200 && $response->error == false) {
307 $apikey = $response->body['api_key'] ?? null;
308 $teamName = $response->body['team_name'] ?? '*unknown*';
309 $created = $response->body['created_at'] ?? null;
310 $isNew = $response->body['is_new'] ?? null;
311
312 if ($apikey) {
313 $logtivityOptions->update(['logtivity_site_api_key' => $apikey]);
314
315 if ($isNew) {
316 $response->message = sprintf(
317 'This site has been created on <a href="%s" target="_blank">Logtivity</a> for team \'%s\'. Logging is now enabled.',
318 logtivity_get_app_url(),
319 $teamName
320 );
321
322 } else {
323 if ($created) {
324 $createdTimestamp = strtotime($created);
325 $creationText = sprintf(
326 'It was created on %s at %s ',
327 wp_date(get_option('date_format'), $createdTimestamp),
328 wp_date(get_option('time_format'), $createdTimestamp)
329 );
330 }
331 $response->message = sprintf(
332 'This site was found on <a href="%s" target="_blank">Logtivity</a>. %sfor the team \'%s\'. Logging is now enabled.',
333 logtivity_get_app_url(),
334 $creationText ?? '',
335 $teamName
336 );
337 }
338 }
339 }
340
341 } else {
342 $response = new WP_Error('logtivity_missing_data', 'Team API Key is required.');
343 }
344
345 return $response;
346 }
347
348 /**
349 * @param ?string $action
350 * @param ?array $meta
351 * @param ?int $userId
352 *
353 * @return Logtivity_Logger
354 */
355 public static function log(?string $action = null, ?array $meta = null, ?int $userId = null): Logtivity_Logger
356 {
357 return Logtivity_Logger::log($action, $meta, $userId);
358 }
359
360 /**
361 * @param array $error
362 *
363 * @return Logtivity_Error_Logger
364 */
365 public static function logError(array $error): Logtivity_Error_Logger
366 {
367 return new Logtivity_Error_Logger($error);
368 }
369
370 /**
371 * @param WP_Upgrader $upgraderObject
372 * @param array $options
373 *
374 * @return void
375 */
376 public function upgradeProcessComplete(WP_Upgrader $upgraderObject, array $options): void
377 {
378 $type = $options['type'] ?? null;
379 $action = $options['action'] ?? null;
380
381 if ($type == 'plugin' && $action == 'update') {
382 $this->setLogtivityToLoadFirst();
383 }
384 }
385
386 /**
387 * @return void
388 */
389 public function setLogtivityToLoadFirst(): void
390 {
391 $path = str_replace(WP_PLUGIN_DIR . '/', '', __FILE__);
392
393 if ($plugins = get_option('active_plugins')) {
394 if ($key = array_search($path, $plugins)) {
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);
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 if (!isset($_GET['activate-multi'])) {
450 $page = (new Logtivity_Options())->isWhiteLabelMode() ? 'lgtvy-logs' : 'logtivity';
451 wp_redirect(admin_url('admin.php?page=' . $page));
452 exit;
453 }
454 }
455 }
456
457 /**
458 * @return void
459 */
460 public function welcomeMessage(): void
461 {
462 if (get_transient('logtivity-welcome-notice')) {
463 echo logtivity_view('activation');
464
465 delete_transient('logtivity-welcome-notice');
466 }
467 }
468
469 /**
470 * @return void
471 */
472 public function checkForSiteUrlChange(): void
473 {
474 if (
475 current_user_can(static::ACCESS_SETTINGS)
476 && logtivity_has_site_url_changed()
477 && (new Logtivity_Options())->isWhiteLabelMode() == false
478 && !get_transient('dismissed-logtivity-site-url-has-changed-notice')
479 ) {
480 echo logtivity_view('site-url-changed-notice');
481 }
482 }
483
484 /**
485 * @return void
486 */
487 public function loadScripts(): void
488 {
489 wp_enqueue_style(
490 'logtivity_google_font_admin_css',
491 'https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,500',
492 false,
493 $this->version
494 );
495 wp_enqueue_style(
496 'logtivity_admin_css',
497 plugin_dir_url(__FILE__) . 'assets/admin.css',
498 ['logtivity_google_font_admin_css'],
499 $this->version
500 );
501 wp_enqueue_script(
502 'logtivity_admin_js',
503 plugin_dir_url(__FILE__) . 'assets/app.js',
504 false,
505 $this->version
506 );
507 }
508 }
509
510 Logtivity::init();
511