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

509 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.1
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.1';
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, 'redirect_on_activate']);
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 protected function activateLoggers(): void
127 {
128 add_action('plugins_loaded', function () {
129 $this->loadCore();
130 $this->updateCheck();
131
132 if ($this->defaultLoggingDisabled() == false) {
133 $this->loadCoreLoggers();
134 $this->loadIntegrations();
135 }
136 });
137
138 }
139
140 /**
141 * @param string $path
142 * @param bool $recurse
143 * @param string $extension
144 *
145 * @return array
146 */
147 protected function getFiles(string $path, bool $recurse = true, string $extension = 'php'): array
148 {
149 if (is_dir($path)) {
150 $files = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
151 } elseif (is_file($path)) {
152 return [realpath($path)];
153 } else {
154 return [];
155 }
156
157 $list = [];
158 foreach ($files as $file) {
159 if ($file->isFile()) {
160 if ($file->getExtension() == $extension) {
161 $list[] = $file->getRealPath();
162 }
163
164 } elseif ($recurse) {
165 $list = array_merge($list, $this->getFiles($file->getRealPath(), $recurse, $extension));
166 }
167 }
168
169 return $list;
170 }
171
172 /**
173 * Review updates based on version
174 *
175 * @return void
176 */
177 public function updateCheck(): void
178 {
179 $currentVersion = get_option('logtivity_version');
180
181 if (version_compare($currentVersion, '3.1.6', '<=')) {
182 $this->checkCapabilities();
183 }
184
185 if ($currentVersion && version_compare($currentVersion, '3.1.7', '<=')) {
186 // Default for updating sites should be no behavior change
187 update_option('logtivity_app_verify_url', 0);
188 }
189
190 update_option('logtivity_version', $this->version);
191 }
192
193 /**
194 * Custom capabilities added prior to v3.1.7
195 *
196 * @return void
197 */
198 protected function checkCapabilities(): void
199 {
200 $capabilities = array_filter(
201 array_keys(logtivity_get_capabilities()),
202 function (string $capability): bool {
203 return in_array($capability, [Logtivity::ACCESS_LOGS, Logtivity::ACCESS_SETTINGS]);
204 }
205 );
206
207 if ($capabilities == false) {
208 // Make sure at least admins can access us
209 if ($role = get_role('administrator')) {
210 if ($role->has_cap(Logtivity::ACCESS_LOGS) == false) {
211 $role->add_cap(Logtivity::ACCESS_LOGS);
212 }
213 if ($role->has_cap(Logtivity::ACCESS_SETTINGS) == false) {
214 $role->add_cap(Logtivity::ACCESS_SETTINGS);
215 }
216 }
217 }
218 }
219
220 /**
221 * Is the default Event logging from within the plugin enabled
222 *
223 * @return bool
224 */
225 protected function defaultLoggingDisabled(): bool
226 {
227 return (bool)(new Logtivity_Options())->getOption('logtivity_disable_default_logging');
228 }
229
230 /**
231 * @return void
232 */
233 protected function loadCoreLoggers(): void
234 {
235 $coreLoggers = $this->getFiles(__DIR__ . '/Loggers/Core');
236 foreach ($coreLoggers as $logger) {
237 require_once $logger;
238 }
239 }
240
241 /**
242 * @return void
243 */
244 protected function loadIntegrations(): void
245 {
246 $loggerFolder = __DIR__ . '/Loggers/';
247
248 foreach ($this->integrations as $key => $folder) {
249 $integrationFolder = $loggerFolder . $folder;
250 if (class_exists($key)) {
251 if (is_dir($integrationFolder . '/Base')) {
252 // Load any base classes
253 $baseFiles = $this->getFiles($integrationFolder . '/Base');
254 foreach ($baseFiles as $file) {
255 require_once $file;
256 }
257 }
258
259 $files = $this->getFiles($integrationFolder, false);
260 foreach ($files as $file) {
261 require_once $file;
262 }
263 }
264 }
265 }
266
267 /**
268 * Main entry for registering a site using the team API Key
269 *
270 * @param ?string $teamApi
271 * @param ?string $teamName
272 * @param ?string $siteName
273 * @param ?string $url
274 *
275 * @return null|Logtivity_Response|WP_Error
276 */
277 public static function registerSite(
278 ?string $teamApi,
279 ?string $teamName = null,
280 ?string $siteName = null,
281 ?string $url = null
282 ) {
283 $logtivityOptions = new Logtivity_Options();
284
285 if ($logtivityOptions->getApiKey()) {
286 $response = new WP_Error(
287 'logtivity_register_site_error',
288 __('You have already entered an API Key for this site.', 'logtivity')
289 );
290
291 } elseif ($teamApi) {
292 $request = [
293 'method' => 'POST',
294 'timeout' => 6,
295 'blocking' => true,
296 'body' => [
297 'team_name' => $teamName,
298 'name' => $siteName ?: get_bloginfo('name'),
299 'url' => $url ?: home_url(),
300 ],
301 'cookies' => [],
302 ];
303
304 $response = new Logtivity_Response($teamApi, '/sites', $request);
305 if ($response->code == 200 && $response->error == false) {
306 $apikey = $response->body['api_key'] ?? null;
307 $teamName = $response->body['team_name'] ?? '*unknown*';
308 $created = $response->body['created_at'] ?? null;
309 $isNew = $response->body['is_new'] ?? null;
310
311 if ($apikey) {
312 $logtivityOptions->update(['logtivity_site_api_key' => $apikey]);
313
314 if ($isNew) {
315 $response->message = sprintf(
316 'This site has been created on <a href="%s" target="_blank">Logtivity</a> for team \'%s\'. Logging is now enabled.',
317 logtivity_get_app_url(),
318 $teamName
319 );
320
321 } else {
322 if ($created) {
323 $createdTimestamp = strtotime($created);
324 $creationText = sprintf(
325 'It was created on %s at %s ',
326 wp_date(get_option('date_format'), $createdTimestamp),
327 wp_date(get_option('time_format'), $createdTimestamp)
328 );
329 }
330 $response->message = sprintf(
331 'This site was found on <a href="%s" target="_blank">Logtivity</a>. %sfor the team \'%s\'. Logging is now enabled.',
332 logtivity_get_app_url(),
333 $creationText ?? '',
334 $teamName
335 );
336 }
337 }
338 }
339
340 } else {
341 $response = new WP_Error('logtivity_missing_data', 'Team API Key is required.');
342 }
343
344 return $response;
345 }
346
347 /**
348 * @param ?string $action
349 * @param ?array $meta
350 * @param ?int $userId
351 *
352 * @return Logtivity_Logger
353 */
354 public static function log(?string $action = null, ?array $meta = null, ?int $userId = null): Logtivity_Logger
355 {
356 return Logtivity_Logger::log($action, $meta, $userId);
357 }
358
359 /**
360 * @param array $error
361 *
362 * @return Logtivity_Error_Logger
363 */
364 public static function logError(array $error): Logtivity_Error_Logger
365 {
366 return new Logtivity_Error_Logger($error);
367 }
368
369 /**
370 * @param WP_Upgrader $upgraderObject
371 * @param array $options
372 *
373 * @return void
374 */
375 public function upgradeProcessComplete(WP_Upgrader $upgraderObject, array $options): void
376 {
377 $type = $options['type'] ?? null;
378 $action = $options['action'] ?? null;
379
380 if ($type == 'plugin' && $action == 'update') {
381 $this->setLogtivityToLoadFirst();
382 }
383 }
384
385 /**
386 * @return void
387 */
388 public function setLogtivityToLoadFirst(): void
389 {
390 $path = str_replace(WP_PLUGIN_DIR . '/', '', __FILE__);
391
392 if ($plugins = get_option('active_plugins')) {
393 if ($key = array_search($path, $plugins)) {
394 array_splice($plugins, $key, 1);
395 array_unshift($plugins, $path);
396 update_option('active_plugins', $plugins);
397 }
398 }
399 }
400
401 /**
402 * @param array $links
403 *
404 * @return string[]
405 */
406 public function addSettingsLinkFromPluginsPage(array $links): array
407 {
408 if (apply_filters('logtivity_hide_settings_page', false)) {
409 return $links;
410 }
411
412 return array_merge(
413 [
414 sprintf('<a href="%s">Settings</a>', admin_url('admin.php?page=logtivity-settings')),
415 ],
416 $links
417 );
418 }
419
420 /**
421 * @return void
422 */
423 public function activated(): void
424 {
425 add_option('logtivity_activate', true);
426
427 $this->checkCapabilities();
428
429 if (apply_filters('logtivity_hide_settings_page', false)) {
430 return;
431 }
432
433 set_transient('logtivity-welcome-notice', true, 5);
434 }
435
436 /**
437 * Redirect to Settings page
438 *
439 * @return void
440 * @since 3.1.11
441 *
442 */
443 public function redirect_on_activate()
444 {
445 if (get_option('logtivity_activate')) {
446 delete_option('logtivity_activate');
447
448 if (!isset($_GET['activate-multi'])) {
449 wp_redirect(admin_url('admin.php?page=logtivity'));
450 exit;
451 }
452 }
453 }
454
455 /**
456 * @return void
457 */
458 public function welcomeMessage(): void
459 {
460 if (get_transient('logtivity-welcome-notice')) {
461 echo logtivity_view('activation');
462
463 delete_transient('logtivity-welcome-notice');
464 }
465 }
466
467 /**
468 * @return void
469 */
470 public function checkForSiteUrlChange(): void
471 {
472 if (
473 current_user_can(static::ACCESS_SETTINGS)
474 && logtivity_has_site_url_changed()
475 && (new Logtivity_Options())->isWhiteLabelMode() == false
476 && !get_transient('dismissed-logtivity-site-url-has-changed-notice')
477 ) {
478 echo logtivity_view('site-url-changed-notice');
479 }
480 }
481
482 /**
483 * @return void
484 */
485 public function loadScripts(): void
486 {
487 wp_enqueue_style(
488 'logtivity_google_font_admin_css',
489 'https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,500',
490 false,
491 $this->version
492 );
493 wp_enqueue_style(
494 'logtivity_admin_css',
495 plugin_dir_url(__FILE__) . 'assets/admin.css',
496 ['logtivity_google_font_admin_css'],
497 $this->version
498 );
499 wp_enqueue_script(
500 'logtivity_admin_js',
501 plugin_dir_url(__FILE__) . 'assets/app.js',
502 false,
503 $this->version
504 );
505 }
506 }
507
508 Logtivity::init();
509